From 18d1d4fd3da091d01433e9d4efa9102b35e0d463 Mon Sep 17 00:00:00 2001 From: JW Wang Date: Tue, 3 Nov 2015 11:36:24 +0800 Subject: [PATCH 001/113] Bug 1218311 - Port the fix of bug 1193614 to VideoSink. r=cpearce. f=dglastonbury. --- dom/media/MediaDecoderStateMachine.cpp | 13 ------------- dom/media/mediasink/VideoSink.cpp | 12 +++++++++--- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/dom/media/MediaDecoderStateMachine.cpp b/dom/media/MediaDecoderStateMachine.cpp index 2cbd14378daf..53e6373b9eba 100644 --- a/dom/media/MediaDecoderStateMachine.cpp +++ b/dom/media/MediaDecoderStateMachine.cpp @@ -890,19 +890,6 @@ MediaDecoderStateMachine::OnVideoDecoded(MediaData* aVideoSample) StopPrerollingVideo(); } - // Schedule the state machine to send stream data as soon as possible if - // the VideoQueue() is empty or contains one frame before the Push(). - // - // The state machine threads requires a frame in VideoQueue() that is `in - // the future` to gather precise timing information. The head of - // VideoQueue() is always `in the past`. - // - // Schedule the state machine as soon as possible to render the video - // frame or delay the state machine thread accurately. - if (VideoQueue().GetSize() <= 2) { - ScheduleStateMachine(); - } - // For non async readers, if the requested video sample was slow to // arrive, increase the amount of audio we buffer to ensure that we // don't run out of audio. This is unnecessary for async readers, diff --git a/dom/media/mediasink/VideoSink.cpp b/dom/media/mediasink/VideoSink.cpp index c5275a042699..219f2125e723 100644 --- a/dom/media/mediasink/VideoSink.cpp +++ b/dom/media/mediasink/VideoSink.cpp @@ -221,9 +221,15 @@ void VideoSink::OnVideoQueueEvent() { AssertOwnerThread(); - // Listen to push event, VideoSink should try rendering ASAP if first frame - // arrives but update scheduler is not triggered yet. - TryUpdateRenderedVideoFrames(); + + // The video queue is empty or contains only one frame before Push() which + // means we are slow in video decoding and don't have enough information to + // schedule next render loop accurately (default timeout is 40ms). We need + // to render incoming frames immediately so render loop can be scheduled + // again accurately. + if (mAudioSink->IsPlaying() && VideoQueue().GetSize() <= 2) { + UpdateRenderedVideoFrames(); + } } void From 636e8bffb2cb6d3211ed348262cca08554b205f3 Mon Sep 17 00:00:00 2001 From: Jan de Mooij Date: Tue, 3 Nov 2015 08:34:56 +0100 Subject: [PATCH 002/113] Bug 1219363 - Fix sort of indexed properties to not include properties already in the Vector. r=jorendorff --- js/src/jit-test/tests/basic/bug1219363.js | 9 +++++++++ js/src/jsiter.cpp | 14 ++++++++------ 2 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 js/src/jit-test/tests/basic/bug1219363.js diff --git a/js/src/jit-test/tests/basic/bug1219363.js b/js/src/jit-test/tests/basic/bug1219363.js new file mode 100644 index 000000000000..03e4008ab08a --- /dev/null +++ b/js/src/jit-test/tests/basic/bug1219363.js @@ -0,0 +1,9 @@ +var x = [1, 2, , 4] +x[100000] = 1; +var y = Object.create(x); +y.a = 1; +y.b = 1; +var arr = []; +for (var z in y) + arr.push(z); +assertEq(arr.join(), "a,b,0,1,3,100000"); diff --git a/js/src/jsiter.cpp b/js/src/jsiter.cpp index 43c05403fa0d..245a430ea4dd 100644 --- a/js/src/jsiter.cpp +++ b/js/src/jsiter.cpp @@ -181,6 +181,7 @@ EnumerateNativeProperties(JSContext* cx, HandleNativeObject pobj, unsigned flags enumerateSymbols = true; } else { /* Collect any dense elements from this object. */ + size_t firstElemIndex = props->length(); size_t initlen = pobj->getDenseInitializedLength(); const Value* vp = pobj->getDenseElements(); bool hasHoles = false; @@ -206,7 +207,10 @@ EnumerateNativeProperties(JSContext* cx, HandleNativeObject pobj, unsigned flags // Collect any sparse elements from this object. bool isIndexed = pobj->isIndexed(); if (isIndexed) { - size_t numElements = props->length(); + // If the dense elements didn't have holes, we don't need to include + // them in the sort. + if (!hasHoles) + firstElemIndex = props->length(); for (Shape::Range r(pobj->lastProperty()); !r.empty(); r.popFront()) { Shape& shape = r.front(); @@ -218,12 +222,10 @@ EnumerateNativeProperties(JSContext* cx, HandleNativeObject pobj, unsigned flags } } - // If the dense elements didn't have holes, we don't need to include - // them in the sort. - size_t startIndex = hasHoles ? 0 : numElements; + MOZ_ASSERT(firstElemIndex <= props->length()); - jsid* ids = props->begin() + startIndex; - size_t n = props->length() - startIndex; + jsid* ids = props->begin() + firstElemIndex; + size_t n = props->length() - firstElemIndex; AutoIdVector tmp(cx); if (!tmp.resize(n)) From bc096ee88ccf25835c77447189232b6da8bf6528 Mon Sep 17 00:00:00 2001 From: Jan de Mooij Date: Tue, 3 Nov 2015 08:38:09 +0100 Subject: [PATCH 003/113] Bug 1155937 - Fix comment in IonBuilder::getPropTryInnerize. r=bz --- js/src/jit/IonBuilder.cpp | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index dc76ce695f94..c170bd09db3b 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -11869,6 +11869,11 @@ IonBuilder::getPropTryInnerize(bool* emitted, MDefinition* obj, PropertyName* na { // See the comment in tryInnerizeWindow for how this works. + // Note that it's important that we do this _before_ we'd try to + // do the optimizations below on obj normally, since some of those + // optimizations have fallback paths that are slower than the path + // we'd produce here. + MOZ_ASSERT(*emitted == false); MDefinition* inner = tryInnerizeWindow(obj); @@ -11876,15 +11881,6 @@ IonBuilder::getPropTryInnerize(bool* emitted, MDefinition* obj, PropertyName* na return true; if (!forceInlineCaches()) { - // Note: the Baseline ICs don't know about this optimization, so it's - // possible the global property's HeapTypeSet has not been initialized - // yet. In this case we'll fall back to getPropTryCache for now. - - // Note that it's important that we do this _before_ we'd try to - // do the optimizations below on obj normally, since some of those - // optimizations have fallback paths that are slower than the path - // we'd produce here. - trackOptimizationAttempt(TrackedStrategy::GetProp_Constant); if (!getPropTryConstant(emitted, inner, NameToId(name), types) || *emitted) return *emitted; From 36617d224da56f4ce4512c3776472019cbd017fe Mon Sep 17 00:00:00 2001 From: Lars T Hansen Date: Mon, 2 Nov 2015 09:07:47 +0100 Subject: [PATCH 004/113] Bug 1218643 - remove support for deprecated asm.js heap length. r=luke --HG-- extra : rebase_source : eca0df20d3c8ec25790944ad6fc63b6855cce9ba extra : histedit_source : 67874acf3ccf41b2f759cbade431fdbde7c38a81 --- js/src/asmjs/AsmJSLink.cpp | 12 ------------ js/src/asmjs/AsmJSValidate.h | 19 ++++++++----------- .../neuter-during-arguments-coercion.js | 2 +- 3 files changed, 9 insertions(+), 24 deletions(-) diff --git a/js/src/asmjs/AsmJSLink.cpp b/js/src/asmjs/AsmJSLink.cpp index e7e5e4dc33d0..873a1cb44db9 100644 --- a/js/src/asmjs/AsmJSLink.cpp +++ b/js/src/asmjs/AsmJSLink.cpp @@ -469,17 +469,6 @@ LinkModuleToHeap(JSContext* cx, AsmJSModule& module, HandlebyteLength(); - if (IsDeprecatedAsmJSHeapLength(heapLength)) { - LinkFail(cx, "ArrayBuffer byteLengths smaller than 64KB are deprecated and " - "will cause a link-time failure in the future"); - - // The goal of deprecation is to give apps some time before linking - // fails. However, if warnings-as-errors is turned on (which happens as - // part of asm.js testing) an exception may be raised. - if (cx->isExceptionPending()) - return false; - } - if (!IsValidAsmJSHeapLength(heapLength)) { ScopedJSFreePtr msg( JS_smprintf("ArrayBuffer byteLength 0x%x is not a valid heap length. The next " @@ -631,7 +620,6 @@ ChangeHeap(JSContext* cx, AsmJSModule& module, const CallArgs& args) } MOZ_ASSERT(IsValidAsmJSHeapLength(heapLength)); - MOZ_ASSERT(!IsDeprecatedAsmJSHeapLength(heapLength)); if (!ArrayBufferObject::prepareForAsmJS(cx, newBuffer, module.usesSignalHandlersForOOB())) return false; diff --git a/js/src/asmjs/AsmJSValidate.h b/js/src/asmjs/AsmJSValidate.h index c6aff6449f97..6255ec541425 100644 --- a/js/src/asmjs/AsmJSValidate.h +++ b/js/src/asmjs/AsmJSValidate.h @@ -51,9 +51,14 @@ extern bool ValidateAsmJS(ExclusiveContext* cx, AsmJSParser& parser, frontend::ParseNode* stmtList, bool* validated); +// The minimum heap length for asm.js. +const size_t AsmJSMinHeapLength = 64 * 1024; + // The assumed page size; dynamically checked in ValidateAsmJS. const size_t AsmJSPageSize = 4096; +static_assert(AsmJSMinHeapLength % AsmJSPageSize == 0, "Invalid page size"); + #if defined(ASMJS_MAY_USE_SIGNAL_HANDLERS_FOR_OOB) // Targets define AsmJSImmediateRange to be the size of an address immediate, @@ -86,8 +91,8 @@ static const size_t AsmJSMappedSize = 4 * 1024ULL * 1024ULL * 1024ULL + inline uint32_t RoundUpToNextValidAsmJSHeapLength(uint32_t length) { - if (length <= 4 * 1024) - return 4 * 1024; + if (length <= AsmJSMinHeapLength) + return AsmJSMinHeapLength; if (length <= 16 * 1024 * 1024) return mozilla::RoundUpPow2(length); @@ -99,7 +104,7 @@ RoundUpToNextValidAsmJSHeapLength(uint32_t length) inline bool IsValidAsmJSHeapLength(uint32_t length) { - bool valid = length >= 4 * 1024 && + bool valid = length >= AsmJSMinHeapLength && (IsPowerOfTwo(length) || (length & 0x00ffffff) == 0); @@ -109,14 +114,6 @@ IsValidAsmJSHeapLength(uint32_t length) return valid; } -// For now, power-of-2 lengths in this range are accepted, but in the future -// we'll change this to cause link-time failure. -inline bool -IsDeprecatedAsmJSHeapLength(uint32_t length) -{ - return length >= 4 * 1024 && length < 64 * 1024 && IsPowerOfTwo(length); -} - // Return whether asm.js optimization is inhibited by the platform or // dynamically disabled: extern bool diff --git a/js/src/jit-test/tests/asm.js/neuter-during-arguments-coercion.js b/js/src/jit-test/tests/asm.js/neuter-during-arguments-coercion.js index c0b4e84c4c9c..122cff61f4b7 100644 --- a/js/src/jit-test/tests/asm.js/neuter-during-arguments-coercion.js +++ b/js/src/jit-test/tests/asm.js/neuter-during-arguments-coercion.js @@ -15,7 +15,7 @@ function f(stdlib, foreign, buffer) if (isAsmJSCompilationAvailable()) assertEq(isAsmJSModule(f), true); -var i32 = new Int32Array(4096); +var i32 = new Int32Array(65536); var buffer = i32.buffer; var set = f(this, null, buffer); if (isAsmJSCompilationAvailable()) From 252be2a835546911783d891315828305d005e3c5 Mon Sep 17 00:00:00 2001 From: Lars T Hansen Date: Thu, 22 Oct 2015 11:34:55 +0200 Subject: [PATCH 005/113] Bug 1217326 - fork remaining atomics. r=h4writer. --HG-- extra : rebase_source : 9bc093ac502b75a58299f0929facf568c11568b2 extra : histedit_source : 9d708f83a70893e3391edd1bc5936f036f289c2e --- js/src/jit/CodeGenerator.cpp | 43 --------- js/src/jit/CodeGenerator.h | 2 - js/src/jit/MacroAssembler.cpp | 89 ------------------ js/src/jit/MacroAssembler.h | 8 -- js/src/jit/arm/CodeGenerator-arm.cpp | 42 +++++++++ js/src/jit/arm/CodeGenerator-arm.h | 2 + js/src/jit/arm/MacroAssembler-arm.cpp | 89 ++++++++++++++++++ js/src/jit/arm/MacroAssembler-arm.h | 8 ++ js/src/jit/arm64/CodeGenerator-arm64.cpp | 44 +++++++++ js/src/jit/arm64/CodeGenerator-arm64.h | 2 + js/src/jit/arm64/MacroAssembler-arm64.cpp | 89 ++++++++++++++++++ js/src/jit/arm64/MacroAssembler-arm64.h | 8 ++ .../x86-shared/CodeGenerator-x86-shared.cpp | 43 +++++++++ .../jit/x86-shared/CodeGenerator-x86-shared.h | 2 + .../x86-shared/MacroAssembler-x86-shared.cpp | 90 +++++++++++++++++++ .../x86-shared/MacroAssembler-x86-shared.h | 8 ++ 16 files changed, 427 insertions(+), 142 deletions(-) diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 1abdaf6d4a6e..7ef45c20b4e1 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -9299,49 +9299,6 @@ CodeGenerator::visitAtomicIsLockFree(LAtomicIsLockFree* lir) masm.bind(&Ldone); } -void -CodeGenerator::visitCompareExchangeTypedArrayElement(LCompareExchangeTypedArrayElement* lir) -{ - Register elements = ToRegister(lir->elements()); - AnyRegister output = ToAnyRegister(lir->output()); - Register temp = lir->temp()->isBogusTemp() ? InvalidReg : ToRegister(lir->temp()); - - Register oldval = ToRegister(lir->oldval()); - Register newval = ToRegister(lir->newval()); - - Scalar::Type arrayType = lir->mir()->arrayType(); - int width = Scalar::byteSize(arrayType); - - if (lir->index()->isConstant()) { - Address dest(elements, ToInt32(lir->index()) * width); - masm.compareExchangeToTypedIntArray(arrayType, dest, oldval, newval, temp, output); - } else { - BaseIndex dest(elements, ToRegister(lir->index()), ScaleFromElemWidth(width)); - masm.compareExchangeToTypedIntArray(arrayType, dest, oldval, newval, temp, output); - } -} - -void -CodeGenerator::visitAtomicExchangeTypedArrayElement(LAtomicExchangeTypedArrayElement* lir) -{ - Register elements = ToRegister(lir->elements()); - AnyRegister output = ToAnyRegister(lir->output()); - Register temp = lir->temp()->isBogusTemp() ? InvalidReg : ToRegister(lir->temp()); - - Register value = ToRegister(lir->value()); - - Scalar::Type arrayType = lir->mir()->arrayType(); - int width = Scalar::byteSize(arrayType); - - if (lir->index()->isConstant()) { - Address dest(elements, ToInt32(lir->index()) * width); - masm.atomicExchangeToTypedIntArray(arrayType, dest, value, temp, output); - } else { - BaseIndex dest(elements, ToRegister(lir->index()), ScaleFromElemWidth(width)); - masm.atomicExchangeToTypedIntArray(arrayType, dest, value, temp, output); - } -} - void CodeGenerator::visitClampIToUint8(LClampIToUint8* lir) { diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h index e1efd640c635..60f18a8d59d7 100644 --- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -286,8 +286,6 @@ class CodeGenerator : public CodeGeneratorSpecific void visitStoreUnboxedScalar(LStoreUnboxedScalar* lir); void visitStoreTypedArrayElementHole(LStoreTypedArrayElementHole* lir); void visitAtomicIsLockFree(LAtomicIsLockFree* lir); - void visitCompareExchangeTypedArrayElement(LCompareExchangeTypedArrayElement* lir); - void visitAtomicExchangeTypedArrayElement(LAtomicExchangeTypedArrayElement* lir); void visitClampIToUint8(LClampIToUint8* lir); void visitClampDToUint8(LClampDToUint8* lir); void visitClampVToUint8(LClampVToUint8* lir); diff --git a/js/src/jit/MacroAssembler.cpp b/js/src/jit/MacroAssembler.cpp index 29541853c1ea..2a182e4c9e72 100644 --- a/js/src/jit/MacroAssembler.cpp +++ b/js/src/jit/MacroAssembler.cpp @@ -446,95 +446,6 @@ template void MacroAssembler::loadFromTypedArray(Scalar::Type arrayType, const A template void MacroAssembler::loadFromTypedArray(Scalar::Type arrayType, const BaseIndex& src, const ValueOperand& dest, bool allowDouble, Register temp, Label* fail); -template -void -MacroAssembler::compareExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, - Register oldval, Register newval, - Register temp, AnyRegister output) -{ - switch (arrayType) { - case Scalar::Int8: - compareExchange8SignExtend(mem, oldval, newval, output.gpr()); - break; - case Scalar::Uint8: - compareExchange8ZeroExtend(mem, oldval, newval, output.gpr()); - break; - case Scalar::Uint8Clamped: - compareExchange8ZeroExtend(mem, oldval, newval, output.gpr()); - break; - case Scalar::Int16: - compareExchange16SignExtend(mem, oldval, newval, output.gpr()); - break; - case Scalar::Uint16: - compareExchange16ZeroExtend(mem, oldval, newval, output.gpr()); - break; - case Scalar::Int32: - compareExchange32(mem, oldval, newval, output.gpr()); - break; - case Scalar::Uint32: - // At the moment, the code in MCallOptimize.cpp requires the output - // type to be double for uint32 arrays. See bug 1077305. - MOZ_ASSERT(output.isFloat()); - compareExchange32(mem, oldval, newval, temp); - convertUInt32ToDouble(temp, output.fpu()); - break; - default: - MOZ_CRASH("Invalid typed array type"); - } -} - -template void -MacroAssembler::compareExchangeToTypedIntArray(Scalar::Type arrayType, const Address& mem, - Register oldval, Register newval, Register temp, - AnyRegister output); -template void -MacroAssembler::compareExchangeToTypedIntArray(Scalar::Type arrayType, const BaseIndex& mem, - Register oldval, Register newval, Register temp, - AnyRegister output); - -template -void -MacroAssembler::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, - Register value, Register temp, AnyRegister output) -{ - switch (arrayType) { - case Scalar::Int8: - atomicExchange8SignExtend(mem, value, output.gpr()); - break; - case Scalar::Uint8: - atomicExchange8ZeroExtend(mem, value, output.gpr()); - break; - case Scalar::Uint8Clamped: - atomicExchange8ZeroExtend(mem, value, output.gpr()); - break; - case Scalar::Int16: - atomicExchange16SignExtend(mem, value, output.gpr()); - break; - case Scalar::Uint16: - atomicExchange16ZeroExtend(mem, value, output.gpr()); - break; - case Scalar::Int32: - atomicExchange32(mem, value, output.gpr()); - break; - case Scalar::Uint32: - // At the moment, the code in MCallOptimize.cpp requires the output - // type to be double for uint32 arrays. See bug 1077305. - MOZ_ASSERT(output.isFloat()); - atomicExchange32(mem, value, temp); - convertUInt32ToDouble(temp, output.fpu()); - break; - default: - MOZ_CRASH("Invalid typed array type"); - } -} - -template void -MacroAssembler::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const Address& mem, - Register value, Register temp, AnyRegister output); -template void -MacroAssembler::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const BaseIndex& mem, - Register value, Register temp, AnyRegister output); - template void MacroAssembler::loadUnboxedProperty(T address, JSValueType type, TypedOrValueRegister output) diff --git a/js/src/jit/MacroAssembler.h b/js/src/jit/MacroAssembler.h index 44a040a7c7a2..cf4cedf45c64 100644 --- a/js/src/jit/MacroAssembler.h +++ b/js/src/jit/MacroAssembler.h @@ -1074,14 +1074,6 @@ class MacroAssembler : public MacroAssemblerSpecific } } - template - void compareExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, Register oldval, Register newval, - Register temp, AnyRegister output); - - template - void atomicExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, Register value, - Register temp, AnyRegister output); - void storeToTypedFloatArray(Scalar::Type arrayType, FloatRegister value, const BaseIndex& dest, unsigned numElems = 0); void storeToTypedFloatArray(Scalar::Type arrayType, FloatRegister value, const Address& dest, diff --git a/js/src/jit/arm/CodeGenerator-arm.cpp b/js/src/jit/arm/CodeGenerator-arm.cpp index f165ce97bcfe..21ea332a1e59 100644 --- a/js/src/jit/arm/CodeGenerator-arm.cpp +++ b/js/src/jit/arm/CodeGenerator-arm.cpp @@ -1690,6 +1690,48 @@ CodeGeneratorARM::visitStoreTypedArrayElementStatic(LStoreTypedArrayElementStati MOZ_CRASH("NYI"); } +void +CodeGeneratorARM::visitCompareExchangeTypedArrayElement(LCompareExchangeTypedArrayElement* lir) +{ + Register elements = ToRegister(lir->elements()); + AnyRegister output = ToAnyRegister(lir->output()); + Register temp = lir->temp()->isBogusTemp() ? InvalidReg : ToRegister(lir->temp()); + + Register oldval = ToRegister(lir->oldval()); + Register newval = ToRegister(lir->newval()); + + Scalar::Type arrayType = lir->mir()->arrayType(); + int width = Scalar::byteSize(arrayType); + + if (lir->index()->isConstant()) { + Address dest(elements, ToInt32(lir->index()) * width); + masm.compareExchangeToTypedIntArray(arrayType, dest, oldval, newval, temp, output); + } else { + BaseIndex dest(elements, ToRegister(lir->index()), ScaleFromElemWidth(width)); + masm.compareExchangeToTypedIntArray(arrayType, dest, oldval, newval, temp, output); + } +} + +void +CodeGeneratorARM::visitAtomicExchangeTypedArrayElement(LAtomicExchangeTypedArrayElement* lir) +{ + Register elements = ToRegister(lir->elements()); + AnyRegister output = ToAnyRegister(lir->output()); + Register temp = lir->temp()->isBogusTemp() ? InvalidReg : ToRegister(lir->temp()); + + Register value = ToRegister(lir->value()); + + Scalar::Type arrayType = lir->mir()->arrayType(); + int width = Scalar::byteSize(arrayType); + + if (lir->index()->isConstant()) { + Address dest(elements, ToInt32(lir->index()) * width); + masm.atomicExchangeToTypedIntArray(arrayType, dest, value, temp, output); + } else { + BaseIndex dest(elements, ToRegister(lir->index()), ScaleFromElemWidth(width)); + masm.atomicExchangeToTypedIntArray(arrayType, dest, value, temp, output); + } +} template void diff --git a/js/src/jit/arm/CodeGenerator-arm.h b/js/src/jit/arm/CodeGenerator-arm.h index 8f1520bdbe03..f03c3999a506 100644 --- a/js/src/jit/arm/CodeGenerator-arm.h +++ b/js/src/jit/arm/CodeGenerator-arm.h @@ -196,6 +196,8 @@ class CodeGeneratorARM : public CodeGeneratorShared void visitStoreTypedArrayElementStatic(LStoreTypedArrayElementStatic* ins); void visitAtomicTypedArrayElementBinop(LAtomicTypedArrayElementBinop* lir); void visitAtomicTypedArrayElementBinopForEffect(LAtomicTypedArrayElementBinopForEffect* lir); + void visitCompareExchangeTypedArrayElement(LCompareExchangeTypedArrayElement* lir); + void visitAtomicExchangeTypedArrayElement(LAtomicExchangeTypedArrayElement* lir); void visitAsmJSCall(LAsmJSCall* ins); void visitAsmJSLoadHeap(LAsmJSLoadHeap* ins); void visitAsmJSStoreHeap(LAsmJSStoreHeap* ins); diff --git a/js/src/jit/arm/MacroAssembler-arm.cpp b/js/src/jit/arm/MacroAssembler-arm.cpp index 4f5ae20825b6..6e30fb7c8560 100644 --- a/js/src/jit/arm/MacroAssembler-arm.cpp +++ b/js/src/jit/arm/MacroAssembler-arm.cpp @@ -4827,6 +4827,95 @@ template void js::jit::MacroAssemblerARMCompat::atomicEffectOp(int nbytes, AtomicOp op, const Register& value, const BaseIndex& mem, Register flagTemp); +template +void +MacroAssemblerARMCompat::compareExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, + Register oldval, Register newval, + Register temp, AnyRegister output) +{ + switch (arrayType) { + case Scalar::Int8: + compareExchange8SignExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Uint8: + compareExchange8ZeroExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Uint8Clamped: + compareExchange8ZeroExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Int16: + compareExchange16SignExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Uint16: + compareExchange16ZeroExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Int32: + compareExchange32(mem, oldval, newval, output.gpr()); + break; + case Scalar::Uint32: + // At the moment, the code in MCallOptimize.cpp requires the output + // type to be double for uint32 arrays. See bug 1077305. + MOZ_ASSERT(output.isFloat()); + compareExchange32(mem, oldval, newval, temp); + convertUInt32ToDouble(temp, output.fpu()); + break; + default: + MOZ_CRASH("Invalid typed array type"); + } +} + +template void +MacroAssemblerARMCompat::compareExchangeToTypedIntArray(Scalar::Type arrayType, const Address& mem, + Register oldval, Register newval, Register temp, + AnyRegister output); +template void +MacroAssemblerARMCompat::compareExchangeToTypedIntArray(Scalar::Type arrayType, const BaseIndex& mem, + Register oldval, Register newval, Register temp, + AnyRegister output); + +template +void +MacroAssemblerARMCompat::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, + Register value, Register temp, AnyRegister output) +{ + switch (arrayType) { + case Scalar::Int8: + atomicExchange8SignExtend(mem, value, output.gpr()); + break; + case Scalar::Uint8: + atomicExchange8ZeroExtend(mem, value, output.gpr()); + break; + case Scalar::Uint8Clamped: + atomicExchange8ZeroExtend(mem, value, output.gpr()); + break; + case Scalar::Int16: + atomicExchange16SignExtend(mem, value, output.gpr()); + break; + case Scalar::Uint16: + atomicExchange16ZeroExtend(mem, value, output.gpr()); + break; + case Scalar::Int32: + atomicExchange32(mem, value, output.gpr()); + break; + case Scalar::Uint32: + // At the moment, the code in MCallOptimize.cpp requires the output + // type to be double for uint32 arrays. See bug 1077305. + MOZ_ASSERT(output.isFloat()); + atomicExchange32(mem, value, temp); + convertUInt32ToDouble(temp, output.fpu()); + break; + default: + MOZ_CRASH("Invalid typed array type"); + } +} + +template void +MacroAssemblerARMCompat::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const Address& mem, + Register value, Register temp, AnyRegister output); +template void +MacroAssemblerARMCompat::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const BaseIndex& mem, + Register value, Register temp, AnyRegister output); + void MacroAssemblerARMCompat::profilerEnterFrame(Register framePtr, Register scratch) { diff --git a/js/src/jit/arm/MacroAssembler-arm.h b/js/src/jit/arm/MacroAssembler-arm.h index fa0da656ece1..ca06e1291554 100644 --- a/js/src/jit/arm/MacroAssembler-arm.h +++ b/js/src/jit/arm/MacroAssembler-arm.h @@ -1616,6 +1616,14 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM atomicEffectOp(4, AtomicFetchXorOp, value, mem, flagTemp); } + template + void compareExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, Register oldval, Register newval, + Register temp, AnyRegister output); + + template + void atomicExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, Register value, + Register temp, AnyRegister output); + void clampIntToUint8(Register reg) { // Look at (reg >> 8) if it is 0, then reg shouldn't be clamped if it is // <0, then we want to clamp to 0, otherwise, we wish to clamp to 255 diff --git a/js/src/jit/arm64/CodeGenerator-arm64.cpp b/js/src/jit/arm64/CodeGenerator-arm64.cpp index 0b2ad6916a53..dd746d5bf359 100644 --- a/js/src/jit/arm64/CodeGenerator-arm64.cpp +++ b/js/src/jit/arm64/CodeGenerator-arm64.cpp @@ -737,3 +737,47 @@ CodeGeneratorARM64::setReturnDoubleRegs(LiveRegisterSet* regs) regs->add(s1); regs->add(ReturnDoubleReg); } + +void +CodeGeneratorARM64::visitCompareExchangeTypedArrayElement(LCompareExchangeTypedArrayElement* lir) +{ + Register elements = ToRegister(lir->elements()); + AnyRegister output = ToAnyRegister(lir->output()); + Register temp = lir->temp()->isBogusTemp() ? InvalidReg : ToRegister(lir->temp()); + + Register oldval = ToRegister(lir->oldval()); + Register newval = ToRegister(lir->newval()); + + Scalar::Type arrayType = lir->mir()->arrayType(); + int width = Scalar::byteSize(arrayType); + + if (lir->index()->isConstant()) { + Address dest(elements, ToInt32(lir->index()) * width); + masm.compareExchangeToTypedIntArray(arrayType, dest, oldval, newval, temp, output); + } else { + BaseIndex dest(elements, ToRegister(lir->index()), ScaleFromElemWidth(width)); + masm.compareExchangeToTypedIntArray(arrayType, dest, oldval, newval, temp, output); + } +} + +void +CodeGeneratorARM64::visitAtomicExchangeTypedArrayElement(LAtomicExchangeTypedArrayElement* lir) +{ + Register elements = ToRegister(lir->elements()); + AnyRegister output = ToAnyRegister(lir->output()); + Register temp = lir->temp()->isBogusTemp() ? InvalidReg : ToRegister(lir->temp()); + + Register value = ToRegister(lir->value()); + + Scalar::Type arrayType = lir->mir()->arrayType(); + int width = Scalar::byteSize(arrayType); + + if (lir->index()->isConstant()) { + Address dest(elements, ToInt32(lir->index()) * width); + masm.atomicExchangeToTypedIntArray(arrayType, dest, value, temp, output); + } else { + BaseIndex dest(elements, ToRegister(lir->index()), ScaleFromElemWidth(width)); + masm.atomicExchangeToTypedIntArray(arrayType, dest, value, temp, output); + } +} + diff --git a/js/src/jit/arm64/CodeGenerator-arm64.h b/js/src/jit/arm64/CodeGenerator-arm64.h index 313e1fbf858a..da817fd10f7b 100644 --- a/js/src/jit/arm64/CodeGenerator-arm64.h +++ b/js/src/jit/arm64/CodeGenerator-arm64.h @@ -207,6 +207,8 @@ class CodeGeneratorARM64 : public CodeGeneratorShared void visitNegF(LNegF* lir); void visitLoadTypedArrayElementStatic(LLoadTypedArrayElementStatic* ins); void visitStoreTypedArrayElementStatic(LStoreTypedArrayElementStatic* ins); + void visitCompareExchangeTypedArrayElement(LCompareExchangeTypedArrayElement* lir); + void visitAtomicExchangeTypedArrayElement(LAtomicExchangeTypedArrayElement* lir); void visitAsmJSCall(LAsmJSCall* ins); void visitAsmJSLoadHeap(LAsmJSLoadHeap* ins); void visitAsmJSStoreHeap(LAsmJSStoreHeap* ins); diff --git a/js/src/jit/arm64/MacroAssembler-arm64.cpp b/js/src/jit/arm64/MacroAssembler-arm64.cpp index 7efc9470be33..50e2f2030066 100644 --- a/js/src/jit/arm64/MacroAssembler-arm64.cpp +++ b/js/src/jit/arm64/MacroAssembler-arm64.cpp @@ -260,6 +260,95 @@ MacroAssemblerCompat::breakpoint() Brk((code++) & 0xffff); } +template +void +MacroAssemblerCompat::compareExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, + Register oldval, Register newval, + Register temp, AnyRegister output) +{ + switch (arrayType) { + case Scalar::Int8: + compareExchange8SignExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Uint8: + compareExchange8ZeroExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Uint8Clamped: + compareExchange8ZeroExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Int16: + compareExchange16SignExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Uint16: + compareExchange16ZeroExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Int32: + compareExchange32(mem, oldval, newval, output.gpr()); + break; + case Scalar::Uint32: + // At the moment, the code in MCallOptimize.cpp requires the output + // type to be double for uint32 arrays. See bug 1077305. + MOZ_ASSERT(output.isFloat()); + compareExchange32(mem, oldval, newval, temp); + convertUInt32ToDouble(temp, output.fpu()); + break; + default: + MOZ_CRASH("Invalid typed array type"); + } +} + +template void +MacroAssemblerCompat::compareExchangeToTypedIntArray(Scalar::Type arrayType, const Address& mem, + Register oldval, Register newval, Register temp, + AnyRegister output); +template void +MacroAssemblerCompat::compareExchangeToTypedIntArray(Scalar::Type arrayType, const BaseIndex& mem, + Register oldval, Register newval, Register temp, + AnyRegister output); + +template +void +MacroAssemblerCompat::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, + Register value, Register temp, AnyRegister output) +{ + switch (arrayType) { + case Scalar::Int8: + atomicExchange8SignExtend(mem, value, output.gpr()); + break; + case Scalar::Uint8: + atomicExchange8ZeroExtend(mem, value, output.gpr()); + break; + case Scalar::Uint8Clamped: + atomicExchange8ZeroExtend(mem, value, output.gpr()); + break; + case Scalar::Int16: + atomicExchange16SignExtend(mem, value, output.gpr()); + break; + case Scalar::Uint16: + atomicExchange16ZeroExtend(mem, value, output.gpr()); + break; + case Scalar::Int32: + atomicExchange32(mem, value, output.gpr()); + break; + case Scalar::Uint32: + // At the moment, the code in MCallOptimize.cpp requires the output + // type to be double for uint32 arrays. See bug 1077305. + MOZ_ASSERT(output.isFloat()); + atomicExchange32(mem, value, temp); + convertUInt32ToDouble(temp, output.fpu()); + break; + default: + MOZ_CRASH("Invalid typed array type"); + } +} + +template void +MacroAssemblerCompat::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const Address& mem, + Register value, Register temp, AnyRegister output); +template void +MacroAssemblerCompat::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const BaseIndex& mem, + Register value, Register temp, AnyRegister output); + //{{{ check_macroassembler_style // =============================================================== // Stack manipulation functions. diff --git a/js/src/jit/arm64/MacroAssembler-arm64.h b/js/src/jit/arm64/MacroAssembler-arm64.h index 6434cbc079f1..c6081de178ab 100644 --- a/js/src/jit/arm64/MacroAssembler-arm64.h +++ b/js/src/jit/arm64/MacroAssembler-arm64.h @@ -2863,6 +2863,14 @@ class MacroAssemblerCompat : public vixl::MacroAssembler atomicEffectOp(4, AtomicFetchXorOp, value, mem); } + template + void compareExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, Register oldval, Register newval, + Register temp, AnyRegister output); + + template + void atomicExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, Register value, + Register temp, AnyRegister output); + // Emit a BLR or NOP instruction. ToggleCall can be used to patch // this instruction. CodeOffsetLabel toggledCall(JitCode* target, bool enabled) { diff --git a/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp b/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp index 40cff8339825..c49042a18ad2 100644 --- a/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp +++ b/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp @@ -3320,6 +3320,49 @@ CodeGeneratorX86Shared::visitSimdSelect(LSimdSelect* ins) masm.bitwiseOrX4(Operand(temp), output); } +void +CodeGeneratorX86Shared::visitCompareExchangeTypedArrayElement(LCompareExchangeTypedArrayElement* lir) +{ + Register elements = ToRegister(lir->elements()); + AnyRegister output = ToAnyRegister(lir->output()); + Register temp = lir->temp()->isBogusTemp() ? InvalidReg : ToRegister(lir->temp()); + + Register oldval = ToRegister(lir->oldval()); + Register newval = ToRegister(lir->newval()); + + Scalar::Type arrayType = lir->mir()->arrayType(); + int width = Scalar::byteSize(arrayType); + + if (lir->index()->isConstant()) { + Address dest(elements, ToInt32(lir->index()) * width); + masm.compareExchangeToTypedIntArray(arrayType, dest, oldval, newval, temp, output); + } else { + BaseIndex dest(elements, ToRegister(lir->index()), ScaleFromElemWidth(width)); + masm.compareExchangeToTypedIntArray(arrayType, dest, oldval, newval, temp, output); + } +} + +void +CodeGeneratorX86Shared::visitAtomicExchangeTypedArrayElement(LAtomicExchangeTypedArrayElement* lir) +{ + Register elements = ToRegister(lir->elements()); + AnyRegister output = ToAnyRegister(lir->output()); + Register temp = lir->temp()->isBogusTemp() ? InvalidReg : ToRegister(lir->temp()); + + Register value = ToRegister(lir->value()); + + Scalar::Type arrayType = lir->mir()->arrayType(); + int width = Scalar::byteSize(arrayType); + + if (lir->index()->isConstant()) { + Address dest(elements, ToInt32(lir->index()) * width); + masm.atomicExchangeToTypedIntArray(arrayType, dest, value, temp, output); + } else { + BaseIndex dest(elements, ToRegister(lir->index()), ScaleFromElemWidth(width)); + masm.atomicExchangeToTypedIntArray(arrayType, dest, value, temp, output); + } +} + template void CodeGeneratorX86Shared::atomicBinopToTypedIntArray(AtomicOp op, Scalar::Type arrayType, const S& value, diff --git a/js/src/jit/x86-shared/CodeGenerator-x86-shared.h b/js/src/jit/x86-shared/CodeGenerator-x86-shared.h index 2200d9152aa5..1672916e7b14 100644 --- a/js/src/jit/x86-shared/CodeGenerator-x86-shared.h +++ b/js/src/jit/x86-shared/CodeGenerator-x86-shared.h @@ -243,6 +243,8 @@ class CodeGeneratorX86Shared : public CodeGeneratorShared virtual void visitMemoryBarrier(LMemoryBarrier* ins); virtual void visitAtomicTypedArrayElementBinop(LAtomicTypedArrayElementBinop* lir); virtual void visitAtomicTypedArrayElementBinopForEffect(LAtomicTypedArrayElementBinopForEffect* lir); + virtual void visitCompareExchangeTypedArrayElement(LCompareExchangeTypedArrayElement* lir); + virtual void visitAtomicExchangeTypedArrayElement(LAtomicExchangeTypedArrayElement* lir); void visitOutOfLineLoadTypedArrayOutOfBounds(OutOfLineLoadTypedArrayOutOfBounds* ool); void visitOffsetBoundsCheck(OffsetBoundsCheck* oolCheck); diff --git a/js/src/jit/x86-shared/MacroAssembler-x86-shared.cpp b/js/src/jit/x86-shared/MacroAssembler-x86-shared.cpp index d1906fb3d84a..d1a2e0652bfe 100644 --- a/js/src/jit/x86-shared/MacroAssembler-x86-shared.cpp +++ b/js/src/jit/x86-shared/MacroAssembler-x86-shared.cpp @@ -143,6 +143,96 @@ MacroAssemblerX86Shared::asMasm() const return *static_cast(this); } +template +void +MacroAssemblerX86Shared::compareExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, + Register oldval, Register newval, + Register temp, AnyRegister output) +{ + switch (arrayType) { + case Scalar::Int8: + compareExchange8SignExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Uint8: + compareExchange8ZeroExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Uint8Clamped: + compareExchange8ZeroExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Int16: + compareExchange16SignExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Uint16: + compareExchange16ZeroExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Int32: + compareExchange32(mem, oldval, newval, output.gpr()); + break; + case Scalar::Uint32: + // At the moment, the code in MCallOptimize.cpp requires the output + // type to be double for uint32 arrays. See bug 1077305. + MOZ_ASSERT(output.isFloat()); + compareExchange32(mem, oldval, newval, temp); + asMasm().convertUInt32ToDouble(temp, output.fpu()); + break; + default: + MOZ_CRASH("Invalid typed array type"); + } +} + +template void +MacroAssemblerX86Shared::compareExchangeToTypedIntArray(Scalar::Type arrayType, const Address& mem, + Register oldval, Register newval, Register temp, + AnyRegister output); +template void +MacroAssemblerX86Shared::compareExchangeToTypedIntArray(Scalar::Type arrayType, const BaseIndex& mem, + Register oldval, Register newval, Register temp, + AnyRegister output); + +template +void +MacroAssemblerX86Shared::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, + Register value, Register temp, AnyRegister output) +{ + switch (arrayType) { + case Scalar::Int8: + atomicExchange8SignExtend(mem, value, output.gpr()); + break; + case Scalar::Uint8: + atomicExchange8ZeroExtend(mem, value, output.gpr()); + break; + case Scalar::Uint8Clamped: + atomicExchange8ZeroExtend(mem, value, output.gpr()); + break; + case Scalar::Int16: + atomicExchange16SignExtend(mem, value, output.gpr()); + break; + case Scalar::Uint16: + atomicExchange16ZeroExtend(mem, value, output.gpr()); + break; + case Scalar::Int32: + atomicExchange32(mem, value, output.gpr()); + break; + case Scalar::Uint32: + // At the moment, the code in MCallOptimize.cpp requires the output + // type to be double for uint32 arrays. See bug 1077305. + MOZ_ASSERT(output.isFloat()); + atomicExchange32(mem, value, temp); + asMasm().convertUInt32ToDouble(temp, output.fpu()); + break; + default: + MOZ_CRASH("Invalid typed array type"); + } +} + +template void +MacroAssemblerX86Shared::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const Address& mem, + Register value, Register temp, AnyRegister output); +template void +MacroAssemblerX86Shared::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const BaseIndex& mem, + Register value, Register temp, AnyRegister output); + + //{{{ check_macroassembler_style // =============================================================== // Stack manipulation functions. diff --git a/js/src/jit/x86-shared/MacroAssembler-x86-shared.h b/js/src/jit/x86-shared/MacroAssembler-x86-shared.h index 406ee1171a16..15dec9c96c27 100644 --- a/js/src/jit/x86-shared/MacroAssembler-x86-shared.h +++ b/js/src/jit/x86-shared/MacroAssembler-x86-shared.h @@ -1455,6 +1455,14 @@ class MacroAssemblerX86Shared : public Assembler ret(); } + template + void compareExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, Register oldval, Register newval, + Register temp, AnyRegister output); + + template + void atomicExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, Register value, + Register temp, AnyRegister output); + protected: bool buildOOLFakeExitFrame(void* fakeReturnAddr); }; From cf0b78ff4cb8f39a5d26374c160d523b883a1749 Mon Sep 17 00:00:00 2001 From: Matt Woodrow Date: Tue, 3 Nov 2015 02:03:47 -0600 Subject: [PATCH 006/113] Bug 1156238. Always stop at the root reference frame when looking for an animated geometry root. r=roc,mattwoodrow This removes the "aStopAtAncestor" argument to agr computing functions. In most cases an AGR was passed for the stop at ancestor, so we'd stop at it anyway since it was an AGR. Most of the remaining cases the root reference frame was passed. And in a few cases something else was passed, which we probably don't want (returning something that isn't an AGR and isn't the root reference frame as an AGR). The ShouldFixToViewport case is a little tricky. We want to get the AGR of the nearest viewport frame, but only if we don't have to cross our root reference frame to get it. This happens in practice for example when a select dropdown has background-attachment: fixed inside it. Except for the ShouldFixToViewport bit, this patch is a subset of part 3 in bug 1205087 (which has more changes, and has been temporarily backed out, the remaining bits can hopefully land soon). The ShouldFixToViewport part is by Timothy Nikkel --- layout/base/FrameLayerBuilder.cpp | 18 ++++++---------- layout/base/nsDisplayList.cpp | 20 +++++++---------- layout/base/nsDisplayList.h | 36 +++++++------------------------ layout/base/nsLayoutUtils.cpp | 22 +++++++++++-------- layout/base/nsLayoutUtils.h | 11 +++++----- 5 files changed, 42 insertions(+), 65 deletions(-) diff --git a/layout/base/FrameLayerBuilder.cpp b/layout/base/FrameLayerBuilder.cpp index a0433c71fc82..372cef755d6e 100644 --- a/layout/base/FrameLayerBuilder.cpp +++ b/layout/base/FrameLayerBuilder.cpp @@ -2119,8 +2119,7 @@ ContainerState::GetLayerCreationHint(const nsIFrame* aAnimatedGeometryRoot) nsIFrame* fParent; for (const nsIFrame* f = aAnimatedGeometryRoot; f != mContainerAnimatedGeometryRoot; - f = nsLayoutUtils::GetAnimatedGeometryRootForFrame(mBuilder, - fParent, mContainerAnimatedGeometryRoot)) { + f = nsLayoutUtils::GetAnimatedGeometryRootForFrame(mBuilder, fParent)) { fParent = nsLayoutUtils::GetCrossDocParentFrame(f); if (!fParent) { break; @@ -2801,7 +2800,7 @@ PaintedLayerDataTree::GetParentAnimatedGeometryRoot(const nsIFrame* aAnimatedGeo } nsIFrame* agr = Builder()->FindAnimatedGeometryRootFor( - const_cast(aAnimatedGeometryRoot), Builder()->RootReferenceFrame()); + const_cast(aAnimatedGeometryRoot)); MOZ_ASSERT_IF(agr, nsLayoutUtils::IsAncestorFrameCrossDoc(Builder()->RootReferenceFrame(), agr)); if (agr != aAnimatedGeometryRoot) { return agr; @@ -2812,7 +2811,7 @@ PaintedLayerDataTree::GetParentAnimatedGeometryRoot(const nsIFrame* aAnimatedGeo if (!parent) { return nullptr; } - return Builder()->FindAnimatedGeometryRootFor(parent, Builder()->RootReferenceFrame()); + return Builder()->FindAnimatedGeometryRootFor(parent); } void @@ -3791,8 +3790,7 @@ GetScrollClipIntersection(nsDisplayListBuilder* aBuilder, const nsIFrame* aAnima nsIFrame* fParent; for (const nsIFrame* f = aAnimatedGeometryRoot; f != aStopAtAnimatedGeometryRoot; - f = nsLayoutUtils::GetAnimatedGeometryRootForFrame(aBuilder, - fParent, aStopAtAnimatedGeometryRoot)) { + f = nsLayoutUtils::GetAnimatedGeometryRootForFrame(aBuilder, fParent)) { fParent = nsLayoutUtils::GetCrossDocParentFrame(f); if (!fParent) { // This means aStopAtAnimatedGeometryRoot was not an ancestor @@ -3906,7 +3904,7 @@ ContainerState::ProcessDisplayItems(nsDisplayList* aList) // take ShouldFixToViewport() into account, so it will return something different // for fixed background items. animatedGeometryRootForScrollMetadata = nsLayoutUtils::GetAnimatedGeometryRootForFrame( - mBuilder, item->Frame(), item->ReferenceFrame()); + mBuilder, item->Frame()); } else { // For inactive layer subtrees, splitting content into PaintedLayers // based on animated geometry roots is pointless. It's more efficient @@ -4759,8 +4757,7 @@ ContainerState::SetupScrollingMetadata(NewLayerEntry* aEntry) nsIFrame* fParent; for (const nsIFrame* f = aEntry->mAnimatedGeometryRootForScrollMetadata; f != mContainerAnimatedGeometryRoot; - f = nsLayoutUtils::GetAnimatedGeometryRootForFrame(this->mBuilder, - fParent, mContainerAnimatedGeometryRoot)) { + f = nsLayoutUtils::GetAnimatedGeometryRootForFrame(this->mBuilder, fParent)) { fParent = nsLayoutUtils::GetCrossDocParentFrame(f); if (!fParent) { // This means mContainerAnimatedGeometryRoot was not an ancestor @@ -4904,8 +4901,7 @@ ContainerState::PostprocessRetainedLayers(nsIntRegion* aOpaqueRegionForContainer if (!e->mOpaqueRegion.IsEmpty()) { const nsIFrame* animatedGeometryRootToCover = animatedGeometryRootForOpaqueness; if (e->mOpaqueForAnimatedGeometryRootParent && - nsLayoutUtils::GetAnimatedGeometryRootForFrame(mBuilder, e->mAnimatedGeometryRoot->GetParent(), - mContainerAnimatedGeometryRoot) + nsLayoutUtils::GetAnimatedGeometryRootForFrame(mBuilder, e->mAnimatedGeometryRoot->GetParent()) == mContainerAnimatedGeometryRoot) { animatedGeometryRootToCover = mContainerAnimatedGeometryRoot; data = FindOpaqueRegionEntry(opaqueRegions, diff --git a/layout/base/nsDisplayList.cpp b/layout/base/nsDisplayList.cpp index 11dcbef8b439..bf1ba56f93da 100644 --- a/layout/base/nsDisplayList.cpp +++ b/layout/base/nsDisplayList.cpp @@ -1079,23 +1079,20 @@ nsDisplayListBuilder::IsAnimatedGeometryRoot(nsIFrame* aFrame, nsIFrame** aParen bool nsDisplayListBuilder::GetCachedAnimatedGeometryRoot(const nsIFrame* aFrame, - const nsIFrame* aStopAtAncestor, nsIFrame** aOutResult) { - AnimatedGeometryRootLookup lookup(aFrame, aStopAtAncestor); - return mAnimatedGeometryRootCache.Get(lookup, aOutResult); + return mAnimatedGeometryRootCache.Get(const_cast(aFrame), aOutResult); } static nsIFrame* ComputeAnimatedGeometryRootFor(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, - const nsIFrame* aStopAtAncestor = nullptr, bool aUseCache = false) { nsIFrame* cursor = aFrame; - while (cursor != aStopAtAncestor) { + while (cursor != aBuilder->RootReferenceFrame()) { if (aUseCache) { nsIFrame* result; - if (aBuilder->GetCachedAnimatedGeometryRoot(cursor, aStopAtAncestor, &result)) { + if (aBuilder->GetCachedAnimatedGeometryRoot(cursor, &result)) { return result; } } @@ -1108,15 +1105,14 @@ ComputeAnimatedGeometryRootFor(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, } nsIFrame* -nsDisplayListBuilder::FindAnimatedGeometryRootFor(nsIFrame* aFrame, const nsIFrame* aStopAtAncestor) +nsDisplayListBuilder::FindAnimatedGeometryRootFor(nsIFrame* aFrame) { if (aFrame == mCurrentFrame) { return mCurrentAnimatedGeometryRoot; } - nsIFrame* result = ComputeAnimatedGeometryRootFor(this, aFrame, aStopAtAncestor, true); - AnimatedGeometryRootLookup lookup(aFrame, aStopAtAncestor); - mAnimatedGeometryRootCache.Put(lookup, result); + nsIFrame* result = ComputeAnimatedGeometryRootFor(this, aFrame, true); + mAnimatedGeometryRootCache.Put(aFrame, result); return result; } @@ -1130,8 +1126,8 @@ nsDisplayListBuilder::RecomputeCurrentAnimatedGeometryRoot() mAnimatedGeometryRootCache.Clear(); mCurrentAnimatedGeometryRoot = ComputeAnimatedGeometryRootFor(this, const_cast(mCurrentFrame)); - AnimatedGeometryRootLookup lookup(mCurrentFrame, nullptr); - mAnimatedGeometryRootCache.Put(lookup, mCurrentAnimatedGeometryRoot); + MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(RootReferenceFrame(), mCurrentAnimatedGeometryRoot)); + mAnimatedGeometryRootCache.Put(const_cast(mCurrentFrame), mCurrentAnimatedGeometryRoot); } void diff --git a/layout/base/nsDisplayList.h b/layout/base/nsDisplayList.h index a518e97394b0..c067a4befcfb 100644 --- a/layout/base/nsDisplayList.h +++ b/layout/base/nsDisplayList.h @@ -260,7 +260,7 @@ public: * Returns the nearest ancestor frame to aFrame that is considered to have * (or will have) animated geometry. This can return aFrame. */ - nsIFrame* FindAnimatedGeometryRootFor(nsIFrame* aFrame, const nsIFrame* aStopAtAncestor = nullptr); + nsIFrame* FindAnimatedGeometryRootFor(nsIFrame* aFrame); /** * @return the root of the display list's frame (sub)tree, whose origin @@ -644,11 +644,11 @@ public: aBuilder->mCurrentAnimatedGeometryRoot = aForChild; } } else { - // Stop at the previous animated geometry root to help cases that - // aren't immediate descendents. aBuilder->mCurrentAnimatedGeometryRoot = - aBuilder->FindAnimatedGeometryRootFor(aForChild, aBuilder->mCurrentAnimatedGeometryRoot); + aBuilder->FindAnimatedGeometryRootFor(aForChild); } + MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(aBuilder->RootReferenceFrame(), + aBuilder->mCurrentAnimatedGeometryRoot)); aBuilder->mCurrentFrame = aForChild; aBuilder->mDirtyRect = aDirtyRect; aBuilder->mIsAtRootOfPseudoStackingContext = aIsRoot; @@ -976,12 +976,11 @@ public: bool IsInWillChangeBudget(nsIFrame* aFrame, const nsSize& aSize); /** - * Look up the cached animated geometry root for aFrame subject to - * aStopAtAncestor. Store the nsIFrame* result into *aOutResult, and return - * true if the cache was hit. Return false if the cache was not hit. + * Look up the cached animated geometry root for aFrame subject Store the + * nsIFrame* result into *aOutResult, and return true if the cache was hit. + * Return false if the cache was not hit. */ bool GetCachedAnimatedGeometryRoot(const nsIFrame* aFrame, - const nsIFrame* aStopAtAncestor, nsIFrame** aOutResult); void SetCommittedScrollInfoItemList(nsDisplayList* aScrollInfoItemStorage) { @@ -1113,27 +1112,8 @@ private: // The animated geometry root for mCurrentFrame. nsIFrame* mCurrentAnimatedGeometryRoot; - struct AnimatedGeometryRootLookup { - const nsIFrame* mFrame; - const nsIFrame* mStopAtFrame; - - AnimatedGeometryRootLookup(const nsIFrame* aFrame, const nsIFrame* aStopAtFrame) - : mFrame(aFrame) - , mStopAtFrame(aStopAtFrame) - { - } - - PLDHashNumber Hash() const { - return mozilla::HashBytes(this, sizeof(*this)); - } - - bool operator==(const AnimatedGeometryRootLookup& aOther) const { - return mFrame == aOther.mFrame && mStopAtFrame == aOther.mStopAtFrame; - } - }; // Cache for storing animated geometry roots for arbitrary frames - nsDataHashtable, nsIFrame*> - mAnimatedGeometryRootCache; + nsDataHashtable, nsIFrame*> mAnimatedGeometryRootCache; // will-change budget tracker nsDataHashtable, DocumentWillChangeBudget> mWillChangeBudget; diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index 3f2eaf34e234..80eb9473af22 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -1391,12 +1391,17 @@ nsLayoutUtils::GetAfterFrame(nsIFrame* aFrame) // static nsIFrame* -nsLayoutUtils::GetClosestFrameOfType(nsIFrame* aFrame, nsIAtom* aFrameType) +nsLayoutUtils::GetClosestFrameOfType(nsIFrame* aFrame, + nsIAtom* aFrameType, + nsIFrame* aStopAt) { for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) { if (frame->GetType() == aFrameType) { return frame; } + if (frame == aStopAt) { + break; + } } return nullptr; } @@ -1866,10 +1871,9 @@ nsLayoutUtils::IsScrollbarThumbLayerized(nsIFrame* aThumbFrame) nsIFrame* nsLayoutUtils::GetAnimatedGeometryRootForFrame(nsDisplayListBuilder* aBuilder, - nsIFrame* aFrame, - const nsIFrame* aStopAtAncestor) + nsIFrame* aFrame) { - return aBuilder->FindAnimatedGeometryRootFor(aFrame, aStopAtAncestor); + return aBuilder->FindAnimatedGeometryRootFor(aFrame); } nsIFrame* @@ -1883,12 +1887,12 @@ nsLayoutUtils::GetAnimatedGeometryRootFor(nsDisplayItem* aItem, // frames in its document. InvalidateFixedBackgroundFramesFromList in // nsGfxScrollFrame will not repaint this item when scrolling occurs. nsIFrame* viewportFrame = - nsLayoutUtils::GetClosestFrameOfType(f, nsGkAtoms::viewportFrame); - NS_ASSERTION(viewportFrame, "no viewport???"); - return GetAnimatedGeometryRootForFrame(aBuilder, viewportFrame, - aBuilder->FindReferenceFrameFor(viewportFrame)); + nsLayoutUtils::GetClosestFrameOfType(f, nsGkAtoms::viewportFrame, aBuilder->RootReferenceFrame()); + if (viewportFrame) { + return GetAnimatedGeometryRootForFrame(aBuilder, viewportFrame); + } } - return GetAnimatedGeometryRootForFrame(aBuilder, f, aItem->ReferenceFrame()); + return GetAnimatedGeometryRootForFrame(aBuilder, f); } // static diff --git a/layout/base/nsLayoutUtils.h b/layout/base/nsLayoutUtils.h index 2480e57a6204..b129ef8ddc49 100644 --- a/layout/base/nsLayoutUtils.h +++ b/layout/base/nsLayoutUtils.h @@ -273,10 +273,13 @@ public: * * @param aFrame the frame to start at * @param aFrameType the frame type to look for + * @param aStopAt a frame to stop at after we checked it * @return a frame of the given type or nullptr if no * such ancestor exists */ - static nsIFrame* GetClosestFrameOfType(nsIFrame* aFrame, nsIAtom* aFrameType); + static nsIFrame* GetClosestFrameOfType(nsIFrame* aFrame, + nsIAtom* aFrameType, + nsIFrame* aStopAt = nullptr); /** * Given a frame, search up the frame tree until we find an @@ -554,12 +557,10 @@ public: /** * Finds the nearest ancestor frame to aFrame that is considered to have (or - * will have) "animated geometry". This could be aFrame. Returns - * aStopAtAncestor if no closer ancestor is found. + * will have) "animated geometry". This could be aFrame. */ static nsIFrame* GetAnimatedGeometryRootForFrame(nsDisplayListBuilder* aBuilder, - nsIFrame* aFrame, - const nsIFrame* aStopAtAncestor); + nsIFrame* aFrame); /** * GetScrollableFrameFor returns the scrollable frame for a scrolled frame From fab15510f7118b476e8052b00fb7b506450addea Mon Sep 17 00:00:00 2001 From: Timothy Nikkel Date: Tue, 3 Nov 2015 02:03:47 -0600 Subject: [PATCH 007/113] Bug 1156238. Fix the computation of animated geometry roots for transform items. r=mattwoodrow Removing the "stop at ancestor" parameter from functions that compute AGR meant that nsLayoutUtils::GetAnimatedGeometryRootFor could no longer pass the display item's reference frame as the "stop at ancestor" which meant that the AGR could cross the reference frame for the item, which we don't want. So we make transformed frames into AGRs. This makes the computation of display items whose frames are transformed tricky. We need the AGR of the transform item to be the ancestor AGR, not the underlying frame of the transform item (which is now an AGR). So we modify nsLayoutUtils::GetAnimatedGeometryRootFor to handle this. (The patch from bug 1205087 didn't suffer from this problem because it special cased the computation of the AGR of transform items. Leaving anybody who called nsLayoutUtils::GetAnimatedGeometryRootFor to get the wrong result.) The computation of the AGR for scroll metadata in ContainerState::ProcessDisplayItems specifically bypassed nsLayoutUtils::GetAnimatedGeometryRootFor to avoid it's special processing of fixed background items. However we do want the AGR for scroll metadata to do this special processing of transform items. So we add a flag to bypass the fixed background behaviour and use it for the scroll metadata AGR. --- layout/base/FrameLayerBuilder.cpp | 4 ++-- layout/base/nsDisplayList.cpp | 6 ++++++ layout/base/nsDisplayList.h | 9 +++++++++ layout/base/nsLayoutUtils.cpp | 14 ++++++++++++-- layout/base/nsLayoutUtils.h | 11 ++++++++++- 5 files changed, 39 insertions(+), 5 deletions(-) diff --git a/layout/base/FrameLayerBuilder.cpp b/layout/base/FrameLayerBuilder.cpp index 372cef755d6e..16f690a26d0a 100644 --- a/layout/base/FrameLayerBuilder.cpp +++ b/layout/base/FrameLayerBuilder.cpp @@ -3903,8 +3903,8 @@ ContainerState::ProcessDisplayItems(nsDisplayList* aList) // Unlike GetAnimatedGeometryRootFor(), GetAnimatedGeometryRootForFrame() does not // take ShouldFixToViewport() into account, so it will return something different // for fixed background items. - animatedGeometryRootForScrollMetadata = nsLayoutUtils::GetAnimatedGeometryRootForFrame( - mBuilder, item->Frame()); + animatedGeometryRootForScrollMetadata = nsLayoutUtils::GetAnimatedGeometryRootFor( + item, mBuilder, nsLayoutUtils::AGR_IGNORE_BACKGROUND_ATTACHMENT_FIXED); } else { // For inactive layer subtrees, splitting content into PaintedLayers // based on animated geometry roots is pointless. It's more efficient diff --git a/layout/base/nsDisplayList.cpp b/layout/base/nsDisplayList.cpp index bf1ba56f93da..e8bd59fdb49c 100644 --- a/layout/base/nsDisplayList.cpp +++ b/layout/base/nsDisplayList.cpp @@ -1041,6 +1041,9 @@ nsDisplayListBuilder::IsAnimatedGeometryRoot(nsIFrame* aFrame, nsIFrame** aParen // for background-attachment:fixed elements. return true; } + if (aFrame->IsTransformed()) { + return true; + } nsIFrame* parent = nsLayoutUtils::GetCrossDocParentFrame(aFrame); if (!parent) @@ -4776,6 +4779,9 @@ nsDisplayTransform::nsDisplayTransform(nsDisplayListBuilder* aBuilder, void nsDisplayTransform::SetReferenceFrameToAncestor(nsDisplayListBuilder* aBuilder) { + if (mFrame == aBuilder->RootReferenceFrame()) { + return; + } nsIFrame *outerFrame = nsLayoutUtils::GetCrossDocParentFrame(mFrame); mReferenceFrame = aBuilder->FindReferenceFrameFor(outerFrame); diff --git a/layout/base/nsDisplayList.h b/layout/base/nsDisplayList.h index c067a4befcfb..f908c89d19d5 100644 --- a/layout/base/nsDisplayList.h +++ b/layout/base/nsDisplayList.h @@ -3939,6 +3939,15 @@ public: mFrame->Combines3DTransformWithAncestors())); } + /** + * Whether this transform item forms a reference frame boundary. + * In other words, the reference frame of the contained items is our frame, + * and the reference frame of this item is some ancestor of our frame. + */ + bool IsReferenceFrameBoundary() { + return !mTransformGetter && !mIsTransformSeparator; + } + private: void ComputeBounds(nsDisplayListBuilder* aBuilder); void SetReferenceFrameToAncestor(nsDisplayListBuilder* aBuilder); diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index 80eb9473af22..007e7867730d 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -1878,10 +1878,12 @@ nsLayoutUtils::GetAnimatedGeometryRootForFrame(nsDisplayListBuilder* aBuilder, nsIFrame* nsLayoutUtils::GetAnimatedGeometryRootFor(nsDisplayItem* aItem, - nsDisplayListBuilder* aBuilder) + nsDisplayListBuilder* aBuilder, + uint32_t aFlags) { nsIFrame* f = aItem->Frame(); - if (aItem->ShouldFixToViewport(aBuilder)) { + if (!(aFlags & AGR_IGNORE_BACKGROUND_ATTACHMENT_FIXED) && + aItem->ShouldFixToViewport(aBuilder)) { // Make its active scrolled root be the active scrolled root of // the enclosing viewport, since it shouldn't be scrolled by scrolled // frames in its document. InvalidateFixedBackgroundFramesFromList in @@ -1892,6 +1894,14 @@ nsLayoutUtils::GetAnimatedGeometryRootFor(nsDisplayItem* aItem, return GetAnimatedGeometryRootForFrame(aBuilder, viewportFrame); } } + if (aItem->GetType() == nsDisplayItem::TYPE_TRANSFORM && + static_cast(aItem)->IsReferenceFrameBoundary() && + f != aBuilder->RootReferenceFrame()) { + nsIFrame* parent = nsLayoutUtils::GetCrossDocParentFrame(f); + if (parent) { + return GetAnimatedGeometryRootForFrame(aBuilder, parent); + } + } return GetAnimatedGeometryRootForFrame(aBuilder, f); } diff --git a/layout/base/nsLayoutUtils.h b/layout/base/nsLayoutUtils.h index b129ef8ddc49..9b2ce653025b 100644 --- a/layout/base/nsLayoutUtils.h +++ b/layout/base/nsLayoutUtils.h @@ -552,8 +552,17 @@ public: * returning aItem->ReferenceFrame() when we can't find another animated * geometry root. */ + enum { + /** + * If the AGR_IGNORE_BACKGROUND_ATTACHMENT_FIXED flag is set, then we + * do not do any special processing for background attachment fixed items, + * instead treating them like any other frame. + */ + AGR_IGNORE_BACKGROUND_ATTACHMENT_FIXED = 0x01 + }; static nsIFrame* GetAnimatedGeometryRootFor(nsDisplayItem* aItem, - nsDisplayListBuilder* aBuilder); + nsDisplayListBuilder* aBuilder, + uint32_t aFlags = 0); /** * Finds the nearest ancestor frame to aFrame that is considered to have (or From ee21c076c2b87e04bb33f8f52e0acd990b1bf736 Mon Sep 17 00:00:00 2001 From: Timothy Nikkel Date: Tue, 3 Nov 2015 02:03:47 -0600 Subject: [PATCH 008/113] Bug 1156238. Skip setting async scroll clips if we aren't painting to the window because they are useless then. r=mstange Displayports only get acted upon when painting to the window, and the async scroll clips only get computed when we use a displayport. In addition we change an assert because if we are painting to the window then our root reference frame is either a root frame, or a popup frame. In either case we should not be able to get to out of flows outside of the frame subtree rooted at the root reference frame by following placeholders. --- layout/base/FrameLayerBuilder.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/layout/base/FrameLayerBuilder.cpp b/layout/base/FrameLayerBuilder.cpp index 16f690a26d0a..dab0acb0fb7b 100644 --- a/layout/base/FrameLayerBuilder.cpp +++ b/layout/base/FrameLayerBuilder.cpp @@ -1062,8 +1062,9 @@ public: mContainerAnimatedGeometryRoot = isAtRoot ? mContainerReferenceFrame : nsLayoutUtils::GetAnimatedGeometryRootFor(aContainerItem, aBuilder); - MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(mBuilder->RootReferenceFrame(), - mContainerAnimatedGeometryRoot)); + MOZ_ASSERT(!mBuilder->IsPaintingToWindow() || + nsLayoutUtils::IsAncestorFrameCrossDoc(mBuilder->RootReferenceFrame(), + mContainerAnimatedGeometryRoot)); NS_ASSERTION(!aContainerItem || !aContainerItem->ShouldFixToViewport(mBuilder), "Container items never return true for ShouldFixToViewport"); mContainerFixedPosFrame = @@ -4741,6 +4742,12 @@ ContainerState::SetupScrollingMetadata(NewLayerEntry* aEntry) return; } + if (!mBuilder->IsPaintingToWindow()) { + // async scrolling not possible, and async scrolling info not computed + // for this paint. + return; + } + nsAutoTArray metricsArray; if (aEntry->mBaseFrameMetrics) { metricsArray.AppendElement(*aEntry->mBaseFrameMetrics); From 9e34144349df1aa6cf3499c4553798d4c5752df4 Mon Sep 17 00:00:00 2001 From: Cykesiopka Date: Mon, 2 Nov 2015 22:09:00 +0100 Subject: [PATCH 009/113] Bug 1110935 - Part 1 - Assert we're on the main thread on public methods. r=keeler --- .../manager/ssl/nsSecureBrowserUIImpl.cpp | 20 ++++++++++++++----- security/manager/ssl/nsSecureBrowserUIImpl.h | 2 +- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/security/manager/ssl/nsSecureBrowserUIImpl.cpp b/security/manager/ssl/nsSecureBrowserUIImpl.cpp index 1aabc02aa5fe..f6c486b21072 100644 --- a/security/manager/ssl/nsSecureBrowserUIImpl.cpp +++ b/security/manager/ssl/nsSecureBrowserUIImpl.cpp @@ -115,6 +115,8 @@ nsSecureBrowserUIImpl::nsSecureBrowserUIImpl() #endif , mTransferringRequests(&gMapOps, sizeof(RequestHashEntry)) { + MOZ_ASSERT(NS_IsMainThread()); + ResetStateTracking(); if (!gSecureDocLog) @@ -128,8 +130,10 @@ NS_IMPL_ISUPPORTS(nsSecureBrowserUIImpl, nsISSLStatusProvider) NS_IMETHODIMP -nsSecureBrowserUIImpl::Init(nsIDOMWindow *aWindow) +nsSecureBrowserUIImpl::Init(nsIDOMWindow* aWindow) { + MOZ_ASSERT(NS_IsMainThread()); + if (MOZ_LOG_TEST(gSecureDocLog, LogLevel::Debug)) { nsCOMPtr window(do_QueryReferent(mWindow)); @@ -186,6 +190,7 @@ nsSecureBrowserUIImpl::Init(nsIDOMWindow *aWindow) NS_IMETHODIMP nsSecureBrowserUIImpl::GetState(uint32_t* aState) { + MOZ_ASSERT(NS_IsMainThread()); ReentrantMonitorAutoEnter lock(mReentrantMonitor); return MapInternalToExternalState(aState, mNotifiedSecurityState, mNotifiedToplevelIsEV); } @@ -303,8 +308,9 @@ nsSecureBrowserUIImpl::MapInternalToExternalState(uint32_t* aState, lockIconStat } NS_IMETHODIMP -nsSecureBrowserUIImpl::SetDocShell(nsIDocShell *aDocShell) +nsSecureBrowserUIImpl::SetDocShell(nsIDocShell* aDocShell) { + MOZ_ASSERT(NS_IsMainThread()); nsresult rv; mDocShell = do_GetWeakReference(aDocShell, &rv); return rv; @@ -481,6 +487,7 @@ nsSecureBrowserUIImpl::OnStateChange(nsIWebProgress* aWebProgress, uint32_t aProgressStateFlags, nsresult aStatus) { + MOZ_ASSERT(NS_IsMainThread()); #ifdef DEBUG nsAutoAtomic atomic(mOnStateLocationChangeReentranceDetection); NS_ASSERTION(mOnStateLocationChangeReentranceDetection == 1, @@ -1261,6 +1268,7 @@ nsSecureBrowserUIImpl::OnLocationChange(nsIWebProgress* aWebProgress, nsIURI* aLocation, uint32_t aFlags) { + MOZ_ASSERT(NS_IsMainThread()); #ifdef DEBUG nsAutoAtomic atomic(mOnStateLocationChangeReentranceDetection); NS_ASSERTION(mOnStateLocationChangeReentranceDetection == 1, @@ -1363,10 +1371,11 @@ nsSecureBrowserUIImpl::OnStatusChange(nsIWebProgress* aWebProgress, } nsresult -nsSecureBrowserUIImpl::OnSecurityChange(nsIWebProgress *aWebProgress, - nsIRequest *aRequest, +nsSecureBrowserUIImpl::OnSecurityChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, uint32_t state) { + MOZ_ASSERT(NS_IsMainThread()); #if defined(DEBUG) nsCOMPtr channel(do_QueryInterface(aRequest)); if (!channel) @@ -1374,7 +1383,7 @@ nsSecureBrowserUIImpl::OnSecurityChange(nsIWebProgress *aWebProgress, nsCOMPtr aURI; channel->GetURI(getter_AddRefs(aURI)); - + if (aURI) { nsAutoCString temp; aURI->GetSpec(temp); @@ -1392,6 +1401,7 @@ NS_IMETHODIMP nsSecureBrowserUIImpl::GetSSLStatus(nsISSLStatus** _result) { NS_ENSURE_ARG_POINTER(_result); + MOZ_ASSERT(NS_IsMainThread()); ReentrantMonitorAutoEnter lock(mReentrantMonitor); diff --git a/security/manager/ssl/nsSecureBrowserUIImpl.h b/security/manager/ssl/nsSecureBrowserUIImpl.h index 54afc63afa94..25f63408d89f 100644 --- a/security/manager/ssl/nsSecureBrowserUIImpl.h +++ b/security/manager/ssl/nsSecureBrowserUIImpl.h @@ -42,7 +42,7 @@ public: nsSecureBrowserUIImpl(); - NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_ISUPPORTS NS_DECL_NSIWEBPROGRESSLISTENER NS_DECL_NSISECUREBROWSERUI NS_DECL_NSISSLSTATUSPROVIDER From f625d9c9b91b0b1d4fb2a86e7c41fac8b7a0f6ea Mon Sep 17 00:00:00 2001 From: Cykesiopka Date: Mon, 2 Nov 2015 22:10:00 +0100 Subject: [PATCH 010/113] Bug 1110935 - Part 2 - Remove ReentrantMonitor and ReentrantMonitorAutoEnter uses. r=keeler --- .../manager/ssl/nsSecureBrowserUIImpl.cpp | 215 +++++++----------- security/manager/ssl/nsSecureBrowserUIImpl.h | 8 +- 2 files changed, 82 insertions(+), 141 deletions(-) diff --git a/security/manager/ssl/nsSecureBrowserUIImpl.cpp b/security/manager/ssl/nsSecureBrowserUIImpl.cpp index f6c486b21072..df7e3c035fb0 100644 --- a/security/manager/ssl/nsSecureBrowserUIImpl.cpp +++ b/security/manager/ssl/nsSecureBrowserUIImpl.cpp @@ -99,8 +99,7 @@ class nsAutoAtomic { #endif nsSecureBrowserUIImpl::nsSecureBrowserUIImpl() - : mReentrantMonitor("nsSecureBrowserUIImpl.mReentrantMonitor") - , mNotifiedSecurityState(lis_no_security) + : mNotifiedSecurityState(lis_no_security) , mNotifiedToplevelIsEV(false) , mNewToplevelSecurityState(STATE_IS_INSECURE) , mNewToplevelIsEV(false) @@ -191,8 +190,8 @@ NS_IMETHODIMP nsSecureBrowserUIImpl::GetState(uint32_t* aState) { MOZ_ASSERT(NS_IsMainThread()); - ReentrantMonitorAutoEnter lock(mReentrantMonitor); - return MapInternalToExternalState(aState, mNotifiedSecurityState, mNotifiedToplevelIsEV); + return MapInternalToExternalState(aState, mNotifiedSecurityState, + mNotifiedToplevelIsEV); } // static @@ -382,10 +381,9 @@ nsSecureBrowserUIImpl::OnProgressChange(nsIWebProgress* aWebProgress, return NS_OK; } -void nsSecureBrowserUIImpl::ResetStateTracking() +void +nsSecureBrowserUIImpl::ResetStateTracking() { - ReentrantMonitorAutoEnter lock(mReentrantMonitor); - mDocumentRequestsInProgress = 0; mTransferringRequests.Clear(); } @@ -429,29 +427,26 @@ nsSecureBrowserUIImpl::EvaluateAndUpdateSecurityState(nsIRequest* aRequest, // assume temp_NewToplevelSecurityState was set in this scope! // see code that is directly above - { - ReentrantMonitorAutoEnter lock(mReentrantMonitor); - mNewToplevelSecurityStateKnown = true; - mNewToplevelSecurityState = temp_NewToplevelSecurityState; - mNewToplevelIsEV = temp_NewToplevelIsEV; - if (updateStatus) { - mSSLStatus = temp_SSLStatus; - } - MOZ_LOG(gSecureDocLog, LogLevel::Debug, - ("SecureUI:%p: remember securityInfo %p\n", this, - info)); - nsCOMPtr associatedContentSecurityFromRequest = - do_QueryInterface(aRequest); - if (associatedContentSecurityFromRequest) - mCurrentToplevelSecurityInfo = aRequest; - else - mCurrentToplevelSecurityInfo = info; - - // The subrequest counters are now in sync with - // mCurrentToplevelSecurityInfo, don't restore after top level - // document load finishes. - mRestoreSubrequests = false; + mNewToplevelSecurityStateKnown = true; + mNewToplevelSecurityState = temp_NewToplevelSecurityState; + mNewToplevelIsEV = temp_NewToplevelIsEV; + if (updateStatus) { + mSSLStatus = temp_SSLStatus; } + MOZ_LOG(gSecureDocLog, LogLevel::Debug, + ("SecureUI:%p: remember securityInfo %p\n", this, + info)); + nsCOMPtr associatedContentSecurityFromRequest( + do_QueryInterface(aRequest)); + if (associatedContentSecurityFromRequest) { + mCurrentToplevelSecurityInfo = aRequest; + } else { + mCurrentToplevelSecurityInfo = info; + } + + // The subrequest counters are now in sync with mCurrentToplevelSecurityInfo, + // don't restore after top level document load finishes. + mRestoreSubrequests = false; UpdateSecurityState(aRequest, withNewLocation, withNewSink || updateStatus); } @@ -465,9 +460,6 @@ nsSecureBrowserUIImpl::UpdateSubrequestMembers(nsISupports* securityInfo, uint32_t reqState = GetSecurityStateFromSecurityInfoAndRequest(securityInfo, request); - // the code above this line should run without a lock - ReentrantMonitorAutoEnter lock(mReentrantMonitor); - if (reqState & STATE_IS_SECURE) { // do nothing } else if (reqState & STATE_IS_BROKEN) { @@ -591,20 +583,16 @@ nsSecureBrowserUIImpl::OnStateChange(nsIWebProgress* aWebProgress, nsCOMPtr ioService; - { - ReentrantMonitorAutoEnter lock(mReentrantMonitor); - window = do_QueryReferent(mWindow); - NS_ASSERTION(window, "Window has gone away?!"); - isViewSource = mIsViewSource; - ioService = mIOService; - } + window = do_QueryReferent(mWindow); + NS_ASSERTION(window, "Window has gone away?!"); + isViewSource = mIsViewSource; + ioService = mIOService; if (!ioService) { ioService = do_GetService(NS_IOSERVICE_CONTRACTID); if (ioService) { - ReentrantMonitorAutoEnter lock(mReentrantMonitor); mIOService = ioService; } } @@ -865,8 +853,6 @@ nsSecureBrowserUIImpl::OnStateChange(nsIWebProgress* aWebProgress, { // The listing of a request in mTransferringRequests // means, there has already been data transfered. - - ReentrantMonitorAutoEnter lock(mReentrantMonitor); mTransferringRequests.Add(aRequest, fallible); return NS_OK; @@ -878,13 +864,10 @@ nsSecureBrowserUIImpl::OnStateChange(nsIWebProgress* aWebProgress, && aProgressStateFlags & STATE_IS_REQUEST) { - { /* scope for the ReentrantMonitorAutoEnter */ - ReentrantMonitorAutoEnter lock(mReentrantMonitor); - PLDHashEntryHdr* entry = mTransferringRequests.Search(aRequest); - if (entry) { - mTransferringRequests.RemoveEntry(entry); - requestHasTransferedData = true; - } + PLDHashEntryHdr* entry = mTransferringRequests.Search(aRequest); + if (entry) { + mTransferringRequests.RemoveEntry(entry); + requestHasTransferedData = true; } if (!requestHasTransferedData) { @@ -930,16 +913,12 @@ nsSecureBrowserUIImpl::OnStateChange(nsIWebProgress* aWebProgress, int32_t newSubBroken = 0; int32_t newSubNo = 0; - { - ReentrantMonitorAutoEnter lock(mReentrantMonitor); - inProgress = (mDocumentRequestsInProgress!=0); + inProgress = (mDocumentRequestsInProgress!=0); - if (allowSecurityStateChange && !inProgress) - { - saveSubBroken = mSubRequestsBrokenSecurity; - saveSubNo = mSubRequestsNoSecurity; - prevContentSecurity = do_QueryInterface(mCurrentToplevelSecurityInfo); - } + if (allowSecurityStateChange && !inProgress) { + saveSubBroken = mSubRequestsBrokenSecurity; + saveSubNo = mSubRequestsNoSecurity; + prevContentSecurity = do_QueryInterface(mCurrentToplevelSecurityInfo); } if (allowSecurityStateChange && !inProgress) @@ -1007,26 +986,21 @@ nsSecureBrowserUIImpl::OnStateChange(nsIWebProgress* aWebProgress, } } - { - ReentrantMonitorAutoEnter lock(mReentrantMonitor); - - if (allowSecurityStateChange && !inProgress) - { - ResetStateTracking(); - mSubRequestsBrokenSecurity = newSubBroken; - mSubRequestsNoSecurity = newSubNo; - mNewToplevelSecurityStateKnown = false; - } - - // By using a counter, this code also works when the toplevel - // document get's redirected, but the STOP request for the - // previous toplevel document has not yet have been received. - MOZ_LOG(gSecureDocLog, LogLevel::Debug, - ("SecureUI:%p: OnStateChange: ++mDocumentRequestsInProgress\n", this - )); - ++mDocumentRequestsInProgress; + if (allowSecurityStateChange && !inProgress) { + ResetStateTracking(); + mSubRequestsBrokenSecurity = newSubBroken; + mSubRequestsNoSecurity = newSubNo; + mNewToplevelSecurityStateKnown = false; } + // By using a counter, this code also works when the toplevel + // document get's redirected, but the STOP request for the + // previous toplevel document has not yet have been received. + MOZ_LOG(gSecureDocLog, LogLevel::Debug, + ("SecureUI:%p: OnStateChange: ++mDocumentRequestsInProgress\n", this + )); + ++mDocumentRequestsInProgress; + return NS_OK; } @@ -1041,13 +1015,9 @@ nsSecureBrowserUIImpl::OnStateChange(nsIWebProgress* aWebProgress, int32_t temp_DocumentRequestsInProgress; nsCOMPtr temp_ToplevelEventSink; - { - ReentrantMonitorAutoEnter lock(mReentrantMonitor); - temp_DocumentRequestsInProgress = mDocumentRequestsInProgress; - if (allowSecurityStateChange) - { - temp_ToplevelEventSink = mToplevelEventSink; - } + temp_DocumentRequestsInProgress = mDocumentRequestsInProgress; + if (allowSecurityStateChange) { + temp_ToplevelEventSink = mToplevelEventSink; } if (temp_DocumentRequestsInProgress <= 0) @@ -1071,16 +1041,12 @@ nsSecureBrowserUIImpl::OnStateChange(nsIWebProgress* aWebProgress, bool sinkChanged = false; bool inProgress; - { - ReentrantMonitorAutoEnter lock(mReentrantMonitor); - if (allowSecurityStateChange) - { - sinkChanged = (mToplevelEventSink != temp_ToplevelEventSink); - mToplevelEventSink = temp_ToplevelEventSink; - } - --mDocumentRequestsInProgress; - inProgress = mDocumentRequestsInProgress > 0; + if (allowSecurityStateChange) { + sinkChanged = (mToplevelEventSink != temp_ToplevelEventSink); + mToplevelEventSink = temp_ToplevelEventSink; } + --mDocumentRequestsInProgress; + inProgress = mDocumentRequestsInProgress > 0; if (allowSecurityStateChange && requestHasTransferedData) { // Data has been transferred for the single toplevel @@ -1108,17 +1074,13 @@ nsSecureBrowserUIImpl::OnStateChange(nsIWebProgress* aWebProgress, // state info holds (it was reset to all zero in OnStateChange(START) // before). nsCOMPtr currentContentSecurity; - { - ReentrantMonitorAutoEnter lock(mReentrantMonitor); - currentContentSecurity = do_QueryInterface(mCurrentToplevelSecurityInfo); + currentContentSecurity = do_QueryInterface(mCurrentToplevelSecurityInfo); - // Drop this indication flag, the restore opration is just being - // done. - mRestoreSubrequests = false; + // Drop this indication flag, the restore operation is just being done. + mRestoreSubrequests = false; - // We can do this since the state didn't actually change. - mNewToplevelSecurityStateKnown = true; - } + // We can do this since the state didn't actually change. + mNewToplevelSecurityStateKnown = true; int32_t subBroken = 0; int32_t subNo = 0; @@ -1131,31 +1093,28 @@ nsSecureBrowserUIImpl::OnStateChange(nsIWebProgress* aWebProgress, this, currentContentSecurity.get(), subBroken, subNo)); } - { - ReentrantMonitorAutoEnter lock(mReentrantMonitor); - mSubRequestsBrokenSecurity = subBroken; - mSubRequestsNoSecurity = subNo; - } + mSubRequestsBrokenSecurity = subBroken; + mSubRequestsNoSecurity = subNo; } - + return NS_OK; } - + if (aProgressStateFlags & STATE_STOP && aProgressStateFlags & STATE_IS_REQUEST) { if (!isSubDocumentRelevant) return NS_OK; - + // if we arrive here, LOAD_DOCUMENT_URI is not set - + // We only care for the security state of sub requests which have actually transfered data. if (allowSecurityStateChange && requestHasTransferedData) { UpdateSubrequestMembers(securityInfo, aRequest); - + // Care for the following scenario: // A new top level document load might have already started, // but the security state of the new top level document might not yet been known. @@ -1167,10 +1126,7 @@ nsSecureBrowserUIImpl::OnStateChange(nsIWebProgress* aWebProgress, // We skip updating the security state in this case. bool temp_NewToplevelSecurityStateKnown; - { - ReentrantMonitorAutoEnter lock(mReentrantMonitor); - temp_NewToplevelSecurityStateKnown = mNewToplevelSecurityStateKnown; - } + temp_NewToplevelSecurityStateKnown = mNewToplevelSecurityStateKnown; if (temp_NewToplevelSecurityStateKnown) { UpdateSecurityState(aRequest, false, false); @@ -1242,11 +1198,8 @@ nsSecureBrowserUIImpl::TellTheWorld(nsIRequest* aRequest) { nsCOMPtr toplevelEventSink; uint32_t state = STATE_IS_INSECURE; - { - ReentrantMonitorAutoEnter lock(mReentrantMonitor); - toplevelEventSink = mToplevelEventSink; - GetState(&state); - } + toplevelEventSink = mToplevelEventSink; + GetState(&state); if (toplevelEventSink) { MOZ_LOG(gSecureDocLog, LogLevel::Debug, @@ -1297,15 +1250,12 @@ nsSecureBrowserUIImpl::OnLocationChange(nsIWebProgress* aWebProgress, temp_IsViewSource = vs; } - { - ReentrantMonitorAutoEnter lock(mReentrantMonitor); - if (updateIsViewSource) { - mIsViewSource = temp_IsViewSource; - } - mCurrentURI = aLocation; - window = do_QueryReferent(mWindow); - NS_ASSERTION(window, "Window has gone away?!"); + if (updateIsViewSource) { + mIsViewSource = temp_IsViewSource; } + mCurrentURI = aLocation; + window = do_QueryReferent(mWindow); + NS_ASSERTION(window, "Window has gone away?!"); // When |aRequest| is null, basically we don't trust that document. But if // docshell insists that the document has not changed at all, we will reuse @@ -1348,10 +1298,7 @@ nsSecureBrowserUIImpl::OnLocationChange(nsIWebProgress* aWebProgress, // We skip updating the security state in this case. bool temp_NewToplevelSecurityStateKnown; - { - ReentrantMonitorAutoEnter lock(mReentrantMonitor); - temp_NewToplevelSecurityStateKnown = mNewToplevelSecurityStateKnown; - } + temp_NewToplevelSecurityStateKnown = mNewToplevelSecurityStateKnown; if (temp_NewToplevelSecurityStateKnown) { UpdateSecurityState(aRequest, true, false); @@ -1403,8 +1350,6 @@ nsSecureBrowserUIImpl::GetSSLStatus(nsISSLStatus** _result) NS_ENSURE_ARG_POINTER(_result); MOZ_ASSERT(NS_IsMainThread()); - ReentrantMonitorAutoEnter lock(mReentrantMonitor); - switch (mNotifiedSecurityState) { case lis_broken_security: @@ -1418,7 +1363,7 @@ nsSecureBrowserUIImpl::GetSSLStatus(nsISSLStatus** _result) *_result = nullptr; return NS_OK; } - + *_result = mSSLStatus; NS_IF_ADDREF(*_result); diff --git a/security/manager/ssl/nsSecureBrowserUIImpl.h b/security/manager/ssl/nsSecureBrowserUIImpl.h index 25f63408d89f..f40319a172e9 100644 --- a/security/manager/ssl/nsSecureBrowserUIImpl.h +++ b/security/manager/ssl/nsSecureBrowserUIImpl.h @@ -39,9 +39,8 @@ class nsSecureBrowserUIImpl : public nsISecureBrowserUI, public nsISSLStatusProvider { public: - nsSecureBrowserUIImpl(); - + NS_DECL_ISUPPORTS NS_DECL_NSIWEBPROGRESSLISTENER NS_DECL_NSISECUREBROWSERUI @@ -50,14 +49,12 @@ public: protected: virtual ~nsSecureBrowserUIImpl() {}; - mozilla::ReentrantMonitor mReentrantMonitor; - nsWeakPtr mWindow; nsWeakPtr mDocShell; nsCOMPtr mIOService; nsCOMPtr mCurrentURI; nsCOMPtr mToplevelEventSink; - + enum lockIconState { lis_no_security, lis_broken_security, @@ -80,7 +77,6 @@ protected: bool mRestoreSubrequests; bool mOnLocationChangeSeen; #ifdef DEBUG - /* related to mReentrantMonitor */ mozilla::Atomic mOnStateLocationChangeReentranceDetection; #endif From 34ca9c027fadfe7c183e0928799a00884780bb8d Mon Sep 17 00:00:00 2001 From: Cykesiopka Date: Mon, 2 Nov 2015 22:11:00 +0100 Subject: [PATCH 011/113] Bug 1110935 - Part 3 - Remove now unnecessary temp variables. r=keeler --- .../manager/ssl/nsSecureBrowserUIImpl.cpp | 104 ++++++------------ 1 file changed, 35 insertions(+), 69 deletions(-) diff --git a/security/manager/ssl/nsSecureBrowserUIImpl.cpp b/security/manager/ssl/nsSecureBrowserUIImpl.cpp index df7e3c035fb0..bf1c4ecfee45 100644 --- a/security/manager/ssl/nsSecureBrowserUIImpl.cpp +++ b/security/manager/ssl/nsSecureBrowserUIImpl.cpp @@ -390,46 +390,36 @@ nsSecureBrowserUIImpl::ResetStateTracking() void nsSecureBrowserUIImpl::EvaluateAndUpdateSecurityState(nsIRequest* aRequest, - nsISupports *info, + nsISupports* info, bool withNewLocation, bool withNewSink) { - /* I explicitly ignore the camelCase variable naming style here, - I want to make it clear these are temp variables that relate to the - member variables with the same suffix.*/ - - uint32_t temp_NewToplevelSecurityState = nsIWebProgressListener::STATE_IS_INSECURE; - bool temp_NewToplevelIsEV = false; + mNewToplevelIsEV = false; bool updateStatus = false; nsCOMPtr temp_SSLStatus; - temp_NewToplevelSecurityState = - GetSecurityStateFromSecurityInfoAndRequest(info, aRequest); + mNewToplevelSecurityState = + GetSecurityStateFromSecurityInfoAndRequest(info, aRequest); - MOZ_LOG(gSecureDocLog, LogLevel::Debug, - ("SecureUI:%p: OnStateChange: remember mNewToplevelSecurityState => %x\n", this, - temp_NewToplevelSecurityState)); + MOZ_LOG(gSecureDocLog, LogLevel::Debug, + ("SecureUI:%p: OnStateChange: remember mNewToplevelSecurityState => %x\n", + this, mNewToplevelSecurityState)); - nsCOMPtr sp = do_QueryInterface(info); - if (sp) { - // Ignore result - updateStatus = true; - (void) sp->GetSSLStatus(getter_AddRefs(temp_SSLStatus)); - if (temp_SSLStatus) { - bool aTemp; - if (NS_SUCCEEDED(temp_SSLStatus->GetIsExtendedValidation(&aTemp))) { - temp_NewToplevelIsEV = aTemp; - } + nsCOMPtr sp(do_QueryInterface(info)); + if (sp) { + // Ignore result + updateStatus = true; + (void) sp->GetSSLStatus(getter_AddRefs(temp_SSLStatus)); + if (temp_SSLStatus) { + bool aTemp; + if (NS_SUCCEEDED(temp_SSLStatus->GetIsExtendedValidation(&aTemp))) { + mNewToplevelIsEV = aTemp; } } - - // assume temp_NewToplevelSecurityState was set in this scope! - // see code that is directly above + } mNewToplevelSecurityStateKnown = true; - mNewToplevelSecurityState = temp_NewToplevelSecurityState; - mNewToplevelIsEV = temp_NewToplevelIsEV; if (updateStatus) { mSSLStatus = temp_SSLStatus; } @@ -578,23 +568,11 @@ nsSecureBrowserUIImpl::OnStateChange(nsIWebProgress* aWebProgress, nsCOMPtr windowForProgress; aWebProgress->GetDOMWindow(getter_AddRefs(windowForProgress)); - nsCOMPtr window; - bool isViewSource; - - nsCOMPtr ioService; - - window = do_QueryReferent(mWindow); + nsCOMPtr window(do_QueryReferent(mWindow)); NS_ASSERTION(window, "Window has gone away?!"); - isViewSource = mIsViewSource; - ioService = mIOService; - if (!ioService) - { - ioService = do_GetService(NS_IOSERVICE_CONTRACTID); - if (ioService) - { - mIOService = ioService; - } + if (!mIOService) { + mIOService = do_GetService(NS_IOSERVICE_CONTRACTID); } bool isNoContentResponse = false; @@ -629,8 +607,9 @@ nsSecureBrowserUIImpl::OnStateChange(nsIWebProgress* aWebProgress, MOZ_LOG(gSecureDocLog, LogLevel::Debug, ("SecureUI:%p: OnStateChange\n", this)); - if (isViewSource) + if (mIsViewSource) { return NS_OK; + } if (!aRequest) { @@ -721,12 +700,12 @@ nsSecureBrowserUIImpl::OnStateChange(nsIWebProgress* aWebProgress, // This will ignore all resource, chrome, data, file, moz-icon, and anno // protocols. Local resources are treated as trusted. - if (uri && ioService) { + if (uri && mIOService) { bool hasFlag; - nsresult rv = - ioService->URIChainHasFlags(uri, - nsIProtocolHandler::URI_IS_LOCAL_RESOURCE, - &hasFlag); + nsresult rv = + mIOService->URIChainHasFlags(uri, + nsIProtocolHandler::URI_IS_LOCAL_RESOURCE, + &hasFlag); if (NS_SUCCEEDED(rv) && hasFlag) { isSubDocumentRelevant = false; } @@ -904,8 +883,6 @@ nsSecureBrowserUIImpl::OnStateChange(nsIWebProgress* aWebProgress, && loadFlags & nsIChannel::LOAD_DOCUMENT_URI) { - bool inProgress; - int32_t saveSubBroken; int32_t saveSubNo; nsCOMPtr prevContentSecurity; @@ -913,7 +890,7 @@ nsSecureBrowserUIImpl::OnStateChange(nsIWebProgress* aWebProgress, int32_t newSubBroken = 0; int32_t newSubNo = 0; - inProgress = (mDocumentRequestsInProgress!=0); + bool inProgress = (mDocumentRequestsInProgress != 0); if (allowSecurityStateChange && !inProgress) { saveSubBroken = mSubRequestsBrokenSecurity; @@ -1012,16 +989,13 @@ nsSecureBrowserUIImpl::OnStateChange(nsIWebProgress* aWebProgress, && loadFlags & nsIChannel::LOAD_DOCUMENT_URI) { - int32_t temp_DocumentRequestsInProgress; nsCOMPtr temp_ToplevelEventSink; - temp_DocumentRequestsInProgress = mDocumentRequestsInProgress; if (allowSecurityStateChange) { temp_ToplevelEventSink = mToplevelEventSink; } - if (temp_DocumentRequestsInProgress <= 0) - { + if (mDocumentRequestsInProgress <= 0) { // Ignore stop requests unless a document load is in progress // Unfortunately on application start, see some stops without having seen any starts... return NS_OK; @@ -1073,8 +1047,8 @@ nsSecureBrowserUIImpl::OnStateChange(nsIWebProgress* aWebProgress, // app handler. Restore mSubRequests* members to what the current security // state info holds (it was reset to all zero in OnStateChange(START) // before). - nsCOMPtr currentContentSecurity; - currentContentSecurity = do_QueryInterface(mCurrentToplevelSecurityInfo); + nsCOMPtr currentContentSecurity( + do_QueryInterface(mCurrentToplevelSecurityInfo)); // Drop this indication flag, the restore operation is just being done. mRestoreSubrequests = false; @@ -1125,10 +1099,7 @@ nsSecureBrowserUIImpl::OnStateChange(nsIWebProgress* aWebProgress, // // We skip updating the security state in this case. - bool temp_NewToplevelSecurityStateKnown; - temp_NewToplevelSecurityStateKnown = mNewToplevelSecurityStateKnown; - - if (temp_NewToplevelSecurityStateKnown) { + if (mNewToplevelSecurityStateKnown) { UpdateSecurityState(aRequest, false, false); } } @@ -1196,17 +1167,15 @@ nsSecureBrowserUIImpl::UpdateSecurityState(nsIRequest* aRequest, void nsSecureBrowserUIImpl::TellTheWorld(nsIRequest* aRequest) { - nsCOMPtr toplevelEventSink; uint32_t state = STATE_IS_INSECURE; - toplevelEventSink = mToplevelEventSink; GetState(&state); - if (toplevelEventSink) { + if (mToplevelEventSink) { MOZ_LOG(gSecureDocLog, LogLevel::Debug, ("SecureUI:%p: UpdateSecurityState: calling OnSecurityChange\n", this)); - toplevelEventSink->OnSecurityChange(aRequest, state); + mToplevelEventSink->OnSecurityChange(aRequest, state); } else { MOZ_LOG(gSecureDocLog, LogLevel::Debug, ("SecureUI:%p: UpdateSecurityState: NO mToplevelEventSink!\n", @@ -1297,10 +1266,7 @@ nsSecureBrowserUIImpl::OnLocationChange(nsIWebProgress* aWebProgress, // // We skip updating the security state in this case. - bool temp_NewToplevelSecurityStateKnown; - temp_NewToplevelSecurityStateKnown = mNewToplevelSecurityStateKnown; - - if (temp_NewToplevelSecurityStateKnown) { + if (mNewToplevelSecurityStateKnown) { UpdateSecurityState(aRequest, true, false); } From a4b97ca1c4f5aa5e5cfa9ea33e3d1038783337a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Pag=C3=A8s?= Date: Mon, 2 Nov 2015 18:56:15 +0100 Subject: [PATCH 012/113] Bug 1173502 - add cmdline options to set prefs (same as in mozprofile). r=dburns --HG-- extra : transplant_source : %15%16%84%D1%DB%A3%0F%E3%13y%04o%95%8B%24w%ADx%81%A4 --- .../client/marionette/runner/base.py | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/testing/marionette/client/marionette/runner/base.py b/testing/marionette/client/marionette/runner/base.py index 4890d130e03b..ba0dccb4497c 100644 --- a/testing/marionette/client/marionette/runner/base.py +++ b/testing/marionette/client/marionette/runner/base.py @@ -15,6 +15,7 @@ import time import traceback import unittest import warnings +import mozprofile import xml.dom.minidom as dom from manifestparser import TestManifest @@ -309,6 +310,17 @@ class BaseMarionetteArguments(ArgumentParser): self.add_argument('--profile', help='profile to use when launching the gecko process. if not passed, then a profile will be ' 'constructed and used') + self.add_argument('--pref', + action='append', + dest='prefs_args', + help=(" A preference to set. Must be a key-value pair" + " separated by a ':'.")) + self.add_argument('--preferences', + action='append', + dest='prefs_files', + help=("read preferences from a JSON or INI file. For" + " INI, use 'file.ini:section' to specify a" + " particular section.")) self.add_argument('--addon', action='append', help="addon to install; repeat for multiple addons.") @@ -398,6 +410,31 @@ class BaseMarionetteArguments(ArgumentParser): container.parse_args_handler(args) return args + def _get_preferences(self, prefs_files, prefs_args): + """ + return user defined profile preferences as a dict + """ + # object that will hold the preferences + prefs = mozprofile.prefs.Preferences() + + # add preferences files + if prefs_files: + for prefs_file in prefs_files: + prefs.add_file(prefs_file) + + separator = ':' + cli_prefs = [] + if prefs_args: + for pref in prefs_args: + if separator not in pref: + continue + cli_prefs.append(pref.split(separator, 1)) + + # string preferences + prefs.add(cli_prefs, cast=True) + + return dict(prefs()) + def verify_usage(self, args): if not args.tests: print 'must specify one or more test files, manifests, or directories' @@ -443,10 +480,12 @@ class BaseMarionetteArguments(ArgumentParser): args.app_args.append('-jsdebugger') args.socket_timeout = None + args.prefs = self._get_preferences(args.prefs_files, args.prefs_args) + if args.e10s: - args.prefs = { + args.prefs.update({ 'browser.tabs.remote.autostart': True - } + }) for container in self.argument_containers: if hasattr(container, 'verify_usage_handler'): From 944e3aaf48630ece06f23a789f1e9c4ebc538be5 Mon Sep 17 00:00:00 2001 From: "Nils Ohlmeier [:drno]" Date: Sat, 8 Aug 2015 00:39:32 -0700 Subject: [PATCH 013/113] Bug 1192403 - improve ICE TCP error message. r=mjf --HG-- extra : transplant_source : -%25t%10%26%91%3EnY%1F%24%22%E2%94%24%B5%98%24c%16 --- .../third_party/nICEr/src/net/nr_socket_multi_tcp.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/media/mtransport/third_party/nICEr/src/net/nr_socket_multi_tcp.c b/media/mtransport/third_party/nICEr/src/net/nr_socket_multi_tcp.c index 3d4cc4bc7fe8..7bc5757a8f37 100644 --- a/media/mtransport/third_party/nICEr/src/net/nr_socket_multi_tcp.c +++ b/media/mtransport/third_party/nICEr/src/net/nr_socket_multi_tcp.c @@ -486,7 +486,9 @@ static int nr_socket_multi_tcp_recvfrom(void *obj,void * restrict buf, if (r!=R_WOULDBLOCK) { NR_SOCKET fd; - + r_log(LOG_ICE,LOG_DEBUG, + "%s:%d function %s(to:%s) failed with error %d",__FILE__, + __LINE__,__FUNCTION__,tcpsock->remote_addr.as_string,r); if (!nr_socket_getfd(tcpsock->inner, &fd)) { NR_ASYNC_CANCEL(fd, NR_ASYNC_WAIT_READ); NR_ASYNC_CANCEL(fd, NR_ASYNC_WAIT_WRITE); @@ -494,7 +496,6 @@ static int nr_socket_multi_tcp_recvfrom(void *obj,void * restrict buf, TAILQ_REMOVE(&sock->sockets, tcpsock, entry); nr_tcp_socket_ctx_destroy(&tcpsock); - r_log(LOG_ICE,LOG_DEBUG,"%s:%d function %s(from:%s) failed with error %d",__FILE__,__LINE__,__FUNCTION__,from->as_string,r); ABORT(r); } } From ef1e43c90397ddebf0d836f4ca2b4f9a0266e98b Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Tue, 3 Nov 2015 21:22:38 +1300 Subject: [PATCH 014/113] Bug 1220763 - 43.0b1 build1 osx en-US beta build failing to upload, r=glandium --HG-- extra : source : d64148d6bb6075f8d02ee921e1a5b3310c34cfec extra : intermediate-source : bc9c6e9960069076f78bd91cb660e05779c01aa2 --- toolkit/mozapps/installer/packager.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toolkit/mozapps/installer/packager.mk b/toolkit/mozapps/installer/packager.mk index fbee3f072ce9..3dcf02f5dd43 100644 --- a/toolkit/mozapps/installer/packager.mk +++ b/toolkit/mozapps/installer/packager.mk @@ -210,7 +210,7 @@ checksum: upload: checksum $(PYTHON) -u $(MOZILLA_DIR)/build/upload.py --base-path $(DIST) \ - --package $(PACKAGE) \ + --package '$(PACKAGE)' \ --properties-file $(DIST)/mach_build_properties.json \ $(UPLOAD_FILES) \ $(CHECKSUM_FILES) From ea522455d5082f04a8ac50de40f657dc47d7e5d9 Mon Sep 17 00:00:00 2001 From: Joel Maher Date: Tue, 3 Nov 2015 01:32:04 -0800 Subject: [PATCH 015/113] Bug 1216549 - osx 10.10.5 error in /test_conformance__canvas__viewport-unchanged-upon-resize.html. r=jgilbert --- dom/canvas/test/_webgl-conformance.ini | 1 + dom/canvas/test/webgl-conformance/mochitest-errata.ini | 3 +++ 2 files changed, 4 insertions(+) diff --git a/dom/canvas/test/_webgl-conformance.ini b/dom/canvas/test/_webgl-conformance.ini index 8b3cd44fdd60..c63158d85514 100644 --- a/dom/canvas/test/_webgl-conformance.ini +++ b/dom/canvas/test/_webgl-conformance.ini @@ -508,6 +508,7 @@ skip-if = os == 'android' skip-if = os == 'mac' [webgl-conformance/_wrappers/test_conformance__canvas__drawingbuffer-test.html] [webgl-conformance/_wrappers/test_conformance__canvas__viewport-unchanged-upon-resize.html] +skip-if = os == 'mac' [webgl-conformance/_wrappers/test_conformance__context__constants.html] [webgl-conformance/_wrappers/test_conformance__context__context-attributes-alpha-depth-stencil-antialias.html] skip-if = (os == 'b2g') diff --git a/dom/canvas/test/webgl-conformance/mochitest-errata.ini b/dom/canvas/test/webgl-conformance/mochitest-errata.ini index 36fd04a280c4..b684119aa8a7 100644 --- a/dom/canvas/test/webgl-conformance/mochitest-errata.ini +++ b/dom/canvas/test/webgl-conformance/mochitest-errata.ini @@ -108,6 +108,9 @@ fail-if = (os == 'linux') [_wrappers/test_conformance__canvas__drawingbuffer-static-canvas-test.html] # Intermittent crash on OSX. skip-if = os == 'mac' +[_wrappers/test_conformance__canvas__viewport-unchanged-upon-resize.html] +# New OSX r7 machines and 10.10.5 is causing perma failure (bug 1216549) +skip-if = os == 'mac' ######################################################################## # Win From acd22ee0b8aa9e45e2b719bcff4301c351a1a249 Mon Sep 17 00:00:00 2001 From: Nick Robson Date: Mon, 2 Nov 2015 14:26:00 +0100 Subject: [PATCH 016/113] Bug 1216284 - Tooltips do not flip correctly on OSX. r=enndeakin --HG-- extra : rebase_source : fb1847a65adfd3d80cd0dc45e30d49729d840eff --- layout/xul/nsMenuPopupFrame.cpp | 9 ++- .../content/tests/chrome/window_tooltip.xul | 62 +++++++++++++++++++ 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/layout/xul/nsMenuPopupFrame.cpp b/layout/xul/nsMenuPopupFrame.cpp index 8c0172ff166b..85606af48990 100644 --- a/layout/xul/nsMenuPopupFrame.cpp +++ b/layout/xul/nsMenuPopupFrame.cpp @@ -1413,10 +1413,15 @@ nsMenuPopupFrame::SetPopupPosition(nsIFrame* aAnchorFrame, bool aIsMove, bool aS screenPoint.MoveBy(margin.left + offsetForContextMenu.x, margin.top + offsetForContextMenu.y); - // screen positioned popups can be flipped vertically but never horizontally #ifdef XP_MACOSX - hFlip = FlipStyle_Outside; + // OSX tooltips follow standard flip rule but other popups flip horizontally not vertically + if (mPopupType == ePopupTypeTooltip) { + vFlip = FlipStyle_Outside; + } else { + hFlip = FlipStyle_Outside; + } #else + // Other OS screen positioned popups can be flipped vertically but never horizontally vFlip = FlipStyle_Outside; #endif // #ifdef XP_MACOSX } diff --git a/toolkit/content/tests/chrome/window_tooltip.xul b/toolkit/content/tests/chrome/window_tooltip.xul index 569804f80578..087c91c3e748 100644 --- a/toolkit/content/tests/chrome/window_tooltip.xul +++ b/toolkit/content/tests/chrome/window_tooltip.xul @@ -229,10 +229,72 @@ var popupTests = [ is(gOriginalWidth, rect.right - rect.left, testname + " tooltip is original width"); is(gOriginalHeight, rect.bottom - rect.top, testname + " tooltip is original height"); } +}, +{ + testname: "hover tooltip at bottom edge of screen", + events: [ "popupshowing thetooltip", "popupshown thetooltip" ], + autohide: "thetooltip", + condition: function() { + // Only checking OSX here because on other platforms popups and tooltips behave the same way + // when there's not enough space to show them below (by flipping vertically) + // However, on OSX most popups are not flipped but tooltips are. + return navigator.platform.indexOf("Mac") > -1; + }, + test: function() { + var buttonRect = document.getElementById("withtext").getBoundingClientRect(); + var windowY = screen.height - + (window.mozInnerScreenY - window.screenY ) - buttonRect.bottom; + + moveWindowTo(window.screenX, windowY, function() { + gButton = document.getElementById("withtooltip"); + disableNonTestMouse(true); + synthesizeMouse(gButton, 2, 2, { type: "mouseover" }); + synthesizeMouse(gButton, 6, 6, { type: "mousemove" }); + synthesizeMouse(gButton, 4, 4, { type: "mousemove" }); + disableNonTestMouse(false); + }); + }, + result: function(testname) { + var buttonrect = document.getElementById("withtooltip").getBoundingClientRect(); + var rect = document.getElementById("thetooltip").getBoundingClientRect(); + var popupstyle = window.getComputedStyle(document.getElementById("thetooltip"), ""); + + is(Math.round(rect.y + rect.height), + Math.round(buttonrect.top + 4 - parseFloat(popupstyle.marginTop)), + testname + " position of tooltip above button"); + } } ]; +var waitSteps = 0; +function moveWindowTo(x, y, callback, arg) +{ + if (!waitSteps) { + oldx = window.screenX; + oldy = window.screenY; + window.moveTo(x, y); + + waitSteps++; + setTimeout(moveWindowTo, 100, x, y, callback, arg); + return; + } + + if (window.screenX == oldx && window.screenY == oldy) { + if (waitSteps++ > 10) { + ok(false, "Window never moved properly to " + x + "," + y); + window.opener.wrappedJSObject.SimpleTest.finish(); + window.close(); + } + + setTimeout(moveWindowTo, 100, x, y, callback, arg); + } + else { + waitSteps = 0; + callback(arg); + } +} + window.opener.wrappedJSObject.SimpleTest.waitForFocus(runTest, window); ]]> From 95414d19ccf480ea0067d1a95e9eb36fb919bda4 Mon Sep 17 00:00:00 2001 From: Liang-Heng Chen Date: Tue, 3 Nov 2015 01:03:00 +0100 Subject: [PATCH 017/113] Bug 1217807 - Part 1: use ServiceWatcher to extend life cycle of mDNS operators; r=schien --HG-- extra : rebase_source : 8a3db0a321e666e8dc297958e085954530a33571 --- .../mdns/libmdns/MDNSResponderOperator.cpp | 59 +++++++++++-------- .../dns/mdns/libmdns/MDNSResponderOperator.h | 34 +++-------- netwerk/dns/mdns/libmdns/MDNSResponderReply.h | 4 +- 3 files changed, 42 insertions(+), 55 deletions(-) diff --git a/netwerk/dns/mdns/libmdns/MDNSResponderOperator.cpp b/netwerk/dns/mdns/libmdns/MDNSResponderOperator.cpp index 47d338b2c013..808bacb650a6 100644 --- a/netwerk/dns/mdns/libmdns/MDNSResponderOperator.cpp +++ b/netwerk/dns/mdns/libmdns/MDNSResponderOperator.cpp @@ -7,6 +7,7 @@ #include "MDNSResponderReply.h" #include "mozilla/Endian.h" #include "mozilla/Logging.h" +#include "mozilla/ScopeExit.h" #include "nsComponentManagerUtils.h" #include "nsCOMPtr.h" #include "nsDebug.h" @@ -88,9 +89,11 @@ public: virtual uint64_t ByteCountSent() override { return 0; } virtual uint64_t ByteCountReceived() override { return 0; } - explicit ServiceWatcher(DNSServiceRef aService) + explicit ServiceWatcher(DNSServiceRef aService, + MDNSResponderOperator* aOperator) : mThread(nullptr) , mSts(nullptr) + , mOperatorHolder(aOperator) , mService(aService) , mFD(nullptr) , mAttached(false) @@ -146,6 +149,7 @@ private: DNSServiceRefDeallocate(mService); mService = nullptr; } + mOperatorHolder = nullptr; } nsresult PostEvent(void(ServiceWatcher::*func)(void)) @@ -243,6 +247,7 @@ private: nsCOMPtr mThread; RefPtr mSts; + RefPtr mOperatorHolder; DNSServiceRef mService; PRFileDesc* mFD; bool mAttached; @@ -280,7 +285,6 @@ MDNSResponderOperator::Start() nsresult MDNSResponderOperator::Stop() { - mThread = nullptr; return ResetService(nullptr); } @@ -296,7 +300,7 @@ MDNSResponderOperator::ResetService(DNSServiceRef aService) } if (aService) { - RefPtr watcher = new ServiceWatcher(aService); + RefPtr watcher = new ServiceWatcher(aService, this); if (NS_WARN_IF(NS_FAILED(rv = watcher->Init()))) { return rv; } @@ -541,7 +545,17 @@ RegisterOperator::Reply(DNSServiceRef aSdRef, if (NS_WARN_IF(NS_FAILED(info->SetDomainName(aDomain)))) { return; } if (kDNSServiceErr_NoError == aErrorCode) { - mListener->OnServiceRegistered(info); + if (aFlags & kDNSServiceFlagsAdd) { + mListener->OnServiceRegistered(info); + } else { + // If a successfully-registered name later suffers a name conflict + // or similar problem and has to be deregistered, the callback will + // be invoked with the kDNSServiceFlagsAdd flag not set. + LOG_E("RegisterOperator::Reply: deregister"); + if (NS_WARN_IF(NS_FAILED(Stop()))) { + return; + } + } } else { mListener->OnRegistrationFailed(info, aErrorCode); } @@ -552,7 +566,6 @@ ResolveOperator::ResolveOperator(nsIDNSServiceInfo* aServiceInfo, : MDNSResponderOperator() , mServiceInfo(aServiceInfo) , mListener(aListener) - , mDeleteProtector() { } @@ -591,17 +604,9 @@ ResolveOperator::Start() return NS_ERROR_FAILURE; } - mDeleteProtector = this; return ResetService(service); } -nsresult -ResolveOperator::Stop() -{ - nsresult rv = MDNSResponderOperator::Stop(); - return rv; -} - void ResolveOperator::Reply(DNSServiceRef aSdRef, DNSServiceFlags aFlags, @@ -615,7 +620,9 @@ ResolveOperator::Reply(DNSServiceRef aSdRef, { MOZ_ASSERT(GetThread() == NS_GetCurrentThread()); - mDeleteProtector = nullptr; + auto guard = MakeScopeExit([this] { + NS_WARN_IF(NS_FAILED(Stop())); + }); if (NS_WARN_IF(kDNSServiceErr_NoError != aErrorCode)) { LOG_E("ResolveOperator::Reply (%d)", aErrorCode); @@ -684,7 +691,6 @@ GetAddrInfoOperator::GetAddrInfoOperator(nsIDNSServiceInfo* aServiceInfo, : MDNSResponderOperator() , mServiceInfo(aServiceInfo) , mListener(aListener) - , mDeleteProtector() { } @@ -718,17 +724,9 @@ GetAddrInfoOperator::Start() return NS_ERROR_FAILURE; } - mDeleteProtector = this; return ResetService(service); } -nsresult -GetAddrInfoOperator::Stop() -{ - nsresult rv = MDNSResponderOperator::Stop(); - return rv; -} - void GetAddrInfoOperator::Reply(DNSServiceRef aSdRef, DNSServiceFlags aFlags, @@ -740,7 +738,9 @@ GetAddrInfoOperator::Reply(DNSServiceRef aSdRef, { MOZ_ASSERT(GetThread() == NS_GetCurrentThread()); - mDeleteProtector = nullptr; + auto guard = MakeScopeExit([this] { + NS_WARN_IF(NS_FAILED(Stop())); + }); if (NS_WARN_IF(kDNSServiceErr_NoError != aErrorCode)) { LOG_E("GetAddrInfoOperator::Reply (%d)", aErrorCode); @@ -757,13 +757,20 @@ GetAddrInfoOperator::Reply(DNSServiceRef aSdRef, nsCOMPtr info = new nsDNSServiceInfo(mServiceInfo); if (NS_WARN_IF(NS_FAILED(info->SetAddress(addressStr)))) { return; } + /** + * |kDNSServiceFlagsMoreComing| means this callback will be one or more + * callback events later, so this instance should be kept alive until all + * follow-up events are processed. + */ + if (aFlags & kDNSServiceFlagsMoreComing) { + guard.release(); + } + if (kDNSServiceErr_NoError == aErrorCode) { mListener->OnServiceResolved(info); } else { mListener->OnResolveFailed(info, aErrorCode); } - - NS_WARN_IF(NS_FAILED(Stop())); } } // namespace net diff --git a/netwerk/dns/mdns/libmdns/MDNSResponderOperator.h b/netwerk/dns/mdns/libmdns/MDNSResponderOperator.h index ccf466855dc7..a932baa7cc15 100644 --- a/netwerk/dns/mdns/libmdns/MDNSResponderOperator.h +++ b/netwerk/dns/mdns/libmdns/MDNSResponderOperator.h @@ -8,10 +8,10 @@ #include "dns_sd.h" #include "mozilla/Atomics.h" +#include "mozilla/RefPtr.h" #include "nsCOMPtr.h" #include "nsIDNSServiceDiscovery.h" #include "nsIThread.h" -#include "mozilla/RefPtr.h" #include "nsString.h" namespace mozilla { @@ -19,6 +19,8 @@ namespace net { class MDNSResponderOperator { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MDNSResponderOperator) + public: MDNSResponderOperator(); @@ -42,18 +44,14 @@ private: Atomic mIsCancelled; }; -class BrowseOperator final : private MDNSResponderOperator +class BrowseOperator final : public MDNSResponderOperator { public: - NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BrowseOperator) - BrowseOperator(const nsACString& aServiceType, nsIDNSServiceDiscoveryListener* aListener); nsresult Start() override; nsresult Stop() override; - using MDNSResponderOperator::Cancel; - using MDNSResponderOperator::GetThread; void Reply(DNSServiceRef aSdRef, DNSServiceFlags aFlags, @@ -70,20 +68,16 @@ private: nsCOMPtr mListener; }; -class RegisterOperator final : private MDNSResponderOperator +class RegisterOperator final : public MDNSResponderOperator { enum { TXT_BUFFER_SIZE = 256 }; public: - NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RegisterOperator) - RegisterOperator(nsIDNSServiceInfo* aServiceInfo, nsIDNSRegistrationListener* aListener); nsresult Start() override; nsresult Stop() override; - using MDNSResponderOperator::Cancel; - using MDNSResponderOperator::GetThread; void Reply(DNSServiceRef aSdRef, DNSServiceFlags aFlags, @@ -99,19 +93,15 @@ private: nsCOMPtr mListener; }; -class ResolveOperator final : private MDNSResponderOperator +class ResolveOperator final : public MDNSResponderOperator { enum { TXT_BUFFER_SIZE = 256 }; public: - NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ResolveOperator) - ResolveOperator(nsIDNSServiceInfo* aServiceInfo, nsIDNSServiceResolveListener* aListener); nsresult Start() override; - nsresult Stop() override; - using MDNSResponderOperator::GetThread; void Reply(DNSServiceRef aSdRef, DNSServiceFlags aFlags, @@ -129,24 +119,17 @@ private: nsCOMPtr mServiceInfo; nsCOMPtr mListener; - - // hold self until callback is made. - RefPtr mDeleteProtector; }; union NetAddr; -class GetAddrInfoOperator final : private MDNSResponderOperator +class GetAddrInfoOperator final : public MDNSResponderOperator { public: - NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GetAddrInfoOperator) - GetAddrInfoOperator(nsIDNSServiceInfo* aServiceInfo, nsIDNSServiceResolveListener* aListener); nsresult Start() override; - nsresult Stop() override; - using MDNSResponderOperator::GetThread; void Reply(DNSServiceRef aSdRef, DNSServiceFlags aFlags, @@ -161,9 +144,6 @@ private: nsCOMPtr mServiceInfo; nsCOMPtr mListener; - - // hold self until callback is made. - RefPtr mDeleteProtector; }; } // namespace net diff --git a/netwerk/dns/mdns/libmdns/MDNSResponderReply.h b/netwerk/dns/mdns/libmdns/MDNSResponderReply.h index 2cb5cd389357..57d3cb02ef62 100644 --- a/netwerk/dns/mdns/libmdns/MDNSResponderReply.h +++ b/netwerk/dns/mdns/libmdns/MDNSResponderReply.h @@ -48,7 +48,7 @@ private: nsCString mServiceName; nsCString mRegType; nsCString mReplyDomain; - BrowseOperator* mContext; + RefPtr mContext; }; class RegisterReplyRunnable final : public nsRunnable @@ -79,7 +79,7 @@ private: nsCString mName; nsCString mRegType; nsCString mDomain; - RegisterOperator* mContext; + RefPtr mContext; }; class ResolveReplyRunnable final : public nsRunnable From f6449556204dcad94eeb3c9da67e6e22a94cf4f8 Mon Sep 17 00:00:00 2001 From: Liang-Heng Chen Date: Tue, 3 Nov 2015 01:04:00 +0100 Subject: [PATCH 018/113] Bug 1217807 - Part 2: Handle network online/offline event in TCPPresentationServer; r=junior --HG-- extra : rebase_source : ca37f6102d65a7820d502f71d998eb33cf68f47c --- .../interfaces/nsITCPPresentationServer.idl | 13 +-- .../provider/MulticastDNSDeviceProvider.cpp | 63 ++++++++------- .../provider/TCPPresentationServer.js | 81 ++++++++++++++----- .../test_multicast_dns_device_provider.js | 3 +- .../xpcshell/test_tcp_control_channel.js | 21 ++--- 5 files changed, 116 insertions(+), 65 deletions(-) diff --git a/dom/presentation/interfaces/nsITCPPresentationServer.idl b/dom/presentation/interfaces/nsITCPPresentationServer.idl index 71a002f8a62f..5589dc8819c3 100644 --- a/dom/presentation/interfaces/nsITCPPresentationServer.idl +++ b/dom/presentation/interfaces/nsITCPPresentationServer.idl @@ -22,16 +22,17 @@ interface nsITCPDeviceInfo: nsISupports readonly attribute uint16_t port; }; -[scriptable, uuid(fbb890a9-9e95-47d1-a425-86fd95881d81)] +[scriptable, uuid(09bddfaf-fcc2-4dc9-b33e-a509a1c2fb6d)] interface nsITCPPresentationServerListener: nsISupports { /** - * Callback while the server socket stops listening. - * @param aReason - * The reason of the socket close. NS_OK for manually |close|. - * on failure. + * Callback while the server socket changes port. + * This event won't be cached so you should get current port after setting + * this listener to make sure the value is updated. + * @param aPort + * The port of the server socket. */ - void onClose(in nsresult aReason); + void onPortChange(in uint16_t aPort); /** * Callback while the remote host is requesting to start a presentation session. diff --git a/dom/presentation/provider/MulticastDNSDeviceProvider.cpp b/dom/presentation/provider/MulticastDNSDeviceProvider.cpp index aefa2fbff29d..b4359220a7e2 100644 --- a/dom/presentation/provider/MulticastDNSDeviceProvider.cpp +++ b/dom/presentation/provider/MulticastDNSDeviceProvider.cpp @@ -13,6 +13,7 @@ #include "nsComponentManagerUtils.h" #include "nsIObserverService.h" #include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" #ifdef MOZ_WIDGET_ANDROID #include "nsIPropertyBag2.h" @@ -25,16 +26,12 @@ #define SERVICE_TYPE "_mozilla_papi._tcp." -inline static PRLogModuleInfo* -GetProviderLog() -{ - static PRLogModuleInfo* log = PR_NewLogModule("MulticastDNSDeviceProvider"); - return log; -} +static mozilla::LazyLogModule sMulticastDNSProviderLogModule("MulticastDNSDeviceProvider"); + #undef LOG_I -#define LOG_I(...) MOZ_LOG(GetProviderLog(), mozilla::LogLevel::Debug, (__VA_ARGS__)) +#define LOG_I(...) MOZ_LOG(sMulticastDNSProviderLogModule, mozilla::LogLevel::Debug, (__VA_ARGS__)) #undef LOG_E -#define LOG_E(...) MOZ_LOG(GetProviderLog(), mozilla::LogLevel::Error, (__VA_ARGS__)) +#define LOG_E(...) MOZ_LOG(sMulticastDNSProviderLogModule, mozilla::LogLevel::Error, (__VA_ARGS__)) namespace mozilla { namespace dom { @@ -261,20 +258,35 @@ MulticastDNSDeviceProvider::RegisterService() return NS_OK; } - MOZ_ASSERT(!mRegisterRequest); - nsresult rv; - if (NS_WARN_IF(NS_FAILED(rv = mPresentationServer->SetListener(mWrappedListener)))) { - return rv; - } - if (NS_WARN_IF(NS_FAILED(rv = mPresentationServer->StartService(0)))) { - return rv; - } + uint16_t servicePort; if (NS_WARN_IF(NS_FAILED(rv = mPresentationServer->GetPort(&servicePort)))) { return rv; } + /** + * If |servicePort| is non-zero, it means PresentationServer is running. + * Otherwise, we should make it start serving. + */ + if (!servicePort) { + if (NS_WARN_IF(NS_FAILED(rv = mPresentationServer->SetListener(mWrappedListener)))) { + return rv; + } + if (NS_WARN_IF(NS_FAILED(rv = mPresentationServer->StartService(0)))) { + return rv; + } + if (NS_WARN_IF(NS_FAILED(rv = mPresentationServer->GetPort(&servicePort)))) { + return rv; + } + } + + // Cancel on going service registration. + if (mRegisterRequest) { + mRegisterRequest->Cancel(NS_OK); + mRegisterRequest = nullptr; + } + /** * Register the presentation control channel server as an mDNS service. */ @@ -755,12 +767,9 @@ MulticastDNSDeviceProvider::OnRegistrationFailed(nsIDNSServiceInfo* aServiceInfo mRegisterRequest = nullptr; - nsresult rv; - if (aErrorCode == nsIDNSRegistrationListener::ERROR_SERVICE_NOT_RUNNING) { - if (NS_WARN_IF(NS_FAILED(rv = RegisterService()))) { - return rv; - } + return NS_DispatchToMainThread( + NS_NewRunnableMethod(this, &MulticastDNSDeviceProvider::RegisterService)); } return NS_OK; @@ -855,17 +864,13 @@ MulticastDNSDeviceProvider::OnResolveFailed(nsIDNSServiceInfo* aServiceInfo, // nsITCPPresentationServerListener NS_IMETHODIMP -MulticastDNSDeviceProvider::OnClose(nsresult aReason) +MulticastDNSDeviceProvider::OnPortChange(uint16_t aPort) { - LOG_I("OnClose: %x", aReason); + LOG_I("OnPortChange: %d", aPort); MOZ_ASSERT(NS_IsMainThread()); - UnregisterService(aReason); - - nsresult rv; - - if (mDiscoverable && NS_WARN_IF(NS_FAILED(rv = RegisterService()))) { - return rv; + if (mDiscoverable) { + RegisterService(); } return NS_OK; diff --git a/dom/presentation/provider/TCPPresentationServer.js b/dom/presentation/provider/TCPPresentationServer.js index ec6e2a4254a8..af4502451a68 100644 --- a/dom/presentation/provider/TCPPresentationServer.js +++ b/dom/presentation/provider/TCPPresentationServer.js @@ -40,16 +40,11 @@ TCPPresentationServer.prototype = { throw Cr.NS_ERROR_FAILURE; } - if (typeof aPort === "undefined") { - DEBUG && log("TCPPresentationServer - aPort should not be undefined"); - throw Cr.NS_ERROR_FAILURE; - } - /** * 0 or undefined indicates opt-out parameter, and a port will be selected * automatically. */ - let serverSocketPort = (aPort !== 0) ? aPort : -1; + let serverSocketPort = (typeof aPort !== "undefined" && aPort !== 0) ? aPort : -1; this._serverSocket = Cc["@mozilla.org/network/server-socket;1"] .createInstance(Ci.nsIServerSocket); @@ -70,7 +65,12 @@ TCPPresentationServer.prototype = { this._port = this._serverSocket.port; - DEBUG && log("TCPPresentationServer - service start on port: " + aPort); + DEBUG && log("TCPPresentationServer - service start on port: " + this._port); + + // Monitor network interface change to restart server socket. + // Only B2G has nsINetworkManager + Services.obs.addObserver(this, "network-active-changed", false); + Services.obs.addObserver(this, "network:offline-status-changed", false); }, get id() { @@ -178,31 +178,74 @@ TCPPresentationServer.prototype = { // nsIServerSocketListener (Triggered by nsIServerSocket.init) onStopListening: function(aServerSocket, aStatus) { DEBUG && log("TCPPresentationServer - onStopListening: " + aStatus); - - if (this._serverSocket) { - DEBUG && log("TCPPresentationServer - should be non-manually closed"); - this.close(); - } else if (aStatus === Cr.NS_BINDING_ABORTED) { - DEBUG && log("TCPPresentationServer - should be manually closed"); - aStatus = Cr.NS_OK; - } - - this._listener && this._listener.onClose(aStatus); }, close: function() { DEBUG && log("TCPPresentationServer - close"); - if (this._serverSocket) { + if (this._isServiceInit()) { DEBUG && log("TCPPresentationServer - close server socket"); this._serverSocket.close(); this._serverSocket = null; + + Services.obs.removeObserver(this, "network-active-changed"); + Services.obs.removeObserver(this, "network:offline-status-changed"); } this._port = 0; }, + // nsIObserver + observe: function(aSubject, aTopic, aData) { + DEBUG && log("TCPPresentationServer - observe: " + aTopic); + switch (aTopic) { + case "network-active-changed": { + if (!aSubject) { + DEBUG && log("No active network"); + return; + } + + /** + * Restart service only when original status is online because other + * cases will be handled by "network:offline-status-changed". + */ + if (!Services.io.offline) { + this._restartService(); + } + break; + } + case "network:offline-status-changed": { + if (aData == "offline") { + DEBUG && log("network offline"); + return; + } + this._restartService(); + break; + } + } + }, + + _restartService: function() { + DEBUG && log("TCPPresentationServer - restart service"); + + // restart server socket + if (this._isServiceInit()) { + let port = this._port; + this.close(); + + try { + this.startService(); + if (this._listener && this._port !== port) { + this._listener.onPortChange(this._port); + } + } catch (e) { + DEBUG && log("TCPPresentationServer - restart service fail: " + e); + } + } + }, + classID: Components.ID("{f4079b8b-ede5-4b90-a112-5b415a931deb}"), QueryInterface : XPCOMUtils.generateQI([Ci.nsIServerSocketListener, - Ci.nsITCPPresentationServer]), + Ci.nsITCPPresentationServer, + Ci.nsIObserver]), }; function ChannelDescription(aInit) { diff --git a/dom/presentation/tests/xpcshell/test_multicast_dns_device_provider.js b/dom/presentation/tests/xpcshell/test_multicast_dns_device_provider.js index 4714b0204966..77b59abaacc3 100644 --- a/dom/presentation/tests/xpcshell/test_multicast_dns_device_provider.js +++ b/dom/presentation/tests/xpcshell/test_multicast_dns_device_provider.js @@ -1002,7 +1002,8 @@ function serverClosed() { Assert.equal(listener.devices.length, 1); let serverListener = provider.QueryInterface(Ci.nsITCPPresentationServerListener); - serverListener.onClose(Cr.NS_ERROR_UNEXPECTED); + let randomPort = 9527; + serverListener.onPortChange(randomPort); Assert.equal(mockObj.serviceRegistered, 2); Assert.equal(mockObj.serviceUnregistered, 1); diff --git a/dom/presentation/tests/xpcshell/test_tcp_control_channel.js b/dom/presentation/tests/xpcshell/test_tcp_control_channel.js index 845c69164297..cb5313ad9018 100644 --- a/dom/presentation/tests/xpcshell/test_tcp_control_channel.js +++ b/dom/presentation/tests/xpcshell/test_tcp_control_channel.js @@ -183,17 +183,17 @@ function testPresentationServer() { } function setOffline() { - let expectedReason; tps.listener = { - onClose: function(aReason) { - Assert.equal(aReason, Cr.NS_ERROR_ABORT, 'TCPPresentationServer close as expected'); - Services.io.offline = false; + onPortChange: function(aPort) { + Assert.notEqual(aPort, 0, 'TCPPresentationServer port changed and the port should be valid'); + tps.close(); run_next_test(); }, - } + }; - // Let the server socket be closed non-manually + // Let the server socket restart automatically. Services.io.offline = true; + Services.io.offline = false; } function oneMoreLoop() { @@ -210,12 +210,13 @@ function oneMoreLoop() { function shutdown() { tps.listener = { - onClose: function(aReason) { - Assert.equal(aReason, Cr.NS_OK, 'TCPPresentationServer close success'); - run_next_test(); + onPortChange: function(aPort) { + Assert.ok(false, 'TCPPresentationServer port changed'); }, - } + }; tps.close(); + Assert.equal(tps.port, 0, "TCPPresentationServer closed"); + run_next_test(); } // Test manually close control channel with NS_ERROR_FAILURE From fb015566d57b82684c08af6e802a137fa861f1ff Mon Sep 17 00:00:00 2001 From: "Carsten \"Tomcat\" Book" Date: Tue, 3 Nov 2015 10:43:03 +0100 Subject: [PATCH 019/113] Backed out changeset ff4ea28c022f (bug 1218643) for mulet m1 test failures --HG-- extra : rebase_source : 357d6d07bf13133836f7ebfe43cd0956347722a5 --- js/src/asmjs/AsmJSLink.cpp | 12 ++++++++++++ js/src/asmjs/AsmJSValidate.h | 19 +++++++++++-------- .../neuter-during-arguments-coercion.js | 2 +- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/js/src/asmjs/AsmJSLink.cpp b/js/src/asmjs/AsmJSLink.cpp index 873a1cb44db9..e7e5e4dc33d0 100644 --- a/js/src/asmjs/AsmJSLink.cpp +++ b/js/src/asmjs/AsmJSLink.cpp @@ -469,6 +469,17 @@ LinkModuleToHeap(JSContext* cx, AsmJSModule& module, HandlebyteLength(); + if (IsDeprecatedAsmJSHeapLength(heapLength)) { + LinkFail(cx, "ArrayBuffer byteLengths smaller than 64KB are deprecated and " + "will cause a link-time failure in the future"); + + // The goal of deprecation is to give apps some time before linking + // fails. However, if warnings-as-errors is turned on (which happens as + // part of asm.js testing) an exception may be raised. + if (cx->isExceptionPending()) + return false; + } + if (!IsValidAsmJSHeapLength(heapLength)) { ScopedJSFreePtr msg( JS_smprintf("ArrayBuffer byteLength 0x%x is not a valid heap length. The next " @@ -620,6 +631,7 @@ ChangeHeap(JSContext* cx, AsmJSModule& module, const CallArgs& args) } MOZ_ASSERT(IsValidAsmJSHeapLength(heapLength)); + MOZ_ASSERT(!IsDeprecatedAsmJSHeapLength(heapLength)); if (!ArrayBufferObject::prepareForAsmJS(cx, newBuffer, module.usesSignalHandlersForOOB())) return false; diff --git a/js/src/asmjs/AsmJSValidate.h b/js/src/asmjs/AsmJSValidate.h index 6255ec541425..c6aff6449f97 100644 --- a/js/src/asmjs/AsmJSValidate.h +++ b/js/src/asmjs/AsmJSValidate.h @@ -51,14 +51,9 @@ extern bool ValidateAsmJS(ExclusiveContext* cx, AsmJSParser& parser, frontend::ParseNode* stmtList, bool* validated); -// The minimum heap length for asm.js. -const size_t AsmJSMinHeapLength = 64 * 1024; - // The assumed page size; dynamically checked in ValidateAsmJS. const size_t AsmJSPageSize = 4096; -static_assert(AsmJSMinHeapLength % AsmJSPageSize == 0, "Invalid page size"); - #if defined(ASMJS_MAY_USE_SIGNAL_HANDLERS_FOR_OOB) // Targets define AsmJSImmediateRange to be the size of an address immediate, @@ -91,8 +86,8 @@ static const size_t AsmJSMappedSize = 4 * 1024ULL * 1024ULL * 1024ULL + inline uint32_t RoundUpToNextValidAsmJSHeapLength(uint32_t length) { - if (length <= AsmJSMinHeapLength) - return AsmJSMinHeapLength; + if (length <= 4 * 1024) + return 4 * 1024; if (length <= 16 * 1024 * 1024) return mozilla::RoundUpPow2(length); @@ -104,7 +99,7 @@ RoundUpToNextValidAsmJSHeapLength(uint32_t length) inline bool IsValidAsmJSHeapLength(uint32_t length) { - bool valid = length >= AsmJSMinHeapLength && + bool valid = length >= 4 * 1024 && (IsPowerOfTwo(length) || (length & 0x00ffffff) == 0); @@ -114,6 +109,14 @@ IsValidAsmJSHeapLength(uint32_t length) return valid; } +// For now, power-of-2 lengths in this range are accepted, but in the future +// we'll change this to cause link-time failure. +inline bool +IsDeprecatedAsmJSHeapLength(uint32_t length) +{ + return length >= 4 * 1024 && length < 64 * 1024 && IsPowerOfTwo(length); +} + // Return whether asm.js optimization is inhibited by the platform or // dynamically disabled: extern bool diff --git a/js/src/jit-test/tests/asm.js/neuter-during-arguments-coercion.js b/js/src/jit-test/tests/asm.js/neuter-during-arguments-coercion.js index 122cff61f4b7..c0b4e84c4c9c 100644 --- a/js/src/jit-test/tests/asm.js/neuter-during-arguments-coercion.js +++ b/js/src/jit-test/tests/asm.js/neuter-during-arguments-coercion.js @@ -15,7 +15,7 @@ function f(stdlib, foreign, buffer) if (isAsmJSCompilationAvailable()) assertEq(isAsmJSModule(f), true); -var i32 = new Int32Array(65536); +var i32 = new Int32Array(4096); var buffer = i32.buffer; var set = f(this, null, buffer); if (isAsmJSCompilationAvailable()) From 6756cbfd3e49aa28e34c66404285b8ed6a532830 Mon Sep 17 00:00:00 2001 From: Jason Orendorff Date: Wed, 5 Aug 2015 18:05:15 -0500 Subject: [PATCH 020/113] Bug 1191570 - Use ToPropertyKey everywhere ES6 says to use it. r=Waldo, r=jandem. --HG-- extra : commitid : 1MjupHnEQRi extra : rebase_source : 2e2d18286794747f18a2554355dc291fc6205f40 --- js/src/builtin/Object.cpp | 6 +- js/src/jit/VMFunctions.cpp | 2 +- .../Expressions/ToPropertyKey-symbols.js | 94 +++++++++++++++++++ js/src/tests/ecma_6/Reflect/propertyKeys.js | 6 ++ js/src/vm/Interpreter-inl.h | 54 ++++------- js/src/vm/Interpreter.cpp | 12 +-- js/src/vm/Opcodes.h | 9 +- 7 files changed, 134 insertions(+), 49 deletions(-) create mode 100644 js/src/tests/ecma_6/Expressions/ToPropertyKey-symbols.js diff --git a/js/src/builtin/Object.cpp b/js/src/builtin/Object.cpp index 8fb81334854e..372c39ea4f6a 100644 --- a/js/src/builtin/Object.cpp +++ b/js/src/builtin/Object.cpp @@ -83,7 +83,7 @@ js::obj_propertyIsEnumerable(JSContext* cx, unsigned argc, Value* vp) /* Step 1. */ RootedId idRoot(cx); - if (!ValueToId(cx, idValue, &idRoot)) + if (!ToPropertyKey(cx, idValue, &idRoot)) return false; /* Step 2. */ @@ -531,7 +531,7 @@ js::obj_hasOwnProperty(JSContext* cx, unsigned argc, Value* vp) /* Step 1. */ RootedId idRoot(cx); - if (!ValueToId(cx, idValue, &idRoot)) + if (!ToPropertyKey(cx, idValue, &idRoot)) return false; /* Step 2. */ @@ -774,7 +774,7 @@ js::obj_defineProperty(JSContext* cx, unsigned argc, Value* vp) if (!GetFirstArgumentAsObject(cx, args, "Object.defineProperty", &obj)) return false; RootedId id(cx); - if (!ValueToId(cx, args.get(1), &id)) + if (!ToPropertyKey(cx, args.get(1), &id)) return false; // Steps 4-5. diff --git a/js/src/jit/VMFunctions.cpp b/js/src/jit/VMFunctions.cpp index 4cc5825a897a..72a53f5762c5 100644 --- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -537,7 +537,7 @@ bool OperatorIn(JSContext* cx, HandleValue key, HandleObject obj, bool* out) { RootedId id(cx); - return ValueToId(cx, key, &id) && + return ToPropertyKey(cx, key, &id) && HasProperty(cx, obj, id, out); } diff --git a/js/src/tests/ecma_6/Expressions/ToPropertyKey-symbols.js b/js/src/tests/ecma_6/Expressions/ToPropertyKey-symbols.js new file mode 100644 index 000000000000..c89edede2d23 --- /dev/null +++ b/js/src/tests/ecma_6/Expressions/ToPropertyKey-symbols.js @@ -0,0 +1,94 @@ +var symbols = [ + Symbol(), Symbol("iterator"), Symbol.for("iterator"), Symbol.iterator +]; + +for (var sym of symbols) { + var key = { + toString() { return sym; } + }; + + // Test that ToPropertyKey can return a symbol in each of the following + // contexts. + + // Computed property names. + var obj = {[key]: 13}; + var found = Reflect.ownKeys(obj); + assertEq(found.length, 1); + assertEq(found[0], sym); + + // Computed accessor property names. + var obj2 = { + get [key]() { return "got"; }, + set [key](v) { this.v = v; } + }; + assertEq(obj2[sym], "got"); + obj2[sym] = 33; + assertEq(obj2.v, 33); + + // Getting and setting properties. + assertEq(obj[key], 13); + obj[key] = 19; + assertEq(obj[sym], 19); + (function () { "use strict"; obj[key] = 20; })(); + assertEq(obj[sym], 20); + obj[key]++; + assertEq(obj[sym], 21); + + // Getting properties of primitive values. + Number.prototype[sym] = "success"; + assertEq(Math.PI[key], "success"); + delete Number.prototype[sym]; + + if (classesEnabled()) { + eval(` + // Getting a super property. + class X { + [sym]() { return "X"; } + } + class Y extends X { + [sym]() { return super[key]() + "Y"; } + } + var y = new Y(); + assertEq(y[sym](), "XY"); + + // Setting a super property. + class Z { + set [sym](v) { + this.self = this; + this.value = v; + } + } + class W extends Z { + set [sym](v) { + this.isW = true; + super[key] = v; + } + } + var w = new W(); + w[key] = "ok"; + assertEq(w.self, w); + assertEq(w.value, "ok"); + assertEq(w.isW, true); + `); + } + + // Deleting properties. + obj = {[sym]: 1}; + assertEq(delete obj[key], true); + assertEq(sym in obj, false); + + // LHS of `in` expressions. + assertEq(key in {iterator: 0}, false); + assertEq(key in {[sym]: 0}, true); + + // Methods of Object and Object.prototype + obj = {}; + Object.defineProperty(obj, key, {value: "ok", enumerable: true}); + assertEq(obj[sym], "ok"); + assertEq(obj.hasOwnProperty(key), true); + assertEq(obj.propertyIsEnumerable(key), true); + var desc = Object.getOwnPropertyDescriptor(obj, key); + assertEq(desc.value, "ok"); +} + +reportCompare(0, 0); diff --git a/js/src/tests/ecma_6/Reflect/propertyKeys.js b/js/src/tests/ecma_6/Reflect/propertyKeys.js index 9dc8423d8de3..6495a96fd9ac 100644 --- a/js/src/tests/ecma_6/Reflect/propertyKeys.js +++ b/js/src/tests/ecma_6/Reflect/propertyKeys.js @@ -44,6 +44,12 @@ var keys = [ [Symbol.toPrimitive](hint) { return hint; } }, expected: "string" + }, + { + value: { + [Symbol.toPrimitive](hint) { return Symbol.for(hint); } + }, + expected: Symbol.for("string") } ]; diff --git a/js/src/vm/Interpreter-inl.h b/js/src/vm/Interpreter-inl.h index d6284be6cccb..f2f99ce1f1de 100644 --- a/js/src/vm/Interpreter-inl.h +++ b/js/src/vm/Interpreter-inl.h @@ -583,7 +583,7 @@ ToIdOperation(JSContext* cx, HandleScript script, jsbytecode* pc, HandleValue ob return false; RootedId id(cx); - if (!ValueToId(cx, idval, &id)) + if (!ToPropertyKey(cx, idval, &id)) return false; res.set(IdToValue(id)); @@ -608,14 +608,11 @@ GetObjectElementOperation(JSContext* cx, JSOp op, JS::HandleObject obj, JS::Hand break; } - if (IsSymbolOrSymbolWrapper(key)) { - RootedId id(cx, SYMBOL_TO_JSID(ToSymbolPrimitive(key))); - if (!GetProperty(cx, obj, receiver, id, res)) + if (key.isString()) { + JSString* str = key.toString(); + JSAtom* name = str->isAtom() ? &str->asAtom() : AtomizeString(cx, str); + if (!name) return false; - break; - } - - if (JSAtom* name = ToAtom(cx, key)) { if (name->isIndex(&index)) { if (GetElementNoGC(cx, obj, receiver, index, res.address())) break; @@ -625,17 +622,11 @@ GetObjectElementOperation(JSContext* cx, JSOp op, JS::HandleObject obj, JS::Hand } } - JSAtom* name = ToAtom(cx, key); - if (!name) + RootedId id(cx); + if (!ToPropertyKey(cx, key, &id)) + return false; + if (!GetProperty(cx, obj, receiver, id, res)) return false; - - if (name->isIndex(&index)) { - if (!GetElement(cx, obj, receiver, index, res)) - return false; - } else { - if (!GetProperty(cx, obj, receiver, name->asPropertyName(), res)) - return false; - } } while (false); #if JS_HAS_NO_SUCH_METHOD @@ -673,14 +664,11 @@ GetPrimitiveElementOperation(JSContext* cx, JSOp op, JS::HandleValue receiver_, break; } - if (IsSymbolOrSymbolWrapper(key)) { - RootedId id(cx, SYMBOL_TO_JSID(ToSymbolPrimitive(key))); - if (!GetProperty(cx, boxed, receiver, id, res)) + if (key.isString()) { + JSString* str = key.toString(); + JSAtom* name = str->isAtom() ? &str->asAtom() : AtomizeString(cx, str); + if (!name) return false; - break; - } - - if (JSAtom* name = ToAtom(cx, key)) { if (name->isIndex(&index)) { if (GetElementNoGC(cx, boxed, receiver, index, res.address())) break; @@ -690,17 +678,11 @@ GetPrimitiveElementOperation(JSContext* cx, JSOp op, JS::HandleValue receiver_, } } - JSAtom* name = ToAtom(cx, key); - if (!name) + RootedId id(cx); + if (!ToPropertyKey(cx, key, &id)) + return false; + if (!GetProperty(cx, boxed, boxed, id, res)) return false; - - if (name->isIndex(&index)) { - if (!GetElement(cx, boxed, receiver, index, res)) - return false; - } else { - if (!GetProperty(cx, boxed, receiver, name->asPropertyName(), res)) - return false; - } } while (false); // Note: we don't call a __noSuchMethod__ hook when |this| was primitive. @@ -784,7 +766,7 @@ InitElemOperation(JSContext* cx, HandleObject obj, HandleValue idval, HandleValu MOZ_ASSERT(!obj->getClass()->setProperty); RootedId id(cx); - if (!ValueToId(cx, idval, &id)) + if (!ToPropertyKey(cx, idval, &id)) return false; return DefineProperty(cx, obj, id, val, nullptr, nullptr, JSPROP_ENUMERATE); diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 44df23b10d20..46a1e1841614 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -2024,7 +2024,7 @@ END_CASE(JSOP_AND) #define FETCH_ELEMENT_ID(n, id) \ JS_BEGIN_MACRO \ - if (!ValueToId(cx, REGS.stackHandleAt(n), &(id))) \ + if (!ToPropertyKey(cx, REGS.stackHandleAt(n), &(id))) \ goto error; \ JS_END_MACRO @@ -2468,7 +2468,7 @@ CASE(JSOP_STRICTDELELEM) ObjectOpResult result; ReservedRooted id(&rootId0); - if (!ValueToId(cx, propval, &id)) + if (!ToPropertyKey(cx, propval, &id)) goto error; if (!DeleteProperty(cx, obj, id, result)) goto error; @@ -4265,7 +4265,7 @@ js::DeleteElementJit(JSContext* cx, HandleValue val, HandleValue index, bool* bp return false; RootedId id(cx); - if (!ValueToId(cx, index, &id)) + if (!ToPropertyKey(cx, index, &id)) return false; ObjectOpResult result; if (!DeleteProperty(cx, obj, id, result)) @@ -4301,7 +4301,7 @@ js::SetObjectElement(JSContext* cx, HandleObject obj, HandleValue index, HandleV bool strict) { RootedId id(cx); - if (!ValueToId(cx, index, &id)) + if (!ToPropertyKey(cx, index, &id)) return false; RootedValue receiver(cx, ObjectValue(*obj)); return SetObjectElementOperation(cx, obj, receiver, id, value, strict); @@ -4313,7 +4313,7 @@ js::SetObjectElement(JSContext* cx, HandleObject obj, HandleValue index, HandleV { MOZ_ASSERT(pc); RootedId id(cx); - if (!ValueToId(cx, index, &id)) + if (!ToPropertyKey(cx, index, &id)) return false; RootedValue receiver(cx, ObjectValue(*obj)); return SetObjectElementOperation(cx, obj, receiver, id, value, strict, script, pc); @@ -4486,7 +4486,7 @@ js::InitGetterSetterOperation(JSContext* cx, jsbytecode* pc, HandleObject obj, H HandleObject val) { RootedId id(cx); - if (!ValueToId(cx, idval, &id)) + if (!ToPropertyKey(cx, idval, &id)) return false; return InitGetterSetterOperation(cx, pc, obj, id, val); diff --git a/js/src/vm/Opcodes.h b/js/src/vm/Opcodes.h index 19832563f420..703eb998b565 100644 --- a/js/src/vm/Opcodes.h +++ b/js/src/vm/Opcodes.h @@ -2023,12 +2023,15 @@ macro(JSOP_REST, 224, "rest", NULL, 1, 0, 1, JOF_BYTE|JOF_TYPESET) \ \ /* - * Pops the top of stack value, converts it into a jsid (int or string), and - * pushes it onto the stack. + * First, throw a TypeError if baseValue is null or undefined. Then, + * replace the top-of-stack value propertyNameValue with + * ToPropertyKey(propertyNameValue). This opcode implements ES6 12.3.2.1 + * steps 7-10. It is also used to implement computed property names; in + * that case, baseValue is always an object, so the first step is a no-op. * Category: Literals * Type: Object * Operands: - * Stack: obj, id => obj, (jsid of id) + * Stack: baseValue, propertyNameValue => baseValue, propertyKey */ \ macro(JSOP_TOID, 225, "toid", NULL, 1, 1, 1, JOF_BYTE) \ \ From 1ebb1e2bb762335982a99823d2f6a0973d3cb242 Mon Sep 17 00:00:00 2001 From: Nicolas Silva Date: Tue, 3 Nov 2015 12:24:26 +0100 Subject: [PATCH 021/113] Bug 1219330 - Handle PlanaYCbCrImage::SetData failure. r=jya, jesup --- dom/media/MediaData.cpp | 28 ++++++++++--------- dom/media/MediaData.h | 2 +- dom/media/VideoSegment.cpp | 5 +++- dom/media/webrtc/MediaEngineDefault.cpp | 8 +++++- .../webrtc/MediaEngineRemoteVideoSource.cpp | 5 +++- gfx/layers/GrallocImages.cpp | 12 ++++---- gfx/layers/GrallocImages.h | 4 +-- gfx/layers/ImageContainer.cpp | 12 ++++---- gfx/layers/ImageContainer.h | 6 ++-- gfx/layers/basic/BasicImages.cpp | 14 ++++++---- gfx/layers/ipc/SharedPlanarYCbCrImage.cpp | 14 ++++++---- gfx/layers/ipc/SharedPlanarYCbCrImage.h | 4 +-- .../src/mediapipeline/MediaPipeline.cpp | 5 +++- 13 files changed, 72 insertions(+), 47 deletions(-) diff --git a/dom/media/MediaData.cpp b/dom/media/MediaData.cpp index d20e4cb6ee1a..3539f221374e 100644 --- a/dom/media/MediaData.cpp +++ b/dom/media/MediaData.cpp @@ -198,14 +198,14 @@ VideoData::ShallowCopyUpdateTimestampAndDuration(const VideoData* aOther, } /* static */ -void VideoData::SetVideoDataToImage(PlanarYCbCrImage* aVideoImage, +bool VideoData::SetVideoDataToImage(PlanarYCbCrImage* aVideoImage, const VideoInfo& aInfo, const YCbCrBuffer &aBuffer, const IntRect& aPicture, bool aCopyData) { if (!aVideoImage) { - return; + return false; } const YCbCrBuffer::Plane &Y = aBuffer.mPlanes[0]; const YCbCrBuffer::Plane &Cb = aBuffer.mPlanes[1]; @@ -229,9 +229,9 @@ void VideoData::SetVideoDataToImage(PlanarYCbCrImage* aVideoImage, aVideoImage->SetDelayedConversion(true); if (aCopyData) { - aVideoImage->SetData(data); + return aVideoImage->SetData(data); } else { - aVideoImage->SetDataNoCopy(data); + return aVideoImage->SetDataNoCopy(data); } } @@ -330,12 +330,10 @@ VideoData::Create(const VideoInfo& aInfo, "Wrong format?"); PlanarYCbCrImage* videoImage = static_cast(v->mImage.get()); - if (!aImage) { - VideoData::SetVideoDataToImage(videoImage, aInfo, aBuffer, aPicture, - true /* aCopyData */); - } else { - VideoData::SetVideoDataToImage(videoImage, aInfo, aBuffer, aPicture, - false /* aCopyData */); + bool shouldCopyData = (aImage == nullptr); + if (!VideoData::SetVideoDataToImage(videoImage, aInfo, aBuffer, aPicture, + shouldCopyData)) { + return nullptr; } #ifdef MOZ_WIDGET_GONK @@ -346,8 +344,10 @@ VideoData::Create(const VideoInfo& aInfo, return nullptr; } videoImage = static_cast(v->mImage.get()); - VideoData::SetVideoDataToImage(videoImage, aInfo, aBuffer, aPicture, - true /* aCopyData */); + if(!VideoData::SetVideoDataToImage(videoImage, aInfo, aBuffer, aPicture, + true /* aCopyData */)) { + return nullptr; + } } #endif return v.forget(); @@ -473,7 +473,9 @@ VideoData::Create(const VideoInfo& aInfo, data.mPicSize = aPicture.Size(); data.mGraphicBuffer = aBuffer; - videoImage->SetData(data); + if (!videoImage->SetData(data)) { + return nullptr; + } return v.forget(); } diff --git a/dom/media/MediaData.h b/dom/media/MediaData.h index d66be54a6c91..6dc4f2ef61d7 100644 --- a/dom/media/MediaData.h +++ b/dom/media/MediaData.h @@ -283,7 +283,7 @@ public: // Initialize PlanarYCbCrImage. Only When aCopyData is true, // video data is copied to PlanarYCbCrImage. - static void SetVideoDataToImage(PlanarYCbCrImage* aVideoImage, + static bool SetVideoDataToImage(PlanarYCbCrImage* aVideoImage, const VideoInfo& aInfo, const YCbCrBuffer &aBuffer, const IntRect& aPicture, diff --git a/dom/media/VideoSegment.cpp b/dom/media/VideoSegment.cpp index adc8995d685c..7ac47b5c901f 100644 --- a/dom/media/VideoSegment.cpp +++ b/dom/media/VideoSegment.cpp @@ -80,7 +80,10 @@ VideoFrame::CreateBlackImage(const gfx::IntSize& aSize) data.mStereoMode = StereoMode::MONO; // SetData copies data, so we can free data. - planar->SetData(data); + if (!planar->SetData(data)) { + MOZ_ASSERT(false); + return nullptr; + } return image.forget(); } diff --git a/dom/media/webrtc/MediaEngineDefault.cpp b/dom/media/webrtc/MediaEngineDefault.cpp index 5885d1f30cef..e735f8d21e99 100644 --- a/dom/media/webrtc/MediaEngineDefault.cpp +++ b/dom/media/webrtc/MediaEngineDefault.cpp @@ -250,10 +250,16 @@ MediaEngineDefaultVideoSource::Notify(nsITimer* aTimer) 0, 0); #endif - ycbcr_image->SetData(data); + bool setData = ycbcr_image->SetData(data); + MOZ_ASSERT(setData); + // SetData copies data, so we can free the frame ReleaseFrame(data); + if (!setData) { + return NS_ERROR_FAILURE; + } + MonitorAutoLock lock(mMonitor); // implicitly releases last image diff --git a/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp b/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp index eac7c49f9300..65a8847fca92 100644 --- a/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp +++ b/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp @@ -315,7 +315,10 @@ MediaEngineRemoteVideoSource::DeliverFrame(unsigned char* buffer, data.mPicSize = IntSize(mWidth, mHeight); data.mStereoMode = StereoMode::MONO; - videoImage->SetData(data); + if (!videoImage->SetData(data)) { + MOZ_ASSERT(false); + return 0; + } #ifdef DEBUG static uint32_t frame_num = 0; diff --git a/gfx/layers/GrallocImages.cpp b/gfx/layers/GrallocImages.cpp index 5083eb4c0fe8..b4c674e86aab 100644 --- a/gfx/layers/GrallocImages.cpp +++ b/gfx/layers/GrallocImages.cpp @@ -57,7 +57,7 @@ GrallocImage::~GrallocImage() { } -void +bool GrallocImage::SetData(const Data& aData) { MOZ_ASSERT(!mTextureClient, "TextureClient is already set"); @@ -70,7 +70,7 @@ GrallocImage::SetData(const Data& aData) if (gfxPlatform::GetPlatform()->IsInGonkEmulator()) { // Emulator does not support HAL_PIXEL_FORMAT_YV12. - return; + return false; } RefPtr textureClient = @@ -88,7 +88,7 @@ GrallocImage::SetData(const Data& aData) sp graphicBuffer = textureClient->GetGraphicBuffer(); if (!result || !graphicBuffer.get()) { mTextureClient = nullptr; - return; + return false; } mTextureClient = textureClient; @@ -96,7 +96,7 @@ GrallocImage::SetData(const Data& aData) void* vaddr; if (graphicBuffer->lock(GraphicBuffer::USAGE_SW_WRITE_OFTEN, &vaddr) != OK) { - return; + return false; } uint8_t* yChannel = static_cast(vaddr); @@ -144,12 +144,14 @@ GrallocImage::SetData(const Data& aData) mData.mYChannel = nullptr; mData.mCrChannel = nullptr; mData.mCbChannel = nullptr; + return true; } -void GrallocImage::SetData(const GrallocData& aData) +bool GrallocImage::SetData(const GrallocData& aData) { mTextureClient = static_cast(aData.mGraphicBuffer.get()); mSize = aData.mPicSize; + return true; } /** diff --git a/gfx/layers/GrallocImages.h b/gfx/layers/GrallocImages.h index 701051175b89..606944ada3aa 100644 --- a/gfx/layers/GrallocImages.h +++ b/gfx/layers/GrallocImages.h @@ -66,13 +66,13 @@ public: * This makes a copy of the data buffers, in order to support functioning * in all different layer managers. */ - virtual void SetData(const Data& aData); + virtual bool SetData(const Data& aData); /** * Share the SurfaceDescriptor without making the copy, in order * to support functioning in all different layer managers. */ - virtual void SetData(const GrallocData& aData); + virtual bool SetData(const GrallocData& aData); // From [android 4.0.4]/hardware/msm7k/libgralloc-qsd8k/gralloc_priv.h enum { diff --git a/gfx/layers/ImageContainer.cpp b/gfx/layers/ImageContainer.cpp index f2e523a05f5c..667a49a100d4 100644 --- a/gfx/layers/ImageContainer.cpp +++ b/gfx/layers/ImageContainer.cpp @@ -484,7 +484,7 @@ CopyPlane(uint8_t *aDst, const uint8_t *aSrc, } } -void +bool PlanarYCbCrImage::CopyData(const Data& aData) { mData = aData; @@ -496,7 +496,7 @@ PlanarYCbCrImage::CopyData(const Data& aData) // get new buffer mBuffer = AllocateBuffer(size); if (!mBuffer) - return; + return false; // update buffer size mBufferSize = size; @@ -513,12 +513,13 @@ PlanarYCbCrImage::CopyData(const Data& aData) mData.mCbCrSize, mData.mCbCrStride, mData.mCrSkip); mSize = aData.mPicSize; + return true; } -void +bool PlanarYCbCrImage::SetData(const Data &aData) { - CopyData(aData); + return CopyData(aData); } gfxImageFormat @@ -529,11 +530,12 @@ PlanarYCbCrImage::GetOffscreenFormat() mOffscreenFormat; } -void +bool PlanarYCbCrImage::SetDataNoCopy(const Data &aData) { mData = aData; mSize = aData.mPicSize; + return true; } uint8_t* diff --git a/gfx/layers/ImageContainer.h b/gfx/layers/ImageContainer.h index 70743ecced11..d61a87e004f7 100644 --- a/gfx/layers/ImageContainer.h +++ b/gfx/layers/ImageContainer.h @@ -656,7 +656,7 @@ public: * This makes a copy of the data buffers, in order to support functioning * in all different layer managers. */ - virtual void SetData(const Data& aData); + virtual bool SetData(const Data& aData); /** * This doesn't make a copy of the data buffers. Can be used when mBuffer is @@ -665,7 +665,7 @@ public: * The GStreamer media backend uses this to decode into PlanarYCbCrImage(s) * directly. */ - virtual void SetDataNoCopy(const Data &aData); + virtual bool SetDataNoCopy(const Data &aData); /** * This allocates and returns a new buffer @@ -709,7 +709,7 @@ protected: * * @param aData Input image data. */ - void CopyData(const Data& aData); + bool CopyData(const Data& aData); /** * Return a buffer to store image data in. diff --git a/gfx/layers/basic/BasicImages.cpp b/gfx/layers/basic/BasicImages.cpp index f50f27888bca..4bd232bac92c 100644 --- a/gfx/layers/basic/BasicImages.cpp +++ b/gfx/layers/basic/BasicImages.cpp @@ -48,7 +48,7 @@ public: } } - virtual void SetData(const Data& aData) override; + virtual bool SetData(const Data& aData) override; virtual void SetDelayedConversion(bool aDelayed) override { mDelayedConversion = aDelayed; } already_AddRefed GetAsSourceSurface() override; @@ -91,20 +91,20 @@ public: } }; -void +bool BasicPlanarYCbCrImage::SetData(const Data& aData) { PlanarYCbCrImage::SetData(aData); if (mDelayedConversion) { - return; + return false; } // Do some sanity checks to prevent integer overflow if (aData.mYSize.width > PlanarYCbCrImage::MAX_DIMENSION || aData.mYSize.height > PlanarYCbCrImage::MAX_DIMENSION) { NS_ERROR("Illegal image source width or height"); - return; + return false; } gfx::SurfaceFormat format = gfx::ImageFormatToSurfaceFormat(GetOffscreenFormat()); @@ -114,7 +114,7 @@ BasicPlanarYCbCrImage::SetData(const Data& aData) if (size.width > PlanarYCbCrImage::MAX_DIMENSION || size.height > PlanarYCbCrImage::MAX_DIMENSION) { NS_ERROR("Illegal image dest width or height"); - return; + return false; } gfxImageFormat iFormat = gfx::SurfaceFormatToImageFormat(format); @@ -122,12 +122,14 @@ BasicPlanarYCbCrImage::SetData(const Data& aData) mDecodedBuffer = AllocateBuffer(size.height * mStride); if (!mDecodedBuffer) { // out of memory - return; + return false; } gfx::ConvertYCbCrToRGB(aData, format, size, mDecodedBuffer, mStride); SetOffscreenFormat(iFormat); mSize = size; + + return true; } already_AddRefed diff --git a/gfx/layers/ipc/SharedPlanarYCbCrImage.cpp b/gfx/layers/ipc/SharedPlanarYCbCrImage.cpp index bd53c0908098..e98aa69a8b0f 100644 --- a/gfx/layers/ipc/SharedPlanarYCbCrImage.cpp +++ b/gfx/layers/ipc/SharedPlanarYCbCrImage.cpp @@ -80,7 +80,7 @@ SharedPlanarYCbCrImage::GetAsSourceSurface() return PlanarYCbCrImage::GetAsSourceSurface(); } -void +bool SharedPlanarYCbCrImage::SetData(const PlanarYCbCrData& aData) { // If mTextureClient has not already been allocated (through Allocate(aData)) @@ -88,20 +88,21 @@ SharedPlanarYCbCrImage::SetData(const PlanarYCbCrData& aData) // been called since it will trigger a full copy. PlanarYCbCrData data = aData; if (!mTextureClient && !Allocate(data)) { - return; + return false; } MOZ_ASSERT(mTextureClient->AsTextureClientYCbCr()); if (!mTextureClient->Lock(OpenMode::OPEN_WRITE_ONLY)) { MOZ_ASSERT(false, "Failed to lock the texture."); - return; + return false; } TextureClientAutoUnlock unlock(mTextureClient); if (!mTextureClient->AsTextureClientYCbCr()->UpdateYCbCr(aData)) { MOZ_ASSERT(false, "Failed to copy YCbCr data into the TextureClient"); - return; + return false; } mTextureClient->MarkImmutable(); + return true; } // needs to be overriden because the parent class sets mBuffer which we @@ -131,12 +132,12 @@ SharedPlanarYCbCrImage::AllocateAndGetNewBuffer(uint32_t aSize) return serializer.GetData(); } -void +bool SharedPlanarYCbCrImage::SetDataNoCopy(const Data &aData) { MOZ_ASSERT(mTextureClient, "This Image should have already allocated data"); if (!mTextureClient) { - return; + return false; } mData = aData; mSize = aData.mPicSize; @@ -159,6 +160,7 @@ SharedPlanarYCbCrImage::SetDataNoCopy(const Data &aData) aData.mYSize, aData.mCbCrSize, aData.mStereoMode); + return true; } uint8_t* diff --git a/gfx/layers/ipc/SharedPlanarYCbCrImage.h b/gfx/layers/ipc/SharedPlanarYCbCrImage.h index b8b4162c7314..1d7acdb72a99 100644 --- a/gfx/layers/ipc/SharedPlanarYCbCrImage.h +++ b/gfx/layers/ipc/SharedPlanarYCbCrImage.h @@ -35,8 +35,8 @@ public: virtual uint8_t* GetBuffer() override; virtual already_AddRefed GetAsSourceSurface() override; - virtual void SetData(const PlanarYCbCrData& aData) override; - virtual void SetDataNoCopy(const Data &aData) override; + virtual bool SetData(const PlanarYCbCrData& aData) override; + virtual bool SetDataNoCopy(const Data &aData) override; virtual bool Allocate(PlanarYCbCrData& aData); virtual uint8_t* AllocateBuffer(uint32_t aSize) override; diff --git a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp index 3b95009ed96e..cc1ba2682e0d 100644 --- a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp +++ b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp @@ -1501,7 +1501,10 @@ void MediaPipelineReceiveVideo::PipelineListener::RenderVideoFrame( yuvData.mPicSize = IntSize(width_, height_); yuvData.mStereoMode = StereoMode::MONO; - yuvImage->SetData(yuvData); + if (!yuvImage->SetData(yuvData)) { + MOZ_ASSERT(false); + return; + } image_ = image.forget(); } From 0b52ed20f74652429b633c1f7a1f5d10878993eb Mon Sep 17 00:00:00 2001 From: "Carsten \"Tomcat\" Book" Date: Tue, 3 Nov 2015 12:28:05 +0100 Subject: [PATCH 022/113] Backed out changeset 9ce253c10b9a (bug 1218311) for perma failures in android m9 in test_replay_metadata.html and test_seek-2.html --HG-- extra : rebase_source : 7ca446e06f76015c9930a5ec14bb97f877a86338 --- dom/media/MediaDecoderStateMachine.cpp | 13 +++++++++++++ dom/media/mediasink/VideoSink.cpp | 12 +++--------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/dom/media/MediaDecoderStateMachine.cpp b/dom/media/MediaDecoderStateMachine.cpp index 53e6373b9eba..2cbd14378daf 100644 --- a/dom/media/MediaDecoderStateMachine.cpp +++ b/dom/media/MediaDecoderStateMachine.cpp @@ -890,6 +890,19 @@ MediaDecoderStateMachine::OnVideoDecoded(MediaData* aVideoSample) StopPrerollingVideo(); } + // Schedule the state machine to send stream data as soon as possible if + // the VideoQueue() is empty or contains one frame before the Push(). + // + // The state machine threads requires a frame in VideoQueue() that is `in + // the future` to gather precise timing information. The head of + // VideoQueue() is always `in the past`. + // + // Schedule the state machine as soon as possible to render the video + // frame or delay the state machine thread accurately. + if (VideoQueue().GetSize() <= 2) { + ScheduleStateMachine(); + } + // For non async readers, if the requested video sample was slow to // arrive, increase the amount of audio we buffer to ensure that we // don't run out of audio. This is unnecessary for async readers, diff --git a/dom/media/mediasink/VideoSink.cpp b/dom/media/mediasink/VideoSink.cpp index 219f2125e723..c5275a042699 100644 --- a/dom/media/mediasink/VideoSink.cpp +++ b/dom/media/mediasink/VideoSink.cpp @@ -221,15 +221,9 @@ void VideoSink::OnVideoQueueEvent() { AssertOwnerThread(); - - // The video queue is empty or contains only one frame before Push() which - // means we are slow in video decoding and don't have enough information to - // schedule next render loop accurately (default timeout is 40ms). We need - // to render incoming frames immediately so render loop can be scheduled - // again accurately. - if (mAudioSink->IsPlaying() && VideoQueue().GetSize() <= 2) { - UpdateRenderedVideoFrames(); - } + // Listen to push event, VideoSink should try rendering ASAP if first frame + // arrives but update scheduler is not triggered yet. + TryUpdateRenderedVideoFrames(); } void From 549bd4a5cd11b02688d4cbeb2859930991837d0d Mon Sep 17 00:00:00 2001 From: Jon Coppeard Date: Tue, 3 Nov 2015 13:47:23 +0000 Subject: [PATCH 023/113] Bug 1219954 - Check for OOM in js::AsmJSFunctionToString() r=bbouvier --- js/src/asmjs/AsmJSLink.cpp | 2 +- js/src/jit-test/tests/asm.js/bug1219954.js | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 js/src/jit-test/tests/asm.js/bug1219954.js diff --git a/js/src/asmjs/AsmJSLink.cpp b/js/src/asmjs/AsmJSLink.cpp index e7e5e4dc33d0..1c160e93a599 100644 --- a/js/src/asmjs/AsmJSLink.cpp +++ b/js/src/asmjs/AsmJSLink.cpp @@ -1297,7 +1297,7 @@ js::AsmJSFunctionToString(JSContext* cx, HandleFunction fun) size_t nameEnd = begin + fun->atom()->length(); Rooted src(cx, source->substring(cx, nameEnd, end)); - if (!AppendUseStrictSource(cx, fun, src, out)) + if (!src || !AppendUseStrictSource(cx, fun, src, out)) return nullptr; } else { Rooted src(cx, source->substring(cx, begin, end)); diff --git a/js/src/jit-test/tests/asm.js/bug1219954.js b/js/src/jit-test/tests/asm.js/bug1219954.js new file mode 100644 index 000000000000..17069e7dc3b3 --- /dev/null +++ b/js/src/jit-test/tests/asm.js/bug1219954.js @@ -0,0 +1,12 @@ +"use strict"; + +if (!('oomTest' in this)) + quit(); + +let g = (function() { + "use asm"; + function f() {} + return f; +})(); + +oomTest(() => "" + g); From d2c9d04eab15037a029caacbffad442a925ce5b6 Mon Sep 17 00:00:00 2001 From: Jon Coppeard Date: Tue, 3 Nov 2015 13:47:55 +0000 Subject: [PATCH 024/113] Bug 1219408 - Throw error if module loader attempts to evaluate an uninstantiated module r=shu --- js/src/builtin/ModuleObject.cpp | 5 ++++- js/src/jit-test/tests/modules/bug-1219408.js | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 js/src/jit-test/tests/modules/bug-1219408.js diff --git a/js/src/builtin/ModuleObject.cpp b/js/src/builtin/ModuleObject.cpp index 6d6ef3449d11..2e09f9564d80 100644 --- a/js/src/builtin/ModuleObject.cpp +++ b/js/src/builtin/ModuleObject.cpp @@ -793,7 +793,10 @@ ModuleObject::evaluate(JSContext* cx, HandleModuleObject self, MutableHandleValu { RootedScript script(cx, self->script()); RootedModuleEnvironmentObject scope(cx, self->environment()); - MOZ_ASSERT(scope); + if (!scope) { + JS_ReportError(cx, "Module declarations have not yet been instantiated"); + return false; + } return Execute(cx, script, *scope, rval.address()); } diff --git a/js/src/jit-test/tests/modules/bug-1219408.js b/js/src/jit-test/tests/modules/bug-1219408.js new file mode 100644 index 000000000000..85d709289406 --- /dev/null +++ b/js/src/jit-test/tests/modules/bug-1219408.js @@ -0,0 +1,2 @@ +// |jit-test| error: Error +parseModule("").evaluation(); From c93c98f4582304839bb45378aaa43b73ff94fd07 Mon Sep 17 00:00:00 2001 From: Julian Seward Date: Tue, 3 Nov 2015 15:03:16 +0100 Subject: [PATCH 025/113] Bug 1218506 - Uninitialised value use in nsHttpTransaction::OnTransportStatus. r=mcmanus. --- netwerk/protocol/http/nsHttpTransaction.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/netwerk/protocol/http/nsHttpTransaction.cpp b/netwerk/protocol/http/nsHttpTransaction.cpp index f4534d859d63..94549231f3bc 100644 --- a/netwerk/protocol/http/nsHttpTransaction.cpp +++ b/netwerk/protocol/http/nsHttpTransaction.cpp @@ -94,6 +94,8 @@ nsHttpTransaction::nsHttpTransaction() , mConnection(nullptr) , mRequestHead(nullptr) , mResponseHead(nullptr) + , mReader(nullptr) + , mWriter(nullptr) , mContentLength(-1) , mContentRead(0) , mTransferSize(0) From cb1d1d16b19e4f6a1c3785fd4195a4dd9b3f4788 Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Tue, 3 Nov 2015 15:18:05 +0100 Subject: [PATCH 026/113] Bug 1176782 part 1 - [css-align] Implement the 'justify-items' property in the style system. r=SimonSapin --- layout/style/nsCSSKeywordList.h | 7 +- layout/style/nsCSSParser.cpp | 72 +++++++++++++++++++ layout/style/nsCSSPropList.h | 10 +++ layout/style/nsCSSProps.cpp | 62 ++++++++++++++++ layout/style/nsCSSProps.h | 10 ++- layout/style/nsCSSValue.cpp | 26 +++++++ layout/style/nsCSSValue.h | 3 + layout/style/nsComputedDOMStyle.cpp | 12 ++++ layout/style/nsComputedDOMStyle.h | 11 ++- layout/style/nsComputedDOMStylePropertyList.h | 1 + layout/style/nsRuleNode.cpp | 19 +++++ layout/style/nsStyleConsts.h | 47 ++++++++++++ layout/style/nsStyleStruct.cpp | 28 +++++++- layout/style/nsStyleStruct.h | 11 +++ layout/style/test/property_database.js | 15 ++++ 15 files changed, 326 insertions(+), 8 deletions(-) diff --git a/layout/style/nsCSSKeywordList.h b/layout/style/nsCSSKeywordList.h index a0b9848f98a1..38bf0aab99b2 100644 --- a/layout/style/nsCSSKeywordList.h +++ b/layout/style/nsCSSKeywordList.h @@ -334,7 +334,9 @@ CSS_KEY(landscape, landscape) CSS_KEY(large, large) CSS_KEY(larger, larger) CSS_KEY(layout, layout) +CSS_KEY(last-baseline, last_baseline) CSS_KEY(left, left) +CSS_KEY(legacy, legacy) CSS_KEY(lighten, lighten) CSS_KEY(lighter, lighter) CSS_KEY(line-through, line_through) @@ -459,6 +461,7 @@ CSS_KEY(ruby-text-container, ruby_text_container) CSS_KEY(running, running) CSS_KEY(s, s) CSS_KEY(s-resize, s_resize) +CSS_KEY(safe, safe) CSS_KEY(saturate, saturate) CSS_KEY(saturation, saturation) CSS_KEY(scale, scale) @@ -478,6 +481,8 @@ CSS_KEY(select-all, select_all) CSS_KEY(select-before, select_before) CSS_KEY(select-menu, select_menu) CSS_KEY(select-same, select_same) +CSS_KEY(self-end, self_end) +CSS_KEY(self-start, self_start) CSS_KEY(semi-condensed, semi_condensed) CSS_KEY(semi-expanded, semi_expanded) CSS_KEY(separate, separate) @@ -506,6 +511,7 @@ CSS_KEY(soft-light, soft_light) CSS_KEY(solid, solid) CSS_KEY(space-around, space_around) CSS_KEY(space-between, space_between) +CSS_KEY(space-evenly, space_evenly) CSS_KEY(span, span) CSS_KEY(spell-out, spell_out) CSS_KEY(square, square) @@ -727,7 +733,6 @@ CSS_KEY(bevel, bevel) CSS_KEY(butt, butt) CSS_KEY(central, central) CSS_KEY(crispedges, crispedges) -//CSS_KEY(end, end) CSS_KEY(evenodd, evenodd) CSS_KEY(geometricprecision, geometricprecision) CSS_KEY(hanging, hanging) diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index f5ea0e3150de..8f5b86862d80 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -935,6 +935,11 @@ protected: nsCSSProperty aEndPropID); bool ParseGridArea(); + // parsing 'align/justify-items/self' from the css-align spec + bool ParseAlignJustifyPosition(nsCSSValue& aResult, + const KTableValue aTable[]); + bool ParseJustifyItems(); + // for 'clip' and '-moz-image-region' bool ParseRect(nsCSSProperty aPropID); bool ParseColumns(); @@ -9378,6 +9383,71 @@ CSSParserImpl::ParseGridArea() return true; } +// [ $aTable && ? ] ? +// $aTable is for or +bool +CSSParserImpl::ParseAlignJustifyPosition(nsCSSValue& aResult, + const KTableValue aTable[]) +{ + nsCSSValue pos, overflowPos; + int32_t value = 0; + if (ParseEnum(pos, aTable)) { + value = pos.GetIntValue(); + if (ParseEnum(overflowPos, nsCSSProps::kAlignOverflowPosition)) { + value |= overflowPos.GetIntValue(); + } + aResult.SetIntValue(value, eCSSUnit_Enumerated); + return true; + } + if (ParseEnum(overflowPos, nsCSSProps::kAlignOverflowPosition)) { + if (ParseEnum(pos, aTable)) { + aResult.SetIntValue(pos.GetIntValue() | overflowPos.GetIntValue(), + eCSSUnit_Enumerated); + return true; + } + return false; // must be followed by a value in $table + } + return true; +} + +// auto | stretch | | +// [ && ? ] | +// [ legacy && [ left | right | center ] ] +bool +CSSParserImpl::ParseJustifyItems() +{ + nsCSSValue value; + if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) { + if (MOZ_UNLIKELY(ParseEnum(value, nsCSSProps::kAlignLegacy))) { + nsCSSValue legacy; + if (!ParseEnum(legacy, nsCSSProps::kAlignLegacyPosition)) { + return false; // leading 'legacy' not followed by 'left' etc is an error + } + value.SetIntValue(value.GetIntValue() | legacy.GetIntValue(), + eCSSUnit_Enumerated); + } else { + if (!ParseEnum(value, nsCSSProps::kAlignAutoStretchBaseline)) { + if (!ParseAlignJustifyPosition(value, nsCSSProps::kAlignSelfPosition) || + value.GetUnit() == eCSSUnit_Null) { + return false; + } + // check for a trailing 'legacy' after 'left' etc + auto val = value.GetIntValue(); + if (val == NS_STYLE_JUSTIFY_CENTER || + val == NS_STYLE_JUSTIFY_LEFT || + val == NS_STYLE_JUSTIFY_RIGHT) { + nsCSSValue legacy; + if (ParseEnum(legacy, nsCSSProps::kAlignLegacy)) { + value.SetIntValue(val | legacy.GetIntValue(), eCSSUnit_Enumerated); + } + } + } + } + } + AppendValue(eCSSProperty_justify_items, value); + return true; +} + // : [ | ]? bool CSSParserImpl::ParseColorStop(nsCSSValueGradient* aGradient) @@ -10501,6 +10571,8 @@ CSSParserImpl::ParsePropertyByFunction(nsCSSProperty aPropID) return ParseGridArea(); case eCSSProperty_image_region: return ParseRect(eCSSProperty_image_region); + case eCSSProperty_justify_items: + return ParseJustifyItems(); case eCSSProperty_list_style: return ParseListStyle(); case eCSSProperty_margin: diff --git a/layout/style/nsCSSPropList.h b/layout/style/nsCSSPropList.h index 96ffd675a0dd..de6193776cae 100644 --- a/layout/style/nsCSSPropList.h +++ b/layout/style/nsCSSPropList.h @@ -1707,6 +1707,16 @@ CSS_PROP_POSITION( kJustifyContentKTable, offsetof(nsStylePosition, mJustifyContent), eStyleAnimType_EnumU8) +CSS_PROP_POSITION( + justify-items, + justify_items, + JustifyItems, + CSS_PROPERTY_PARSE_FUNCTION, + "", + 0, + nullptr, + CSS_PROP_NO_OFFSET, + eStyleAnimType_None) CSS_PROP_DISPLAY( float, float, diff --git a/layout/style/nsCSSProps.cpp b/layout/style/nsCSSProps.cpp index 35152aa4b8ae..9367c1287d1e 100644 --- a/layout/style/nsCSSProps.cpp +++ b/layout/style/nsCSSProps.cpp @@ -1242,6 +1242,68 @@ const KTableValue nsCSSProps::kEmptyCellsKTable[] = { eCSSKeyword_UNKNOWN,-1 }; +const KTableValue nsCSSProps::kAlignAllKeywords[] = { + eCSSKeyword_auto, NS_STYLE_ALIGN_AUTO, + eCSSKeyword_start, NS_STYLE_ALIGN_START, + eCSSKeyword_end, NS_STYLE_ALIGN_END, + eCSSKeyword_flex_start, NS_STYLE_ALIGN_FLEX_START, + eCSSKeyword_flex_end, NS_STYLE_ALIGN_FLEX_END, + eCSSKeyword_center, NS_STYLE_ALIGN_CENTER, + eCSSKeyword_left, NS_STYLE_ALIGN_LEFT, + eCSSKeyword_right, NS_STYLE_ALIGN_RIGHT, + eCSSKeyword_baseline, NS_STYLE_ALIGN_BASELINE, + eCSSKeyword_last_baseline, NS_STYLE_ALIGN_LAST_BASELINE, + eCSSKeyword_stretch, NS_STYLE_ALIGN_STRETCH, + eCSSKeyword_self_start, NS_STYLE_ALIGN_SELF_START, + eCSSKeyword_self_end, NS_STYLE_ALIGN_SELF_END, + eCSSKeyword_space_between, NS_STYLE_ALIGN_SPACE_BETWEEN, + eCSSKeyword_space_around, NS_STYLE_ALIGN_SPACE_AROUND, + eCSSKeyword_space_evenly, NS_STYLE_ALIGN_SPACE_EVENLY, + eCSSKeyword_legacy, NS_STYLE_ALIGN_LEGACY, + eCSSKeyword_safe, NS_STYLE_ALIGN_SAFE, + eCSSKeyword_true, NS_STYLE_ALIGN_TRUE, + eCSSKeyword_UNKNOWN,-1 +}; + +const KTableValue nsCSSProps::kAlignOverflowPosition[] = { + eCSSKeyword_true, NS_STYLE_ALIGN_TRUE, + eCSSKeyword_safe, NS_STYLE_ALIGN_SAFE, + eCSSKeyword_UNKNOWN,-1 +}; + +const KTableValue nsCSSProps::kAlignSelfPosition[] = { + eCSSKeyword_start, NS_STYLE_ALIGN_START, + eCSSKeyword_end, NS_STYLE_ALIGN_END, + eCSSKeyword_flex_start, NS_STYLE_ALIGN_FLEX_START, + eCSSKeyword_flex_end, NS_STYLE_ALIGN_FLEX_END, + eCSSKeyword_center, NS_STYLE_ALIGN_CENTER, + eCSSKeyword_left, NS_STYLE_ALIGN_LEFT, + eCSSKeyword_right, NS_STYLE_ALIGN_RIGHT, + eCSSKeyword_self_start, NS_STYLE_ALIGN_SELF_START, + eCSSKeyword_self_end, NS_STYLE_ALIGN_SELF_END, + eCSSKeyword_UNKNOWN,-1 +}; + +const KTableValue nsCSSProps::kAlignLegacy[] = { + eCSSKeyword_legacy, NS_STYLE_ALIGN_LEGACY, + eCSSKeyword_UNKNOWN,-1 +}; + +const KTableValue nsCSSProps::kAlignLegacyPosition[] = { + eCSSKeyword_center, NS_STYLE_ALIGN_CENTER, + eCSSKeyword_left, NS_STYLE_ALIGN_LEFT, + eCSSKeyword_right, NS_STYLE_ALIGN_RIGHT, + eCSSKeyword_UNKNOWN,-1 +}; + +const KTableValue nsCSSProps::kAlignAutoStretchBaseline[] = { + eCSSKeyword_auto, NS_STYLE_ALIGN_AUTO, + eCSSKeyword_stretch, NS_STYLE_ALIGN_STRETCH, + eCSSKeyword_baseline, NS_STYLE_ALIGN_BASELINE, + eCSSKeyword_last_baseline, NS_STYLE_ALIGN_LAST_BASELINE, + eCSSKeyword_UNKNOWN,-1 +}; + const KTableValue nsCSSProps::kAlignContentKTable[] = { eCSSKeyword_flex_start, NS_STYLE_ALIGN_CONTENT_FLEX_START, eCSSKeyword_flex_end, NS_STYLE_ALIGN_CONTENT_FLEX_END, diff --git a/layout/style/nsCSSProps.h b/layout/style/nsCSSProps.h index 6d4b213cf567..7ba777d558e0 100644 --- a/layout/style/nsCSSProps.h +++ b/layout/style/nsCSSProps.h @@ -688,12 +688,20 @@ public: static KTableValue kDisplayKTable[]; static const KTableValue kElevationKTable[]; static const KTableValue kEmptyCellsKTable[]; + // -- tables for the align-/justify-content/items/self properties -- + static const KTableValue kAlignAllKeywords[]; + static const KTableValue kAlignOverflowPosition[]; // + static const KTableValue kAlignSelfPosition[]; // + static const KTableValue kAlignLegacy[]; // 'legacy' + static const KTableValue kAlignLegacyPosition[]; // 'left/right/center' + static const KTableValue kAlignAutoStretchBaseline[]; // 'auto/stretch/baseline/last-baseline' static const KTableValue kAlignContentKTable[]; static const KTableValue kAlignItemsKTable[]; static const KTableValue kAlignSelfKTable[]; + static const KTableValue kJustifyContentKTable[]; + // ------------------------------------------------------------------ static const KTableValue kFlexDirectionKTable[]; static const KTableValue kFlexWrapKTable[]; - static const KTableValue kJustifyContentKTable[]; static const KTableValue kFloatKTable[]; static const KTableValue kFloatEdgeKTable[]; static const KTableValue kFontKTable[]; diff --git a/layout/style/nsCSSValue.cpp b/layout/style/nsCSSValue.cpp index 42795358f5e3..daee3186ac81 100644 --- a/layout/style/nsCSSValue.cpp +++ b/layout/style/nsCSSValue.cpp @@ -1009,6 +1009,28 @@ nsCSSValue::AppendInsetToString(nsCSSProperty aProperty, nsAString& aResult, } } +/* static */ void +nsCSSValue::AppendAlignJustifyValueToString(int32_t aValue, nsAString& aResult) +{ + auto legacy = aValue & NS_STYLE_ALIGN_LEGACY; + if (legacy) { + aValue &= ~legacy; + aResult.AppendLiteral("legacy "); + } + auto overflowPos = aValue & (NS_STYLE_ALIGN_SAFE | NS_STYLE_ALIGN_TRUE); + aValue &= ~overflowPos; + MOZ_ASSERT(!(aValue & NS_STYLE_ALIGN_FLAG_BITS), + "unknown bits in align/justify value"); + const auto& kwtable(nsCSSProps::kAlignAllKeywords); + AppendASCIItoUTF16(nsCSSProps::ValueToKeyword(aValue, kwtable), aResult); + if (MOZ_UNLIKELY(overflowPos != 0)) { + MOZ_ASSERT(legacy == 0, "'legacy' together with "); + aResult.Append(' '); + AppendASCIItoUTF16(nsCSSProps::ValueToKeyword(overflowPos, kwtable), + aResult); + } +} + void nsCSSValue::AppendToString(nsCSSProperty aProperty, nsAString& aResult, Serialization aSerialization) const @@ -1295,6 +1317,10 @@ nsCSSValue::AppendToString(nsCSSProperty aProperty, nsAString& aResult, aResult); break; + case eCSSProperty_justify_items: + AppendAlignJustifyValueToString(intValue, aResult); + break; + default: const nsAFlatCString& name = nsCSSProps::LookupPropertyValue(aProperty, intValue); AppendASCIItoUTF16(name, aResult); diff --git a/layout/style/nsCSSValue.h b/layout/style/nsCSSValue.h index 31b0928679ba..6648c3423cf1 100644 --- a/layout/style/nsCSSValue.h +++ b/layout/style/nsCSSValue.h @@ -726,6 +726,9 @@ public: const nsCSSValue* aValues[], nsAString& aResult, Serialization aValueSerialization); + static void + AppendAlignJustifyValueToString(int32_t aValue, nsAString& aResult); + private: static const char16_t* GetBufferValue(nsStringBuffer* aBuffer) { return static_cast(aBuffer->Data()); diff --git a/layout/style/nsComputedDOMStyle.cpp b/layout/style/nsComputedDOMStyle.cpp index aaaf12b22171..8afe214aea98 100644 --- a/layout/style/nsComputedDOMStyle.cpp +++ b/layout/style/nsComputedDOMStyle.cpp @@ -3981,6 +3981,18 @@ nsComputedDOMStyle::DoGetJustifyContent() return val; } +CSSValue* +nsComputedDOMStyle::DoGetJustifyItems() +{ + nsROCSSPrimitiveValue* val = new nsROCSSPrimitiveValue; + nsAutoString str; + auto justify = StylePosition()-> + ComputedJustifyItems(StyleDisplay(), mStyleContext->GetParent()); + nsCSSValue::AppendAlignJustifyValueToString(justify, str); + val->SetString(str); + return val; +} + CSSValue* nsComputedDOMStyle::DoGetFloatEdge() { diff --git a/layout/style/nsComputedDOMStyle.h b/layout/style/nsComputedDOMStyle.h index 155a2c31b512..5aa03c2ce297 100644 --- a/layout/style/nsComputedDOMStyle.h +++ b/layout/style/nsComputedDOMStyle.h @@ -469,16 +469,21 @@ private: mozilla::dom::CSSValue* DoGetAnimationPlayState(); /* CSS Flexbox properties */ - mozilla::dom::CSSValue* DoGetAlignContent(); - mozilla::dom::CSSValue* DoGetAlignItems(); - mozilla::dom::CSSValue* DoGetAlignSelf(); mozilla::dom::CSSValue* DoGetFlexBasis(); mozilla::dom::CSSValue* DoGetFlexDirection(); mozilla::dom::CSSValue* DoGetFlexGrow(); mozilla::dom::CSSValue* DoGetFlexShrink(); mozilla::dom::CSSValue* DoGetFlexWrap(); + + /* CSS Flexbox/Grid properties */ mozilla::dom::CSSValue* DoGetOrder(); + + /* CSS Box Alignment properties */ + mozilla::dom::CSSValue* DoGetAlignContent(); + mozilla::dom::CSSValue* DoGetAlignItems(); + mozilla::dom::CSSValue* DoGetAlignSelf(); mozilla::dom::CSSValue* DoGetJustifyContent(); + mozilla::dom::CSSValue* DoGetJustifyItems(); /* SVG properties */ mozilla::dom::CSSValue* DoGetFill(); diff --git a/layout/style/nsComputedDOMStylePropertyList.h b/layout/style/nsComputedDOMStylePropertyList.h index 36dda5d878a1..b8aac95ac409 100644 --- a/layout/style/nsComputedDOMStylePropertyList.h +++ b/layout/style/nsComputedDOMStylePropertyList.h @@ -150,6 +150,7 @@ COMPUTED_STYLE_PROP(image_orientation, ImageOrientation) COMPUTED_STYLE_PROP(ime_mode, IMEMode) COMPUTED_STYLE_PROP(isolation, Isolation) COMPUTED_STYLE_PROP(justify_content, JustifyContent) +COMPUTED_STYLE_PROP(justify_items, JustifyItems) COMPUTED_STYLE_PROP(left, Left) COMPUTED_STYLE_PROP(letter_spacing, LetterSpacing) COMPUTED_STYLE_PROP(line_height, LineHeight) diff --git a/layout/style/nsRuleNode.cpp b/layout/style/nsRuleNode.cpp index 388a8ac4c84b..18c468f96fd3 100644 --- a/layout/style/nsRuleNode.cpp +++ b/layout/style/nsRuleNode.cpp @@ -7859,6 +7859,25 @@ nsRuleNode::ComputePositionData(void* aStartStruct, 0, 0, 0, 0); } + // justify-items: enum, inherit, initial + const auto& justifyItemsValue = *aRuleData->ValueForJustifyItems(); + if (MOZ_UNLIKELY(justifyItemsValue.GetUnit() == eCSSUnit_Inherit)) { + if (MOZ_LIKELY(parentContext)) { + pos->mJustifyItems = + parentPos->ComputedJustifyItems(parentContext->StyleDisplay(), + parentContext); + } else { + pos->mJustifyItems = NS_STYLE_JUSTIFY_AUTO; + } + conditions.SetUncacheable(); + } else { + SetDiscrete(justifyItemsValue, + pos->mJustifyItems, conditions, + SETDSC_ENUMERATED | SETDSC_UNSET_INITIAL, + parentPos->mJustifyItems, // unused, we handle 'inherit' above + NS_STYLE_JUSTIFY_AUTO, 0, 0, 0, 0); + } + // flex-basis: auto, length, percent, enum, calc, inherit, initial // (Note: The flags here should match those used for 'width' property above.) SetCoord(*aRuleData->ValueForFlexBasis(), pos->mFlexBasis, parentPos->mFlexBasis, diff --git a/layout/style/nsStyleConsts.h b/layout/style/nsStyleConsts.h index 483ac07d7681..d0db1617356d 100644 --- a/layout/style/nsStyleConsts.h +++ b/layout/style/nsStyleConsts.h @@ -471,6 +471,53 @@ static inline mozilla::css::Side operator++(mozilla::css::Side& side, int) { NS_STYLE_CONTAIN_STYLE | \ NS_STYLE_CONTAIN_PAINT) +// Shared constants for all align/justify properties (nsStylePosition): +#define NS_STYLE_ALIGN_AUTO 0 +#define NS_STYLE_ALIGN_START 1 +#define NS_STYLE_ALIGN_END 2 +#define NS_STYLE_ALIGN_FLEX_START 3 +#define NS_STYLE_ALIGN_FLEX_END 4 +#define NS_STYLE_ALIGN_CENTER 5 +#define NS_STYLE_ALIGN_LEFT 6 +#define NS_STYLE_ALIGN_RIGHT 7 +#define NS_STYLE_ALIGN_BASELINE 8 +#define NS_STYLE_ALIGN_LAST_BASELINE 9 +#define NS_STYLE_ALIGN_STRETCH 10 +#define NS_STYLE_ALIGN_SELF_START 11 +#define NS_STYLE_ALIGN_SELF_END 12 +#define NS_STYLE_ALIGN_SPACE_BETWEEN 13 +#define NS_STYLE_ALIGN_SPACE_AROUND 14 +#define NS_STYLE_ALIGN_SPACE_EVENLY 15 +#define NS_STYLE_ALIGN_LEGACY 0x10 // mutually exclusive w. SAFE & TRUE +#define NS_STYLE_ALIGN_SAFE 0x20 +#define NS_STYLE_ALIGN_TRUE 0x40 // mutually exclusive w. SAFE +#define NS_STYLE_ALIGN_FLAG_BITS 0xF0 +#define NS_STYLE_ALIGN_ALL_BITS 0xFF +#define NS_STYLE_ALIGN_ALL_SHIFT 8 + +#define NS_STYLE_JUSTIFY_AUTO NS_STYLE_ALIGN_AUTO +#define NS_STYLE_JUSTIFY_START NS_STYLE_ALIGN_START +#define NS_STYLE_JUSTIFY_END NS_STYLE_ALIGN_END +#define NS_STYLE_JUSTIFY_FLEX_START NS_STYLE_ALIGN_FLEX_START +#define NS_STYLE_JUSTIFY_FLEX_END NS_STYLE_ALIGN_FLEX_END +#define NS_STYLE_JUSTIFY_CENTER NS_STYLE_ALIGN_CENTER +#define NS_STYLE_JUSTIFY_LEFT NS_STYLE_ALIGN_LEFT +#define NS_STYLE_JUSTIFY_RIGHT NS_STYLE_ALIGN_RIGHT +#define NS_STYLE_JUSTIFY_BASELINE NS_STYLE_ALIGN_BASELINE +#define NS_STYLE_JUSTIFY_LAST_BASELINE NS_STYLE_ALIGN_LAST_BASELINE +#define NS_STYLE_JUSTIFY_STRETCH NS_STYLE_ALIGN_STRETCH +#define NS_STYLE_JUSTIFY_SELF_START NS_STYLE_ALIGN_SELF_START +#define NS_STYLE_JUSTIFY_SELF_END NS_STYLE_ALIGN_SELF_END +#define NS_STYLE_JUSTIFY_SPACE_BETWEEN NS_STYLE_ALIGN_SPACE_BETWEEN +#define NS_STYLE_JUSTIFY_SPACE_AROUND NS_STYLE_ALIGN_SPACE_AROUND +#define NS_STYLE_JUSTIFY_SPACE_EVENLY NS_STYLE_ALIGN_SPACE_EVENLY +#define NS_STYLE_JUSTIFY_LEGACY NS_STYLE_ALIGN_LEGACY +#define NS_STYLE_JUSTIFY_SAFE NS_STYLE_ALIGN_SAFE +#define NS_STYLE_JUSTIFY_TRUE NS_STYLE_ALIGN_TRUE +#define NS_STYLE_JUSTIFY_FLAG_BITS NS_STYLE_ALIGN_FLAG_BITS +#define NS_STYLE_JUSTIFY_ALL_BITS NS_STYLE_ALIGN_ALL_BITS +#define NS_STYLE_JUSTIFY_ALL_SHIFT NS_STYLE_ALIGN_ALL_SHIFT + // See nsStylePosition #define NS_STYLE_ALIGN_CONTENT_FLEX_START 0 #define NS_STYLE_ALIGN_CONTENT_FLEX_END 1 diff --git a/layout/style/nsStyleStruct.cpp b/layout/style/nsStyleStruct.cpp index 9dca383f1bd9..35e14b640273 100644 --- a/layout/style/nsStyleStruct.cpp +++ b/layout/style/nsStyleStruct.cpp @@ -1434,6 +1434,7 @@ nsStylePosition::nsStylePosition(void) mAlignContent = NS_STYLE_ALIGN_CONTENT_STRETCH; mAlignItems = NS_STYLE_ALIGN_ITEMS_INITIAL_VALUE; mAlignSelf = NS_STYLE_ALIGN_SELF_AUTO; + mJustifyItems = NS_STYLE_JUSTIFY_AUTO; mFlexDirection = NS_STYLE_FLEX_DIRECTION_ROW; mFlexWrap = NS_STYLE_FLEX_WRAP_NOWRAP; mJustifyContent = NS_STYLE_JUSTIFY_CONTENT_FLEX_START; @@ -1473,6 +1474,7 @@ nsStylePosition::nsStylePosition(const nsStylePosition& aSource) , mAlignContent(aSource.mAlignContent) , mAlignItems(aSource.mAlignItems) , mAlignSelf(aSource.mAlignSelf) + , mJustifyItems(aSource.mJustifyItems) , mFlexDirection(aSource.mFlexDirection) , mFlexWrap(aSource.mFlexWrap) , mJustifyContent(aSource.mJustifyContent) @@ -1585,9 +1587,10 @@ nsStylePosition::CalcDifference(const nsStylePosition& aOther, return NS_CombineHint(hint, nsChangeHint_AllReflowHints); } - // Changing justify-content on a flexbox might affect the positioning of its - // children, but it won't affect any sizing. - if (mJustifyContent != aOther.mJustifyContent) { + // Changing 'justify-content/items' might affect the positioning, + // but it won't affect any sizing. + if (mJustifyContent != aOther.mJustifyContent || + mJustifyItems != aOther.mJustifyItems) { NS_UpdateHint(hint, nsChangeHint_NeedReflow); } @@ -1669,6 +1672,25 @@ nsStylePosition::WidthCoordDependsOnContainer(const nsStyleCoord &aCoord) aCoord.GetIntValue() == NS_STYLE_WIDTH_AVAILABLE)); } +uint8_t +nsStylePosition::ComputedJustifyItems(const nsStyleDisplay* aDisplay, + nsStyleContext* aParent) const +{ + if (mJustifyItems != NS_STYLE_JUSTIFY_AUTO) { + return mJustifyItems; + } + if (MOZ_LIKELY(aParent)) { + auto inheritedJustifyItems = + aParent->StylePosition()->ComputedJustifyItems(aParent->StyleDisplay(), + aParent->GetParent()); + if (inheritedJustifyItems & NS_STYLE_JUSTIFY_LEGACY) { + return inheritedJustifyItems; + } + } + return aDisplay->IsFlexOrGridDisplayType() ? NS_STYLE_JUSTIFY_STRETCH + : NS_STYLE_JUSTIFY_START; +} + // -------------------- // nsStyleTable // diff --git a/layout/style/nsStyleStruct.h b/layout/style/nsStyleStruct.h index a996536329c4..959e5ccf419b 100644 --- a/layout/style/nsStyleStruct.h +++ b/layout/style/nsStyleStruct.h @@ -1396,6 +1396,13 @@ struct nsStylePosition { // different scope, since we're now using it in multiple style structs. typedef nsStyleBackground::Position Position; + /** + * Return the computed value for 'justify-items' given our 'display' value in + * aDisplay and the parent StyleContext aParent (or null for the root). + */ + uint8_t ComputedJustifyItems(const nsStyleDisplay* aDisplay, + nsStyleContext* aParent) const; + Position mObjectPosition; // [reset] nsStyleSides mOffset; // [reset] coord, percent, calc, auto nsStyleCoord mWidth; // [reset] coord, percent, enum, calc, auto @@ -1414,6 +1421,10 @@ struct nsStylePosition { uint8_t mAlignContent; // [reset] see nsStyleConsts.h uint8_t mAlignItems; // [reset] see nsStyleConsts.h uint8_t mAlignSelf; // [reset] see nsStyleConsts.h +private: + friend class nsRuleNode; + uint8_t mJustifyItems; // [reset] see nsStyleConsts.h +public: uint8_t mFlexDirection; // [reset] see nsStyleConsts.h uint8_t mFlexWrap; // [reset] see nsStyleConsts.h uint8_t mJustifyContent; // [reset] see nsStyleConsts.h diff --git a/layout/style/test/property_database.js b/layout/style/test/property_database.js index 9e1d4422af04..b55807f5751e 100644 --- a/layout/style/test/property_database.js +++ b/layout/style/test/property_database.js @@ -4071,6 +4071,21 @@ var gCSSProperties = { other_values: [ "flex-start", "flex-end", "center", "baseline" ], invalid_values: [ "space-between", "abc", "30px" ] }, + "justify-items": { + domProp: "justifyItems", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "auto", "start" ], + other_values: [ "end", "flex-start", "flex-end", "self-start", "self-end", + "center", "left", "right", "baseline", "stretch", + "legacy left", "right legacy", "legacy center", + "true right", "left true", "safe right", "center safe" ], + invalid_values: [ "space-between", "abc", "30px", "legacy", "legacy start", + "end legacy", "legacy baseline", "legacy legacy", "true", + "safe legacy left", "legacy left safe", "legacy safe left", + "safe left legacy", "legacy left legacy", "baseline true", + "safe true", "safe left true", "safe stretch" ] + }, "flex": { domProp: "flex", inherited: false, From 9ef57353d7130dee357f525b7451f51459bd370b Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Tue, 3 Nov 2015 15:18:05 +0100 Subject: [PATCH 027/113] Bug 1176782 part 2 - [css-align] Implement the 'justify-self' property in the style system. r=SimonSapin --- layout/style/nsCSSParser.cpp | 21 ++++++ layout/style/nsCSSPropList.h | 10 +++ layout/style/nsCSSValue.cpp | 1 + layout/style/nsComputedDOMStyle.cpp | 12 ++++ layout/style/nsComputedDOMStyle.h | 1 + layout/style/nsComputedDOMStylePropertyList.h | 1 + layout/style/nsRuleNode.cpp | 23 +++++++ layout/style/nsStyleStruct.cpp | 68 ++++++++++++++++++- layout/style/nsStyleStruct.h | 12 ++++ layout/style/test/property_database.js | 12 ++++ 10 files changed, 159 insertions(+), 2 deletions(-) diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index 8f5b86862d80..c1d850fec945 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -939,6 +939,7 @@ protected: bool ParseAlignJustifyPosition(nsCSSValue& aResult, const KTableValue aTable[]); bool ParseJustifyItems(); + bool ParseJustifySelf(); // for 'clip' and '-moz-image-region' bool ParseRect(nsCSSProperty aPropID); @@ -9448,6 +9449,24 @@ CSSParserImpl::ParseJustifyItems() return true; } +// auto | stretch | | +// [ ? && ] +bool +CSSParserImpl::ParseJustifySelf() +{ + nsCSSValue value; + if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) { + if (!ParseEnum(value, nsCSSProps::kAlignAutoStretchBaseline)) { + if (!ParseAlignJustifyPosition(value, nsCSSProps::kAlignSelfPosition) || + value.GetUnit() == eCSSUnit_Null) { + return false; + } + } + } + AppendValue(eCSSProperty_justify_self, value); + return true; +} + // : [ | ]? bool CSSParserImpl::ParseColorStop(nsCSSValueGradient* aGradient) @@ -10573,6 +10592,8 @@ CSSParserImpl::ParsePropertyByFunction(nsCSSProperty aPropID) return ParseRect(eCSSProperty_image_region); case eCSSProperty_justify_items: return ParseJustifyItems(); + case eCSSProperty_justify_self: + return ParseJustifySelf(); case eCSSProperty_list_style: return ParseListStyle(); case eCSSProperty_margin: diff --git a/layout/style/nsCSSPropList.h b/layout/style/nsCSSPropList.h index de6193776cae..e6263bd3c8b4 100644 --- a/layout/style/nsCSSPropList.h +++ b/layout/style/nsCSSPropList.h @@ -1717,6 +1717,16 @@ CSS_PROP_POSITION( nullptr, CSS_PROP_NO_OFFSET, eStyleAnimType_None) +CSS_PROP_POSITION( + justify-self, + justify_self, + JustifySelf, + CSS_PROPERTY_PARSE_FUNCTION, + "", + 0, + nullptr, + CSS_PROP_NO_OFFSET, + eStyleAnimType_None) CSS_PROP_DISPLAY( float, float, diff --git a/layout/style/nsCSSValue.cpp b/layout/style/nsCSSValue.cpp index daee3186ac81..f9661a1e8224 100644 --- a/layout/style/nsCSSValue.cpp +++ b/layout/style/nsCSSValue.cpp @@ -1318,6 +1318,7 @@ nsCSSValue::AppendToString(nsCSSProperty aProperty, nsAString& aResult, break; case eCSSProperty_justify_items: + case eCSSProperty_justify_self: AppendAlignJustifyValueToString(intValue, aResult); break; diff --git a/layout/style/nsComputedDOMStyle.cpp b/layout/style/nsComputedDOMStyle.cpp index 8afe214aea98..ab4162d79b24 100644 --- a/layout/style/nsComputedDOMStyle.cpp +++ b/layout/style/nsComputedDOMStyle.cpp @@ -3993,6 +3993,18 @@ nsComputedDOMStyle::DoGetJustifyItems() return val; } +CSSValue* +nsComputedDOMStyle::DoGetJustifySelf() +{ + nsROCSSPrimitiveValue* val = new nsROCSSPrimitiveValue; + nsAutoString str; + auto justify = StylePosition()-> + ComputedJustifySelf(StyleDisplay(), mStyleContext->GetParent()); + nsCSSValue::AppendAlignJustifyValueToString(justify, str); + val->SetString(str); + return val; +} + CSSValue* nsComputedDOMStyle::DoGetFloatEdge() { diff --git a/layout/style/nsComputedDOMStyle.h b/layout/style/nsComputedDOMStyle.h index 5aa03c2ce297..68ed136ebd2c 100644 --- a/layout/style/nsComputedDOMStyle.h +++ b/layout/style/nsComputedDOMStyle.h @@ -484,6 +484,7 @@ private: mozilla::dom::CSSValue* DoGetAlignSelf(); mozilla::dom::CSSValue* DoGetJustifyContent(); mozilla::dom::CSSValue* DoGetJustifyItems(); + mozilla::dom::CSSValue* DoGetJustifySelf(); /* SVG properties */ mozilla::dom::CSSValue* DoGetFill(); diff --git a/layout/style/nsComputedDOMStylePropertyList.h b/layout/style/nsComputedDOMStylePropertyList.h index b8aac95ac409..471cdcac389e 100644 --- a/layout/style/nsComputedDOMStylePropertyList.h +++ b/layout/style/nsComputedDOMStylePropertyList.h @@ -151,6 +151,7 @@ COMPUTED_STYLE_PROP(ime_mode, IMEMode) COMPUTED_STYLE_PROP(isolation, Isolation) COMPUTED_STYLE_PROP(justify_content, JustifyContent) COMPUTED_STYLE_PROP(justify_items, JustifyItems) +COMPUTED_STYLE_PROP(justify_self, JustifySelf) COMPUTED_STYLE_PROP(left, Left) COMPUTED_STYLE_PROP(letter_spacing, LetterSpacing) COMPUTED_STYLE_PROP(line_height, LineHeight) diff --git a/layout/style/nsRuleNode.cpp b/layout/style/nsRuleNode.cpp index 18c468f96fd3..1e40e09297bf 100644 --- a/layout/style/nsRuleNode.cpp +++ b/layout/style/nsRuleNode.cpp @@ -7878,6 +7878,29 @@ nsRuleNode::ComputePositionData(void* aStartStruct, NS_STYLE_JUSTIFY_AUTO, 0, 0, 0, 0); } + // justify-self: enum, inherit, initial + const auto& justifySelfValue = *aRuleData->ValueForJustifySelf(); + if (MOZ_UNLIKELY(justifySelfValue.GetUnit() == eCSSUnit_Inherit)) { + if (MOZ_LIKELY(parentContext)) { + nsStyleContext* grandparentContext = parentContext->GetParent(); + if (MOZ_LIKELY(grandparentContext)) { + aContext->AddStyleBit(NS_STYLE_USES_GRANDANCESTOR_STYLE); + } + pos->mJustifySelf = + parentPos->ComputedJustifySelf(parentContext->StyleDisplay(), + grandparentContext); + } else { + pos->mJustifySelf = NS_STYLE_JUSTIFY_START; + } + conditions.SetUncacheable(); + } else { + SetDiscrete(justifySelfValue, + pos->mJustifySelf, conditions, + SETDSC_ENUMERATED | SETDSC_UNSET_INITIAL, + parentPos->mJustifySelf, // not used, we handle 'inherit' above + NS_STYLE_JUSTIFY_AUTO, 0, 0, 0, 0); + } + // flex-basis: auto, length, percent, enum, calc, inherit, initial // (Note: The flags here should match those used for 'width' property above.) SetCoord(*aRuleData->ValueForFlexBasis(), pos->mFlexBasis, parentPos->mFlexBasis, diff --git a/layout/style/nsStyleStruct.cpp b/layout/style/nsStyleStruct.cpp index 35e14b640273..dfc89a3a3875 100644 --- a/layout/style/nsStyleStruct.cpp +++ b/layout/style/nsStyleStruct.cpp @@ -1435,6 +1435,7 @@ nsStylePosition::nsStylePosition(void) mAlignItems = NS_STYLE_ALIGN_ITEMS_INITIAL_VALUE; mAlignSelf = NS_STYLE_ALIGN_SELF_AUTO; mJustifyItems = NS_STYLE_JUSTIFY_AUTO; + mJustifySelf = NS_STYLE_JUSTIFY_AUTO; mFlexDirection = NS_STYLE_FLEX_DIRECTION_ROW; mFlexWrap = NS_STYLE_FLEX_WRAP_NOWRAP; mJustifyContent = NS_STYLE_JUSTIFY_CONTENT_FLEX_START; @@ -1475,6 +1476,7 @@ nsStylePosition::nsStylePosition(const nsStylePosition& aSource) , mAlignItems(aSource.mAlignItems) , mAlignSelf(aSource.mAlignSelf) , mJustifyItems(aSource.mJustifyItems) + , mJustifySelf(aSource.mJustifySelf) , mFlexDirection(aSource.mFlexDirection) , mFlexWrap(aSource.mFlexWrap) , mJustifyContent(aSource.mJustifyContent) @@ -1587,10 +1589,11 @@ nsStylePosition::CalcDifference(const nsStylePosition& aOther, return NS_CombineHint(hint, nsChangeHint_AllReflowHints); } - // Changing 'justify-content/items' might affect the positioning, + // Changing 'justify-content/items/self' might affect the positioning, // but it won't affect any sizing. if (mJustifyContent != aOther.mJustifyContent || - mJustifyItems != aOther.mJustifyItems) { + mJustifyItems != aOther.mJustifyItems || + mJustifySelf != aOther.mJustifySelf) { NS_UpdateHint(hint, nsChangeHint_NeedReflow); } @@ -1672,6 +1675,43 @@ nsStylePosition::WidthCoordDependsOnContainer(const nsStyleCoord &aCoord) aCoord.GetIntValue() == NS_STYLE_WIDTH_AVAILABLE)); } +static nsStyleContext* +GetAlignmentContainer(nsStyleContext* aParent, + const nsStylePosition** aPosition, + const nsStyleDisplay** aDisplay) +{ + while (aParent && + aParent->StyleDisplay()->mDisplay == NS_STYLE_DISPLAY_CONTENTS) { + aParent = aParent->GetParent(); + } + if (aParent) { + *aPosition = aParent->StylePosition(); + *aDisplay = aParent->StyleDisplay(); + } + return aParent; +} + +uint8_t +nsStylePosition::MapLeftRightToStart(uint8_t aAlign, LogicalAxis aAxis, + const nsStyleDisplay* aDisplay) const +{ + auto val = aAlign & ~NS_STYLE_ALIGN_FLAG_BITS; + if (val == NS_STYLE_ALIGN_LEFT || val == NS_STYLE_ALIGN_RIGHT) { + switch (aDisplay->mDisplay) { + case NS_STYLE_DISPLAY_FLEX: + case NS_STYLE_DISPLAY_INLINE_FLEX: + // XXX TODO + // NOTE: make sure to strip off 'legacy' bit when mapping to 'start' + break; + default: + if (aAxis == eLogicalAxisBlock) { + return NS_STYLE_ALIGN_START | (aAlign & NS_STYLE_ALIGN_FLAG_BITS); + } + } + } + return aAlign; +} + uint8_t nsStylePosition::ComputedJustifyItems(const nsStyleDisplay* aDisplay, nsStyleContext* aParent) const @@ -1691,6 +1731,30 @@ nsStylePosition::ComputedJustifyItems(const nsStyleDisplay* aDisplay, : NS_STYLE_JUSTIFY_START; } +uint8_t +nsStylePosition::ComputedJustifySelf(const nsStyleDisplay* aDisplay, + nsStyleContext* aParent) const +{ + const nsStylePosition* containerPos = this; + const nsStyleDisplay* containerDisp = aDisplay; + GetAlignmentContainer(aParent, &containerPos, &containerDisp); + if (mJustifySelf != NS_STYLE_JUSTIFY_AUTO) { + return containerPos->MapLeftRightToStart(mJustifySelf, eLogicalAxisInline, + containerDisp); + } + if (MOZ_UNLIKELY(aDisplay->IsAbsolutelyPositionedStyle())) { + return NS_STYLE_JUSTIFY_AUTO; + } + if (MOZ_LIKELY(aParent)) { + auto inheritedJustifyItems = aParent->StylePosition()-> + ComputedJustifyItems(aParent->StyleDisplay(), aParent->GetParent()); + inheritedJustifyItems &= ~NS_STYLE_JUSTIFY_LEGACY; + return containerPos->MapLeftRightToStart(inheritedJustifyItems, + eLogicalAxisInline, containerDisp); + } + return NS_STYLE_JUSTIFY_START; +} + // -------------------- // nsStyleTable // diff --git a/layout/style/nsStyleStruct.h b/layout/style/nsStyleStruct.h index 959e5ccf419b..46a2cb1605de 100644 --- a/layout/style/nsStyleStruct.h +++ b/layout/style/nsStyleStruct.h @@ -1403,6 +1403,13 @@ struct nsStylePosition { uint8_t ComputedJustifyItems(const nsStyleDisplay* aDisplay, nsStyleContext* aParent) const; + /** + * Return the computed value for 'justify-self' given our 'display' value in + * aDisplay and the parent StyleContext aParent (or null for the root). + */ + uint8_t ComputedJustifySelf(const nsStyleDisplay* aDisplay, + nsStyleContext* aParent) const; + Position mObjectPosition; // [reset] nsStyleSides mOffset; // [reset] coord, percent, calc, auto nsStyleCoord mWidth; // [reset] coord, percent, enum, calc, auto @@ -1423,7 +1430,12 @@ struct nsStylePosition { uint8_t mAlignSelf; // [reset] see nsStyleConsts.h private: friend class nsRuleNode; + // Helper for the ComputedAlign/Justify* methods. + uint8_t MapLeftRightToStart(uint8_t aAlign, mozilla::LogicalAxis aAxis, + const nsStyleDisplay* aDisplay) const; + uint8_t mJustifyItems; // [reset] see nsStyleConsts.h + uint8_t mJustifySelf; // [reset] see nsStyleConsts.h public: uint8_t mFlexDirection; // [reset] see nsStyleConsts.h uint8_t mFlexWrap; // [reset] see nsStyleConsts.h diff --git a/layout/style/test/property_database.js b/layout/style/test/property_database.js index b55807f5751e..eb1bb75c51a1 100644 --- a/layout/style/test/property_database.js +++ b/layout/style/test/property_database.js @@ -4086,6 +4086,18 @@ var gCSSProperties = { "safe left legacy", "legacy left legacy", "baseline true", "safe true", "safe left true", "safe stretch" ] }, + "justify-self": { + domProp: "justifySelf", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "auto", "start" ], + other_values: [ "end", "flex-start", "flex-end", "self-start", + "self-end", "center", "left", "right", "baseline", + "last-baseline", "stretch", "left true", "true right", + "safe right", "center safe" ], + invalid_values: [ "space-between", "abc", "30px", "none", + "legacy left", "right legacy" ] + }, "flex": { domProp: "flex", inherited: false, From 52594979753ba3aed481bfb103af39f7418097b1 Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Tue, 3 Nov 2015 15:18:05 +0100 Subject: [PATCH 028/113] Bug 1176782 part 3 - [css-align] Implement additional syntax and values for the 'justify-content' property in the style system. r=cam --- layout/generic/nsFlexContainerFrame.cpp | 4 +- layout/style/nsCSSParser.cpp | 44 +++++++++++++++++++++ layout/style/nsCSSPropList.h | 10 ++--- layout/style/nsCSSProps.cpp | 35 ++++++++++++----- layout/style/nsCSSProps.h | 3 ++ layout/style/nsCSSValue.cpp | 18 +++++++++ layout/style/nsComputedDOMStyle.cpp | 15 +++++-- layout/style/nsRuleNode.cpp | 25 ++++++++---- layout/style/nsStyleConsts.h | 12 +++--- layout/style/nsStyleStruct.cpp | 52 ++++++++++++------------- layout/style/nsStyleStruct.h | 8 +++- layout/style/test/property_database.js | 23 +++++++---- 12 files changed, 183 insertions(+), 66 deletions(-) diff --git a/layout/generic/nsFlexContainerFrame.cpp b/layout/generic/nsFlexContainerFrame.cpp index 0f442bc07fa2..5d9d9dcb234a 100644 --- a/layout/generic/nsFlexContainerFrame.cpp +++ b/layout/generic/nsFlexContainerFrame.cpp @@ -3775,7 +3775,9 @@ nsFlexContainerFrame::DoFlexLayout(nsPresContext* aPresContext, // Main-Axis Alignment - Flexbox spec section 9.5 // ============================================== - line->PositionItemsInMainAxis(aReflowState.mStylePosition->mJustifyContent, + auto justifyContent = + aReflowState.mStylePosition->ComputedJustifyContent(aReflowState.mStyleDisplay); + line->PositionItemsInMainAxis(justifyContent, aContentBoxMainSize, aAxisTracker); diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index c1d850fec945..7d0b6f0a9d71 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -940,6 +940,8 @@ protected: const KTableValue aTable[]); bool ParseJustifyItems(); bool ParseJustifySelf(); + // parsing 'align/justify-content' from the css-align spec + bool ParseAlignJustifyContent(nsCSSProperty aPropID); // for 'clip' and '-moz-image-region' bool ParseRect(nsCSSProperty aPropID); @@ -9467,6 +9469,46 @@ CSSParserImpl::ParseJustifySelf() return true; } +// auto | | [ || +// [ ? && ] ] +// (the part after the || is called <*-position> below) +bool +CSSParserImpl::ParseAlignJustifyContent(nsCSSProperty aPropID) +{ + nsCSSValue value; + if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) { + if (!ParseEnum(value, nsCSSProps::kAlignAutoBaseline)) { + nsCSSValue fallbackValue; + if (!ParseEnum(value, nsCSSProps::kAlignContentDistribution)) { + if (!ParseAlignJustifyPosition(fallbackValue, + nsCSSProps::kAlignContentPosition) || + fallbackValue.GetUnit() == eCSSUnit_Null) { + return false; + } + // optional after <*-position> ... + if (!ParseEnum(value, nsCSSProps::kAlignContentDistribution)) { + // ... is missing so the <*-position> is the value, not the fallback + value = fallbackValue; + fallbackValue.Reset(); + } + } else { + // any optional <*-position> is a fallback value + if (!ParseAlignJustifyPosition(fallbackValue, + nsCSSProps::kAlignContentPosition)) { + return false; + } + } + if (fallbackValue.GetUnit() != eCSSUnit_Null) { + auto fallback = fallbackValue.GetIntValue(); + value.SetIntValue(value.GetIntValue() | (fallback << 8), + eCSSUnit_Enumerated); + } + } + } + AppendValue(aPropID, value); + return true; +} + // : [ | ]? bool CSSParserImpl::ParseColorStop(nsCSSValueGradient* aGradient) @@ -10590,6 +10632,8 @@ CSSParserImpl::ParsePropertyByFunction(nsCSSProperty aPropID) return ParseGridArea(); case eCSSProperty_image_region: return ParseRect(eCSSProperty_image_region); + case eCSSProperty_justify_content: + return ParseAlignJustifyContent(aPropID); case eCSSProperty_justify_items: return ParseJustifyItems(); case eCSSProperty_justify_self: diff --git a/layout/style/nsCSSPropList.h b/layout/style/nsCSSPropList.h index e6263bd3c8b4..e76a8f16d572 100644 --- a/layout/style/nsCSSPropList.h +++ b/layout/style/nsCSSPropList.h @@ -1701,12 +1701,12 @@ CSS_PROP_POSITION( justify-content, justify_content, JustifyContent, - CSS_PROPERTY_PARSE_VALUE, + CSS_PROPERTY_PARSE_FUNCTION, "", - VARIANT_HK, - kJustifyContentKTable, - offsetof(nsStylePosition, mJustifyContent), - eStyleAnimType_EnumU8) + 0, + nullptr, + CSS_PROP_NO_OFFSET, + eStyleAnimType_None) CSS_PROP_POSITION( justify-items, justify_items, diff --git a/layout/style/nsCSSProps.cpp b/layout/style/nsCSSProps.cpp index 9367c1287d1e..08486b80f935 100644 --- a/layout/style/nsCSSProps.cpp +++ b/layout/style/nsCSSProps.cpp @@ -1304,6 +1304,32 @@ const KTableValue nsCSSProps::kAlignAutoStretchBaseline[] = { eCSSKeyword_UNKNOWN,-1 }; +const KTableValue nsCSSProps::kAlignAutoBaseline[] = { + eCSSKeyword_auto, NS_STYLE_ALIGN_AUTO, + eCSSKeyword_baseline, NS_STYLE_ALIGN_BASELINE, + eCSSKeyword_last_baseline, NS_STYLE_ALIGN_LAST_BASELINE, + eCSSKeyword_UNKNOWN,-1 +}; + +const KTableValue nsCSSProps::kAlignContentDistribution[] = { + eCSSKeyword_stretch, NS_STYLE_ALIGN_STRETCH, + eCSSKeyword_space_between, NS_STYLE_ALIGN_SPACE_BETWEEN, + eCSSKeyword_space_around, NS_STYLE_ALIGN_SPACE_AROUND, + eCSSKeyword_space_evenly, NS_STYLE_ALIGN_SPACE_EVENLY, + eCSSKeyword_UNKNOWN,-1 +}; + +const KTableValue nsCSSProps::kAlignContentPosition[] = { + eCSSKeyword_start, NS_STYLE_ALIGN_START, + eCSSKeyword_end, NS_STYLE_ALIGN_END, + eCSSKeyword_flex_start, NS_STYLE_ALIGN_FLEX_START, + eCSSKeyword_flex_end, NS_STYLE_ALIGN_FLEX_END, + eCSSKeyword_center, NS_STYLE_ALIGN_CENTER, + eCSSKeyword_left, NS_STYLE_ALIGN_LEFT, + eCSSKeyword_right, NS_STYLE_ALIGN_RIGHT, + eCSSKeyword_UNKNOWN,-1 +}; + const KTableValue nsCSSProps::kAlignContentKTable[] = { eCSSKeyword_flex_start, NS_STYLE_ALIGN_CONTENT_FLEX_START, eCSSKeyword_flex_end, NS_STYLE_ALIGN_CONTENT_FLEX_END, @@ -1356,15 +1382,6 @@ const KTableValue nsCSSProps::kHyphensKTable[] = { eCSSKeyword_UNKNOWN,-1 }; -const KTableValue nsCSSProps::kJustifyContentKTable[] = { - eCSSKeyword_flex_start, NS_STYLE_JUSTIFY_CONTENT_FLEX_START, - eCSSKeyword_flex_end, NS_STYLE_JUSTIFY_CONTENT_FLEX_END, - eCSSKeyword_center, NS_STYLE_JUSTIFY_CONTENT_CENTER, - eCSSKeyword_space_between, NS_STYLE_JUSTIFY_CONTENT_SPACE_BETWEEN, - eCSSKeyword_space_around, NS_STYLE_JUSTIFY_CONTENT_SPACE_AROUND, - eCSSKeyword_UNKNOWN,-1 -}; - const KTableValue nsCSSProps::kFloatKTable[] = { eCSSKeyword_none, NS_STYLE_FLOAT_NONE, eCSSKeyword_left, NS_STYLE_FLOAT_LEFT, diff --git a/layout/style/nsCSSProps.h b/layout/style/nsCSSProps.h index 7ba777d558e0..c3b0c76654cb 100644 --- a/layout/style/nsCSSProps.h +++ b/layout/style/nsCSSProps.h @@ -695,6 +695,9 @@ public: static const KTableValue kAlignLegacy[]; // 'legacy' static const KTableValue kAlignLegacyPosition[]; // 'left/right/center' static const KTableValue kAlignAutoStretchBaseline[]; // 'auto/stretch/baseline/last-baseline' + static const KTableValue kAlignAutoBaseline[]; // 'auto/baseline/last-baseline' + static const KTableValue kAlignContentDistribution[]; // + static const KTableValue kAlignContentPosition[]; // static const KTableValue kAlignContentKTable[]; static const KTableValue kAlignItemsKTable[]; static const KTableValue kAlignSelfKTable[]; diff --git a/layout/style/nsCSSValue.cpp b/layout/style/nsCSSValue.cpp index f9661a1e8224..26efdb4a5b79 100644 --- a/layout/style/nsCSSValue.cpp +++ b/layout/style/nsCSSValue.cpp @@ -1021,6 +1021,11 @@ nsCSSValue::AppendAlignJustifyValueToString(int32_t aValue, nsAString& aResult) aValue &= ~overflowPos; MOZ_ASSERT(!(aValue & NS_STYLE_ALIGN_FLAG_BITS), "unknown bits in align/justify value"); + MOZ_ASSERT((aValue != NS_STYLE_ALIGN_AUTO && + aValue != NS_STYLE_ALIGN_BASELINE && + aValue != NS_STYLE_ALIGN_LAST_BASELINE) || + (!legacy && !overflowPos), + "auto/baseline/last-baseline never have any flags"); const auto& kwtable(nsCSSProps::kAlignAllKeywords); AppendASCIItoUTF16(nsCSSProps::ValueToKeyword(aValue, kwtable), aResult); if (MOZ_UNLIKELY(overflowPos != 0)) { @@ -1317,6 +1322,19 @@ nsCSSValue::AppendToString(nsCSSProperty aProperty, nsAString& aResult, aResult); break; + case eCSSProperty_justify_content: { + AppendAlignJustifyValueToString(intValue & NS_STYLE_ALIGN_ALL_BITS, aResult); + auto fallback = intValue >> NS_STYLE_ALIGN_ALL_SHIFT; + if (fallback) { + MOZ_ASSERT(nsCSSProps::ValueToKeywordEnum(fallback & ~NS_STYLE_ALIGN_FLAG_BITS, + nsCSSProps::kAlignSelfPosition) + != eCSSKeyword_UNKNOWN, "unknown fallback value"); + aResult.Append(' '); + AppendAlignJustifyValueToString(fallback, aResult); + } + break; + } + case eCSSProperty_justify_items: case eCSSProperty_justify_self: AppendAlignJustifyValueToString(intValue, aResult); diff --git a/layout/style/nsComputedDOMStyle.cpp b/layout/style/nsComputedDOMStyle.cpp index ab4162d79b24..dece16264e00 100644 --- a/layout/style/nsComputedDOMStyle.cpp +++ b/layout/style/nsComputedDOMStyle.cpp @@ -3975,9 +3975,18 @@ CSSValue* nsComputedDOMStyle::DoGetJustifyContent() { nsROCSSPrimitiveValue* val = new nsROCSSPrimitiveValue; - val->SetIdent( - nsCSSProps::ValueToKeywordEnum(StylePosition()->mJustifyContent, - nsCSSProps::kJustifyContentKTable)); + nsAutoString str; + auto justify = StylePosition()->ComputedJustifyContent(StyleDisplay()); + nsCSSValue::AppendAlignJustifyValueToString(justify & NS_STYLE_JUSTIFY_ALL_BITS, str); + auto fallback = justify >> NS_STYLE_JUSTIFY_ALL_SHIFT; + if (fallback) { + MOZ_ASSERT(nsCSSProps::ValueToKeywordEnum(fallback & ~NS_STYLE_JUSTIFY_FLAG_BITS, + nsCSSProps::kAlignSelfPosition) + != eCSSKeyword_UNKNOWN, "unknown fallback value"); + str.Append(' '); + nsCSSValue::AppendAlignJustifyValueToString(fallback, str); + } + val->SetString(str); return val; } diff --git a/layout/style/nsRuleNode.cpp b/layout/style/nsRuleNode.cpp index 1e40e09297bf..7b900a672f8a 100644 --- a/layout/style/nsRuleNode.cpp +++ b/layout/style/nsRuleNode.cpp @@ -7859,6 +7859,24 @@ nsRuleNode::ComputePositionData(void* aStartStruct, 0, 0, 0, 0); } + // justify-content: enum, inherit, initial + const auto& justifyContentValue = *aRuleData->ValueForJustifyContent(); + if (MOZ_UNLIKELY(justifyContentValue.GetUnit() == eCSSUnit_Inherit)) { + if (MOZ_LIKELY(parentContext)) { + pos->mJustifyContent = + parentPos->ComputedJustifyContent(parentContext->StyleDisplay()); + } else { + pos->mJustifyContent = NS_STYLE_JUSTIFY_AUTO; + } + conditions.SetUncacheable(); + } else { + SetDiscrete(justifyContentValue, + pos->mJustifyContent, conditions, + SETDSC_ENUMERATED | SETDSC_UNSET_INITIAL, + parentPos->mJustifyContent, // unused, we handle 'inherit' above + NS_STYLE_JUSTIFY_AUTO, 0, 0, 0, 0); + } + // justify-items: enum, inherit, initial const auto& justifyItemsValue = *aRuleData->ValueForJustifyItems(); if (MOZ_UNLIKELY(justifyItemsValue.GetUnit() == eCSSUnit_Inherit)) { @@ -7941,13 +7959,6 @@ nsRuleNode::ComputePositionData(void* aStartStruct, parentPos->mOrder, NS_STYLE_ORDER_INITIAL, 0, 0, 0, 0); - // justify-content: enum, inherit, initial - SetDiscrete(*aRuleData->ValueForJustifyContent(), - pos->mJustifyContent, conditions, - SETDSC_ENUMERATED | SETDSC_UNSET_INITIAL, - parentPos->mJustifyContent, - NS_STYLE_JUSTIFY_CONTENT_FLEX_START, 0, 0, 0, 0); - // object-fit: enum, inherit, initial SetDiscrete(*aRuleData->ValueForObjectFit(), pos->mObjectFit, conditions, diff --git a/layout/style/nsStyleConsts.h b/layout/style/nsStyleConsts.h index d0db1617356d..c8ca393a3296 100644 --- a/layout/style/nsStyleConsts.h +++ b/layout/style/nsStyleConsts.h @@ -558,12 +558,12 @@ static inline mozilla::css::Side operator++(mozilla::css::Side& side, int) { // (rather than an internal numerical representation of some keyword). #define NS_STYLE_ORDER_INITIAL 0 -// See nsStylePosition -#define NS_STYLE_JUSTIFY_CONTENT_FLEX_START 0 -#define NS_STYLE_JUSTIFY_CONTENT_FLEX_END 1 -#define NS_STYLE_JUSTIFY_CONTENT_CENTER 2 -#define NS_STYLE_JUSTIFY_CONTENT_SPACE_BETWEEN 3 -#define NS_STYLE_JUSTIFY_CONTENT_SPACE_AROUND 4 +// XXX remove in a later patch after updating flexbox code with the new names +#define NS_STYLE_JUSTIFY_CONTENT_FLEX_START NS_STYLE_JUSTIFY_FLEX_START +#define NS_STYLE_JUSTIFY_CONTENT_FLEX_END NS_STYLE_JUSTIFY_FLEX_END +#define NS_STYLE_JUSTIFY_CONTENT_CENTER NS_STYLE_JUSTIFY_CENTER +#define NS_STYLE_JUSTIFY_CONTENT_SPACE_BETWEEN NS_STYLE_JUSTIFY_SPACE_BETWEEN +#define NS_STYLE_JUSTIFY_CONTENT_SPACE_AROUND NS_STYLE_JUSTIFY_SPACE_AROUND // See nsStyleDisplay #define NS_STYLE_FLOAT_NONE 0 diff --git a/layout/style/nsStyleStruct.cpp b/layout/style/nsStyleStruct.cpp index dfc89a3a3875..f9f1fdeb36fc 100644 --- a/layout/style/nsStyleStruct.cpp +++ b/layout/style/nsStyleStruct.cpp @@ -1434,11 +1434,11 @@ nsStylePosition::nsStylePosition(void) mAlignContent = NS_STYLE_ALIGN_CONTENT_STRETCH; mAlignItems = NS_STYLE_ALIGN_ITEMS_INITIAL_VALUE; mAlignSelf = NS_STYLE_ALIGN_SELF_AUTO; + mJustifyContent = NS_STYLE_JUSTIFY_AUTO; mJustifyItems = NS_STYLE_JUSTIFY_AUTO; mJustifySelf = NS_STYLE_JUSTIFY_AUTO; mFlexDirection = NS_STYLE_FLEX_DIRECTION_ROW; mFlexWrap = NS_STYLE_FLEX_WRAP_NOWRAP; - mJustifyContent = NS_STYLE_JUSTIFY_CONTENT_FLEX_START; mObjectFit = NS_STYLE_OBJECT_FIT_FILL; mOrder = NS_STYLE_ORDER_INITIAL; mFlexGrow = 0.0f; @@ -1475,11 +1475,11 @@ nsStylePosition::nsStylePosition(const nsStylePosition& aSource) , mAlignContent(aSource.mAlignContent) , mAlignItems(aSource.mAlignItems) , mAlignSelf(aSource.mAlignSelf) + , mJustifyContent(aSource.mJustifyContent) , mJustifyItems(aSource.mJustifyItems) , mJustifySelf(aSource.mJustifySelf) , mFlexDirection(aSource.mFlexDirection) , mFlexWrap(aSource.mFlexWrap) - , mJustifyContent(aSource.mJustifyContent) , mObjectFit(aSource.mObjectFit) , mOrder(aSource.mOrder) , mFlexGrow(aSource.mFlexGrow) @@ -1675,22 +1675,6 @@ nsStylePosition::WidthCoordDependsOnContainer(const nsStyleCoord &aCoord) aCoord.GetIntValue() == NS_STYLE_WIDTH_AVAILABLE)); } -static nsStyleContext* -GetAlignmentContainer(nsStyleContext* aParent, - const nsStylePosition** aPosition, - const nsStyleDisplay** aDisplay) -{ - while (aParent && - aParent->StyleDisplay()->mDisplay == NS_STYLE_DISPLAY_CONTENTS) { - aParent = aParent->GetParent(); - } - if (aParent) { - *aPosition = aParent->StylePosition(); - *aDisplay = aParent->StyleDisplay(); - } - return aParent; -} - uint8_t nsStylePosition::MapLeftRightToStart(uint8_t aAlign, LogicalAxis aAxis, const nsStyleDisplay* aDisplay) const @@ -1712,12 +1696,32 @@ nsStylePosition::MapLeftRightToStart(uint8_t aAlign, LogicalAxis aAxis, return aAlign; } +uint16_t +nsStylePosition::ComputedJustifyContent(const nsStyleDisplay* aDisplay) const +{ + switch (aDisplay->mDisplay) { + case NS_STYLE_DISPLAY_FLEX: + case NS_STYLE_DISPLAY_INLINE_FLEX: + // XXX maybe map 'auto' too? (ISSUE 8 in the spec) + // https://drafts.csswg.org/css-align-3/#content-distribution + if (mJustifyContent == NS_STYLE_JUSTIFY_STRETCH) { + return NS_STYLE_JUSTIFY_FLEX_START; + } + break; + } + uint8_t val = mJustifyContent & NS_STYLE_JUSTIFY_ALL_BITS; + val = MapLeftRightToStart(val, eLogicalAxisInline, aDisplay); + uint8_t fallback = mJustifyContent >> NS_STYLE_JUSTIFY_ALL_SHIFT; + fallback = MapLeftRightToStart(fallback, eLogicalAxisInline, aDisplay); + return (uint16_t(fallback) << NS_STYLE_JUSTIFY_ALL_SHIFT) | uint16_t(val); +} + uint8_t nsStylePosition::ComputedJustifyItems(const nsStyleDisplay* aDisplay, nsStyleContext* aParent) const { if (mJustifyItems != NS_STYLE_JUSTIFY_AUTO) { - return mJustifyItems; + return MapLeftRightToStart(mJustifyItems, eLogicalAxisInline, aDisplay); } if (MOZ_LIKELY(aParent)) { auto inheritedJustifyItems = @@ -1735,12 +1739,8 @@ uint8_t nsStylePosition::ComputedJustifySelf(const nsStyleDisplay* aDisplay, nsStyleContext* aParent) const { - const nsStylePosition* containerPos = this; - const nsStyleDisplay* containerDisp = aDisplay; - GetAlignmentContainer(aParent, &containerPos, &containerDisp); if (mJustifySelf != NS_STYLE_JUSTIFY_AUTO) { - return containerPos->MapLeftRightToStart(mJustifySelf, eLogicalAxisInline, - containerDisp); + return MapLeftRightToStart(mJustifySelf, eLogicalAxisInline, aDisplay); } if (MOZ_UNLIKELY(aDisplay->IsAbsolutelyPositionedStyle())) { return NS_STYLE_JUSTIFY_AUTO; @@ -1749,8 +1749,8 @@ nsStylePosition::ComputedJustifySelf(const nsStyleDisplay* aDisplay, auto inheritedJustifyItems = aParent->StylePosition()-> ComputedJustifyItems(aParent->StyleDisplay(), aParent->GetParent()); inheritedJustifyItems &= ~NS_STYLE_JUSTIFY_LEGACY; - return containerPos->MapLeftRightToStart(inheritedJustifyItems, - eLogicalAxisInline, containerDisp); + return MapLeftRightToStart(inheritedJustifyItems, eLogicalAxisInline, + aDisplay); } return NS_STYLE_JUSTIFY_START; } diff --git a/layout/style/nsStyleStruct.h b/layout/style/nsStyleStruct.h index 46a2cb1605de..90877c5f1f47 100644 --- a/layout/style/nsStyleStruct.h +++ b/layout/style/nsStyleStruct.h @@ -1396,6 +1396,12 @@ struct nsStylePosition { // different scope, since we're now using it in multiple style structs. typedef nsStyleBackground::Position Position; + /** + * Return the computed value for 'justify-content' given our 'display' value + * in aDisplay. + */ + uint16_t ComputedJustifyContent(const nsStyleDisplay* aDisplay) const; + /** * Return the computed value for 'justify-items' given our 'display' value in * aDisplay and the parent StyleContext aParent (or null for the root). @@ -1434,12 +1440,12 @@ private: uint8_t MapLeftRightToStart(uint8_t aAlign, mozilla::LogicalAxis aAxis, const nsStyleDisplay* aDisplay) const; + uint16_t mJustifyContent; // [reset] fallback value in the high byte uint8_t mJustifyItems; // [reset] see nsStyleConsts.h uint8_t mJustifySelf; // [reset] see nsStyleConsts.h public: uint8_t mFlexDirection; // [reset] see nsStyleConsts.h uint8_t mFlexWrap; // [reset] see nsStyleConsts.h - uint8_t mJustifyContent; // [reset] see nsStyleConsts.h uint8_t mObjectFit; // [reset] see nsStyleConsts.h int32_t mOrder; // [reset] integer float mFlexGrow; // [reset] float diff --git a/layout/style/test/property_database.js b/layout/style/test/property_database.js index eb1bb75c51a1..3c9de3f0f0b7 100644 --- a/layout/style/test/property_database.js +++ b/layout/style/test/property_database.js @@ -4071,6 +4071,21 @@ var gCSSProperties = { other_values: [ "flex-start", "flex-end", "center", "baseline" ], invalid_values: [ "space-between", "abc", "30px" ] }, + "justify-content": { + domProp: "justifyContent", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "auto" ], + other_values: [ "start", "end", "flex-start", "flex-end", "center", "left", + "right", "space-between", "space-around", "space-evenly", + "baseline", "last-baseline", "stretch", "start safe", + "true end", "true end stretch", "end safe space-evenly" ], + invalid_values: [ "30px", "5%", "self-end", "safe", "auto true", "true safe", + "safe baseline", "baseline true", "baseline end", "auto end", + "safe end true start", "safe end true", "auto safe start", + "true end start", "end start safe", "space-around true", + "safe stretch"] + }, "justify-items": { domProp: "justifyItems", inherited: false, @@ -4286,14 +4301,6 @@ var gCSSProperties = { other_values: [ "1", "99999", "-1", "-50" ], invalid_values: [ "0px", "1.0", "1.", "1%", "0.2", "3em", "stretch" ] }, - "justify-content": { - domProp: "justifyContent", - inherited: false, - type: CSS_TYPE_LONGHAND, - initial_values: [ "flex-start" ], - other_values: [ "flex-end", "center", "space-between", "space-around" ], - invalid_values: [ "baseline", "stretch", "30px", "5%" ] - }, // Aliases "-moz-transform": { From 343fce8c76439f23b5617ed1415a6148bade4eb6 Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Tue, 3 Nov 2015 15:18:05 +0100 Subject: [PATCH 029/113] Bug 1176782 part 4 - [css-align] Implement additional syntax and values for the 'align-items' property in the style system. r=cam --- layout/generic/nsFlexContainerFrame.cpp | 3 ++- layout/style/nsCSSParser.cpp | 10 +++++---- layout/style/nsCSSPropList.h | 10 ++++----- layout/style/nsCSSProps.cpp | 19 +++++------------ layout/style/nsCSSProps.h | 1 - layout/style/nsCSSValue.cpp | 1 + layout/style/nsComputedDOMStyle.cpp | 27 +++++++++++++------------ layout/style/nsRuleNode.cpp | 27 +++++++++++++++++-------- layout/style/nsStyleConsts.h | 15 ++++++-------- layout/style/nsStyleStruct.cpp | 12 ++++++++++- layout/style/nsStyleStruct.h | 8 +++++++- layout/style/test/property_database.js | 11 +++++++--- 12 files changed, 84 insertions(+), 60 deletions(-) diff --git a/layout/generic/nsFlexContainerFrame.cpp b/layout/generic/nsFlexContainerFrame.cpp index 5d9d9dcb234a..89fc0a1be19d 100644 --- a/layout/generic/nsFlexContainerFrame.cpp +++ b/layout/generic/nsFlexContainerFrame.cpp @@ -1594,8 +1594,9 @@ FlexItem::FlexItem(nsHTMLReflowState& aFlexItemReflowState, // Resolve "align-self: auto" to parent's "align-items" value. if (mAlignSelf == NS_STYLE_ALIGN_SELF_AUTO) { + auto parent = mFrame->StyleContext()->GetParent(); mAlignSelf = - mFrame->StyleContext()->GetParent()->StylePosition()->mAlignItems; + parent->StylePosition()->ComputedAlignItems(parent->StyleDisplay()); } // If the flex item's inline axis is the same as the cross axis, then diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index 7d0b6f0a9d71..b2394b10a464 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -939,7 +939,7 @@ protected: bool ParseAlignJustifyPosition(nsCSSValue& aResult, const KTableValue aTable[]); bool ParseJustifyItems(); - bool ParseJustifySelf(); + bool ParseAlignItemsSelfJustifySelf(nsCSSProperty aPropID); // parsing 'align/justify-content' from the css-align spec bool ParseAlignJustifyContent(nsCSSProperty aPropID); @@ -9454,7 +9454,7 @@ CSSParserImpl::ParseJustifyItems() // auto | stretch | | // [ ? && ] bool -CSSParserImpl::ParseJustifySelf() +CSSParserImpl::ParseAlignItemsSelfJustifySelf(nsCSSProperty aPropID) { nsCSSValue value; if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) { @@ -9465,7 +9465,7 @@ CSSParserImpl::ParseJustifySelf() } } } - AppendValue(eCSSProperty_justify_self, value); + AppendValue(aPropID, value); return true; } @@ -10632,12 +10632,14 @@ CSSParserImpl::ParsePropertyByFunction(nsCSSProperty aPropID) return ParseGridArea(); case eCSSProperty_image_region: return ParseRect(eCSSProperty_image_region); + case eCSSProperty_align_items: + return ParseAlignItemsSelfJustifySelf(aPropID); case eCSSProperty_justify_content: return ParseAlignJustifyContent(aPropID); case eCSSProperty_justify_items: return ParseJustifyItems(); case eCSSProperty_justify_self: - return ParseJustifySelf(); + return ParseAlignItemsSelfJustifySelf(aPropID); case eCSSProperty_list_style: return ParseListStyle(); case eCSSProperty_margin: diff --git a/layout/style/nsCSSPropList.h b/layout/style/nsCSSPropList.h index e76a8f16d572..7aee052ed874 100644 --- a/layout/style/nsCSSPropList.h +++ b/layout/style/nsCSSPropList.h @@ -1596,12 +1596,12 @@ CSS_PROP_POSITION( align-items, align_items, AlignItems, - CSS_PROPERTY_PARSE_VALUE, + CSS_PROPERTY_PARSE_FUNCTION, "", - VARIANT_HK, - kAlignItemsKTable, - offsetof(nsStylePosition, mAlignItems), - eStyleAnimType_EnumU8) + 0, + nullptr, + CSS_PROP_NO_OFFSET, + eStyleAnimType_None) CSS_PROP_POSITION( align-self, align_self, diff --git a/layout/style/nsCSSProps.cpp b/layout/style/nsCSSProps.cpp index 08486b80f935..bb7454fa0bdf 100644 --- a/layout/style/nsCSSProps.cpp +++ b/layout/style/nsCSSProps.cpp @@ -1340,22 +1340,13 @@ const KTableValue nsCSSProps::kAlignContentKTable[] = { eCSSKeyword_UNKNOWN,-1 }; -const KTableValue nsCSSProps::kAlignItemsKTable[] = { - eCSSKeyword_flex_start, NS_STYLE_ALIGN_ITEMS_FLEX_START, - eCSSKeyword_flex_end, NS_STYLE_ALIGN_ITEMS_FLEX_END, - eCSSKeyword_center, NS_STYLE_ALIGN_ITEMS_CENTER, - eCSSKeyword_baseline, NS_STYLE_ALIGN_ITEMS_BASELINE, - eCSSKeyword_stretch, NS_STYLE_ALIGN_ITEMS_STRETCH, - eCSSKeyword_UNKNOWN,-1 -}; - // Note: 'align-self' takes the same keywords as 'align-items', plus 'auto'. const KTableValue nsCSSProps::kAlignSelfKTable[] = { - eCSSKeyword_flex_start, NS_STYLE_ALIGN_ITEMS_FLEX_START, - eCSSKeyword_flex_end, NS_STYLE_ALIGN_ITEMS_FLEX_END, - eCSSKeyword_center, NS_STYLE_ALIGN_ITEMS_CENTER, - eCSSKeyword_baseline, NS_STYLE_ALIGN_ITEMS_BASELINE, - eCSSKeyword_stretch, NS_STYLE_ALIGN_ITEMS_STRETCH, + eCSSKeyword_flex_start, NS_STYLE_ALIGN_FLEX_START, + eCSSKeyword_flex_end, NS_STYLE_ALIGN_FLEX_END, + eCSSKeyword_center, NS_STYLE_ALIGN_CENTER, + eCSSKeyword_baseline, NS_STYLE_ALIGN_BASELINE, + eCSSKeyword_stretch, NS_STYLE_ALIGN_STRETCH, eCSSKeyword_auto, NS_STYLE_ALIGN_SELF_AUTO, eCSSKeyword_UNKNOWN,-1 }; diff --git a/layout/style/nsCSSProps.h b/layout/style/nsCSSProps.h index c3b0c76654cb..02c2aad38dd4 100644 --- a/layout/style/nsCSSProps.h +++ b/layout/style/nsCSSProps.h @@ -699,7 +699,6 @@ public: static const KTableValue kAlignContentDistribution[]; // static const KTableValue kAlignContentPosition[]; // static const KTableValue kAlignContentKTable[]; - static const KTableValue kAlignItemsKTable[]; static const KTableValue kAlignSelfKTable[]; static const KTableValue kJustifyContentKTable[]; // ------------------------------------------------------------------ diff --git a/layout/style/nsCSSValue.cpp b/layout/style/nsCSSValue.cpp index 26efdb4a5b79..2979e4e2e1c8 100644 --- a/layout/style/nsCSSValue.cpp +++ b/layout/style/nsCSSValue.cpp @@ -1335,6 +1335,7 @@ nsCSSValue::AppendToString(nsCSSProperty aProperty, nsAString& aResult, break; } + case eCSSProperty_align_items: case eCSSProperty_justify_items: case eCSSProperty_justify_self: AppendAlignJustifyValueToString(intValue, aResult); diff --git a/layout/style/nsComputedDOMStyle.cpp b/layout/style/nsComputedDOMStyle.cpp index dece16264e00..0e0f34b26ce2 100644 --- a/layout/style/nsComputedDOMStyle.cpp +++ b/layout/style/nsComputedDOMStyle.cpp @@ -3870,16 +3870,6 @@ nsComputedDOMStyle::DoGetAlignContent() return val; } -CSSValue* -nsComputedDOMStyle::DoGetAlignItems() -{ - nsROCSSPrimitiveValue* val = new nsROCSSPrimitiveValue; - val->SetIdent( - nsCSSProps::ValueToKeywordEnum(StylePosition()->mAlignItems, - nsCSSProps::kAlignItemsKTable)); - return val; -} - CSSValue* nsComputedDOMStyle::DoGetAlignSelf() { @@ -3890,11 +3880,11 @@ nsComputedDOMStyle::DoGetAlignSelf() // "align-self: auto" needs to compute to parent's align-items value. nsStyleContext* parentStyleContext = mStyleContext->GetParent(); if (parentStyleContext) { - computedAlignSelf = - parentStyleContext->StylePosition()->mAlignItems; + computedAlignSelf = parentStyleContext->StylePosition()-> + ComputedAlignItems(parentStyleContext->StyleDisplay()); } else { // No parent --> use default. - computedAlignSelf = NS_STYLE_ALIGN_ITEMS_INITIAL_VALUE; + computedAlignSelf = NS_STYLE_ALIGN_STRETCH; // XXX temporary, will be removed in later patch } } @@ -3971,6 +3961,17 @@ nsComputedDOMStyle::DoGetOrder() return val; } +CSSValue* +nsComputedDOMStyle::DoGetAlignItems() +{ + nsROCSSPrimitiveValue* val = new nsROCSSPrimitiveValue; + nsAutoString str; + auto align = StylePosition()->ComputedAlignItems(StyleDisplay()); + nsCSSValue::AppendAlignJustifyValueToString(align, str); + val->SetString(str); + return val; +} + CSSValue* nsComputedDOMStyle::DoGetJustifyContent() { diff --git a/layout/style/nsRuleNode.cpp b/layout/style/nsRuleNode.cpp index 7b900a672f8a..4fe6e6bade84 100644 --- a/layout/style/nsRuleNode.cpp +++ b/layout/style/nsRuleNode.cpp @@ -7802,11 +7802,22 @@ nsRuleNode::ComputePositionData(void* aStartStruct, NS_STYLE_ALIGN_CONTENT_STRETCH, 0, 0, 0, 0); // align-items: enum, inherit, initial - SetDiscrete(*aRuleData->ValueForAlignItems(), - pos->mAlignItems, conditions, - SETDSC_ENUMERATED | SETDSC_UNSET_INITIAL, - parentPos->mAlignItems, - NS_STYLE_ALIGN_ITEMS_INITIAL_VALUE, 0, 0, 0, 0); + const auto& alignItemsValue = *aRuleData->ValueForAlignItems(); + if (MOZ_UNLIKELY(alignItemsValue.GetUnit() == eCSSUnit_Inherit)) { + if (MOZ_LIKELY(parentContext)) { + pos->mAlignItems = + parentPos->ComputedAlignItems(parentContext->StyleDisplay()); + } else { + pos->mAlignItems = NS_STYLE_ALIGN_AUTO; + } + conditions.SetUncacheable(); + } else { + SetDiscrete(alignItemsValue, + pos->mAlignItems, conditions, + SETDSC_ENUMERATED | SETDSC_UNSET_INITIAL, + parentPos->mAlignItems, // unused, we handle 'inherit' above + NS_STYLE_ALIGN_AUTO, 0, 0, 0, 0); + } // align-self: enum, inherit, initial // NOTE: align-self's initial value is the special keyword "auto", which is @@ -7828,7 +7839,7 @@ nsRuleNode::ComputePositionData(void* aStartStruct, if (!parentContext) { // We're the root node. Nothing to inherit from --> just use default // value. - inheritedAlignSelf = NS_STYLE_ALIGN_ITEMS_INITIAL_VALUE; + inheritedAlignSelf = NS_STYLE_ALIGN_AUTO; // XXX will be removed in next patch } else { // Our parent's "auto" value should resolve to our grandparent's value // for "align-items". So, that's what we're supposed to inherit. @@ -7836,13 +7847,13 @@ nsRuleNode::ComputePositionData(void* aStartStruct, if (!grandparentContext) { // No grandparent --> our parent is the root node, so its // "align-self: auto" computes to the default "align-items" value: - inheritedAlignSelf = NS_STYLE_ALIGN_ITEMS_INITIAL_VALUE; + inheritedAlignSelf = NS_STYLE_ALIGN_AUTO; // XXX will be removed in next patch } else { // Normal case -- we have a grandparent. // Its "align-items" value is what we should end up inheriting. const nsStylePosition* grandparentPos = grandparentContext->StylePosition(); - inheritedAlignSelf = grandparentPos->mAlignItems; + inheritedAlignSelf = grandparentPos->ComputedAlignItems(grandparentContext->StyleDisplay()); aContext->AddStyleBit(NS_STYLE_USES_GRANDANCESTOR_STYLE); } } diff --git a/layout/style/nsStyleConsts.h b/layout/style/nsStyleConsts.h index c8ca393a3296..dd78881e585c 100644 --- a/layout/style/nsStyleConsts.h +++ b/layout/style/nsStyleConsts.h @@ -526,15 +526,12 @@ static inline mozilla::css::Side operator++(mozilla::css::Side& side, int) { #define NS_STYLE_ALIGN_CONTENT_SPACE_AROUND 4 #define NS_STYLE_ALIGN_CONTENT_STRETCH 5 -// See nsStylePosition -#define NS_STYLE_ALIGN_ITEMS_FLEX_START 0 -#define NS_STYLE_ALIGN_ITEMS_FLEX_END 1 -#define NS_STYLE_ALIGN_ITEMS_CENTER 2 -#define NS_STYLE_ALIGN_ITEMS_BASELINE 3 -#define NS_STYLE_ALIGN_ITEMS_STRETCH 4 - -// For convenience/clarity (since we use this default value in multiple places) -#define NS_STYLE_ALIGN_ITEMS_INITIAL_VALUE NS_STYLE_ALIGN_ITEMS_STRETCH +// XXX remove in a later patch after updating flexbox code with the new names +#define NS_STYLE_ALIGN_ITEMS_FLEX_START NS_STYLE_ALIGN_FLEX_START +#define NS_STYLE_ALIGN_ITEMS_FLEX_END NS_STYLE_ALIGN_FLEX_END +#define NS_STYLE_ALIGN_ITEMS_CENTER NS_STYLE_ALIGN_CENTER +#define NS_STYLE_ALIGN_ITEMS_BASELINE NS_STYLE_ALIGN_BASELINE +#define NS_STYLE_ALIGN_ITEMS_STRETCH NS_STYLE_ALIGN_STRETCH // The "align-self" property accepts all of the normal "align-items" values // (above) plus a special 'auto' value that computes to the parent's diff --git a/layout/style/nsStyleStruct.cpp b/layout/style/nsStyleStruct.cpp index f9f1fdeb36fc..445e4e5d17b4 100644 --- a/layout/style/nsStyleStruct.cpp +++ b/layout/style/nsStyleStruct.cpp @@ -1432,7 +1432,7 @@ nsStylePosition::nsStylePosition(void) mGridAutoFlow = NS_STYLE_GRID_AUTO_FLOW_ROW; mBoxSizing = NS_STYLE_BOX_SIZING_CONTENT; mAlignContent = NS_STYLE_ALIGN_CONTENT_STRETCH; - mAlignItems = NS_STYLE_ALIGN_ITEMS_INITIAL_VALUE; + mAlignItems = NS_STYLE_ALIGN_AUTO; mAlignSelf = NS_STYLE_ALIGN_SELF_AUTO; mJustifyContent = NS_STYLE_JUSTIFY_AUTO; mJustifyItems = NS_STYLE_JUSTIFY_AUTO; @@ -1696,6 +1696,16 @@ nsStylePosition::MapLeftRightToStart(uint8_t aAlign, LogicalAxis aAxis, return aAlign; } +uint8_t +nsStylePosition::ComputedAlignItems(const nsStyleDisplay* aDisplay) const +{ + if (mAlignItems != NS_STYLE_ALIGN_AUTO) { + return MapLeftRightToStart(mAlignItems, eLogicalAxisBlock, aDisplay); + } + return aDisplay->IsFlexOrGridDisplayType() ? NS_STYLE_ALIGN_STRETCH + : NS_STYLE_ALIGN_START; +} + uint16_t nsStylePosition::ComputedJustifyContent(const nsStyleDisplay* aDisplay) const { diff --git a/layout/style/nsStyleStruct.h b/layout/style/nsStyleStruct.h index 90877c5f1f47..7520e5905ade 100644 --- a/layout/style/nsStyleStruct.h +++ b/layout/style/nsStyleStruct.h @@ -1396,6 +1396,12 @@ struct nsStylePosition { // different scope, since we're now using it in multiple style structs. typedef nsStyleBackground::Position Position; + /** + * Return the computed value for 'align-items' given our 'display' value in + * aDisplay. + */ + uint8_t ComputedAlignItems(const nsStyleDisplay* aDisplay) const; + /** * Return the computed value for 'justify-content' given our 'display' value * in aDisplay. @@ -1432,7 +1438,6 @@ struct nsStylePosition { uint8_t mGridAutoFlow; // [reset] enumerated. See nsStyleConsts.h uint8_t mBoxSizing; // [reset] see nsStyleConsts.h uint8_t mAlignContent; // [reset] see nsStyleConsts.h - uint8_t mAlignItems; // [reset] see nsStyleConsts.h uint8_t mAlignSelf; // [reset] see nsStyleConsts.h private: friend class nsRuleNode; @@ -1440,6 +1445,7 @@ private: uint8_t MapLeftRightToStart(uint8_t aAlign, mozilla::LogicalAxis aAxis, const nsStyleDisplay* aDisplay) const; + uint8_t mAlignItems; // [reset] see nsStyleConsts.h uint16_t mJustifyContent; // [reset] fallback value in the high byte uint8_t mJustifyItems; // [reset] see nsStyleConsts.h uint8_t mJustifySelf; // [reset] see nsStyleConsts.h diff --git a/layout/style/test/property_database.js b/layout/style/test/property_database.js index 3c9de3f0f0b7..eacea7543dcd 100644 --- a/layout/style/test/property_database.js +++ b/layout/style/test/property_database.js @@ -4058,9 +4058,14 @@ var gCSSProperties = { domProp: "alignItems", inherited: false, type: CSS_TYPE_LONGHAND, - initial_values: [ "stretch" ], - other_values: [ "flex-start", "flex-end", "center", "baseline" ], - invalid_values: [ "space-between", "abc", "30px" ] + initial_values: [ "auto", "start" ], + // Can't test 'left'/'right' here since that computes to 'start' for blocks. + other_values: [ "end", "flex-start", "flex-end", "self-start", "self-end", + "center", "stretch", "baseline", "true left", + "center true", "safe right", "center safe" ], + invalid_values: [ "space-between", "abc", "5%", "legacy", "legacy end", + "end legacy", "true", "true baseline", "auto true", + "safe left true", "safe stretch", "end end" ] }, "align-self": { domProp: "alignSelf", From ab2709bc5532f62731772e65656e04bd8d39aa6c Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Tue, 3 Nov 2015 15:18:06 +0100 Subject: [PATCH 030/113] Bug 1176782 part 5 - [css-align] Implement additional syntax and values for the 'align-self' property in the style system. r=cam --- layout/generic/nsFlexContainerFrame.cpp | 20 +++++---- layout/style/nsCSSParser.cpp | 2 + layout/style/nsCSSPropList.h | 10 ++--- layout/style/nsCSSProps.cpp | 11 ----- layout/style/nsCSSValue.cpp | 1 + layout/style/nsComputedDOMStyle.cpp | 38 ++++++----------- layout/style/nsRuleNode.cpp | 56 +++++++------------------ layout/style/nsStyleStruct.cpp | 22 +++++++++- layout/style/nsStyleStruct.h | 9 +++- layout/style/test/property_database.js | 10 +++-- 10 files changed, 80 insertions(+), 99 deletions(-) diff --git a/layout/generic/nsFlexContainerFrame.cpp b/layout/generic/nsFlexContainerFrame.cpp index 89fc0a1be19d..49687faedd27 100644 --- a/layout/generic/nsFlexContainerFrame.cpp +++ b/layout/generic/nsFlexContainerFrame.cpp @@ -1565,8 +1565,8 @@ FlexItem::FlexItem(nsHTMLReflowState& aFlexItemReflowState, mIsStretched(false), mIsStrut(false), // mNeedsMinSizeAutoResolution is initialized in CheckForMinSizeAuto() - mWM(aFlexItemReflowState.GetWritingMode()), - mAlignSelf(aFlexItemReflowState.mStylePosition->mAlignSelf) + mWM(aFlexItemReflowState.GetWritingMode()) + // mAlignSelf, see below { MOZ_ASSERT(mFrame, "expecting a non-null child frame"); MOZ_ASSERT(mFrame->GetType() != nsGkAtoms::placeholderFrame, @@ -1574,6 +1574,15 @@ FlexItem::FlexItem(nsHTMLReflowState& aFlexItemReflowState, MOZ_ASSERT(!(mFrame->GetStateBits() & NS_FRAME_OUT_OF_FLOW), "out-of-flow frames should not be treated as flex items"); + mAlignSelf = aFlexItemReflowState.mStylePosition->ComputedAlignSelf( + aFlexItemReflowState.mStyleDisplay, + mFrame->StyleContext()->GetParent()); + if (MOZ_UNLIKELY(mAlignSelf == NS_STYLE_ALIGN_AUTO)) { + // Happens in rare edge cases when 'position' was ignored by the frame + // constructor (and the style system computed 'auto' based on 'position'). + mAlignSelf = NS_STYLE_ALIGN_STRETCH; + } + SetFlexBaseSizeAndMainSize(aFlexBaseSize); CheckForMinSizeAuto(aFlexItemReflowState, aAxisTracker); @@ -1592,13 +1601,6 @@ FlexItem::FlexItem(nsHTMLReflowState& aFlexItemReflowState, } #endif // DEBUG - // Resolve "align-self: auto" to parent's "align-items" value. - if (mAlignSelf == NS_STYLE_ALIGN_SELF_AUTO) { - auto parent = mFrame->StyleContext()->GetParent(); - mAlignSelf = - parent->StylePosition()->ComputedAlignItems(parent->StyleDisplay()); - } - // If the flex item's inline axis is the same as the cross axis, then // 'align-self:baseline' is identical to 'flex-start'. If that's the case, we // just directly convert our align-self value here, so that we don't have to diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index b2394b10a464..8074f7bd335c 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -10634,6 +10634,8 @@ CSSParserImpl::ParsePropertyByFunction(nsCSSProperty aPropID) return ParseRect(eCSSProperty_image_region); case eCSSProperty_align_items: return ParseAlignItemsSelfJustifySelf(aPropID); + case eCSSProperty_align_self: + return ParseAlignItemsSelfJustifySelf(aPropID); case eCSSProperty_justify_content: return ParseAlignJustifyContent(aPropID); case eCSSProperty_justify_items: diff --git a/layout/style/nsCSSPropList.h b/layout/style/nsCSSPropList.h index 7aee052ed874..169858389278 100644 --- a/layout/style/nsCSSPropList.h +++ b/layout/style/nsCSSPropList.h @@ -1606,12 +1606,12 @@ CSS_PROP_POSITION( align-self, align_self, AlignSelf, - CSS_PROPERTY_PARSE_VALUE, + CSS_PROPERTY_PARSE_FUNCTION, "", - VARIANT_HK, - kAlignSelfKTable, - offsetof(nsStylePosition, mAlignSelf), - eStyleAnimType_EnumU8) + 0, + nullptr, + CSS_PROP_NO_OFFSET, + eStyleAnimType_None) CSS_PROP_SHORTHAND( flex, flex, diff --git a/layout/style/nsCSSProps.cpp b/layout/style/nsCSSProps.cpp index bb7454fa0bdf..53bf4bd860b1 100644 --- a/layout/style/nsCSSProps.cpp +++ b/layout/style/nsCSSProps.cpp @@ -1340,17 +1340,6 @@ const KTableValue nsCSSProps::kAlignContentKTable[] = { eCSSKeyword_UNKNOWN,-1 }; -// Note: 'align-self' takes the same keywords as 'align-items', plus 'auto'. -const KTableValue nsCSSProps::kAlignSelfKTable[] = { - eCSSKeyword_flex_start, NS_STYLE_ALIGN_FLEX_START, - eCSSKeyword_flex_end, NS_STYLE_ALIGN_FLEX_END, - eCSSKeyword_center, NS_STYLE_ALIGN_CENTER, - eCSSKeyword_baseline, NS_STYLE_ALIGN_BASELINE, - eCSSKeyword_stretch, NS_STYLE_ALIGN_STRETCH, - eCSSKeyword_auto, NS_STYLE_ALIGN_SELF_AUTO, - eCSSKeyword_UNKNOWN,-1 -}; - const KTableValue nsCSSProps::kFlexDirectionKTable[] = { eCSSKeyword_row, NS_STYLE_FLEX_DIRECTION_ROW, eCSSKeyword_row_reverse, NS_STYLE_FLEX_DIRECTION_ROW_REVERSE, diff --git a/layout/style/nsCSSValue.cpp b/layout/style/nsCSSValue.cpp index 2979e4e2e1c8..17b2f2a28b20 100644 --- a/layout/style/nsCSSValue.cpp +++ b/layout/style/nsCSSValue.cpp @@ -1336,6 +1336,7 @@ nsCSSValue::AppendToString(nsCSSProperty aProperty, nsAString& aResult, } case eCSSProperty_align_items: + case eCSSProperty_align_self: case eCSSProperty_justify_items: case eCSSProperty_justify_self: AppendAlignJustifyValueToString(intValue, aResult); diff --git a/layout/style/nsComputedDOMStyle.cpp b/layout/style/nsComputedDOMStyle.cpp index 0e0f34b26ce2..097a7f2e2497 100644 --- a/layout/style/nsComputedDOMStyle.cpp +++ b/layout/style/nsComputedDOMStyle.cpp @@ -3870,32 +3870,6 @@ nsComputedDOMStyle::DoGetAlignContent() return val; } -CSSValue* -nsComputedDOMStyle::DoGetAlignSelf() -{ - nsROCSSPrimitiveValue* val = new nsROCSSPrimitiveValue; - uint8_t computedAlignSelf = StylePosition()->mAlignSelf; - - if (computedAlignSelf == NS_STYLE_ALIGN_SELF_AUTO) { - // "align-self: auto" needs to compute to parent's align-items value. - nsStyleContext* parentStyleContext = mStyleContext->GetParent(); - if (parentStyleContext) { - computedAlignSelf = parentStyleContext->StylePosition()-> - ComputedAlignItems(parentStyleContext->StyleDisplay()); - } else { - // No parent --> use default. - computedAlignSelf = NS_STYLE_ALIGN_STRETCH; // XXX temporary, will be removed in later patch - } - } - - MOZ_ASSERT(computedAlignSelf != NS_STYLE_ALIGN_SELF_AUTO, - "Should have swapped out 'auto' for something non-auto"); - val->SetIdent( - nsCSSProps::ValueToKeywordEnum(computedAlignSelf, - nsCSSProps::kAlignSelfKTable)); - return val; -} - CSSValue* nsComputedDOMStyle::DoGetFlexBasis() { @@ -3972,6 +3946,18 @@ nsComputedDOMStyle::DoGetAlignItems() return val; } +CSSValue* +nsComputedDOMStyle::DoGetAlignSelf() +{ + nsROCSSPrimitiveValue* val = new nsROCSSPrimitiveValue; + auto align = StylePosition()-> + ComputedAlignSelf(StyleDisplay(), mStyleContext->GetParent()); + nsAutoString str; + nsCSSValue::AppendAlignJustifyValueToString(align, str); + val->SetString(str); + return val; +} + CSSValue* nsComputedDOMStyle::DoGetJustifyContent() { diff --git a/layout/style/nsRuleNode.cpp b/layout/style/nsRuleNode.cpp index 4fe6e6bade84..655dcb1eed9d 100644 --- a/layout/style/nsRuleNode.cpp +++ b/layout/style/nsRuleNode.cpp @@ -7820,54 +7820,26 @@ nsRuleNode::ComputePositionData(void* aStartStruct, } // align-self: enum, inherit, initial - // NOTE: align-self's initial value is the special keyword "auto", which is - // supposed to compute to our parent's computed value of "align-items". So - // technically, "auto" itself is never a valid computed value for align-self, - // since it always computes to something else. Despite that, we do actually - // store "auto" in nsStylePosition::mAlignSelf, as NS_STYLE_ALIGN_SELF_AUTO - // (and then resolve it as-necessary). We do this because "auto" is the - // initial value for this property, so if we were to actually resolve it in - // nsStylePosition, we'd never be able to share any nsStylePosition structs - // in the rule tree, since their mAlignSelf values would depend on the parent - // style, by default. - if (aRuleData->ValueForAlignSelf()->GetUnit() == eCSSUnit_Inherit) { - // Special handling for "align-self: inherit", in case we're inheriting - // "align-self: auto", in which case we need to resolve the parent's "auto" - // and inherit that resolved value. - uint8_t inheritedAlignSelf = parentPos->mAlignSelf; - if (inheritedAlignSelf == NS_STYLE_ALIGN_SELF_AUTO) { - if (!parentContext) { - // We're the root node. Nothing to inherit from --> just use default - // value. - inheritedAlignSelf = NS_STYLE_ALIGN_AUTO; // XXX will be removed in next patch - } else { - // Our parent's "auto" value should resolve to our grandparent's value - // for "align-items". So, that's what we're supposed to inherit. - nsStyleContext* grandparentContext = parentContext->GetParent(); - if (!grandparentContext) { - // No grandparent --> our parent is the root node, so its - // "align-self: auto" computes to the default "align-items" value: - inheritedAlignSelf = NS_STYLE_ALIGN_AUTO; // XXX will be removed in next patch - } else { - // Normal case -- we have a grandparent. - // Its "align-items" value is what we should end up inheriting. - const nsStylePosition* grandparentPos = - grandparentContext->StylePosition(); - inheritedAlignSelf = grandparentPos->ComputedAlignItems(grandparentContext->StyleDisplay()); - aContext->AddStyleBit(NS_STYLE_USES_GRANDANCESTOR_STYLE); - } + const auto& alignSelfValue = *aRuleData->ValueForAlignSelf(); + if (MOZ_UNLIKELY(alignSelfValue.GetUnit() == eCSSUnit_Inherit)) { + if (MOZ_LIKELY(parentContext)) { + nsStyleContext* grandparentContext = parentContext->GetParent(); + if (MOZ_LIKELY(grandparentContext)) { + aContext->AddStyleBit(NS_STYLE_USES_GRANDANCESTOR_STYLE); } + pos->mAlignSelf = + parentPos->ComputedAlignSelf(parentContext->StyleDisplay(), + grandparentContext); + } else { + pos->mAlignSelf = NS_STYLE_ALIGN_START; } - - pos->mAlignSelf = inheritedAlignSelf; conditions.SetUncacheable(); } else { - SetDiscrete(*aRuleData->ValueForAlignSelf(), + SetDiscrete(alignSelfValue, pos->mAlignSelf, conditions, SETDSC_ENUMERATED | SETDSC_UNSET_INITIAL, - parentPos->mAlignSelf, // (unused -- we handled inherit above) - NS_STYLE_ALIGN_SELF_AUTO, // initial == auto - 0, 0, 0, 0); + parentPos->mAlignSelf, // unused, we handle 'inherit' above + NS_STYLE_ALIGN_AUTO, 0, 0, 0, 0); } // justify-content: enum, inherit, initial diff --git a/layout/style/nsStyleStruct.cpp b/layout/style/nsStyleStruct.cpp index 445e4e5d17b4..163439a4a74b 100644 --- a/layout/style/nsStyleStruct.cpp +++ b/layout/style/nsStyleStruct.cpp @@ -1433,7 +1433,7 @@ nsStylePosition::nsStylePosition(void) mBoxSizing = NS_STYLE_BOX_SIZING_CONTENT; mAlignContent = NS_STYLE_ALIGN_CONTENT_STRETCH; mAlignItems = NS_STYLE_ALIGN_AUTO; - mAlignSelf = NS_STYLE_ALIGN_SELF_AUTO; + mAlignSelf = NS_STYLE_ALIGN_AUTO; mJustifyContent = NS_STYLE_JUSTIFY_AUTO; mJustifyItems = NS_STYLE_JUSTIFY_AUTO; mJustifySelf = NS_STYLE_JUSTIFY_AUTO; @@ -1706,6 +1706,26 @@ nsStylePosition::ComputedAlignItems(const nsStyleDisplay* aDisplay) const : NS_STYLE_ALIGN_START; } +uint8_t +nsStylePosition::ComputedAlignSelf(const nsStyleDisplay* aDisplay, + nsStyleContext* aParent) const +{ + if (mAlignSelf != NS_STYLE_ALIGN_AUTO) { + return MapLeftRightToStart(mAlignSelf, eLogicalAxisBlock, aDisplay); + } + if (MOZ_UNLIKELY(aDisplay->IsAbsolutelyPositionedStyle())) { + return NS_STYLE_ALIGN_AUTO; + } + if (MOZ_LIKELY(aParent)) { + auto parentAlignItems = aParent->StylePosition()-> + ComputedAlignItems(aParent->StyleDisplay()); + MOZ_ASSERT(!(parentAlignItems & NS_STYLE_ALIGN_LEGACY), + "align-items can't have 'legacy'"); + return MapLeftRightToStart(parentAlignItems, eLogicalAxisBlock, aDisplay); + } + return NS_STYLE_ALIGN_START; +} + uint16_t nsStylePosition::ComputedJustifyContent(const nsStyleDisplay* aDisplay) const { diff --git a/layout/style/nsStyleStruct.h b/layout/style/nsStyleStruct.h index 7520e5905ade..fc53fa6cb12e 100644 --- a/layout/style/nsStyleStruct.h +++ b/layout/style/nsStyleStruct.h @@ -1402,6 +1402,13 @@ struct nsStylePosition { */ uint8_t ComputedAlignItems(const nsStyleDisplay* aDisplay) const; + /** + * Return the computed value for 'align-self' given our 'display' value in + * aDisplay and the parent StyleContext aParent (or null for the root). + */ + uint8_t ComputedAlignSelf(const nsStyleDisplay* aDisplay, + nsStyleContext* aParent) const; + /** * Return the computed value for 'justify-content' given our 'display' value * in aDisplay. @@ -1438,7 +1445,6 @@ struct nsStylePosition { uint8_t mGridAutoFlow; // [reset] enumerated. See nsStyleConsts.h uint8_t mBoxSizing; // [reset] see nsStyleConsts.h uint8_t mAlignContent; // [reset] see nsStyleConsts.h - uint8_t mAlignSelf; // [reset] see nsStyleConsts.h private: friend class nsRuleNode; // Helper for the ComputedAlign/Justify* methods. @@ -1446,6 +1452,7 @@ private: const nsStyleDisplay* aDisplay) const; uint8_t mAlignItems; // [reset] see nsStyleConsts.h + uint8_t mAlignSelf; // [reset] see nsStyleConsts.h uint16_t mJustifyContent; // [reset] fallback value in the high byte uint8_t mJustifyItems; // [reset] see nsStyleConsts.h uint8_t mJustifySelf; // [reset] see nsStyleConsts.h diff --git a/layout/style/test/property_database.js b/layout/style/test/property_database.js index eacea7543dcd..1f3cda6b209b 100644 --- a/layout/style/test/property_database.js +++ b/layout/style/test/property_database.js @@ -4071,10 +4071,12 @@ var gCSSProperties = { domProp: "alignSelf", inherited: false, type: CSS_TYPE_LONGHAND, - // (Assuming defaults on the parent, 'auto' will compute to 'stretch'.) - initial_values: [ "auto", "stretch" ], - other_values: [ "flex-start", "flex-end", "center", "baseline" ], - invalid_values: [ "space-between", "abc", "30px" ] + // (Assuming defaults on the parent, 'auto' will compute to 'start'.) + initial_values: [ "auto", "start" ], + other_values: [ "flex-start", "flex-end", "center", "stretch", + "baseline", "last-baseline", "right safe", "true center", + "self-start", "self-end safe" ], + invalid_values: [ "space-between", "abc", "30px", "stretch safe", "safe" ] }, "justify-content": { domProp: "justifyContent", From b06df0786735b606cb75515d3ac7792040beac0a Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Tue, 3 Nov 2015 15:18:06 +0100 Subject: [PATCH 031/113] Bug 1176782 part 6 - [css-align] Implement additional syntax and values for the 'align-content' property in the style system. r=cam --- layout/generic/nsFlexContainerFrame.cpp | 3 ++- layout/style/nsCSSParser.cpp | 2 ++ layout/style/nsCSSPropList.h | 10 +++++----- layout/style/nsCSSProps.cpp | 10 ---------- layout/style/nsCSSProps.h | 1 - layout/style/nsCSSValue.cpp | 1 + layout/style/nsComputedDOMStyle.cpp | 26 +++++++++++++++---------- layout/style/nsRuleNode.cpp | 21 +++++++++++++++----- layout/style/nsStyleConsts.h | 15 +++++++------- layout/style/nsStyleStruct.cpp | 12 +++++++++++- layout/style/nsStyleStruct.h | 8 +++++++- layout/style/test/property_database.js | 19 +++++++++--------- 12 files changed, 78 insertions(+), 50 deletions(-) diff --git a/layout/generic/nsFlexContainerFrame.cpp b/layout/generic/nsFlexContainerFrame.cpp index 49687faedd27..21ebb80b5a4d 100644 --- a/layout/generic/nsFlexContainerFrame.cpp +++ b/layout/generic/nsFlexContainerFrame.cpp @@ -3739,7 +3739,8 @@ nsFlexContainerFrame::DoFlexLayout(nsPresContext* aPresContext, // scope of a particular flex line) CrossAxisPositionTracker crossAxisPosnTracker(lines.getFirst(), - aReflowState.mStylePosition->mAlignContent, + aReflowState.mStylePosition->ComputedAlignContent( + aReflowState.mStyleDisplay), contentBoxCrossSize, isCrossSizeDefinite, aAxisTracker); diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index 8074f7bd335c..a8b72129e401 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -10632,6 +10632,8 @@ CSSParserImpl::ParsePropertyByFunction(nsCSSProperty aPropID) return ParseGridArea(); case eCSSProperty_image_region: return ParseRect(eCSSProperty_image_region); + case eCSSProperty_align_content: + return ParseAlignJustifyContent(aPropID); case eCSSProperty_align_items: return ParseAlignItemsSelfJustifySelf(aPropID); case eCSSProperty_align_self: diff --git a/layout/style/nsCSSPropList.h b/layout/style/nsCSSPropList.h index 169858389278..cac7071e5625 100644 --- a/layout/style/nsCSSPropList.h +++ b/layout/style/nsCSSPropList.h @@ -1586,12 +1586,12 @@ CSS_PROP_POSITION( align-content, align_content, AlignContent, - CSS_PROPERTY_PARSE_VALUE, + CSS_PROPERTY_PARSE_FUNCTION, "", - VARIANT_HK, - kAlignContentKTable, - offsetof(nsStylePosition, mAlignContent), - eStyleAnimType_EnumU8) + 0, + nullptr, + CSS_PROP_NO_OFFSET, + eStyleAnimType_None) CSS_PROP_POSITION( align-items, align_items, diff --git a/layout/style/nsCSSProps.cpp b/layout/style/nsCSSProps.cpp index 53bf4bd860b1..4b8a39862355 100644 --- a/layout/style/nsCSSProps.cpp +++ b/layout/style/nsCSSProps.cpp @@ -1330,16 +1330,6 @@ const KTableValue nsCSSProps::kAlignContentPosition[] = { eCSSKeyword_UNKNOWN,-1 }; -const KTableValue nsCSSProps::kAlignContentKTable[] = { - eCSSKeyword_flex_start, NS_STYLE_ALIGN_CONTENT_FLEX_START, - eCSSKeyword_flex_end, NS_STYLE_ALIGN_CONTENT_FLEX_END, - eCSSKeyword_center, NS_STYLE_ALIGN_CONTENT_CENTER, - eCSSKeyword_space_between, NS_STYLE_ALIGN_CONTENT_SPACE_BETWEEN, - eCSSKeyword_space_around, NS_STYLE_ALIGN_CONTENT_SPACE_AROUND, - eCSSKeyword_stretch, NS_STYLE_ALIGN_CONTENT_STRETCH, - eCSSKeyword_UNKNOWN,-1 -}; - const KTableValue nsCSSProps::kFlexDirectionKTable[] = { eCSSKeyword_row, NS_STYLE_FLEX_DIRECTION_ROW, eCSSKeyword_row_reverse, NS_STYLE_FLEX_DIRECTION_ROW_REVERSE, diff --git a/layout/style/nsCSSProps.h b/layout/style/nsCSSProps.h index 02c2aad38dd4..30f75593f882 100644 --- a/layout/style/nsCSSProps.h +++ b/layout/style/nsCSSProps.h @@ -698,7 +698,6 @@ public: static const KTableValue kAlignAutoBaseline[]; // 'auto/baseline/last-baseline' static const KTableValue kAlignContentDistribution[]; // static const KTableValue kAlignContentPosition[]; // - static const KTableValue kAlignContentKTable[]; static const KTableValue kAlignSelfKTable[]; static const KTableValue kJustifyContentKTable[]; // ------------------------------------------------------------------ diff --git a/layout/style/nsCSSValue.cpp b/layout/style/nsCSSValue.cpp index 17b2f2a28b20..a62001c6d460 100644 --- a/layout/style/nsCSSValue.cpp +++ b/layout/style/nsCSSValue.cpp @@ -1322,6 +1322,7 @@ nsCSSValue::AppendToString(nsCSSProperty aProperty, nsAString& aResult, aResult); break; + case eCSSProperty_align_content: case eCSSProperty_justify_content: { AppendAlignJustifyValueToString(intValue & NS_STYLE_ALIGN_ALL_BITS, aResult); auto fallback = intValue >> NS_STYLE_ALIGN_ALL_SHIFT; diff --git a/layout/style/nsComputedDOMStyle.cpp b/layout/style/nsComputedDOMStyle.cpp index 097a7f2e2497..d373bc263f69 100644 --- a/layout/style/nsComputedDOMStyle.cpp +++ b/layout/style/nsComputedDOMStyle.cpp @@ -3860,16 +3860,6 @@ nsComputedDOMStyle::DoGetBorderImageRepeat() return valueList; } -CSSValue* -nsComputedDOMStyle::DoGetAlignContent() -{ - nsROCSSPrimitiveValue* val = new nsROCSSPrimitiveValue; - val->SetIdent( - nsCSSProps::ValueToKeywordEnum(StylePosition()->mAlignContent, - nsCSSProps::kAlignContentKTable)); - return val; -} - CSSValue* nsComputedDOMStyle::DoGetFlexBasis() { @@ -3935,6 +3925,22 @@ nsComputedDOMStyle::DoGetOrder() return val; } +CSSValue* +nsComputedDOMStyle::DoGetAlignContent() +{ + nsROCSSPrimitiveValue* val = new nsROCSSPrimitiveValue; + nsAutoString str; + auto align = StylePosition()->ComputedAlignContent(StyleDisplay()); + nsCSSValue::AppendAlignJustifyValueToString(align & NS_STYLE_ALIGN_ALL_BITS, str); + auto fallback = align >> NS_STYLE_ALIGN_ALL_SHIFT; + if (fallback) { + str.Append(' '); + nsCSSValue::AppendAlignJustifyValueToString(fallback, str); + } + val->SetString(str); + return val; +} + CSSValue* nsComputedDOMStyle::DoGetAlignItems() { diff --git a/layout/style/nsRuleNode.cpp b/layout/style/nsRuleNode.cpp index 655dcb1eed9d..0659165f8107 100644 --- a/layout/style/nsRuleNode.cpp +++ b/layout/style/nsRuleNode.cpp @@ -7795,11 +7795,22 @@ nsRuleNode::ComputePositionData(void* aStartStruct, NS_STYLE_BOX_SIZING_CONTENT, 0, 0, 0, 0); // align-content: enum, inherit, initial - SetDiscrete(*aRuleData->ValueForAlignContent(), - pos->mAlignContent, conditions, - SETDSC_ENUMERATED | SETDSC_UNSET_INITIAL, - parentPos->mAlignContent, - NS_STYLE_ALIGN_CONTENT_STRETCH, 0, 0, 0, 0); + const auto& alignContentValue = *aRuleData->ValueForAlignContent(); + if (MOZ_UNLIKELY(alignContentValue.GetUnit() == eCSSUnit_Inherit)) { + if (MOZ_LIKELY(parentContext)) { + pos->mAlignContent = + parentPos->ComputedAlignContent(parentContext->StyleDisplay()); + } else { + pos->mAlignContent = NS_STYLE_ALIGN_AUTO; + } + conditions.SetUncacheable(); + } else { + SetDiscrete(alignContentValue, + pos->mAlignContent, conditions, + SETDSC_ENUMERATED | SETDSC_UNSET_INITIAL, + parentPos->mAlignContent, // unused, we handle 'inherit' above + NS_STYLE_ALIGN_AUTO, 0, 0, 0, 0); + } // align-items: enum, inherit, initial const auto& alignItemsValue = *aRuleData->ValueForAlignItems(); diff --git a/layout/style/nsStyleConsts.h b/layout/style/nsStyleConsts.h index dd78881e585c..f577ed9c451f 100644 --- a/layout/style/nsStyleConsts.h +++ b/layout/style/nsStyleConsts.h @@ -518,13 +518,14 @@ static inline mozilla::css::Side operator++(mozilla::css::Side& side, int) { #define NS_STYLE_JUSTIFY_ALL_BITS NS_STYLE_ALIGN_ALL_BITS #define NS_STYLE_JUSTIFY_ALL_SHIFT NS_STYLE_ALIGN_ALL_SHIFT -// See nsStylePosition -#define NS_STYLE_ALIGN_CONTENT_FLEX_START 0 -#define NS_STYLE_ALIGN_CONTENT_FLEX_END 1 -#define NS_STYLE_ALIGN_CONTENT_CENTER 2 -#define NS_STYLE_ALIGN_CONTENT_SPACE_BETWEEN 3 -#define NS_STYLE_ALIGN_CONTENT_SPACE_AROUND 4 -#define NS_STYLE_ALIGN_CONTENT_STRETCH 5 +// XXX remove in a later patch after updating flexbox code with the new names +#define NS_STYLE_ALIGN_CONTENT_AUTO NS_STYLE_ALIGN_AUTO +#define NS_STYLE_ALIGN_CONTENT_FLEX_START NS_STYLE_ALIGN_FLEX_START +#define NS_STYLE_ALIGN_CONTENT_FLEX_END NS_STYLE_ALIGN_FLEX_END +#define NS_STYLE_ALIGN_CONTENT_CENTER NS_STYLE_ALIGN_CENTER +#define NS_STYLE_ALIGN_CONTENT_STRETCH NS_STYLE_ALIGN_STRETCH +#define NS_STYLE_ALIGN_CONTENT_SPACE_BETWEEN NS_STYLE_ALIGN_SPACE_BETWEEN +#define NS_STYLE_ALIGN_CONTENT_SPACE_AROUND NS_STYLE_ALIGN_SPACE_AROUND // XXX remove in a later patch after updating flexbox code with the new names #define NS_STYLE_ALIGN_ITEMS_FLEX_START NS_STYLE_ALIGN_FLEX_START diff --git a/layout/style/nsStyleStruct.cpp b/layout/style/nsStyleStruct.cpp index 163439a4a74b..0cf322335496 100644 --- a/layout/style/nsStyleStruct.cpp +++ b/layout/style/nsStyleStruct.cpp @@ -1431,7 +1431,7 @@ nsStylePosition::nsStylePosition(void) mGridAutoFlow = NS_STYLE_GRID_AUTO_FLOW_ROW; mBoxSizing = NS_STYLE_BOX_SIZING_CONTENT; - mAlignContent = NS_STYLE_ALIGN_CONTENT_STRETCH; + mAlignContent = NS_STYLE_ALIGN_AUTO; mAlignItems = NS_STYLE_ALIGN_AUTO; mAlignSelf = NS_STYLE_ALIGN_AUTO; mJustifyContent = NS_STYLE_JUSTIFY_AUTO; @@ -1696,6 +1696,16 @@ nsStylePosition::MapLeftRightToStart(uint8_t aAlign, LogicalAxis aAxis, return aAlign; } +uint16_t +nsStylePosition::ComputedAlignContent(const nsStyleDisplay* aDisplay) const +{ + uint8_t val = mAlignContent & NS_STYLE_ALIGN_ALL_BITS; + val = MapLeftRightToStart(val, eLogicalAxisBlock, aDisplay); + uint8_t fallback = mAlignContent >> NS_STYLE_ALIGN_ALL_SHIFT; + fallback = MapLeftRightToStart(fallback, eLogicalAxisBlock, aDisplay); + return (uint16_t(fallback) << NS_STYLE_ALIGN_ALL_SHIFT) | uint16_t(val); +} + uint8_t nsStylePosition::ComputedAlignItems(const nsStyleDisplay* aDisplay) const { diff --git a/layout/style/nsStyleStruct.h b/layout/style/nsStyleStruct.h index fc53fa6cb12e..e421adad1616 100644 --- a/layout/style/nsStyleStruct.h +++ b/layout/style/nsStyleStruct.h @@ -1396,6 +1396,12 @@ struct nsStylePosition { // different scope, since we're now using it in multiple style structs. typedef nsStyleBackground::Position Position; + /** + * Return the computed value for 'align-content' given our 'display' value in + * aDisplay. + */ + uint16_t ComputedAlignContent(const nsStyleDisplay* aDisplay) const; + /** * Return the computed value for 'align-items' given our 'display' value in * aDisplay. @@ -1444,13 +1450,13 @@ struct nsStylePosition { nsStyleCoord mGridAutoRowsMax; // [reset] coord, percent, enum, calc, flex uint8_t mGridAutoFlow; // [reset] enumerated. See nsStyleConsts.h uint8_t mBoxSizing; // [reset] see nsStyleConsts.h - uint8_t mAlignContent; // [reset] see nsStyleConsts.h private: friend class nsRuleNode; // Helper for the ComputedAlign/Justify* methods. uint8_t MapLeftRightToStart(uint8_t aAlign, mozilla::LogicalAxis aAxis, const nsStyleDisplay* aDisplay) const; + uint16_t mAlignContent; // [reset] fallback value in the high byte uint8_t mAlignItems; // [reset] see nsStyleConsts.h uint8_t mAlignSelf; // [reset] see nsStyleConsts.h uint16_t mJustifyContent; // [reset] fallback value in the high byte diff --git a/layout/style/test/property_database.js b/layout/style/test/property_database.js index 1f3cda6b209b..778f8d752e85 100644 --- a/layout/style/test/property_database.js +++ b/layout/style/test/property_database.js @@ -4044,15 +4044,16 @@ var gCSSProperties = { domProp: "alignContent", inherited: false, type: CSS_TYPE_LONGHAND, - initial_values: [ "stretch" ], - other_values: [ - "flex-start", - "flex-end", - "center", - "space-between", - "space-around" - ], - invalid_values: [ "abc", "30px", "0", "auto" ] + initial_values: [ "auto" ], + other_values: [ "start", "end", "flex-start", "flex-end", "center", "left", + "right", "space-between", "space-around", "space-evenly", + "baseline", "last-baseline", "stretch", "start safe", + "true end", "true end stretch", "end safe space-evenly" ], + invalid_values: [ "none", "5", "self-end", "safe", "auto true", "true safe", + "safe baseline", "baseline true", "baseline end", "end auto", + "safe end true start", "safe end true", "auto safe start", + "true end start", "end start safe", "space-between true", + "stretch safe" ] }, "align-items": { domProp: "alignItems", From 7c6b7fe75122c98eea02774a16028fdff555a269 Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Tue, 3 Nov 2015 15:18:06 +0100 Subject: [PATCH 032/113] Bug 1176782 part 7 - [css-align] Update the flexbox layout code to use the new align/justify style constants and remove the old in the style system. r=cam --- layout/generic/nsFlexContainerFrame.cpp | 102 ++++++++++++------------ layout/style/nsStyleConsts.h | 22 ----- 2 files changed, 51 insertions(+), 73 deletions(-) diff --git a/layout/generic/nsFlexContainerFrame.cpp b/layout/generic/nsFlexContainerFrame.cpp index 21ebb80b5a4d..dcb2688a10e4 100644 --- a/layout/generic/nsFlexContainerFrame.cpp +++ b/layout/generic/nsFlexContainerFrame.cpp @@ -1526,7 +1526,7 @@ nsFlexContainerFrame:: // establishes the container's baseline. Also save the ascent if this child // needs to be baseline-aligned. (Else, we don't care about ascent/baseline.) if (aFlexItem.Frame() == mFrames.FirstChild() || - aFlexItem.GetAlignSelf() == NS_STYLE_ALIGN_ITEMS_BASELINE) { + aFlexItem.GetAlignSelf() == NS_STYLE_ALIGN_BASELINE) { aFlexItem.SetAscent(childDesiredSize.BlockStartAscent()); } @@ -1611,9 +1611,9 @@ FlexItem::FlexItem(nsHTMLReflowState& aFlexItemReflowState, // FIXME: Once we support writing-mode (vertical text), this // IsCrossAxisHorizontal check won't be sufficient anymore -- we'll actually // need to compare our inline axis vs. the cross axis. - if (mAlignSelf == NS_STYLE_ALIGN_ITEMS_BASELINE && + if (mAlignSelf == NS_STYLE_ALIGN_BASELINE && aAxisTracker.IsCrossAxisHorizontal()) { - mAlignSelf = NS_STYLE_ALIGN_ITEMS_FLEX_START; + mAlignSelf = NS_STYLE_ALIGN_FLEX_START; } } @@ -1646,7 +1646,7 @@ FlexItem::FlexItem(nsIFrame* aChildFrame, nscoord aCrossSize, mIsStrut(true), // (this is the constructor for making struts, after all) mNeedsMinSizeAutoResolution(false), mWM(aContainerWM), - mAlignSelf(NS_STYLE_ALIGN_ITEMS_FLEX_START) + mAlignSelf(NS_STYLE_ALIGN_FLEX_START) { MOZ_ASSERT(mFrame, "expecting a non-null child frame"); MOZ_ASSERT(NS_STYLE_VISIBILITY_COLLAPSE == @@ -2449,20 +2449,20 @@ MainAxisPositionTracker:: // and 'space-around' behaves like 'center'. In those cases, it's simplest to // just pretend we have a different 'justify-content' value and share code. if (mPackingSpaceRemaining < 0) { - if (mJustifyContent == NS_STYLE_JUSTIFY_CONTENT_SPACE_BETWEEN) { - mJustifyContent = NS_STYLE_JUSTIFY_CONTENT_FLEX_START; - } else if (mJustifyContent == NS_STYLE_JUSTIFY_CONTENT_SPACE_AROUND) { - mJustifyContent = NS_STYLE_JUSTIFY_CONTENT_CENTER; + if (mJustifyContent == NS_STYLE_JUSTIFY_SPACE_BETWEEN) { + mJustifyContent = NS_STYLE_JUSTIFY_FLEX_START; + } else if (mJustifyContent == NS_STYLE_JUSTIFY_SPACE_AROUND) { + mJustifyContent = NS_STYLE_JUSTIFY_CENTER; } } // If our main axis is (internally) reversed, swap the justify-content // "flex-start" and "flex-end" behaviors: if (aAxisTracker.AreAxesInternallyReversed()) { - if (mJustifyContent == NS_STYLE_JUSTIFY_CONTENT_FLEX_START) { - mJustifyContent = NS_STYLE_JUSTIFY_CONTENT_FLEX_END; - } else if (mJustifyContent == NS_STYLE_JUSTIFY_CONTENT_FLEX_END) { - mJustifyContent = NS_STYLE_JUSTIFY_CONTENT_FLEX_START; + if (mJustifyContent == NS_STYLE_JUSTIFY_FLEX_START) { + mJustifyContent = NS_STYLE_JUSTIFY_FLEX_END; + } else if (mJustifyContent == NS_STYLE_JUSTIFY_FLEX_END) { + mJustifyContent = NS_STYLE_JUSTIFY_FLEX_START; } } @@ -2472,25 +2472,25 @@ MainAxisPositionTracker:: mPackingSpaceRemaining != 0 && !aLine->IsEmpty()) { switch (mJustifyContent) { - case NS_STYLE_JUSTIFY_CONTENT_FLEX_START: + case NS_STYLE_JUSTIFY_FLEX_START: // All packing space should go at the end --> nothing to do here. break; - case NS_STYLE_JUSTIFY_CONTENT_FLEX_END: + case NS_STYLE_JUSTIFY_FLEX_END: // All packing space goes at the beginning mPosition += mPackingSpaceRemaining; break; - case NS_STYLE_JUSTIFY_CONTENT_CENTER: + case NS_STYLE_JUSTIFY_CENTER: // Half the packing space goes at the beginning mPosition += mPackingSpaceRemaining / 2; break; - case NS_STYLE_JUSTIFY_CONTENT_SPACE_BETWEEN: + case NS_STYLE_JUSTIFY_SPACE_BETWEEN: MOZ_ASSERT(mPackingSpaceRemaining >= 0, "negative packing space should make us use 'flex-start' " "instead of 'space-between'"); // 1 packing space between each flex item, no packing space at ends. mNumPackingSpacesRemaining = aLine->NumItems() - 1; break; - case NS_STYLE_JUSTIFY_CONTENT_SPACE_AROUND: + case NS_STYLE_JUSTIFY_SPACE_AROUND: MOZ_ASSERT(mPackingSpaceRemaining >= 0, "negative packing space should make us use 'center' " "instead of 'space-around'"); @@ -2551,8 +2551,8 @@ void MainAxisPositionTracker::TraversePackingSpace() { if (mNumPackingSpacesRemaining) { - MOZ_ASSERT(mJustifyContent == NS_STYLE_JUSTIFY_CONTENT_SPACE_BETWEEN || - mJustifyContent == NS_STYLE_JUSTIFY_CONTENT_SPACE_AROUND, + MOZ_ASSERT(mJustifyContent == NS_STYLE_JUSTIFY_SPACE_BETWEEN || + mJustifyContent == NS_STYLE_JUSTIFY_SPACE_AROUND, "mNumPackingSpacesRemaining only applies for " "space-between/space-around"); @@ -2617,21 +2617,21 @@ CrossAxisPositionTracker:: // it's simplest to just pretend we have a different 'align-content' value // and share code. if (mPackingSpaceRemaining < 0) { - if (mAlignContent == NS_STYLE_ALIGN_CONTENT_SPACE_BETWEEN || - mAlignContent == NS_STYLE_ALIGN_CONTENT_STRETCH) { - mAlignContent = NS_STYLE_ALIGN_CONTENT_FLEX_START; - } else if (mAlignContent == NS_STYLE_ALIGN_CONTENT_SPACE_AROUND) { - mAlignContent = NS_STYLE_ALIGN_CONTENT_CENTER; + if (mAlignContent == NS_STYLE_ALIGN_SPACE_BETWEEN || + mAlignContent == NS_STYLE_ALIGN_STRETCH) { + mAlignContent = NS_STYLE_ALIGN_FLEX_START; + } else if (mAlignContent == NS_STYLE_ALIGN_SPACE_AROUND) { + mAlignContent = NS_STYLE_ALIGN_CENTER; } } // If our cross axis is (internally) reversed, swap the align-content // "flex-start" and "flex-end" behaviors: if (aAxisTracker.AreAxesInternallyReversed()) { - if (mAlignContent == NS_STYLE_ALIGN_CONTENT_FLEX_START) { - mAlignContent = NS_STYLE_ALIGN_CONTENT_FLEX_END; - } else if (mAlignContent == NS_STYLE_ALIGN_CONTENT_FLEX_END) { - mAlignContent = NS_STYLE_ALIGN_CONTENT_FLEX_START; + if (mAlignContent == NS_STYLE_ALIGN_FLEX_START) { + mAlignContent = NS_STYLE_ALIGN_FLEX_END; + } else if (mAlignContent == NS_STYLE_ALIGN_FLEX_END) { + mAlignContent = NS_STYLE_ALIGN_FLEX_START; } } @@ -2639,25 +2639,25 @@ CrossAxisPositionTracker:: // past any leading packing-space. if (mPackingSpaceRemaining != 0) { switch (mAlignContent) { - case NS_STYLE_ALIGN_CONTENT_FLEX_START: + case NS_STYLE_ALIGN_FLEX_START: // All packing space should go at the end --> nothing to do here. break; - case NS_STYLE_ALIGN_CONTENT_FLEX_END: + case NS_STYLE_ALIGN_FLEX_END: // All packing space goes at the beginning mPosition += mPackingSpaceRemaining; break; - case NS_STYLE_ALIGN_CONTENT_CENTER: + case NS_STYLE_ALIGN_CENTER: // Half the packing space goes at the beginning mPosition += mPackingSpaceRemaining / 2; break; - case NS_STYLE_ALIGN_CONTENT_SPACE_BETWEEN: + case NS_STYLE_ALIGN_SPACE_BETWEEN: MOZ_ASSERT(mPackingSpaceRemaining >= 0, "negative packing space should make us use 'flex-start' " "instead of 'space-between'"); // 1 packing space between each flex line, no packing space at ends. mNumPackingSpacesRemaining = numLines - 1; break; - case NS_STYLE_ALIGN_CONTENT_SPACE_AROUND: { + case NS_STYLE_ALIGN_SPACE_AROUND: { MOZ_ASSERT(mPackingSpaceRemaining >= 0, "negative packing space should make us use 'center' " "instead of 'space-around'"); @@ -2677,7 +2677,7 @@ CrossAxisPositionTracker:: mNumPackingSpacesRemaining--; break; } - case NS_STYLE_ALIGN_CONTENT_STRETCH: { + case NS_STYLE_ALIGN_STRETCH: { // Split space equally between the lines: MOZ_ASSERT(mPackingSpaceRemaining > 0, "negative packing space should make us use 'flex-start' " @@ -2709,8 +2709,8 @@ void CrossAxisPositionTracker::TraversePackingSpace() { if (mNumPackingSpacesRemaining) { - MOZ_ASSERT(mAlignContent == NS_STYLE_ALIGN_CONTENT_SPACE_BETWEEN || - mAlignContent == NS_STYLE_ALIGN_CONTENT_SPACE_AROUND, + MOZ_ASSERT(mAlignContent == NS_STYLE_ALIGN_SPACE_BETWEEN || + mAlignContent == NS_STYLE_ALIGN_SPACE_AROUND, "mNumPackingSpacesRemaining only applies for " "space-between/space-around"); @@ -2745,7 +2745,7 @@ FlexLine::ComputeCrossSizeAndBaseline(const FlexboxAxisTracker& aAxisTracker) nscoord curOuterCrossSize = item->GetOuterCrossSize(aAxisTracker.GetCrossAxis()); - if (item->GetAlignSelf() == NS_STYLE_ALIGN_ITEMS_BASELINE && + if (item->GetAlignSelf() == NS_STYLE_ALIGN_BASELINE && item->GetNumAutoMarginsInAxis(aAxisTracker.GetCrossAxis()) == 0) { // FIXME: Once we support "writing-mode", we'll have to do baseline // alignment in vertical flex containers here (w/ horizontal cross-axes). @@ -2818,7 +2818,7 @@ FlexItem::ResolveStretchedCrossSize(nscoord aLineCrossSize, // We stretch IFF we are align-self:stretch, have no auto margins in // cross axis, and have cross-axis size property == "auto". If any of those // conditions don't hold up, we won't stretch. - if (mAlignSelf != NS_STYLE_ALIGN_ITEMS_STRETCH || + if (mAlignSelf != NS_STYLE_ALIGN_STRETCH || GetNumAutoMarginsInAxis(crossAxis) != 0 || eStyleUnit_Auto != aAxisTracker.ComputedCrossSize(mFrame).GetUnit()) { return; @@ -2897,33 +2897,33 @@ SingleLineCrossAxisPositionTracker:: uint8_t alignSelf = aItem.GetAlignSelf(); // NOTE: 'stretch' behaves like 'flex-start' once we've stretched any // auto-sized items (which we've already done). - if (alignSelf == NS_STYLE_ALIGN_ITEMS_STRETCH) { - alignSelf = NS_STYLE_ALIGN_ITEMS_FLEX_START; + if (alignSelf == NS_STYLE_ALIGN_STRETCH) { + alignSelf = NS_STYLE_ALIGN_FLEX_START; } // If our cross axis is (internally) reversed, swap the align-self // "flex-start" and "flex-end" behaviors: if (aAxisTracker.AreAxesInternallyReversed()) { - if (alignSelf == NS_STYLE_ALIGN_ITEMS_FLEX_START) { - alignSelf = NS_STYLE_ALIGN_ITEMS_FLEX_END; - } else if (alignSelf == NS_STYLE_ALIGN_ITEMS_FLEX_END) { - alignSelf = NS_STYLE_ALIGN_ITEMS_FLEX_START; + if (alignSelf == NS_STYLE_ALIGN_FLEX_START) { + alignSelf = NS_STYLE_ALIGN_FLEX_END; + } else if (alignSelf == NS_STYLE_ALIGN_FLEX_END) { + alignSelf = NS_STYLE_ALIGN_FLEX_START; } } switch (alignSelf) { - case NS_STYLE_ALIGN_ITEMS_FLEX_START: + case NS_STYLE_ALIGN_FLEX_START: // No space to skip over -- we're done. break; - case NS_STYLE_ALIGN_ITEMS_FLEX_END: + case NS_STYLE_ALIGN_FLEX_END: mPosition += aLine.GetLineCrossSize() - aItem.GetOuterCrossSize(mAxis); break; - case NS_STYLE_ALIGN_ITEMS_CENTER: + case NS_STYLE_ALIGN_CENTER: // Note: If cross-size is odd, the "after" space will get the extra unit. mPosition += (aLine.GetLineCrossSize() - aItem.GetOuterCrossSize(mAxis)) / 2; break; - case NS_STYLE_ALIGN_ITEMS_BASELINE: { + case NS_STYLE_ALIGN_BASELINE: { // Normally, baseline-aligned items are collectively aligned with the // line's cross-start edge; however, if our cross axis is (internally) // reversed, we instead align them with the cross-end edge. @@ -3447,7 +3447,7 @@ nsFlexContainerFrame::SizeItemInCrossAxis( MOZ_ASSERT(!aItem.HadMeasuringReflow(), "We shouldn't need more than one measuring reflow"); - if (aItem.GetAlignSelf() == NS_STYLE_ALIGN_ITEMS_STRETCH) { + if (aItem.GetAlignSelf() == NS_STYLE_ALIGN_STRETCH) { // This item's got "align-self: stretch", so we probably imposed a // stretched computed height on it during its previous reflow. We're // not imposing that height for *this* measuring reflow, so we need to @@ -3504,7 +3504,7 @@ nsFlexContainerFrame::SizeItemInCrossAxis( // establishes the container's baseline. Also save the ascent if this child // needs to be baseline-aligned. (Else, we don't care about baseline/ascent.) if (aItem.Frame() == mFrames.FirstChild() || - aItem.GetAlignSelf() == NS_STYLE_ALIGN_ITEMS_BASELINE) { + aItem.GetAlignSelf() == NS_STYLE_ALIGN_BASELINE) { aItem.SetAscent(childDesiredSize.BlockStartAscent()); } } @@ -4021,7 +4021,7 @@ nsFlexContainerFrame::ReflowFlexItem(nsPresContext* aPresContext, // Override reflow state's computed cross-size, for stretched items. if (aItem.IsStretched()) { - MOZ_ASSERT(aItem.GetAlignSelf() == NS_STYLE_ALIGN_ITEMS_STRETCH, + MOZ_ASSERT(aItem.GetAlignSelf() == NS_STYLE_ALIGN_STRETCH, "stretched item w/o 'align-self: stretch'?"); if (aAxisTracker.IsCrossAxisHorizontal()) { childReflowState.SetComputedWidth(aItem.GetCrossSize()); diff --git a/layout/style/nsStyleConsts.h b/layout/style/nsStyleConsts.h index f577ed9c451f..2f1522132ff1 100644 --- a/layout/style/nsStyleConsts.h +++ b/layout/style/nsStyleConsts.h @@ -518,28 +518,6 @@ static inline mozilla::css::Side operator++(mozilla::css::Side& side, int) { #define NS_STYLE_JUSTIFY_ALL_BITS NS_STYLE_ALIGN_ALL_BITS #define NS_STYLE_JUSTIFY_ALL_SHIFT NS_STYLE_ALIGN_ALL_SHIFT -// XXX remove in a later patch after updating flexbox code with the new names -#define NS_STYLE_ALIGN_CONTENT_AUTO NS_STYLE_ALIGN_AUTO -#define NS_STYLE_ALIGN_CONTENT_FLEX_START NS_STYLE_ALIGN_FLEX_START -#define NS_STYLE_ALIGN_CONTENT_FLEX_END NS_STYLE_ALIGN_FLEX_END -#define NS_STYLE_ALIGN_CONTENT_CENTER NS_STYLE_ALIGN_CENTER -#define NS_STYLE_ALIGN_CONTENT_STRETCH NS_STYLE_ALIGN_STRETCH -#define NS_STYLE_ALIGN_CONTENT_SPACE_BETWEEN NS_STYLE_ALIGN_SPACE_BETWEEN -#define NS_STYLE_ALIGN_CONTENT_SPACE_AROUND NS_STYLE_ALIGN_SPACE_AROUND - -// XXX remove in a later patch after updating flexbox code with the new names -#define NS_STYLE_ALIGN_ITEMS_FLEX_START NS_STYLE_ALIGN_FLEX_START -#define NS_STYLE_ALIGN_ITEMS_FLEX_END NS_STYLE_ALIGN_FLEX_END -#define NS_STYLE_ALIGN_ITEMS_CENTER NS_STYLE_ALIGN_CENTER -#define NS_STYLE_ALIGN_ITEMS_BASELINE NS_STYLE_ALIGN_BASELINE -#define NS_STYLE_ALIGN_ITEMS_STRETCH NS_STYLE_ALIGN_STRETCH - -// The "align-self" property accepts all of the normal "align-items" values -// (above) plus a special 'auto' value that computes to the parent's -// "align-items" value. Our computed style code internally represents 'auto' -// with this enum until we actually evaluate it: -#define NS_STYLE_ALIGN_SELF_AUTO 5 - // See nsStylePosition #define NS_STYLE_FLEX_DIRECTION_ROW 0 #define NS_STYLE_FLEX_DIRECTION_ROW_REVERSE 1 From 87a0b397a7cec015bd7edc4205b6aa22e343739b Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Tue, 3 Nov 2015 15:18:06 +0100 Subject: [PATCH 033/113] Bug 1176782 part 8 - [css-align] Update three tests that assumed that the computed value of 'align-items' is 'stretch' on all types of boxes which is no longer true. --- .../css-display/display-contents-acid.html | 2 +- .../flexbox-align-self-horiz-003-ref.xhtml | 2 +- .../test/test_flexbox_align_self_auto.html | 20 +++++++++---------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/layout/reftests/css-display/display-contents-acid.html b/layout/reftests/css-display/display-contents-acid.html index 4302730d14bb..bdd0d08eee83 100644 --- a/layout/reftests/css-display/display-contents-acid.html +++ b/layout/reftests/css-display/display-contents-acid.html @@ -32,7 +32,7 @@ .inline { display:inline; } .columns { -moz-columns:2; columns:2; height:4em; } -.contents { display:contents; } +.contents { display:contents; align-items:inherit; justify-items:inherit; } .c1 { color:lime; } .c2 { background:blue; color:pink; } diff --git a/layout/reftests/w3c-css/submitted/flexbox/flexbox-align-self-horiz-003-ref.xhtml b/layout/reftests/w3c-css/submitted/flexbox/flexbox-align-self-horiz-003-ref.xhtml index fdda16171c7e..f47f7f1dbfe6 100644 --- a/layout/reftests/w3c-css/submitted/flexbox/flexbox-align-self-horiz-003-ref.xhtml +++ b/layout/reftests/w3c-css/submitted/flexbox/flexbox-align-self-horiz-003-ref.xhtml @@ -87,7 +87,7 @@
auto
unspec
initial
-
inherit
+
inherit
diff --git a/layout/style/test/test_flexbox_align_self_auto.html b/layout/style/test/test_flexbox_align_self_auto.html index 5a5e37ab95db..6072a9a6960b 100644 --- a/layout/style/test/test_flexbox_align_self_auto.html +++ b/layout/style/test/test_flexbox_align_self_auto.html @@ -63,22 +63,22 @@ function getComputedAlignSelf(elem) { */ function testGeneralNode(elem) { // Test initial computed style - // (Initial value should be 'auto', which should compute to 'stretch') - is(getComputedAlignSelf(elem), "stretch", - "initial computed value of 'align-self' should be 'stretch', " + + // (Initial value should be 'auto', which should compute to 'start') + is(getComputedAlignSelf(elem), "start", + "initial computed value of 'align-self' should be 'start', " + "if we haven't explicitly set any style on the parent"); // Test value after setting align-self explicitly to "auto" elem.style.alignSelf = "auto"; - is(getComputedAlignSelf(elem), "stretch", - "computed value of 'align-self: auto' should be 'stretch', " + + is(getComputedAlignSelf(elem), "start", + "computed value of 'align-self: auto' should be 'start', " + "if we haven't explicitly set any style on the parent"); elem.style.alignSelf = ""; // clean up // Test value after setting align-self explicitly to "inherit" elem.style.alignSelf = "inherit"; - is(getComputedAlignSelf(elem), "stretch", - "computed value of 'align-self: inherit' should be 'stretch', " + + is(getComputedAlignSelf(elem), "start", + "computed value of 'align-self: inherit' should be 'start', " + "if we haven't explicitly set any style on the parent"); elem.style.alignSelf = ""; // clean up } @@ -111,11 +111,11 @@ function testNodeThatHasParent(elem) { // Finally: test computed style after setting "align-self" to "inherit" // and leaving parent at its initial value (which should be "auto", which - // should compute to "stretch") + // should compute to "start") elem.style.alignSelf = "inherit"; - is(getComputedAlignSelf(elem), "stretch", + is(getComputedAlignSelf(elem), "start", "computed value of 'align-self: inherit' should take parent's " + - "computed 'align-self' value (which should be 'stretch', " + + "computed 'align-self' value (which should be 'start', " + "if we haven't explicitly set any other style"); elem.style.alignSelf = ""; // clean up } From a77e1f7b8dd651190fa2e1da71d74c15bac73684 Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Tue, 3 Nov 2015 15:18:06 +0100 Subject: [PATCH 034/113] Bug 1151214 part 2 - [css-flexbox][css-align] Shim implemention for the new align/justify property values in flexbox layout (just to avoid fatal assertions). r=dholbert --- layout/generic/nsFlexContainerFrame.cpp | 62 +++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/layout/generic/nsFlexContainerFrame.cpp b/layout/generic/nsFlexContainerFrame.cpp index dcb2688a10e4..471935d4ee76 100644 --- a/layout/generic/nsFlexContainerFrame.cpp +++ b/layout/generic/nsFlexContainerFrame.cpp @@ -1583,6 +1583,9 @@ FlexItem::FlexItem(nsHTMLReflowState& aFlexItemReflowState, mAlignSelf = NS_STYLE_ALIGN_STRETCH; } + // XXX strip off the bit until we implement that + mAlignSelf &= ~NS_STYLE_ALIGN_FLAG_BITS; + SetFlexBaseSizeAndMainSize(aFlexBaseSize); CheckForMinSizeAuto(aFlexItemReflowState, aAxisTracker); @@ -1836,6 +1839,7 @@ private: nscoord mPackingSpaceRemaining; uint32_t mNumAutoMarginsInMainAxis; uint32_t mNumPackingSpacesRemaining; + // XXX this should be uint16_t when we add explicit fallback handling uint8_t mJustifyContent; }; @@ -1869,6 +1873,7 @@ private: nscoord mPackingSpaceRemaining; uint32_t mNumPackingSpacesRemaining; + // XXX this should be uint16_t when we add explicit fallback handling uint8_t mAlignContent; }; @@ -2431,6 +2436,14 @@ MainAxisPositionTracker:: mNumPackingSpacesRemaining(0), mJustifyContent(aJustifyContent) { + // 'auto' behaves as 'stretch' which behaves as 'flex-start' in the main axis + if (mJustifyContent == NS_STYLE_JUSTIFY_AUTO) { + mJustifyContent = NS_STYLE_JUSTIFY_FLEX_START; + } + + // XXX strip off the bit until we implement that + mJustifyContent &= ~NS_STYLE_JUSTIFY_FLAG_BITS; + // mPackingSpaceRemaining is initialized to the container's main size. Now // we'll subtract out the main sizes of our flex items, so that it ends up // with the *actual* amount of packing space. @@ -2456,6 +2469,13 @@ MainAxisPositionTracker:: } } + // Map 'start'/'end' to 'flex-start'/'flex-end'. + if (mJustifyContent == NS_STYLE_JUSTIFY_START) { + mJustifyContent = NS_STYLE_JUSTIFY_FLEX_START; + } else if (mJustifyContent == NS_STYLE_JUSTIFY_END) { + mJustifyContent = NS_STYLE_JUSTIFY_FLEX_END; + } + // If our main axis is (internally) reversed, swap the justify-content // "flex-start" and "flex-end" behaviors: if (aAxisTracker.AreAxesInternallyReversed()) { @@ -2472,6 +2492,12 @@ MainAxisPositionTracker:: mPackingSpaceRemaining != 0 && !aLine->IsEmpty()) { switch (mJustifyContent) { + case NS_STYLE_JUSTIFY_LEFT: + case NS_STYLE_JUSTIFY_RIGHT: + case NS_STYLE_JUSTIFY_BASELINE: + case NS_STYLE_JUSTIFY_LAST_BASELINE: + case NS_STYLE_JUSTIFY_SPACE_EVENLY: + NS_WARNING("NYI: justify-content:left/right/baseline/last-baseline/space-evenly"); case NS_STYLE_JUSTIFY_FLEX_START: // All packing space should go at the end --> nothing to do here. break; @@ -2584,6 +2610,14 @@ CrossAxisPositionTracker:: { MOZ_ASSERT(aFirstLine, "null first line pointer"); + // 'auto' behaves as 'stretch' + if (mAlignContent == NS_STYLE_ALIGN_AUTO) { + mAlignContent = NS_STYLE_ALIGN_STRETCH; + } + + // XXX strip of the bit until we implement that + mAlignContent &= ~NS_STYLE_ALIGN_FLAG_BITS; + if (aIsCrossSizeDefinite && !aFirstLine->getNext()) { // "If the flex container has only a single line (even if it's a // multi-line flex container) and has a definite cross size, the cross @@ -2625,6 +2659,13 @@ CrossAxisPositionTracker:: } } + // Map 'start'/'end' to 'flex-start'/'flex-end'. + if (mAlignContent == NS_STYLE_ALIGN_START) { + mAlignContent = NS_STYLE_ALIGN_FLEX_START; + } else if (mAlignContent == NS_STYLE_ALIGN_END) { + mAlignContent = NS_STYLE_ALIGN_FLEX_END; + } + // If our cross axis is (internally) reversed, swap the align-content // "flex-start" and "flex-end" behaviors: if (aAxisTracker.AreAxesInternallyReversed()) { @@ -2639,6 +2680,14 @@ CrossAxisPositionTracker:: // past any leading packing-space. if (mPackingSpaceRemaining != 0) { switch (mAlignContent) { + case NS_STYLE_JUSTIFY_LEFT: + case NS_STYLE_JUSTIFY_RIGHT: + case NS_STYLE_ALIGN_SELF_START: + case NS_STYLE_ALIGN_SELF_END: + case NS_STYLE_ALIGN_SPACE_EVENLY: + case NS_STYLE_ALIGN_BASELINE: + case NS_STYLE_ALIGN_LAST_BASELINE: + NS_WARNING("NYI: align-self:left/right/self-start/self-end/space-evenly/baseline/last-baseline"); case NS_STYLE_ALIGN_FLEX_START: // All packing space should go at the end --> nothing to do here. break; @@ -2901,6 +2950,13 @@ SingleLineCrossAxisPositionTracker:: alignSelf = NS_STYLE_ALIGN_FLEX_START; } + // Map 'start'/'end' to 'flex-start'/'flex-end'. + if (alignSelf == NS_STYLE_ALIGN_START) { + alignSelf = NS_STYLE_ALIGN_FLEX_START; + } else if (alignSelf == NS_STYLE_ALIGN_END) { + alignSelf = NS_STYLE_ALIGN_FLEX_END; + } + // If our cross axis is (internally) reversed, swap the align-self // "flex-start" and "flex-end" behaviors: if (aAxisTracker.AreAxesInternallyReversed()) { @@ -2912,6 +2968,12 @@ SingleLineCrossAxisPositionTracker:: } switch (alignSelf) { + case NS_STYLE_JUSTIFY_LEFT: + case NS_STYLE_JUSTIFY_RIGHT: + case NS_STYLE_ALIGN_SELF_START: + case NS_STYLE_ALIGN_SELF_END: + case NS_STYLE_ALIGN_LAST_BASELINE: + NS_WARNING("NYI: align-self:left/right/self-start/self-end/last-baseline"); case NS_STYLE_ALIGN_FLEX_START: // No space to skip over -- we're done. break; From 0391daa755f70bd124b2e59ad7e04e83788914fc Mon Sep 17 00:00:00 2001 From: Andrea Marchesini Date: Tue, 3 Nov 2015 15:12:23 +0000 Subject: [PATCH 035/113] Bug 1219939 - make nsTemporaryFileInputStream nsISeekableStream, r=jduell --- netwerk/base/nsTemporaryFileInputStream.cpp | 74 ++++++++++++++++++--- netwerk/base/nsTemporaryFileInputStream.h | 6 +- 2 files changed, 71 insertions(+), 9 deletions(-) diff --git a/netwerk/base/nsTemporaryFileInputStream.cpp b/netwerk/base/nsTemporaryFileInputStream.cpp index 4eb7161c1842..2d24a7782805 100644 --- a/netwerk/base/nsTemporaryFileInputStream.cpp +++ b/netwerk/base/nsTemporaryFileInputStream.cpp @@ -7,11 +7,12 @@ #include "nsStreamUtils.h" #include -NS_IMPL_ISUPPORTS(nsTemporaryFileInputStream, nsIInputStream) +NS_IMPL_ISUPPORTS(nsTemporaryFileInputStream, nsIInputStream, nsISeekableStream) nsTemporaryFileInputStream::nsTemporaryFileInputStream(FileDescOwner* aFileDescOwner, uint64_t aStartPos, uint64_t aEndPos) : mFileDescOwner(aFileDescOwner), mStartPos(aStartPos), + mCurPos(aStartPos), mEndPos(aEndPos), mClosed(false) { @@ -31,9 +32,9 @@ nsTemporaryFileInputStream::Available(uint64_t * bytesAvailable) if (mClosed) return NS_BASE_STREAM_CLOSED; - NS_ASSERTION(mStartPos <= mEndPos, "StartPos should less equal than EndPos!"); + NS_ASSERTION(mCurPos <= mEndPos, "CurPos should less equal than EndPos!"); - *bytesAvailable = mEndPos - mStartPos; + *bytesAvailable = mEndPos - mCurPos; return NS_OK; } @@ -50,7 +51,7 @@ nsTemporaryFileInputStream::ReadSegments(nsWriteSegmentFun writer, uint32_t * result) { NS_ASSERTION(result, "null ptr"); - NS_ASSERTION(mStartPos <= mEndPos, "bad stream state"); + NS_ASSERTION(mCurPos <= mEndPos, "bad stream state"); *result = 0; if (mClosed) { @@ -58,10 +59,10 @@ nsTemporaryFileInputStream::ReadSegments(nsWriteSegmentFun writer, } mozilla::MutexAutoLock lock(mFileDescOwner->FileMutex()); - PR_Seek64(mFileDescOwner->mFD, mStartPos, PR_SEEK_SET); + PR_Seek64(mFileDescOwner->mFD, mCurPos, PR_SEEK_SET); // Limit requested count to the amount remaining in our section of the file. - count = std::min(count, uint32_t(mEndPos - mStartPos)); + count = std::min(count, uint32_t(mEndPos - mCurPos)); char buf[4096]; while (*result < count) { @@ -81,7 +82,7 @@ nsTemporaryFileInputStream::ReadSegments(nsWriteSegmentFun writer, // from writer are not propagated to ReadSegments' caller. // // If writer fails, leaving bytes still in buf, that's okay: we - // only update mStartPos to reflect successful writes, so the call + // only update mCurPos to reflect successful writes, so the call // to PR_Seek64 at the top will restart us at the right spot. return NS_OK; } @@ -89,7 +90,7 @@ nsTemporaryFileInputStream::ReadSegments(nsWriteSegmentFun writer, "writer should not write more than we asked it to write"); bytesWritten += writerCount; *result += writerCount; - mStartPos += writerCount; + mCurPos += writerCount; } } @@ -103,3 +104,60 @@ nsTemporaryFileInputStream::IsNonBlocking(bool * nonBlocking) return NS_OK; } +NS_IMETHODIMP +nsTemporaryFileInputStream::Seek(int32_t aWhence, int64_t aOffset) +{ + if (mClosed) { + return NS_BASE_STREAM_CLOSED; + } + + switch (aWhence) { + case nsISeekableStream::NS_SEEK_SET: + aOffset += mStartPos; + break; + + case nsISeekableStream::NS_SEEK_CUR: + aOffset += mCurPos; + break; + + case nsISeekableStream::NS_SEEK_END: + aOffset += mEndPos; + break; + + default: + return NS_ERROR_FAILURE; + } + + if (aOffset < (int64_t)mStartPos || aOffset > (int64_t)mEndPos) { + return NS_ERROR_INVALID_ARG; + } + + mCurPos = aOffset; + return NS_OK; +} + +NS_IMETHODIMP +nsTemporaryFileInputStream::Tell(int64_t* aPos) +{ + if (!aPos) { + return NS_ERROR_FAILURE; + } + + if (mClosed) { + return NS_BASE_STREAM_CLOSED; + } + + MOZ_ASSERT(mStartPos <= mCurPos, "StartPos should less equal than CurPos!"); + *aPos = mCurPos - mStartPos; + return NS_OK; +} + +NS_IMETHODIMP +nsTemporaryFileInputStream::SetEOF() +{ + if (mClosed) { + return NS_BASE_STREAM_CLOSED; + } + + return Close(); +} diff --git a/netwerk/base/nsTemporaryFileInputStream.h b/netwerk/base/nsTemporaryFileInputStream.h index 32bdf468a807..8299daa7d36b 100644 --- a/netwerk/base/nsTemporaryFileInputStream.h +++ b/netwerk/base/nsTemporaryFileInputStream.h @@ -7,11 +7,13 @@ #define nsTemporaryFileInputStream_h__ #include "mozilla/Mutex.h" -#include "nsIInputStream.h" #include "nsAutoPtr.h" +#include "nsIInputStream.h" +#include "nsISeekableStream.h" #include "prio.h" class nsTemporaryFileInputStream : public nsIInputStream + , public nsISeekableStream { public: //used to release a PRFileDesc @@ -43,12 +45,14 @@ public: NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIINPUTSTREAM + NS_DECL_NSISEEKABLESTREAM private: virtual ~nsTemporaryFileInputStream() { } RefPtr mFileDescOwner; uint64_t mStartPos; + uint64_t mCurPos; uint64_t mEndPos; bool mClosed; }; From 50a1920a4f8492ff738498e82487eac0768b841e Mon Sep 17 00:00:00 2001 From: Mason Chang Date: Tue, 3 Nov 2015 07:38:34 -0800 Subject: [PATCH 036/113] Bug 1217905. Don't transform dest rects if destination context has a rotation with inset box shadows. r=mstange --- layout/base/nsCSSRendering.cpp | 36 ++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/layout/base/nsCSSRendering.cpp b/layout/base/nsCSSRendering.cpp index b3d405fa9d2c..973a9c79d8b5 100644 --- a/layout/base/nsCSSRendering.cpp +++ b/layout/base/nsCSSRendering.cpp @@ -5584,6 +5584,8 @@ nsContextBoxBlur::InsetBoxBlur(gfxContext* aDestinationCtx, return false; } + gfxContextAutoSaveRestore autoRestore(aDestinationCtx); + IntSize blurRadius; IntSize spreadRadius; // Convert the blur and spread radius to device pixels @@ -5597,11 +5599,20 @@ nsContextBoxBlur::InsetBoxBlur(gfxContext* aDestinationCtx, // inset blur to the invert of the dest context, then rescale it back // when we draw to the destination surface. gfxSize scale = aDestinationCtx->CurrentMatrix().ScaleFactors(true); - Matrix currentMatrix = ToMatrix(aDestinationCtx->CurrentMatrix()); + Matrix transform = ToMatrix(aDestinationCtx->CurrentMatrix()); - Rect transformedDestRect = currentMatrix.TransformBounds(aDestinationRect); - Rect transformedShadowClipRect = currentMatrix.TransformBounds(aShadowClipRect); - Rect transformedSkipRect = currentMatrix.TransformBounds(aSkipRect); + // XXX: we could probably handle negative scales but for now it's easier just to fallback + if (!transform.HasNonAxisAlignedTransform() && transform._11 > 0.0 && transform._22 > 0.0) { + // If we don't have a rotation, we're pre-transforming all the rects. + aDestinationCtx->SetMatrix(gfxMatrix()); + } else { + // Don't touch anything, we have a rotation. + transform = Matrix(); + } + + Rect transformedDestRect = transform.TransformBounds(aDestinationRect); + Rect transformedShadowClipRect = transform.TransformBounds(aShadowClipRect); + Rect transformedSkipRect = transform.TransformBounds(aSkipRect); transformedDestRect.Round(); transformedShadowClipRect.Round(); @@ -5612,16 +5623,11 @@ nsContextBoxBlur::InsetBoxBlur(gfxContext* aDestinationCtx, aInnerClipRectRadii[i].height = std::floor(scale.height * aInnerClipRectRadii[i].height); } - { - gfxContextAutoSaveRestore autoRestore(aDestinationCtx); - aDestinationCtx->SetMatrix(gfxMatrix()); - - mAlphaBoxBlur.BlurInsetBox(aDestinationCtx, transformedDestRect, - transformedShadowClipRect, - blurRadius, spreadRadius, - aShadowColor, aHasBorderRadius, - aInnerClipRectRadii, transformedSkipRect, - aShadowOffset); - } + mAlphaBoxBlur.BlurInsetBox(aDestinationCtx, transformedDestRect, + transformedShadowClipRect, + blurRadius, spreadRadius, + aShadowColor, aHasBorderRadius, + aInnerClipRectRadii, transformedSkipRect, + aShadowOffset); return true; } From acb8fed93cd5a88b5b567bf50d1327ed5862ac52 Mon Sep 17 00:00:00 2001 From: Alexander Surkov Date: Tue, 3 Nov 2015 11:03:23 -0500 Subject: [PATCH 037/113] Bug 499917 - take into account aria-owns when calculating group attributes, test only, r=yzen --- .../tests/mochitest/attributes/test_obj_group.html | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/accessible/tests/mochitest/attributes/test_obj_group.html b/accessible/tests/mochitest/attributes/test_obj_group.html index 28f45ad4b834..d5fe8947167e 100644 --- a/accessible/tests/mochitest/attributes/test_obj_group.html +++ b/accessible/tests/mochitest/attributes/test_obj_group.html @@ -191,6 +191,12 @@ testGroupAttrs("table_cell", 3, 4); testGroupAttrs("table_row", 2, 2); + ////////////////////////////////////////////////////////////////////////// + // ARIA list constructed by ARIA owns + testGroupAttrs("t1_li1", 1, 3); + testGroupAttrs("t1_li2", 2, 3); + testGroupAttrs("t1_li3", 3, 3); + // Test that group position information updates after deleting node. testGroupAttrs("tree4_ti1", 1, 2, 1); testGroupAttrs("tree4_ti2", 2, 2, 1); @@ -453,5 +459,11 @@
cell
+ +
+
Apples
+
Oranges
+ +
Bananas
From 6606e57d29c6f6482f4bc379bba574d3d5690cd9 Mon Sep 17 00:00:00 2001 From: Alexander Surkov Date: Tue, 3 Nov 2015 11:03:34 -0500 Subject: [PATCH 038/113] Bug 582024 - ARIA active-descendant should work for ARIA owned elements, r=yzen --- accessible/generic/Accessible.cpp | 24 ++++++++++--------- .../test_focus_aria_activedescendant.html | 13 ++++++---- .../tests/mochitest/states/test_aria.html | 4 +++- 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/accessible/generic/Accessible.cpp b/accessible/generic/Accessible.cpp index a3e72048af21..fce078d694c4 100644 --- a/accessible/generic/Accessible.cpp +++ b/accessible/generic/Accessible.cpp @@ -1238,11 +1238,13 @@ Accessible::ApplyARIAState(uint64_t* aState) const *aState &= ~states::READONLY; if (mContent->HasID()) { - // If has a role & ID and aria-activedescendant on the container, assume focusable - nsIContent *ancestorContent = mContent; - while ((ancestorContent = ancestorContent->GetParent()) != nullptr) { - if (ancestorContent->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_activedescendant)) { - // ancestor has activedescendant property, this content could be active + // If has a role & ID and aria-activedescendant on the container, assume + // focusable. + const Accessible* ancestor = this; + while ((ancestor = ancestor->Parent()) && !ancestor->IsDoc()) { + dom::Element* el = ancestor->Elm(); + if (el && + el->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_activedescendant)) { *aState |= states::FOCUSABLE; break; } @@ -1251,12 +1253,12 @@ Accessible::ApplyARIAState(uint64_t* aState) const } if (*aState & states::FOCUSABLE) { - // Special case: aria-disabled propagates from ancestors down to any focusable descendant - nsIContent *ancestorContent = mContent; - while ((ancestorContent = ancestorContent->GetParent()) != nullptr) { - if (ancestorContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_disabled, - nsGkAtoms::_true, eCaseMatters)) { - // ancestor has aria-disabled property, this is disabled + // Propogate aria-disabled from ancestors down to any focusable descendant. + const Accessible* ancestor = this; + while ((ancestor = ancestor->Parent()) && !ancestor->IsDoc()) { + dom::Element* el = ancestor->Elm(); + if (el && el->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_disabled, + nsGkAtoms::_true, eCaseMatters)) { *aState |= states::UNAVAILABLE; break; } diff --git a/accessible/tests/mochitest/events/test_focus_aria_activedescendant.html b/accessible/tests/mochitest/events/test_focus_aria_activedescendant.html index 4c2a2a29624e..4cd57fe3b386 100644 --- a/accessible/tests/mochitest/events/test_focus_aria_activedescendant.html +++ b/accessible/tests/mochitest/events/test_focus_aria_activedescendant.html @@ -51,7 +51,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=429547 var container = getNode(aID); var itemNode = document.createElement("div"); itemNode.setAttribute("id", aNewItemID); - itemNode.textContent = "item3"; + itemNode.textContent = aNewItemID; container.appendChild(itemNode); container.setAttribute("aria-activedescendant", aNewItemID); @@ -68,14 +68,15 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=429547 { gQueue = new eventQueue(); - gQueue.push(new synthFocus("container", new focusChecker("item1"))); - gQueue.push(new changeARIAActiveDescendant("container", "item2")); + gQueue.push(new synthFocus("listbox", new focusChecker("item1"))); + gQueue.push(new changeARIAActiveDescendant("listbox", "item2")); + gQueue.push(new changeARIAActiveDescendant("listbox", "item3")); gQueue.push(new synthFocus("combobox_entry", new focusChecker("combobox_entry"))); gQueue.push(new changeARIAActiveDescendant("combobox", "combobox_option2")); todo(false, "No focus for inserted element, bug 687011"); - //gQueue.push(new insertItemNFocus("container", "item3")); + //gQueue.push(new insertItemNFocus("listbox", "item4")); gQueue.invoke(); // Will call SimpleTest.finish(); } @@ -101,10 +102,12 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=429547
   
-
+
item1
item2
+
item3
diff --git a/accessible/tests/mochitest/states/test_aria.html b/accessible/tests/mochitest/states/test_aria.html index d0f268fca297..7d1ecf650831 100644 --- a/accessible/tests/mochitest/states/test_aria.html +++ b/accessible/tests/mochitest/states/test_aria.html @@ -500,7 +500,8 @@
-
+
Item 1
Item 2
Item 3
@@ -508,6 +509,7 @@
A slider
+
Item 5
Date: Tue, 3 Nov 2015 11:12:46 -0500 Subject: [PATCH 039/113] Bug 1213120 - Ensure we don't try to use an uninitialized map. r=BenWa --HG-- extra : commitid : KBCIVOZsVmU --- gfx/layers/ipc/CompositorParent.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/gfx/layers/ipc/CompositorParent.cpp b/gfx/layers/ipc/CompositorParent.cpp index 9a02642b4caf..459ff3074446 100644 --- a/gfx/layers/ipc/CompositorParent.cpp +++ b/gfx/layers/ipc/CompositorParent.cpp @@ -1650,6 +1650,7 @@ CompositorParent::SetControllerForLayerTree(uint64_t aLayersId, /*static*/ APZCTreeManager* CompositorParent::GetAPZCTreeManager(uint64_t aLayersId) { + EnsureLayerTreeMapReady(); const CompositorParent::LayerTreeState* state = CompositorParent::GetIndirectShadowTree(aLayersId); if (state && state->mParent) { return state->mParent->mApzcTreeManager; From 8abe3313666bf41cbec548b66f0077bf300132aa Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Tue, 3 Nov 2015 17:52:40 +0100 Subject: [PATCH 040/113] Bug 1151213 part 1 - [css-grid][css-align] Implement layout for the 'align-self' and 'justify-self' properties on grid items. r=dholbert --- layout/generic/nsGridContainerFrame.cpp | 305 ++++++++++++++++++++++-- layout/generic/nsHTMLReflowState.cpp | 24 +- 2 files changed, 292 insertions(+), 37 deletions(-) diff --git a/layout/generic/nsGridContainerFrame.cpp b/layout/generic/nsGridContainerFrame.cpp index ddfbe776a9ac..4f26d6b17163 100644 --- a/layout/generic/nsGridContainerFrame.cpp +++ b/layout/generic/nsGridContainerFrame.cpp @@ -829,6 +829,243 @@ GetDisplayFlagsForGridItem(nsIFrame* aFrame) return nsIFrame::DISPLAY_CHILD_FORCE_PSEUDO_STACKING_CONTEXT; } +static nscoord +SpaceToFill(WritingMode aWM, const LogicalSize& aSize, nscoord aMargin, + LogicalAxis aAxis, nscoord aCBSize) +{ + nscoord size = aAxis == eLogicalAxisBlock ? aSize.BSize(aWM) + : aSize.ISize(aWM); + return aCBSize - (size + aMargin); +} + +static bool +AlignJustifySelf(uint8_t aAlignment, bool aOverflowSafe, LogicalAxis aAxis, + bool aSameSide, nscoord aCBSize, const nsHTMLReflowState& aRS, + const LogicalSize& aChildSize, LogicalSize* aContentSize, + LogicalPoint* aPos) +{ + MOZ_ASSERT(aAlignment != NS_STYLE_ALIGN_AUTO, "unexpected 'auto' " + "computed value for normal flow grid item"); + MOZ_ASSERT(aAlignment != NS_STYLE_ALIGN_LEFT && + aAlignment != NS_STYLE_ALIGN_RIGHT, + "caller should map that to the corresponding START/END"); + + // Map some alignment values to 'start' / 'end'. + switch (aAlignment) { + case NS_STYLE_ALIGN_SELF_START: // align/justify-self: self-start + aAlignment = MOZ_LIKELY(aSameSide) ? NS_STYLE_ALIGN_START + : NS_STYLE_ALIGN_END; + break; + case NS_STYLE_ALIGN_SELF_END: // align/justify-self: self-end + aAlignment = MOZ_LIKELY(aSameSide) ? NS_STYLE_ALIGN_END + : NS_STYLE_ALIGN_START; + break; + case NS_STYLE_ALIGN_FLEX_START: // same as 'start' for Grid + aAlignment = NS_STYLE_ALIGN_START; + break; + case NS_STYLE_ALIGN_FLEX_END: // same as 'end' for Grid + aAlignment = NS_STYLE_ALIGN_END; + break; + } + + // XXX try to condense this code a bit by adding the necessary convenience + // methods? (bug 1209710) + + // Get the item's margin corresponding to the container's start/end side. + const LogicalMargin margin = aRS.ComputedLogicalMargin(); + WritingMode wm = aRS.GetWritingMode(); + nscoord marginStart, marginEnd; + if (aAxis == eLogicalAxisBlock) { + if (MOZ_LIKELY(aSameSide)) { + marginStart = margin.BStart(wm); + marginEnd = margin.BEnd(wm); + } else { + marginStart = margin.BEnd(wm); + marginEnd = margin.BStart(wm); + } + } else { + if (MOZ_LIKELY(aSameSide)) { + marginStart = margin.IStart(wm); + marginEnd = margin.IEnd(wm); + } else { + marginStart = margin.IEnd(wm); + marginEnd = margin.IStart(wm); + } + } + + // https://drafts.csswg.org/css-align-3/#overflow-values + // This implements = 'safe'. + if (MOZ_UNLIKELY(aOverflowSafe) && aAlignment != NS_STYLE_ALIGN_START) { + nscoord space = SpaceToFill(wm, aChildSize, marginStart + marginEnd, + aAxis, aCBSize); + // XXX we might want to include == 0 here as an optimization - + // I need to see what the baseline/last-baseline code looks like first. + if (space < 0) { + aAlignment = NS_STYLE_ALIGN_START; + } + } + + // Set the position and size (aPos/aContentSize) for the requested alignment. + bool didResize = false; + nscoord offset = 0; + switch (aAlignment) { + case NS_STYLE_ALIGN_BASELINE: + case NS_STYLE_ALIGN_LAST_BASELINE: + NS_WARNING("NYI: baseline/last-baseline for grid (bug 1151204)"); // XXX + case NS_STYLE_ALIGN_START: + offset = marginStart; + break; + case NS_STYLE_ALIGN_END: { + nscoord size = aAxis == eLogicalAxisBlock ? aChildSize.BSize(wm) + : aChildSize.ISize(wm); + offset = aCBSize - (size + marginEnd); + break; + } + case NS_STYLE_ALIGN_CENTER: + offset = SpaceToFill(wm, aChildSize, marginStart + marginEnd, + aAxis, aCBSize) / 2; + break; + case NS_STYLE_ALIGN_STRETCH: { + offset = marginStart; + const auto& styleMargin = aRS.mStyleMargin->mMargin; + if (aAxis == eLogicalAxisBlock + ? (aRS.mStylePosition->BSize(wm).GetUnit() == eStyleUnit_Auto && + styleMargin.GetBStartUnit(wm) != eStyleUnit_Auto && + styleMargin.GetBEndUnit(wm) != eStyleUnit_Auto) + : (aRS.mStylePosition->ISize(wm).GetUnit() == eStyleUnit_Auto && + styleMargin.GetIStartUnit(wm) != eStyleUnit_Auto && + styleMargin.GetIEndUnit(wm) != eStyleUnit_Auto)) { + nscoord size = aAxis == eLogicalAxisBlock ? aChildSize.BSize(wm) + : aChildSize.ISize(wm); + nscoord gap = aCBSize - (size + marginStart + marginEnd); + if (gap > 0) { + // Note: The ComputedMax* values are always content-box max values, + // even for box-sizing:border-box. + LogicalMargin bp = aRS.ComputedLogicalBorderPadding(); + // XXX ApplySkipSides is probably not very useful here since we + // might not have created any next-in-flow yet. Use the reflow status + // instead? Do all fragments stretch? (bug 1144096). + bp.ApplySkipSides(aRS.frame->GetLogicalSkipSides()); + nscoord bpInAxis = aAxis == eLogicalAxisBlock ? bp.BStartEnd(wm) + : bp.IStartEnd(wm); + nscoord contentSize = size - bpInAxis; + NS_ASSERTION(contentSize >= 0, "huh?"); + const nscoord unstretchedContentSize = contentSize; + contentSize += gap; + nscoord max = aAxis == eLogicalAxisBlock ? aRS.ComputedMaxBSize() + : aRS.ComputedMaxISize(); + if (MOZ_UNLIKELY(contentSize > max)) { + contentSize = max; + gap = contentSize - unstretchedContentSize; + } + // |gap| is now how much the content size is actually allowed to grow. + didResize = gap > 0; + if (didResize) { + (aAxis == eLogicalAxisBlock ? aContentSize->BSize(wm) + : aContentSize->ISize(wm)) = contentSize; + if (MOZ_UNLIKELY(!aSameSide)) { + offset += gap; + } + } + } + } + break; + } + default: + MOZ_ASSERT_UNREACHABLE("unknown align-/justify-self value"); + } + if (offset != 0) { + nscoord& pos = aAxis == eLogicalAxisBlock ? aPos->B(wm) : aPos->I(wm); + pos += MOZ_LIKELY(aSameSide) ? offset : -offset; + } + return didResize; +} + +static bool +SameSide(WritingMode aContainerWM, LogicalSide aContainerSide, + WritingMode aChildWM, LogicalSide aChildSide) +{ + MOZ_ASSERT(aContainerWM.PhysicalAxis(GetAxis(aContainerSide)) == + aChildWM.PhysicalAxis(GetAxis(aChildSide))); + return aContainerWM.PhysicalSide(aContainerSide) == + aChildWM.PhysicalSide(aChildSide); +} + +static Maybe +AlignSelf(uint8_t aAlignSelf, const LogicalRect& aCB, const WritingMode aCBWM, + const nsHTMLReflowState& aRS, const LogicalSize& aSize, + LogicalSize* aContentSize, LogicalPoint* aPos) +{ + Maybe resizedAxis; + auto alignSelf = aAlignSelf; + bool overflowSafe = alignSelf & NS_STYLE_ALIGN_SAFE; + alignSelf &= ~NS_STYLE_ALIGN_FLAG_BITS; + MOZ_ASSERT(alignSelf != NS_STYLE_ALIGN_LEFT && + alignSelf != NS_STYLE_ALIGN_RIGHT, + "Grid's 'align-self' axis is never parallel to the container's " + "inline axis, so these should've computed to 'start' already"); + if (MOZ_UNLIKELY(alignSelf == NS_STYLE_ALIGN_AUTO)) { + // Happens in rare edge cases when 'position' was ignored by the frame + // constructor (and the style system computed 'auto' based on 'position'). + alignSelf = NS_STYLE_ALIGN_STRETCH; + } + WritingMode childWM = aRS.GetWritingMode(); + bool isOrthogonal = aCBWM.IsOrthogonalTo(childWM); + // |sameSide| is true if the container's start side in this axis is the same + // as the child's start side, in the child's parallel axis. + bool sameSide = SameSide(aCBWM, eLogicalSideBStart, + childWM, isOrthogonal ? eLogicalSideIStart + : eLogicalSideBStart); + LogicalAxis axis = isOrthogonal ? eLogicalAxisInline : eLogicalAxisBlock; + if (AlignJustifySelf(alignSelf, overflowSafe, axis, sameSide, + aCB.BSize(aCBWM), aRS, aSize, aContentSize, aPos)) { + resizedAxis.emplace(axis); + } + return resizedAxis; +} + +static Maybe +JustifySelf(uint8_t aJustifySelf, const LogicalRect& aCB, const WritingMode aCBWM, + const nsHTMLReflowState& aRS, const LogicalSize& aSize, + LogicalSize* aContentSize, LogicalPoint* aPos) +{ + Maybe resizedAxis; + auto justifySelf = aJustifySelf; + bool overflowSafe = justifySelf & NS_STYLE_JUSTIFY_SAFE; + justifySelf &= ~NS_STYLE_JUSTIFY_FLAG_BITS; + if (MOZ_UNLIKELY(justifySelf == NS_STYLE_ALIGN_AUTO)) { + // Happens in rare edge cases when 'position' was ignored by the frame + // constructor (and the style system computed 'auto' based on 'position'). + justifySelf = NS_STYLE_ALIGN_STRETCH; + } + WritingMode childWM = aRS.GetWritingMode(); + bool isOrthogonal = aCBWM.IsOrthogonalTo(childWM); + // |sameSide| is true if the container's start side in this axis is the same + // as the child's start side, in the child's parallel axis. + bool sameSide = SameSide(aCBWM, eLogicalSideIStart, + childWM, isOrthogonal ? eLogicalSideBStart + : eLogicalSideIStart); + // Grid's 'justify-self' axis is always parallel to the container's inline + // axis, so justify-self:left|right always applies. + switch (justifySelf) { + case NS_STYLE_JUSTIFY_LEFT: + justifySelf = aCBWM.IsBidiLTR() ? NS_STYLE_JUSTIFY_START + : NS_STYLE_JUSTIFY_END; + break; + case NS_STYLE_JUSTIFY_RIGHT: + justifySelf = aCBWM.IsBidiLTR() ? NS_STYLE_JUSTIFY_END + : NS_STYLE_JUSTIFY_START; + break; + } + + LogicalAxis axis = isOrthogonal ? eLogicalAxisBlock : eLogicalAxisInline; + if (AlignJustifySelf(justifySelf, overflowSafe, axis, sameSide, + aCB.ISize(aCBWM), aRS, aSize, aContentSize, aPos)) { + resizedAxis.emplace(axis); + } + return resizedAxis; +} + //---------------------------------------------------------------------- // Frame class boilerplate @@ -2367,6 +2604,7 @@ nsGridContainerFrame::ReflowChildren(GridReflowState& aState, (aContentArea.Size(wm) + aState.mReflowState->ComputedLogicalBorderPadding().Size(wm)).GetPhysicalSize(wm); nsPresContext* pc = PresContext(); + nsStyleContext* containerSC = StyleContext(); for (; !aState.mIter.AtEnd(); aState.mIter.Next()) { nsIFrame* child = *aState.mIter; const bool isGridItem = child->GetType() != nsGkAtoms::placeholderFrame; @@ -2383,36 +2621,63 @@ nsGridContainerFrame::ReflowChildren(GridReflowState& aState, } WritingMode childWM = child->GetWritingMode(); LogicalSize childCBSize = cb.Size(wm).ConvertTo(childWM, wm); - nsHTMLReflowState childRS(pc, *aState.mReflowState, child, childCBSize); - const LogicalMargin margin = childRS.ComputedLogicalMargin(); - if (childRS.ComputedBSize() == NS_AUTOHEIGHT && MOZ_LIKELY(isGridItem)) { - // XXX the start of an align-self:stretch impl. Needs min-/max-bsize - // clamping though, and check the prop value is actually 'stretch'! - LogicalMargin bp = childRS.ComputedLogicalBorderPadding(); - bp.ApplySkipSides(child->GetLogicalSkipSides()); - nscoord bSize = childCBSize.BSize(childWM) - bp.BStartEnd(childWM) - - margin.BStartEnd(childWM); - childRS.SetComputedBSize(std::max(bSize, 0)); - } + // XXX temporary workaround to avoid being INCOMPLETE until we have + // support for fragmentation (bug 1144096) + childCBSize.BSize(childWM) = NS_UNCONSTRAINEDSIZE; + + Maybe childRS; // Maybe<> so we can reuse the space + childRS.emplace(pc, *aState.mReflowState, child, childCBSize); // We need the width of the child before we can correctly convert // the writing-mode of its origin, so we reflow at (0, 0) using a dummy // containerSize, and then pass the correct position to FinishReflowChild. - nsHTMLReflowMetrics childSize(childRS); + Maybe childSize; // Maybe<> so we can reuse the space + childSize.emplace(*childRS); nsReflowStatus childStatus; const nsSize dummyContainerSize; - ReflowChild(child, pc, childSize, childRS, childWM, LogicalPoint(childWM), + ReflowChild(child, pc, *childSize, *childRS, childWM, LogicalPoint(childWM), dummyContainerSize, 0, childStatus); LogicalPoint childPos = cb.Origin(wm).ConvertTo(childWM, wm, - containerSize - childSize.PhysicalSize() - - margin.Size(childWM).GetPhysicalSize(childWM)); - childPos.I(childWM) += margin.IStart(childWM); - childPos.B(childWM) += margin.BStart(childWM); - childRS.ApplyRelativePositioning(&childPos, containerSize); - FinishReflowChild(child, pc, childSize, &childRS, childWM, childPos, + containerSize - childSize->PhysicalSize()); + // Apply align/justify-self and reflow again if that affects the size. + if (isGridItem) { + LogicalSize oldSize = childSize->Size(childWM); // from the ReflowChild() + LogicalSize newContentSize(childWM); + auto align = childRS->mStylePosition->ComputedAlignSelf( + childRS->mStyleDisplay, containerSC); + Maybe alignResize = + AlignSelf(align, cb, wm, *childRS, oldSize, &newContentSize, &childPos); + auto justify = childRS->mStylePosition->ComputedJustifySelf( + childRS->mStyleDisplay, containerSC); + Maybe justifyResize = + JustifySelf(justify, cb, wm, *childRS, oldSize, &newContentSize, &childPos); + if (alignResize || justifyResize) { + FinishReflowChild(child, pc, *childSize, childRS.ptr(), childWM, + LogicalPoint(childWM), containerSize, + NS_FRAME_NO_MOVE_FRAME | NS_FRAME_NO_SIZE_VIEW); + childSize.reset(); // In reverse declaration order since it runs + childRS.reset(); // destructors. + childRS.emplace(pc, *aState.mReflowState, child, childCBSize); + if ((alignResize && alignResize.value() == eLogicalAxisBlock) || + (justifyResize && justifyResize.value() == eLogicalAxisBlock)) { + childRS->SetComputedBSize(newContentSize.BSize(childWM)); + childRS->SetBResize(true); + } + if ((alignResize && alignResize.value() == eLogicalAxisInline) || + (justifyResize && justifyResize.value() == eLogicalAxisInline)) { + childRS->SetComputedISize(newContentSize.ISize(childWM)); + childRS->SetIResize(true); + } + childSize.emplace(*childRS); + ReflowChild(child, pc, *childSize, *childRS, childWM, + LogicalPoint(childWM), dummyContainerSize, 0, childStatus); + } + } + childRS->ApplyRelativePositioning(&childPos, containerSize); + FinishReflowChild(child, pc, *childSize, childRS.ptr(), childWM, childPos, containerSize, 0); ConsiderChildOverflow(aDesiredSize.mOverflowAreas, child); - // XXX deal with 'childStatus' not being COMPLETE + // XXX deal with 'childStatus' not being COMPLETE (bug 1144096) } if (IsAbsoluteContainer()) { diff --git a/layout/generic/nsHTMLReflowState.cpp b/layout/generic/nsHTMLReflowState.cpp index 5ec61ea07f1e..dc191660d193 100644 --- a/layout/generic/nsHTMLReflowState.cpp +++ b/layout/generic/nsHTMLReflowState.cpp @@ -18,7 +18,6 @@ #include "nsFontMetrics.h" #include "nsBlockFrame.h" #include "nsLineBox.h" -#include "nsFlexContainerFrame.h" #include "nsImageFrame.h" #include "nsTableFrame.h" #include "nsTableCellFrame.h" @@ -2037,18 +2036,6 @@ IsSideCaption(nsIFrame* aFrame, const nsStyleDisplay* aStyleDisplay, captionSide == NS_STYLE_CAPTION_SIDE_RIGHT; } -static nsFlexContainerFrame* -GetFlexContainer(nsIFrame* aFrame) -{ - nsIFrame* parent = aFrame->GetParent(); - if (!parent || - parent->GetType() != nsGkAtoms::flexContainerFrame) { - return nullptr; - } - - return static_cast(parent); -} - // Flex items resolve block-axis percentage margin & padding against the flex // container's block-size (which is the containing block block-size). // For everything else: the CSS21 spec requires that margin and padding @@ -2306,8 +2293,9 @@ nsHTMLReflowState::InitConstraints(nsPresContext* aPresContext, ComputeSizeFlags(computeSizeFlags | ComputeSizeFlags::eShrinkWrap); } - const nsFlexContainerFrame* flexContainerFrame = GetFlexContainer(frame); - if (flexContainerFrame) { + nsIFrame* parent = frame->GetParent(); + nsIAtom* parentFrameType = parent ? parent->GetType() : nullptr; + if (parentFrameType == nsGkAtoms::flexContainerFrame) { computeSizeFlags = ComputeSizeFlags(computeSizeFlags | ComputeSizeFlags::eShrinkWrap); @@ -2343,11 +2331,13 @@ nsHTMLReflowState::InitConstraints(nsPresContext* aPresContext, NS_ASSERTION(ComputedBSize() == NS_UNCONSTRAINEDSIZE || ComputedBSize() >= 0, "Bogus block-size"); - // Exclude inline tables and flex items from the block margin calculations + // Exclude inline tables, side captions, flex and grid items from block + // margin calculations. if (isBlock && !IsSideCaption(frame, mStyleDisplay, cbwm) && mStyleDisplay->mDisplay != NS_STYLE_DISPLAY_INLINE_TABLE && - !flexContainerFrame) { + parentFrameType != nsGkAtoms::flexContainerFrame && + parentFrameType != nsGkAtoms::gridContainerFrame) { CalculateBlockSideMargins(aFrameType); } } From 6708be703fa844a7bd6bc40dfb9076941968ce57 Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Tue, 3 Nov 2015 17:52:40 +0100 Subject: [PATCH 041/113] Bug 1151213 part 2 - [css-grid][css-align] Reftests for the 'align-self' and 'justify-self' properties on grid items. There are also some corrections here for existing reftests because the old temporary 'stretch' scaffolding was wrong in many ways. --- .../css-grid/grid-abspos-items-001-ref.html | 25 +-- .../css-grid/grid-abspos-items-001.html | 25 +-- .../css-grid/grid-abspos-items-002-ref.html | 25 +-- .../css-grid/grid-abspos-items-002.html | 25 +-- .../css-grid/grid-item-align-001-ref.html | 190 ++++++++++++++++++ .../css-grid/grid-item-align-001.html | 107 ++++++++++ .../css-grid/grid-item-align-002-ref.html | 127 ++++++++++++ .../css-grid/grid-item-align-002.html | 108 ++++++++++ .../css-grid/grid-item-align-003-ref.html | 118 +++++++++++ .../css-grid/grid-item-align-003.html | 128 ++++++++++++ .../css-grid/grid-item-justify-001-ref.html | 121 +++++++++++ .../css-grid/grid-item-justify-001.html | 108 ++++++++++ .../css-grid/grid-item-justify-002-ref.html | 120 +++++++++++ .../css-grid/grid-item-justify-002.html | 108 ++++++++++ .../css-grid/grid-item-stretch-001-ref.html | 109 ++++++++++ .../css-grid/grid-item-stretch-001.html | 117 +++++++++++ .../grid-placement-auto-implicit-001-ref.html | 26 ++- .../grid-placement-auto-implicit-001.html | 13 +- ...d-placement-definite-implicit-001-ref.html | 2 +- .../grid-placement-definite-implicit-001.html | 2 +- .../css-grid/grid-relpos-items-001-ref.html | 17 +- .../css-grid/grid-relpos-items-001.html | 7 +- layout/reftests/css-grid/reftest.list | 6 + 23 files changed, 1558 insertions(+), 76 deletions(-) create mode 100644 layout/reftests/css-grid/grid-item-align-001-ref.html create mode 100644 layout/reftests/css-grid/grid-item-align-001.html create mode 100644 layout/reftests/css-grid/grid-item-align-002-ref.html create mode 100644 layout/reftests/css-grid/grid-item-align-002.html create mode 100644 layout/reftests/css-grid/grid-item-align-003-ref.html create mode 100644 layout/reftests/css-grid/grid-item-align-003.html create mode 100644 layout/reftests/css-grid/grid-item-justify-001-ref.html create mode 100644 layout/reftests/css-grid/grid-item-justify-001.html create mode 100644 layout/reftests/css-grid/grid-item-justify-002-ref.html create mode 100644 layout/reftests/css-grid/grid-item-justify-002.html create mode 100644 layout/reftests/css-grid/grid-item-stretch-001-ref.html create mode 100644 layout/reftests/css-grid/grid-item-stretch-001.html diff --git a/layout/reftests/css-grid/grid-abspos-items-001-ref.html b/layout/reftests/css-grid/grid-abspos-items-001-ref.html index 93f8c75939e3..f4a05afe7c6a 100644 --- a/layout/reftests/css-grid/grid-abspos-items-001-ref.html +++ b/layout/reftests/css-grid/grid-abspos-items-001-ref.html @@ -30,6 +30,7 @@ body,html { color:black; background:white; font-size:16px; padding:0; margin:0; position: absolute; left: 13px; top: 31px; height: 12px; width: 44px; + background: blue; } .abs { @@ -82,64 +83,64 @@ span {
-a + b
-a + c
-a + d
-a + e
-a + f
-a + g
-a + b
-a + c
-a + d
-a + e
-a + f
-a + g
diff --git a/layout/reftests/css-grid/grid-abspos-items-001.html b/layout/reftests/css-grid/grid-abspos-items-001.html index f80b79b15b4b..97ac1f77b2d3 100644 --- a/layout/reftests/css-grid/grid-abspos-items-001.html +++ b/layout/reftests/css-grid/grid-abspos-items-001.html @@ -29,6 +29,7 @@ body,html { color:black; background:white; font-size:16px; padding:0; margin:0; .a { grid-column: 1 / 3; grid-row: 3 / 5; + background: blue; } .abs { @@ -85,64 +86,64 @@ span {
-a + b
-a + c
-a + d
-a + e
-a + f
-a + g
-a +
b
-a + c
-a + d
-a +
e
-a + f
-a + g
diff --git a/layout/reftests/css-grid/grid-abspos-items-002-ref.html b/layout/reftests/css-grid/grid-abspos-items-002-ref.html index a581c47a4b1b..c031bd734b22 100644 --- a/layout/reftests/css-grid/grid-abspos-items-002-ref.html +++ b/layout/reftests/css-grid/grid-abspos-items-002-ref.html @@ -31,6 +31,7 @@ body,html { color:black; background:white; font-size:16px; padding:0; margin:0; position: absolute; left: 13px; top: 31px; height: 12px; width: 44px; + background: blue; } .abs { @@ -83,64 +84,64 @@ span {
-a + b
-a + c
-a + d
-a + e
-a + f
-a + g
-a + b
-a + c
-a + d
-a + e
-a + f
-a + g
diff --git a/layout/reftests/css-grid/grid-abspos-items-002.html b/layout/reftests/css-grid/grid-abspos-items-002.html index 015349a23634..a045ab0be107 100644 --- a/layout/reftests/css-grid/grid-abspos-items-002.html +++ b/layout/reftests/css-grid/grid-abspos-items-002.html @@ -29,6 +29,7 @@ body,html { color:black; background:white; font-size:16px; padding:0; margin:0; .a { grid-column: 1 / 3; grid-row: 3 / 5; + background: blue; } .abs { @@ -85,64 +86,64 @@ span {
-a + b
-a + c
-a + d
-a + e
-a + f
-a + g
-a +
b
-a + c
-a + d
-a +
e
-a + f
-a + g
diff --git a/layout/reftests/css-grid/grid-item-align-001-ref.html b/layout/reftests/css-grid/grid-item-align-001-ref.html new file mode 100644 index 000000000000..359baacd4035 --- /dev/null +++ b/layout/reftests/css-grid/grid-item-align-001-ref.html @@ -0,0 +1,190 @@ + + + + + Reference: align-self (part 1 of 2) + + + + + + + + + + diff --git a/layout/reftests/css-grid/grid-item-align-001.html b/layout/reftests/css-grid/grid-item-align-001.html new file mode 100644 index 000000000000..da5b0b80037e --- /dev/null +++ b/layout/reftests/css-grid/grid-item-align-001.html @@ -0,0 +1,107 @@ + + + + + CSS Grid Test: align-self (part 1 of 2) + + + + + + + + + + + + diff --git a/layout/reftests/css-grid/grid-item-align-002-ref.html b/layout/reftests/css-grid/grid-item-align-002-ref.html new file mode 100644 index 000000000000..07cb0e114066 --- /dev/null +++ b/layout/reftests/css-grid/grid-item-align-002-ref.html @@ -0,0 +1,127 @@ + + + + + Reference: align-self (part 2 of 2) + + + + + + + + + + diff --git a/layout/reftests/css-grid/grid-item-align-002.html b/layout/reftests/css-grid/grid-item-align-002.html new file mode 100644 index 000000000000..c6282ea6919f --- /dev/null +++ b/layout/reftests/css-grid/grid-item-align-002.html @@ -0,0 +1,108 @@ + + + + + CSS Grid Test: align-self (part 2 of 2) + + + + + + + + + + + + diff --git a/layout/reftests/css-grid/grid-item-align-003-ref.html b/layout/reftests/css-grid/grid-item-align-003-ref.html new file mode 100644 index 000000000000..a8524d2fa5ca --- /dev/null +++ b/layout/reftests/css-grid/grid-item-align-003-ref.html @@ -0,0 +1,118 @@ + + + + + Reference: align-self with overflow + + + + + + + + + + diff --git a/layout/reftests/css-grid/grid-item-align-003.html b/layout/reftests/css-grid/grid-item-align-003.html new file mode 100644 index 000000000000..e38324724a3a --- /dev/null +++ b/layout/reftests/css-grid/grid-item-align-003.html @@ -0,0 +1,128 @@ + + + + + CSS Grid Test: align-self with overflow + + + + + + + + + + + + diff --git a/layout/reftests/css-grid/grid-item-justify-001-ref.html b/layout/reftests/css-grid/grid-item-justify-001-ref.html new file mode 100644 index 000000000000..3af8fba42def --- /dev/null +++ b/layout/reftests/css-grid/grid-item-justify-001-ref.html @@ -0,0 +1,121 @@ + + + + + CSS Grid Test: justify-self (part 1 of 2) + + + + + + + + + + diff --git a/layout/reftests/css-grid/grid-item-justify-001.html b/layout/reftests/css-grid/grid-item-justify-001.html new file mode 100644 index 000000000000..97cfa855fe73 --- /dev/null +++ b/layout/reftests/css-grid/grid-item-justify-001.html @@ -0,0 +1,108 @@ + + + + + CSS Grid Test: justify-self (part 1 of 2) + + + + + + + + + + + + diff --git a/layout/reftests/css-grid/grid-item-justify-002-ref.html b/layout/reftests/css-grid/grid-item-justify-002-ref.html new file mode 100644 index 000000000000..2d0f1e75da67 --- /dev/null +++ b/layout/reftests/css-grid/grid-item-justify-002-ref.html @@ -0,0 +1,120 @@ + + + + + CSS Grid Test: justify-self (part 2 of 2) + + + + + + + + + + diff --git a/layout/reftests/css-grid/grid-item-justify-002.html b/layout/reftests/css-grid/grid-item-justify-002.html new file mode 100644 index 000000000000..6512ddded04b --- /dev/null +++ b/layout/reftests/css-grid/grid-item-justify-002.html @@ -0,0 +1,108 @@ + + + + + CSS Grid Test: justify-self (part 2 of 2) + + + + + + + + + + + + diff --git a/layout/reftests/css-grid/grid-item-stretch-001-ref.html b/layout/reftests/css-grid/grid-item-stretch-001-ref.html new file mode 100644 index 000000000000..3f53b119d8fc --- /dev/null +++ b/layout/reftests/css-grid/grid-item-stretch-001-ref.html @@ -0,0 +1,109 @@ + + + + + Reference: align-items:stretch / justify-items:stretch + + + + +
+There should be no red areas.
+All grey areas should have a black dot in each corner.
+
+ + + + + + + diff --git a/layout/reftests/css-grid/grid-item-stretch-001.html b/layout/reftests/css-grid/grid-item-stretch-001.html new file mode 100644 index 000000000000..cf5cc0fd2053 --- /dev/null +++ b/layout/reftests/css-grid/grid-item-stretch-001.html @@ -0,0 +1,117 @@ + + + + + CSS Grid Test: align-items:stretch / justify-items:stretch + + + + + + + +
+There should be no red areas.
+All grey areas should have a black dot in each corner.
+
+ + + + + + + diff --git a/layout/reftests/css-grid/grid-placement-auto-implicit-001-ref.html b/layout/reftests/css-grid/grid-placement-auto-implicit-001-ref.html index 6b1c2db1c379..6d97fbb3706e 100644 --- a/layout/reftests/css-grid/grid-placement-auto-implicit-001-ref.html +++ b/layout/reftests/css-grid/grid-placement-auto-implicit-001-ref.html @@ -12,7 +12,7 @@ body,html { color:black; background:white; font-size:12px; padding:0; margin:0; .grid { border: 1px solid blue; - width:300px; + width:270px; } .a { background:lime; } @@ -50,6 +50,7 @@ span {
ceab
+
cfab
@@ -74,24 +75,27 @@ span {
ggab
+ +
+
- -b
- + +b
+
- -b
- + + +b
- + b
- -b
- + +b
+
diff --git a/layout/reftests/css-grid/grid-placement-auto-implicit-001.html b/layout/reftests/css-grid/grid-placement-auto-implicit-001.html index 75622c2270fb..13c3f914fbc1 100644 --- a/layout/reftests/css-grid/grid-placement-auto-implicit-001.html +++ b/layout/reftests/css-grid/grid-placement-auto-implicit-001.html @@ -20,7 +20,7 @@ body,html { color:black; background:white; font-size:12px; padding:0; margin:0; grid-auto-columns: 3px; grid-auto-rows: 20px; border: 1px solid blue; - width: 300px; + width: 270px; } .a { grid-area: 1 / 2; background:lime; } @@ -76,24 +76,27 @@ span {
abgg
+ +
+
- + b
- + b
- + b
- + b
diff --git a/layout/reftests/css-grid/grid-placement-definite-implicit-001-ref.html b/layout/reftests/css-grid/grid-placement-definite-implicit-001-ref.html index ad860c60289f..a9684cfd1dfd 100644 --- a/layout/reftests/css-grid/grid-placement-definite-implicit-001-ref.html +++ b/layout/reftests/css-grid/grid-placement-definite-implicit-001-ref.html @@ -75,7 +75,7 @@ span { c
-b +
diff --git a/layout/reftests/css-grid/grid-placement-definite-implicit-001.html b/layout/reftests/css-grid/grid-placement-definite-implicit-001.html index 63b273fc3980..147458370189 100644 --- a/layout/reftests/css-grid/grid-placement-definite-implicit-001.html +++ b/layout/reftests/css-grid/grid-placement-definite-implicit-001.html @@ -79,7 +79,7 @@ span { c
-b +
+ + + + diff --git a/layout/reftests/css-grid/grid-align-content-001.html b/layout/reftests/css-grid/grid-align-content-001.html new file mode 100644 index 000000000000..d1b12c722114 --- /dev/null +++ b/layout/reftests/css-grid/grid-align-content-001.html @@ -0,0 +1,90 @@ + + + + + CSS Grid Test: align-content + + + + + + + + + + + + diff --git a/layout/reftests/css-grid/grid-justify-content-001-ref.html b/layout/reftests/css-grid/grid-justify-content-001-ref.html new file mode 100644 index 000000000000..a6b86f136e18 --- /dev/null +++ b/layout/reftests/css-grid/grid-justify-content-001-ref.html @@ -0,0 +1,92 @@ + + + + + CSS Grid Test: justify-content + + + + + + + + + + diff --git a/layout/reftests/css-grid/grid-justify-content-001.html b/layout/reftests/css-grid/grid-justify-content-001.html new file mode 100644 index 000000000000..ab231c0fe630 --- /dev/null +++ b/layout/reftests/css-grid/grid-justify-content-001.html @@ -0,0 +1,90 @@ + + + + + CSS Grid Test: justify-content + + + + + + + + + + + + diff --git a/layout/reftests/css-grid/grid-justify-content-002-ref.html b/layout/reftests/css-grid/grid-justify-content-002-ref.html new file mode 100644 index 000000000000..6f976a113693 --- /dev/null +++ b/layout/reftests/css-grid/grid-justify-content-002-ref.html @@ -0,0 +1,62 @@ + + + + + CSS Grid Test: Testing track distribution rounding errors + + + + + + + + + diff --git a/layout/reftests/css-grid/grid-justify-content-002.html b/layout/reftests/css-grid/grid-justify-content-002.html new file mode 100644 index 000000000000..73220d041c08 --- /dev/null +++ b/layout/reftests/css-grid/grid-justify-content-002.html @@ -0,0 +1,67 @@ + + + + + CSS Grid Test: Testing track distribution rounding errors + + + + + + + + + + + diff --git a/layout/reftests/css-grid/grid-justify-content-003-ref.html b/layout/reftests/css-grid/grid-justify-content-003-ref.html new file mode 100644 index 000000000000..e22c4c9dd67b --- /dev/null +++ b/layout/reftests/css-grid/grid-justify-content-003-ref.html @@ -0,0 +1,115 @@ + + + + + Reference: Testing track fallback values + + + + + + + + + diff --git a/layout/reftests/css-grid/grid-justify-content-003.html b/layout/reftests/css-grid/grid-justify-content-003.html new file mode 100644 index 000000000000..66fe01003699 --- /dev/null +++ b/layout/reftests/css-grid/grid-justify-content-003.html @@ -0,0 +1,96 @@ + + + + + CSS Grid Test: Testing 'justify-content' fallback values + + + + + + + + + + + diff --git a/layout/reftests/css-grid/reftest.list b/layout/reftests/css-grid/reftest.list index 65446bd69cf5..0571ed64943a 100644 --- a/layout/reftests/css-grid/reftest.list +++ b/layout/reftests/css-grid/reftest.list @@ -56,3 +56,7 @@ fuzzy-if(winWidget,70,130) fuzzy-if(cocoaWidget,85,180) == grid-col-max-sizing-m == grid-item-justify-001.html grid-item-justify-001-ref.html == grid-item-justify-002.html grid-item-justify-002-ref.html == grid-item-stretch-001.html grid-item-stretch-001-ref.html +== grid-align-content-001.html grid-align-content-001-ref.html +== grid-justify-content-001.html grid-justify-content-001-ref.html +== grid-justify-content-002.html grid-justify-content-002-ref.html +== grid-justify-content-003.html grid-justify-content-003-ref.html From 5bd4b9906b200927397d7059de755256b0b70c4b Mon Sep 17 00:00:00 2001 From: Jim Chen Date: Tue, 3 Nov 2015 11:53:57 -0500 Subject: [PATCH 044/113] Bug 1216666 - Separate out Gecko selection from Java selection; r=esawin Currently, the Gecko-side selection tries to mirror the Java-side selection, but this is difficult because the Gecko selection has some subtle but important behavior differences from the Java selection. This patch separates out Gecko selection from Java selection so that they're only loosely coupled. The two selections will periodically synchronize through events and notifications, but at certain times they may fall out-of-sync, for example when a composition is active. This shouldn't affect functionality in a major way, and it's an acceptable trade-off. --- mobile/android/base/GeckoEditable.java | 127 ++++-------------- mobile/android/base/GeckoInputConnection.java | 7 +- widget/android/nsWindow.cpp | 49 +------ 3 files changed, 32 insertions(+), 151 deletions(-) diff --git a/mobile/android/base/GeckoEditable.java b/mobile/android/base/GeckoEditable.java index fecff502a3c6..da00fd0536ac 100644 --- a/mobile/android/base/GeckoEditable.java +++ b/mobile/android/base/GeckoEditable.java @@ -66,13 +66,12 @@ final class GeckoEditable extends JNIObject private Handler mIcPostHandler; private GeckoEditableListener mListener; - private int mSavedSelectionStart; - private volatile int mGeckoUpdateSeqno; private int mIcUpdateSeqno; private int mLastIcUpdateSeqno; private boolean mUpdateGecko; private boolean mFocused; // Used by IC thread private boolean mGeckoFocused; // Used by Gecko thread + private boolean mIgnoreSelectionChange; private volatile boolean mSuppressCompositions; private volatile boolean mSuppressKeyUp; @@ -161,11 +160,8 @@ final class GeckoEditable extends JNIObject static final int TYPE_EVENT = 0; // For Editable.replace() call; use with IME_REPLACE_TEXT static final int TYPE_REPLACE_TEXT = 1; - /* For Editable.setSpan(Selection...) call; use with IME_SYNCHRONIZE - Note that we don't use this with IME_SET_SELECTION because we don't want to update the - Gecko selection at the point of this action. The Gecko selection is updated only after - IC has updated its selection (during IME_SYNCHRONIZE reply) */ - static final int TYPE_SET_SELECTION = 2; + // For Editable.replace() call involving compositions; use with IME_COMPOSE_TEXT + static final int TYPE_COMPOSE_TEXT = 2; // For Editable.setSpan() call; use with IME_SYNCHRONIZE static final int TYPE_SET_SPAN = 3; // For Editable.removeSpan() call; use with IME_SYNCHRONIZE @@ -174,8 +170,6 @@ final class GeckoEditable extends JNIObject static final int TYPE_ACKNOWLEDGE_FOCUS = 5; // For switching handler; use with IME_SYNCHRONIZE static final int TYPE_SET_HANDLER = 6; - // For Editable.replace() call involving compositions; use with IME_COMPOSE_TEXT - static final int TYPE_COMPOSE_TEXT = 7; final int mType; int mStart; @@ -217,19 +211,6 @@ final class GeckoEditable extends JNIObject return action; } - static Action newSetSelection(int start, int end) { - // start == -1 when the start offset should remain the same - // end == -1 when the end offset should remain the same - if (start < -1 || end < -1) { - Log.e(LOGTAG, "invalid selection offsets: " + start + " to " + end); - throw new IllegalArgumentException("invalid selection offsets"); - } - final Action action = new Action(TYPE_SET_SELECTION); - action.mStart = start; - action.mEnd = end; - return action; - } - static Action newSetSpan(Object object, int start, int end, int flags) { if (start < 0 || start > end) { Log.e(LOGTAG, "invalid span offsets: " + start + " to " + end); @@ -292,7 +273,6 @@ final class GeckoEditable extends JNIObject switch (action.mType) { case Action.TYPE_EVENT: - case Action.TYPE_SET_SELECTION: case Action.TYPE_SET_SPAN: case Action.TYPE_REMOVE_SPAN: case Action.TYPE_SET_HANDLER: @@ -427,7 +407,6 @@ final class GeckoEditable extends JNIObject ThreadUtils.assertOnGeckoThread(); } mActionQueue = new ActionQueue(); - mSavedSelectionStart = -1; mUpdateGecko = true; mText = new SpannableStringBuilder(); @@ -507,18 +486,11 @@ final class GeckoEditable extends JNIObject } private void geckoUpdateGecko(final boolean force) { - /* We do not increment the seqno here, but only check it, because geckoUpdateGecko is a - request for update. If we incremented the seqno here, geckoUpdateGecko would have - prevented other updates from occurring */ - final int seqnoWhenPosted = mGeckoUpdateSeqno; - geckoPostToIc(new Runnable() { @Override public void run() { mActionQueue.syncWithGecko(); - if (seqnoWhenPosted == mGeckoUpdateSeqno) { - icUpdateGecko(force); - } + icUpdateGecko(force); } }); } @@ -808,34 +780,6 @@ final class GeckoEditable extends JNIObject getConstantName(Action.class, "TYPE_", action.mType) + ")"); } switch (action.mType) { - case Action.TYPE_SET_SELECTION: - final int len = mText.length(); - final int curStart = Selection.getSelectionStart(mText); - final int curEnd = Selection.getSelectionEnd(mText); - // start == -1 when the start offset should remain the same - // end == -1 when the end offset should remain the same - final int selStart = Math.min(action.mStart < 0 ? curStart : action.mStart, len); - final int selEnd = Math.min(action.mEnd < 0 ? curEnd : action.mEnd, len); - - if (selStart < action.mStart || selEnd < action.mEnd) { - Log.w(LOGTAG, "IME sync error: selection out of bounds"); - } - Selection.setSelection(mText, selStart, selEnd); - geckoPostToIc(new Runnable() { - @Override - public void run() { - mActionQueue.syncWithGecko(); - final int start = Selection.getSelectionStart(mText); - final int end = Selection.getSelectionEnd(mText); - if (selStart == start && selEnd == end) { - // There has not been another new selection in the mean time that - // made this notification out-of-date - mListener.onSelectionChange(start, end); - } - } - }); - break; - case Action.TYPE_SET_SPAN: mText.setSpan(action.mSpanObject, action.mStart, action.mEnd, action.mSpanFlags); break; @@ -968,7 +912,7 @@ final class GeckoEditable extends JNIObject } @WrapForJNI @Override - public void onSelectionChange(final int start, final int end) { + public void onSelectionChange(int start, int end) { if (DEBUG) { // GeckoEditableListener methods should all be called from the Gecko thread ThreadUtils.assertOnGeckoThread(); @@ -979,34 +923,22 @@ final class GeckoEditable extends JNIObject start + " to " + end + ", length: " + mText.length()); throw new IllegalArgumentException("invalid selection notification range"); } - final int seqnoWhenPosted = ++mGeckoUpdateSeqno; - /* An event (keypress, etc.) has potentially changed the selection, - synchronize the selection here. There is not a race with the IC thread - because the IC thread should be blocked on the event action */ - final Action action = mActionQueue.peek(); - if (action != null && action.mType == Action.TYPE_EVENT) { + if (mIgnoreSelectionChange) { + start = Selection.getSelectionStart(mText); + end = Selection.getSelectionEnd(mText); + mIgnoreSelectionChange = false; + + } else { Selection.setSelection(mText, start, end); - return; } + final int newStart = start; + final int newEnd = end; geckoPostToIc(new Runnable() { @Override public void run() { - mActionQueue.syncWithGecko(); - /* check to see there has not been another action that potentially changed the - selection. If so, we can skip this update because we know there is another - update right after this one that will replace the effect of this update */ - if (mGeckoUpdateSeqno == seqnoWhenPosted) { - /* In this case, Gecko's selection has changed and it's notifying us to change - Java's selection. In the normal case, whenever Java's selection changes, - we go back and set Gecko's selection as well. However, in this case, - since Gecko's selection is already up-to-date, we skip this step. */ - boolean oldUpdateGecko = mUpdateGecko; - mUpdateGecko = false; - Selection.setSelection(mProxy, start, end); - mUpdateGecko = oldUpdateGecko; - } + mListener.onSelectionChange(newStart, newEnd); } }); } @@ -1053,12 +985,6 @@ final class GeckoEditable extends JNIObject final int newEnd = start + text.length(); final Action action = mActionQueue.peek(); - /* Text changes affect the selection as well, and we may not receive another selection - update as a result of selection notification masking on the Gecko side; therefore, - in order to prevent previous stale selection notifications from occurring, we need - to increment the seqno here as well */ - ++mGeckoUpdateSeqno; - if (action != null && action.mType == Action.TYPE_ACKNOWLEDGE_FOCUS) { // Simply replace the text for newly-focused editors. mText.replace(0, mText.length(), text); @@ -1104,11 +1030,18 @@ final class GeckoEditable extends JNIObject Spanned.SPAN_POINT_POINT); } + // Ignore the next selection change because the selection change is a + // side-effect of the replace-text event we sent. + mIgnoreSelectionChange = true; + } else { // Gecko side initiated the text change. if (isSameText(start, oldEnd, mChangedText)) { - // Nothing to do because the text is the same. - // This could happen when the composition is updated for example. + // Nothing to do because the text is the same. This could happen when + // the composition is updated for example. Ignore the next selection + // change because the selection change is a side-effect of the + // update-composition event we sent. + mIgnoreSelectionChange = true; return; } geckoReplaceText(start, oldEnd, mChangedText); @@ -1236,19 +1169,7 @@ final class GeckoEditable extends JNIObject @Override public void setSpan(Object what, int start, int end, int flags) { - if (what == Selection.SELECTION_START) { - if ((flags & Spanned.SPAN_INTERMEDIATE) != 0) { - // We will get the end offset next, just save the start for now - mSavedSelectionStart = start; - } else { - mActionQueue.offer(Action.newSetSelection(start, -1)); - } - } else if (what == Selection.SELECTION_END) { - mActionQueue.offer(Action.newSetSelection(mSavedSelectionStart, end)); - mSavedSelectionStart = -1; - } else { - mActionQueue.offer(Action.newSetSpan(what, start, end, flags)); - } + mActionQueue.offer(Action.newSetSpan(what, start, end, flags)); } // Appendable interface diff --git a/mobile/android/base/GeckoInputConnection.java b/mobile/android/base/GeckoInputConnection.java index 4c60e9377c81..25adcda58683 100644 --- a/mobile/android/base/GeckoInputConnection.java +++ b/mobile/android/base/GeckoInputConnection.java @@ -519,7 +519,12 @@ class GeckoInputConnection mBatchSelectionChanged = true; return; } - notifySelectionChange(start, end); + + final Editable editable = getEditable(); + if (editable != null) { + notifySelectionChange(Selection.getSelectionStart(editable), + Selection.getSelectionEnd(editable)); + } } private void notifySelectionChange(int start, int end) { diff --git a/widget/android/nsWindow.cpp b/widget/android/nsWindow.cpp index a36798154828..4f9c8b6b9c67 100644 --- a/widget/android/nsWindow.cpp +++ b/widget/android/nsWindow.cpp @@ -241,7 +241,6 @@ public: , mIMEMaskEventsCount(1) // Mask IME events since there's no focus yet , mIMEUpdatingContext(false) , mIMESelectionChanged(false) - , mIMEMaskSelectionUpdate(false) {} ~Natives(); @@ -279,8 +278,8 @@ private: * Gecko controls the text content, and Java shadows the Gecko text through text updates - * Java controls the selection, and Gecko shadows the Java selection - through set selection events + * Gecko and Java maintain separate selections, and synchronize when + needed through selection updates and set-selection events * Java controls the composition, and Gecko shadows the Java composition through update composition events */ @@ -317,7 +316,6 @@ private: int32_t mIMEMaskEventsCount; // Mask events when > 0 bool mIMEUpdatingContext; bool mIMESelectionChanged; - bool mIMEMaskSelectionUpdate; void SendIMEDummyKeyEvents(); void AddIMETextChange(const IMETextChange& aChange); @@ -1914,18 +1912,6 @@ ConvertAndroidColor(uint32_t aArgb) (aArgb & 0xff000000) >> 24); } -class AutoIMEMask { -private: - bool mOldMask, *mMask; -public: - AutoIMEMask(bool &aMask) : mOldMask(aMask), mMask(&aMask) { - aMask = true; - } - ~AutoIMEMask() { - *mMask = mOldMask; - } -}; - /* * Get the current composition object, if any. */ @@ -2164,10 +2150,6 @@ nsWindow::Natives::NotifyIME(const IMENotification& aIMENotification) } case NOTIFY_IME_OF_SELECTION_CHANGE: { - if (mIMEMaskSelectionUpdate) { - return true; - } - ALOGIME("IME: NOTIFY_IME_OF_SELECTION_CHANGE"); PostFlushIMEChanges(); @@ -2274,7 +2256,6 @@ nsWindow::Natives::OnImeSynchronize() void nsWindow::Natives::OnImeAcknowledgeFocus() { - MOZ_ASSERT(!mIMEMaskSelectionUpdate); MOZ_ASSERT(mIMEMaskEventsCount > 0); if (--mIMEMaskEventsCount > 0) { @@ -2302,8 +2283,6 @@ void nsWindow::Natives::OnImeReplaceText(int32_t aStart, int32_t aEnd, jni::String::Param aText, bool aComposing) { - MOZ_ASSERT(!mIMEMaskSelectionUpdate); - if (mIMEMaskEventsCount > 0) { // Not focused; still reply to events, but don't do anything else. return OnImeSynchronize(); @@ -2311,12 +2290,8 @@ nsWindow::Natives::OnImeReplaceText(int32_t aStart, int32_t aEnd, /* Replace text in Gecko thread from aStart to aEnd with the string text. - - Selection updates are masked so the result of our temporary - selection event is not passed on to Java */ RefPtr kungFuDeathGrip(&window); - AutoIMEMask selMask(mIMEMaskSelectionUpdate); nsString string(aText); const auto composition(window.GetIMEComposition()); @@ -2410,8 +2385,6 @@ nsWindow::Natives::OnImeReplaceText(int32_t aStart, int32_t aEnd, void nsWindow::Natives::OnImeSetSelection(int32_t aStart, int32_t aEnd) { - MOZ_ASSERT(!mIMEMaskSelectionUpdate); - if (mIMEMaskEventsCount > 0) { // Not focused. return; @@ -2419,12 +2392,8 @@ nsWindow::Natives::OnImeSetSelection(int32_t aStart, int32_t aEnd) /* Set Gecko selection to aStart to aEnd. - - Selection updates are masked to prevent Java from being - notified of the new selection */ RefPtr kungFuDeathGrip(&window); - AutoIMEMask selMask(mIMEMaskSelectionUpdate); WidgetSelectionEvent selEvent(true, eSetSelection, &window); window.InitEvent(selEvent, nullptr); @@ -2453,8 +2422,6 @@ nsWindow::Natives::OnImeSetSelection(int32_t aStart, int32_t aEnd) void nsWindow::Natives::OnImeRemoveComposition() { - MOZ_ASSERT(!mIMEMaskSelectionUpdate); - if (mIMEMaskEventsCount > 0) { // Not focused. return; @@ -2463,11 +2430,7 @@ nsWindow::Natives::OnImeRemoveComposition() /* * Remove any previous composition. This is only used for * visual indication and does not affect the text content. - * - * Selection updates are masked so the result of - * temporary events are not passed on to Java */ - AutoIMEMask selMask(mIMEMaskSelectionUpdate); window.RemoveIMEComposition(); mIMERanges->Clear(); } @@ -2478,8 +2441,6 @@ nsWindow::Natives::OnImeAddCompositionRange( int32_t aRangeLineStyle, bool aRangeBoldLine, int32_t aRangeForeColor, int32_t aRangeBackColor, int32_t aRangeLineColor) { - MOZ_ASSERT(!mIMEMaskSelectionUpdate); - if (mIMEMaskEventsCount > 0) { // Not focused. return; @@ -2504,8 +2465,6 @@ nsWindow::Natives::OnImeAddCompositionRange( void nsWindow::Natives::OnImeUpdateComposition(int32_t aStart, int32_t aEnd) { - MOZ_ASSERT(!mIMEMaskSelectionUpdate); - if (mIMEMaskEventsCount > 0) { // Not focused. return; @@ -2518,12 +2477,8 @@ nsWindow::Natives::OnImeUpdateComposition(int32_t aStart, int32_t aEnd) Only the offsets are specified and not the text content to eliminate the possibility of this event altering the text content unintentionally. - - Selection updates are masked so the result of - temporary events are not passed on to Java */ RefPtr kungFuDeathGrip(&window); - AutoIMEMask selMask(mIMEMaskSelectionUpdate); const auto composition(window.GetIMEComposition()); MOZ_ASSERT(!composition || !composition->IsEditorHandlingEvent()); From 244a51f96ca26e4ae6cce10608932ffb0f745a1f Mon Sep 17 00:00:00 2001 From: Jim Chen Date: Tue, 3 Nov 2015 11:53:58 -0500 Subject: [PATCH 045/113] Bug 1216666 - Update composition as part of replacing text or changing span; r=esawin Currently, GeckoEditable periodically fires update composition events to update the Gecko composition styling. To make the code more efficient and more robust in dealing with content JS code, this patch merges these events into events like replacing text, setting span, and removing span. As a result, a setComposingText call now results in one replacing text event instead of a replacing text event plus an update composition event. --- mobile/android/base/GeckoEditable.java | 253 ++++++++++-------- mobile/android/base/GeckoEditableClient.java | 2 +- mobile/android/base/GeckoInputConnection.java | 4 +- .../gecko/tests/testInputConnection.java | 4 + widget/android/GeneratedJNINatives.h | 8 - widget/android/GeneratedJNIWrappers.cpp | 6 - widget/android/GeneratedJNIWrappers.h | 37 +-- widget/android/nsWindow.cpp | 110 +++----- 8 files changed, 193 insertions(+), 231 deletions(-) diff --git a/mobile/android/base/GeckoEditable.java b/mobile/android/base/GeckoEditable.java index da00fd0536ac..37a23458cfb9 100644 --- a/mobile/android/base/GeckoEditable.java +++ b/mobile/android/base/GeckoEditable.java @@ -27,6 +27,7 @@ import android.os.Handler; import android.os.Looper; import android.text.Editable; import android.text.InputFilter; +import android.text.NoCopySpan; import android.text.Selection; import android.text.Spannable; import android.text.SpannableString; @@ -66,12 +67,11 @@ final class GeckoEditable extends JNIObject private Handler mIcPostHandler; private GeckoEditableListener mListener; - private int mIcUpdateSeqno; - private int mLastIcUpdateSeqno; - private boolean mUpdateGecko; + /* package */ boolean mInBatchMode; // Used by IC thread + /* package */ boolean mNeedCompositionUpdate; // Used by IC thread private boolean mFocused; // Used by IC thread private boolean mGeckoFocused; // Used by Gecko thread - private boolean mIgnoreSelectionChange; + private boolean mIgnoreSelectionChange; // Used by Gecko thread private volatile boolean mSuppressCompositions; private volatile boolean mSuppressKeyUp; @@ -132,13 +132,7 @@ final class GeckoEditable extends JNIObject private native void onImeAcknowledgeFocus(); @WrapForJNI - private native void onImeReplaceText(int start, int end, String text, boolean composing); - - @WrapForJNI - private native void onImeSetSelection(int start, int end); - - @WrapForJNI - private native void onImeRemoveComposition(); + private native void onImeReplaceText(int start, int end, String text); @WrapForJNI private native void onImeAddCompositionRange(int start, int end, int rangeType, @@ -160,24 +154,22 @@ final class GeckoEditable extends JNIObject static final int TYPE_EVENT = 0; // For Editable.replace() call; use with IME_REPLACE_TEXT static final int TYPE_REPLACE_TEXT = 1; - // For Editable.replace() call involving compositions; use with IME_COMPOSE_TEXT - static final int TYPE_COMPOSE_TEXT = 2; // For Editable.setSpan() call; use with IME_SYNCHRONIZE - static final int TYPE_SET_SPAN = 3; + static final int TYPE_SET_SPAN = 2; // For Editable.removeSpan() call; use with IME_SYNCHRONIZE - static final int TYPE_REMOVE_SPAN = 4; + static final int TYPE_REMOVE_SPAN = 3; // For focus events (in notifyIME); use with IME_ACKNOWLEDGE_FOCUS - static final int TYPE_ACKNOWLEDGE_FOCUS = 5; + static final int TYPE_ACKNOWLEDGE_FOCUS = 4; // For switching handler; use with IME_SYNCHRONIZE - static final int TYPE_SET_HANDLER = 6; + static final int TYPE_SET_HANDLER = 5; final int mType; + boolean mUpdateComposition; int mStart; int mEnd; CharSequence mSequence; Object mSpanObject; int mSpanFlags; - boolean mShouldUpdate; Handler mHandler; Action(int type) { @@ -190,28 +182,14 @@ final class GeckoEditable extends JNIObject throw new IllegalArgumentException("invalid replace text offsets"); } - int actionType = TYPE_REPLACE_TEXT; - - if (text instanceof Spanned) { - final Spanned spanned = (Spanned) text; - final Object[] spans = spanned.getSpans(0, spanned.length(), Object.class); - - for (Object span : spans) { - if ((spanned.getSpanFlags(span) & Spanned.SPAN_COMPOSING) != 0) { - actionType = TYPE_COMPOSE_TEXT; - break; - } - } - } - - final Action action = new Action(actionType); + final Action action = new Action(TYPE_REPLACE_TEXT); action.mSequence = text; action.mStart = start; action.mEnd = end; return action; } - static Action newSetSpan(Object object, int start, int end, int flags) { + static Action newSetSpan(Object object, int start, int end, int flags, boolean update) { if (start < 0 || start > end) { Log.e(LOGTAG, "invalid span offsets: " + start + " to " + end); throw new IllegalArgumentException("invalid span offsets"); @@ -221,12 +199,14 @@ final class GeckoEditable extends JNIObject action.mStart = start; action.mEnd = end; action.mSpanFlags = flags; + action.mUpdateComposition = update; return action; } - static Action newRemoveSpan(Object object) { + static Action newRemoveSpan(Object object, boolean update) { final Action action = new Action(TYPE_REMOVE_SPAN); action.mSpanObject = object; + action.mUpdateComposition = update; return action; } @@ -255,13 +235,7 @@ final class GeckoEditable extends JNIObject Log.d(LOGTAG, "offer: Action(" + getConstantName(Action.class, "TYPE_", action.mType) + ")"); } - /* Events don't need update because they generate text/selection - notifications which will do the updating for us */ - if (action.mType != Action.TYPE_EVENT && - action.mType != Action.TYPE_ACKNOWLEDGE_FOCUS && - action.mType != Action.TYPE_SET_HANDLER) { - action.mShouldUpdate = mUpdateGecko; - } + if (mActions.isEmpty()) { mActionsActive.acquireUninterruptibly(); mActions.offer(action); @@ -280,14 +254,14 @@ final class GeckoEditable extends JNIObject break; case Action.TYPE_REPLACE_TEXT: - // try key events first - sendCharKeyEvents(action); - - // fall-through - - case Action.TYPE_COMPOSE_TEXT: - onImeReplaceText(action.mStart, action.mEnd, action.mSequence.toString(), - action.mType == Action.TYPE_COMPOSE_TEXT); + // Because we get composition styling here essentially for free, + // we don't need to check if we're in batch mode. + if (!icMaybeSendComposition( + action.mSequence, /* useEntireText */ true, /* notifyGecko */ false)) { + // Since we don't have a composition, we can try sending key events. + sendCharKeyEvents(action); + } + onImeReplaceText(action.mStart, action.mEnd, action.mSequence.toString()); break; case Action.TYPE_ACKNOWLEDGE_FOCUS: @@ -297,8 +271,6 @@ final class GeckoEditable extends JNIObject default: throw new IllegalStateException("Action not processed"); } - - ++mIcUpdateSeqno; } private KeyEvent [] synthesizeKeyEvents(CharSequence cs) { @@ -407,7 +379,6 @@ final class GeckoEditable extends JNIObject ThreadUtils.assertOnGeckoThread(); } mActionQueue = new ActionQueue(); - mUpdateGecko = true; mText = new SpannableStringBuilder(); mChangedText = new SpannableStringBuilder(); @@ -485,16 +456,6 @@ final class GeckoEditable extends JNIObject mIcPostHandler.post(runnable); } - private void geckoUpdateGecko(final boolean force) { - geckoPostToIc(new Runnable() { - @Override - public void run() { - mActionQueue.syncWithGecko(); - icUpdateGecko(force); - } - }); - } - private Object getField(Object obj, String field, Object def) { try { return obj.getClass().getField(field).get(obj); @@ -503,54 +464,84 @@ final class GeckoEditable extends JNIObject } } - private void icUpdateGecko(boolean force) { + /** + * Send composition ranges to Gecko if the text has composing spans. + * + * @param sequence Text with possible composing spans + * @param useEntireText If text has composing spans, treat the entire text as + * a Gecko composition, instead of just the spanned part. + * @param notifyGecko Notify Gecko of the new composition ranges; + * otherwise, the caller is responsible for notifying Gecko. + * @return Whether there was a composition + */ + private boolean icMaybeSendComposition(final CharSequence sequence, + final boolean useEntireText, + final boolean notifyGecko) { + int selStart = Selection.getSelectionStart(sequence); + int selEnd = Selection.getSelectionEnd(sequence); - // Skip if receiving a repeated request, or - // if suppressing compositions during text selection. - if ((!force && mIcUpdateSeqno == mLastIcUpdateSeqno) || - mSuppressCompositions) { - if (DEBUG) { - Log.d(LOGTAG, "icUpdateGecko() skipped"); + if (sequence instanceof Spanned) { + final Spanned text = (Spanned) sequence; + final Object[] spans = text.getSpans(0, text.length(), Object.class); + boolean found = false; + int composingStart = useEntireText ? 0 : Integer.MAX_VALUE; + int composingEnd = useEntireText ? text.length() : 0; + + for (Object span : spans) { + if ((text.getSpanFlags(span) & Spanned.SPAN_COMPOSING) == 0) { + continue; + } + if (!useEntireText) { + composingStart = Math.min(composingStart, text.getSpanStart(span)); + composingEnd = Math.max(composingEnd, text.getSpanEnd(span)); + } + found = true; + } + + if (useEntireText && (selStart < 0 || selEnd < 0)) { + selStart = composingEnd; + selEnd = composingEnd; + } + + if (found) { + icSendComposition(text, selStart, selEnd, composingStart, composingEnd); + if (notifyGecko) { + onImeUpdateComposition(composingStart, composingEnd); + } + return true; } - return; } - mLastIcUpdateSeqno = mIcUpdateSeqno; - mActionQueue.syncWithGecko(); + + if (notifyGecko) { + // Set the selection by using a composition without ranges + onImeUpdateComposition(selStart, selEnd); + } if (DEBUG) { - Log.d(LOGTAG, "icUpdateGecko()"); + Log.d(LOGTAG, "icSendComposition(): no composition"); } + return false; + } - final int selStart = mText.getSpanStart(Selection.SELECTION_START); - final int selEnd = mText.getSpanEnd(Selection.SELECTION_END); - int composingStart = mText.length(); - int composingEnd = 0; - Object[] spans = mText.getSpans(0, composingStart, Object.class); - - for (Object span : spans) { - if ((mText.getSpanFlags(span) & Spanned.SPAN_COMPOSING) != 0) { - composingStart = Math.min(composingStart, mText.getSpanStart(span)); - composingEnd = Math.max(composingEnd, mText.getSpanEnd(span)); - } + private void icSendComposition(final Spanned text, + final int selStart, final int selEnd, + final int composingStart, final int composingEnd) { + if (DEBUG) { + assertOnIcThread(); + Log.d(LOGTAG, "icSendComposition(\"" + text + "\", " + + composingStart + ", " + composingEnd + ")"); } if (DEBUG) { Log.d(LOGTAG, " range = " + composingStart + "-" + composingEnd); Log.d(LOGTAG, " selection = " + selStart + "-" + selEnd); } - if (composingStart >= composingEnd) { - if (selStart >= 0 && selEnd >= 0) { - onImeSetSelection(selStart, selEnd); - } else { - onImeRemoveComposition(); - } - return; - } if (selEnd >= composingStart && selEnd <= composingEnd) { onImeAddCompositionRange( selEnd - composingStart, selEnd - composingStart, IME_RANGE_CARETPOSITION, 0, 0, false, 0, 0, 0); } + int rangeStart = composingStart; TextPaint tp = new TextPaint(); TextPaint emptyTp = new TextPaint(); @@ -561,7 +552,7 @@ final class GeckoEditable extends JNIObject int rangeType, rangeStyles = 0, rangeLineStyle = IME_RANGE_LINE_NONE; boolean rangeBoldLine = false; int rangeForeColor = 0, rangeBackColor = 0, rangeLineColor = 0; - int rangeEnd = mText.nextSpanTransition(rangeStart, composingEnd, Object.class); + int rangeEnd = text.nextSpanTransition(rangeStart, composingEnd, Object.class); if (selStart > rangeStart && selStart < rangeEnd) { rangeEnd = selStart; @@ -569,7 +560,7 @@ final class GeckoEditable extends JNIObject rangeEnd = selEnd; } CharacterStyle[] styleSpans = - mText.getSpans(rangeStart, rangeEnd, CharacterStyle.class); + text.getSpans(rangeStart, rangeEnd, CharacterStyle.class); if (DEBUG) { Log.d(LOGTAG, " found " + styleSpans.length + " spans @ " + @@ -634,8 +625,6 @@ final class GeckoEditable extends JNIObject " : " + Integer.toHexString(rangeBackColor)); } } while (rangeStart < composingEnd); - - onImeUpdateComposition(composingStart, composingEnd); } // GeckoEditableClient interface @@ -654,6 +643,10 @@ final class GeckoEditable extends JNIObject the second sync event will have a reply, during which we see that there is a pending event-type action, and update the selection/composition/etc. accordingly. */ + if (mNeedCompositionUpdate) { + // Make sure Gecko selection is in-sync with Java selection first. + icUpdateComposition(); + } onKeyEvent(event, action, metaState, /* isSynthesizedImeKey */ false); mActionQueue.offer(new Action(Action.TYPE_EVENT)); } @@ -671,18 +664,48 @@ final class GeckoEditable extends JNIObject } @Override - public void setUpdateGecko(boolean update) { + public void setBatchMode(boolean inBatchMode) { if (!onIcThread()) { // Android may be holding an old InputConnection; ignore if (DEBUG) { - Log.i(LOGTAG, "setUpdateGecko() called on non-IC thread"); + Log.i(LOGTAG, "setBatchMode() called on non-IC thread"); } return; } - if (update) { - icUpdateGecko(false); + if (!inBatchMode && mNeedCompositionUpdate) { + icUpdateComposition(); } - mUpdateGecko = update; + mInBatchMode = inBatchMode; + } + + private void icUpdateComposition() { + if (DEBUG) { + assertOnIcThread(); + } + mNeedCompositionUpdate = false; + mActionQueue.syncWithGecko(); + icMaybeSendComposition(mText, /* useEntireText */ false, /* notifyGecko */ true); + } + + private void geckoScheduleCompositionUpdate() { + if (DEBUG) { + ThreadUtils.assertOnGeckoThread(); + } + // May be called from either Gecko or IC thread + geckoPostToIc(new Runnable() { + @Override + public void run() { + if (mInBatchMode || !mNeedCompositionUpdate) { + return; + } + if (!mActionQueue.isEmpty()) { + // We are likely to block here, so wait a little and try again. + mIcPostHandler.postDelayed(this, 10); + return; + } + icUpdateComposition(); + } + }); } @Override @@ -792,8 +815,9 @@ final class GeckoEditable extends JNIObject geckoSetIcHandler(action.mHandler); break; } - if (action.mShouldUpdate) { - geckoUpdateGecko(false); + + if (action.mUpdateComposition) { + geckoScheduleCompositionUpdate(); } } @@ -997,8 +1021,7 @@ final class GeckoEditable extends JNIObject Object.class, mChangedText, 0); if (action != null && - (action.mType == Action.TYPE_REPLACE_TEXT || - action.mType == Action.TYPE_COMPOSE_TEXT) && + action.mType == Action.TYPE_REPLACE_TEXT && start <= action.mStart && action.mStart + action.mSequence.length() <= newEnd) { @@ -1164,12 +1187,26 @@ final class GeckoEditable extends JNIObject what == Selection.SELECTION_END) { Log.w(LOGTAG, "selection removed with removeSpan()"); } - mActionQueue.offer(Action.newRemoveSpan(what)); + + // "what" could be a composing span (BaseInputConnection.COMPOSING extends + // NoCopySpan), so we should update the Gecko composition + final boolean update = !mNeedCompositionUpdate && what instanceof NoCopySpan; + mActionQueue.offer(Action.newRemoveSpan(what, update)); + mNeedCompositionUpdate |= update; } @Override public void setSpan(Object what, int start, int end, int flags) { - mActionQueue.offer(Action.newSetSpan(what, start, end, flags)); + // If mUpdateComposition is true, it means something in the queue will be + // scheduling an update, so it would be redundant to have this action schedule + // another update. + final boolean update = !mNeedCompositionUpdate && + (flags & Spanned.SPAN_INTERMEDIATE) == 0 && ( + (flags & Spanned.SPAN_COMPOSING) != 0 || + what == Selection.SELECTION_START || + what == Selection.SELECTION_END); + mActionQueue.offer(Action.newSetSpan(what, start, end, flags, update)); + mNeedCompositionUpdate |= update; } // Appendable interface diff --git a/mobile/android/base/GeckoEditableClient.java b/mobile/android/base/GeckoEditableClient.java index 38bdf04e59a1..23896d590a30 100644 --- a/mobile/android/base/GeckoEditableClient.java +++ b/mobile/android/base/GeckoEditableClient.java @@ -15,7 +15,7 @@ import android.view.KeyEvent; interface GeckoEditableClient { void sendKeyEvent(KeyEvent event, int action, int metaState); Editable getEditable(); - void setUpdateGecko(boolean update); + void setBatchMode(boolean isBatchMode); void setSuppressKeyUp(boolean suppress); Handler getInputConnectionHandler(); boolean setInputConnectionHandler(Handler handler); diff --git a/mobile/android/base/GeckoInputConnection.java b/mobile/android/base/GeckoInputConnection.java index 25adcda58683..296c8c9418ea 100644 --- a/mobile/android/base/GeckoInputConnection.java +++ b/mobile/android/base/GeckoInputConnection.java @@ -250,7 +250,7 @@ class GeckoInputConnection @Override public synchronized boolean beginBatchEdit() { mBatchEditCount++; - mEditableClient.setUpdateGecko(false); + mEditableClient.setBatchMode(true); return true; } @@ -269,7 +269,7 @@ class GeckoInputConnection Selection.getSelectionEnd(editable)); mBatchSelectionChanged = false; } - mEditableClient.setUpdateGecko(true); + mEditableClient.setBatchMode(false); } } else { Log.w(LOGTAG, "endBatchEdit() called, but mBatchEditCount == 0?!"); diff --git a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testInputConnection.java b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testInputConnection.java index 4cc8d2c81f9b..69692f677c77 100644 --- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testInputConnection.java +++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testInputConnection.java @@ -147,6 +147,10 @@ public class testInputConnection extends UITest { ic.commitText(dummyText, 1); assertTextAndSelectionAt("Can commit text", ic, dummyText, dummyText.length()); + // Finish processing events from the old input field. + processGeckoEvents(ic); + processInputConnectionEvents(); + final KeyEvent tabKey = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_TAB); ic.sendKeyEvent(tabKey); ic.sendKeyEvent(KeyEvent.changeAction(tabKey, KeyEvent.ACTION_UP)); diff --git a/widget/android/GeneratedJNINatives.h b/widget/android/GeneratedJNINatives.h index 5b7a37e43307..cc289ba10b93 100644 --- a/widget/android/GeneratedJNINatives.h +++ b/widget/android/GeneratedJNINatives.h @@ -69,18 +69,10 @@ public: mozilla::jni::NativeStub ::template Wrap<&Impl::OnImeAddCompositionRange>), - mozilla::jni::MakeNativeMethod( - mozilla::jni::NativeStub - ::template Wrap<&Impl::OnImeRemoveComposition>), - mozilla::jni::MakeNativeMethod( mozilla::jni::NativeStub ::template Wrap<&Impl::OnImeReplaceText>), - mozilla::jni::MakeNativeMethod( - mozilla::jni::NativeStub - ::template Wrap<&Impl::OnImeSetSelection>), - mozilla::jni::MakeNativeMethod( mozilla::jni::NativeStub ::template Wrap<&Impl::OnImeSynchronize>), diff --git a/widget/android/GeneratedJNIWrappers.cpp b/widget/android/GeneratedJNIWrappers.cpp index a969c117c32e..2cf1cfaf4c95 100644 --- a/widget/android/GeneratedJNIWrappers.cpp +++ b/widget/android/GeneratedJNIWrappers.cpp @@ -769,15 +769,9 @@ constexpr char GeckoEditable::OnImeAcknowledgeFocus_t::signature[]; constexpr char GeckoEditable::OnImeAddCompositionRange_t::name[]; constexpr char GeckoEditable::OnImeAddCompositionRange_t::signature[]; -constexpr char GeckoEditable::OnImeRemoveComposition_t::name[]; -constexpr char GeckoEditable::OnImeRemoveComposition_t::signature[]; - constexpr char GeckoEditable::OnImeReplaceText_t::name[]; constexpr char GeckoEditable::OnImeReplaceText_t::signature[]; -constexpr char GeckoEditable::OnImeSetSelection_t::name[]; -constexpr char GeckoEditable::OnImeSetSelection_t::signature[]; - constexpr char GeckoEditable::OnImeSynchronize_t::name[]; constexpr char GeckoEditable::OnImeSynchronize_t::signature[]; diff --git a/widget/android/GeneratedJNIWrappers.h b/widget/android/GeneratedJNIWrappers.h index 78784bac617b..27a82af2b7a5 100644 --- a/widget/android/GeneratedJNIWrappers.h +++ b/widget/android/GeneratedJNIWrappers.h @@ -1866,21 +1866,6 @@ public: mozilla::jni::ExceptionMode::ABORT; }; -public: - struct OnImeRemoveComposition_t { - typedef GeckoEditable Owner; - typedef void ReturnType; - typedef void SetterType; - typedef mozilla::jni::Args<> Args; - static constexpr char name[] = "onImeRemoveComposition"; - static constexpr char signature[] = - "()V"; - static const bool isStatic = false; - static const bool isMultithreaded = false; - static const mozilla::jni::ExceptionMode exceptionMode = - mozilla::jni::ExceptionMode::ABORT; - }; - public: struct OnImeReplaceText_t { typedef GeckoEditable Owner; @@ -1889,28 +1874,10 @@ public: typedef mozilla::jni::Args< int32_t, int32_t, - mozilla::jni::String::Param, - bool> Args; + mozilla::jni::String::Param> Args; static constexpr char name[] = "onImeReplaceText"; static constexpr char signature[] = - "(IILjava/lang/String;Z)V"; - static const bool isStatic = false; - static const bool isMultithreaded = false; - static const mozilla::jni::ExceptionMode exceptionMode = - mozilla::jni::ExceptionMode::ABORT; - }; - -public: - struct OnImeSetSelection_t { - typedef GeckoEditable Owner; - typedef void ReturnType; - typedef void SetterType; - typedef mozilla::jni::Args< - int32_t, - int32_t> Args; - static constexpr char name[] = "onImeSetSelection"; - static constexpr char signature[] = - "(II)V"; + "(IILjava/lang/String;)V"; static const bool isStatic = false; static const bool isMultithreaded = false; static const mozilla::jni::ExceptionMode exceptionMode = diff --git a/widget/android/nsWindow.cpp b/widget/android/nsWindow.cpp index 4f9c8b6b9c67..4785d6652f35 100644 --- a/widget/android/nsWindow.cpp +++ b/widget/android/nsWindow.cpp @@ -206,8 +206,6 @@ class nsWindow::Natives final // Events that result in user-visible changes count as UI events. if (Base::lambda.IsTarget(&Natives::OnKeyEvent) || Base::lambda.IsTarget(&Natives::OnImeReplaceText) || - Base::lambda.IsTarget(&Natives::OnImeSetSelection) || - Base::lambda.IsTarget(&Natives::OnImeRemoveComposition) || Base::lambda.IsTarget(&Natives::OnImeUpdateComposition)) { return nsAppShell::Event::Type::kUIActivity; @@ -343,13 +341,7 @@ public: // Replace a range of text with new text. void OnImeReplaceText(int32_t aStart, int32_t aEnd, - jni::String::Param aText, bool aComposing); - - // Set selection to a certain range. - void OnImeSetSelection(int32_t aStart, int32_t aEnd); - - // Remove any active composition. - void OnImeRemoveComposition(); + jni::String::Param aText); // Add styling for a range within the active composition. void OnImeAddCompositionRange(int32_t aStart, int32_t aEnd, @@ -2281,7 +2273,7 @@ nsWindow::Natives::OnImeAcknowledgeFocus() void nsWindow::Natives::OnImeReplaceText(int32_t aStart, int32_t aEnd, - jni::String::Param aText, bool aComposing) + jni::String::Param aText) { if (mIMEMaskEventsCount > 0) { // Not focused; still reply to events, but don't do anything else. @@ -2354,25 +2346,37 @@ nsWindow::Natives::OnImeReplaceText(int32_t aStart, int32_t aEnd, AddIMETextChange(dummyChange); } + const bool composing = !mIMERanges->IsEmpty(); + // Previous events may have destroyed our composition; bail in that case. if (window.GetIMEComposition()) { WidgetCompositionEvent event(true, eCompositionChange, &window); window.InitEvent(event, nullptr); event.mData = string; - // Include proper text ranges to make the editor happy. - TextRange range; - range.mStartOffset = 0; - range.mEndOffset = event.mData.Length(); - range.mRangeType = NS_TEXTRANGE_RAWINPUT; - event.mRanges = new TextRangeArray(); - event.mRanges->AppendElement(range); + if (composing) { + event.mRanges = new TextRangeArray(); + mIMERanges.swap(event.mRanges); + + } else if (event.mData.Length()) { + // Include proper text ranges to make the editor happy. + TextRange range; + range.mStartOffset = 0; + range.mEndOffset = event.mData.Length(); + range.mRangeType = NS_TEXTRANGE_RAWINPUT; + event.mRanges = new TextRangeArray(); + event.mRanges->AppendElement(range); + } window.DispatchEvent(&event); + + } else if (composing) { + // Ensure IME ranges are empty. + mIMERanges->Clear(); } // Don't end composition when composing text or composition was destroyed. - if (!aComposing) { + if (!composing) { window.RemoveIMEComposition(); } @@ -2382,59 +2386,6 @@ nsWindow::Natives::OnImeReplaceText(int32_t aStart, int32_t aEnd, OnImeSynchronize(); } -void -nsWindow::Natives::OnImeSetSelection(int32_t aStart, int32_t aEnd) -{ - if (mIMEMaskEventsCount > 0) { - // Not focused. - return; - } - - /* - Set Gecko selection to aStart to aEnd. - */ - RefPtr kungFuDeathGrip(&window); - WidgetSelectionEvent selEvent(true, eSetSelection, &window); - - window.InitEvent(selEvent, nullptr); - window.RemoveIMEComposition(); - - if (aStart < 0 || aEnd < 0) { - WidgetQueryContentEvent event(true, eQuerySelectedText, &window); - window.InitEvent(event, nullptr); - window.DispatchEvent(&event); - MOZ_ASSERT(event.mSucceeded); - - if (aStart < 0) - aStart = int32_t(event.GetSelectionStart()); - if (aEnd < 0) - aEnd = int32_t(event.GetSelectionEnd()); - } - - selEvent.mOffset = std::min(aStart, aEnd); - selEvent.mLength = std::max(aStart, aEnd) - selEvent.mOffset; - selEvent.mReversed = aStart > aEnd; - selEvent.mExpandToClusterBoundary = false; - - window.DispatchEvent(&selEvent); -} - -void -nsWindow::Natives::OnImeRemoveComposition() -{ - if (mIMEMaskEventsCount > 0) { - // Not focused. - return; - } - - /* - * Remove any previous composition. This is only used for - * visual indication and does not affect the text content. - */ - window.RemoveIMEComposition(); - mIMERanges->Clear(); -} - void nsWindow::Natives::OnImeAddCompositionRange( int32_t aStart, int32_t aEnd, int32_t aRangeType, int32_t aRangeStyle, @@ -2470,6 +2421,23 @@ nsWindow::Natives::OnImeUpdateComposition(int32_t aStart, int32_t aEnd) return; } + // A composition with no ranges means we want to set the selection. + if (mIMERanges->IsEmpty()) { + MOZ_ASSERT(aStart >= 0 && aEnd >= 0); + window.RemoveIMEComposition(); + + WidgetSelectionEvent selEvent(true, eSetSelection, &window); + window.InitEvent(selEvent, nullptr); + + selEvent.mOffset = std::min(aStart, aEnd); + selEvent.mLength = std::max(aStart, aEnd) - selEvent.mOffset; + selEvent.mReversed = aStart > aEnd; + selEvent.mExpandToClusterBoundary = false; + + window.DispatchEvent(&selEvent); + return; + } + /* Update the composition from aStart to aEnd using information from added ranges. This is only used for From ed23d45bdecf390f30bfb86c310244c392b1edb8 Mon Sep 17 00:00:00 2001 From: Jim Chen Date: Tue, 3 Nov 2015 11:53:58 -0500 Subject: [PATCH 046/113] Bug 1216666 - Only send key events for single-character strings; r=esawin GeckoEditable currently synthesizes key events when committing strings, to improve web compatibility. However, synthesizing keys is causing performance problems when the string is long. This patch limits key synthesis to single-character strings. For longer strings, we commit the string as a whole. We have other ways of ensuring web compatibility now (sending dummy key events), so this restriction should not cause regressions. --- mobile/android/base/GeckoEditable.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/android/base/GeckoEditable.java b/mobile/android/base/GeckoEditable.java index 37a23458cfb9..fc5bbebcfeed 100644 --- a/mobile/android/base/GeckoEditable.java +++ b/mobile/android/base/GeckoEditable.java @@ -294,7 +294,7 @@ final class GeckoEditable extends JNIObject } private void sendCharKeyEvents(Action action) { - if (action.mSequence.length() == 0 || + if (action.mSequence.length() != 1 || (action.mSequence instanceof Spannable && ((Spannable)action.mSequence).nextSpanTransition( -1, Integer.MAX_VALUE, null) < Integer.MAX_VALUE)) { From 423789fdaa5be2c079138c6fc01e32c19ff30762 Mon Sep 17 00:00:00 2001 From: Jim Chen Date: Tue, 3 Nov 2015 11:53:58 -0500 Subject: [PATCH 047/113] Bug 1215163 - Add additional tests to testInputConnection; r=esawin Add tests for setComposingRange and sendKeyEvent to testInputConnection. --- .../gecko/tests/testInputConnection.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testInputConnection.java b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testInputConnection.java index 69692f677c77..1a83af74332e 100644 --- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testInputConnection.java +++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testInputConnection.java @@ -93,12 +93,44 @@ public class testInputConnection extends UITest { ic.finishComposingText(); assertTextAndSelectionAt("Can finish composition", ic, "foobar", 6); + // Test setComposingRegion + ic.setComposingRegion(0, 3); + assertTextAndSelectionAt("Can set composing region", ic, "foobar", 6); + + ic.setComposingText("far", 1); + assertTextAndSelectionAt("Can set composing region text", ic, "farbar", 3); + + ic.setComposingRegion(1, 4); + assertTextAndSelectionAt("Can set existing composing region", ic, "farbar", 3); + + ic.setComposingText("rab", 3); + assertTextAndSelectionAt("Can set new composing region text", ic, "frabar", 6); + // Test getTextBeforeCursor fAssertEquals("Can retrieve text before cursor", "bar", ic.getTextBeforeCursor(3, 0)); // Test getTextAfterCursor fAssertEquals("Can retrieve text after cursor", "", ic.getTextAfterCursor(3, 0)); + ic.finishComposingText(); + assertTextAndSelectionAt("Can finish composition", ic, "frabar", 6); + + // Test sendKeyEvent + final KeyEvent shiftKey = new KeyEvent(KeyEvent.ACTION_DOWN, + KeyEvent.KEYCODE_SHIFT_LEFT); + final KeyEvent leftKey = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_LEFT); + final KeyEvent tKey = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_T); + + ic.sendKeyEvent(shiftKey); + ic.sendKeyEvent(leftKey); + ic.sendKeyEvent(KeyEvent.changeAction(leftKey, KeyEvent.ACTION_UP)); + ic.sendKeyEvent(KeyEvent.changeAction(shiftKey, KeyEvent.ACTION_UP)); + assertTextAndSelection("Can select using key event", ic, "frabar", 6, 5); + + ic.sendKeyEvent(tKey); + ic.sendKeyEvent(KeyEvent.changeAction(tKey, KeyEvent.ACTION_UP)); + assertTextAndSelectionAt("Can type using event", ic, "frabat", 6); + ic.deleteSurroundingText(6, 0); assertTextAndSelectionAt("Can clear text", ic, "", 0); From 498b10c3cb2c45fc5a6bf36beace74f163da5e65 Mon Sep 17 00:00:00 2001 From: Jim Chen Date: Tue, 3 Nov 2015 11:53:58 -0500 Subject: [PATCH 048/113] Bug 1219833 - Respect composition underline color; r=masayuki nsTextFrame didn't use the composition underline color if the composition didn't have a foreground color defined. This patch makes it use the underline color if foreground color is not defined. --- layout/generic/nsTextFrame.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/layout/generic/nsTextFrame.cpp b/layout/generic/nsTextFrame.cpp index 5d080978de76..c3a2d7c9446b 100644 --- a/layout/generic/nsTextFrame.cpp +++ b/layout/generic/nsTextFrame.cpp @@ -5407,8 +5407,8 @@ nsTextFrame::DrawSelectionDecorations(gfxContext* aContext, // If underline color is defined and that doesn't depend on the // foreground color, we should use the color directly. if (aRangeStyle.IsUnderlineColorDefined() && - aRangeStyle.IsForegroundColorDefined() && - aRangeStyle.mUnderlineColor != aRangeStyle.mForegroundColor) { + (!aRangeStyle.IsForegroundColorDefined() || + aRangeStyle.mUnderlineColor != aRangeStyle.mForegroundColor)) { color = aRangeStyle.mUnderlineColor; } // If foreground color or background color is defined, the both colors From 50a3cf956cac9ae589d7647feaacff5460eea8f5 Mon Sep 17 00:00:00 2001 From: Nathan Froyd Date: Fri, 30 Oct 2015 15:12:25 -0400 Subject: [PATCH 049/113] Bug 1220714 - use UniquePtr instead of nsAutoArrayPtr in layout/; r=dholbert --- layout/generic/nsFrameSetFrame.cpp | 28 ++++++++++++++-------------- layout/style/CSSVariableResolver.cpp | 5 +++-- layout/style/nsCSSRuleProcessor.cpp | 4 ++-- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/layout/generic/nsFrameSetFrame.cpp b/layout/generic/nsFrameSetFrame.cpp index d78e8f45b01e..b85e27511395 100644 --- a/layout/generic/nsFrameSetFrame.cpp +++ b/layout/generic/nsFrameSetFrame.cpp @@ -430,12 +430,12 @@ void nsHTMLFramesetFrame::CalculateRowCol(nsPresContext* aPresContext, int32_t fixedTotal = 0; int32_t numFixed = 0; - nsAutoArrayPtr fixed(new int32_t[aNumSpecs]); + auto fixed = MakeUnique(aNumSpecs); int32_t numPercent = 0; - nsAutoArrayPtr percent(new int32_t[aNumSpecs]); + auto percent = MakeUnique(aNumSpecs); int32_t relativeSums = 0; int32_t numRelative = 0; - nsAutoArrayPtr relative(new int32_t[aNumSpecs]); + auto relative = MakeUnique(aNumSpecs); if (MOZ_UNLIKELY(!fixed || !percent || !relative)) { return; // NS_ERROR_OUT_OF_MEMORY @@ -467,7 +467,7 @@ void nsHTMLFramesetFrame::CalculateRowCol(nsPresContext* aPresContext, // scale the fixed sizes if they total too much (or too little and there aren't any percent or relative) if ((fixedTotal > aSize) || ((fixedTotal < aSize) && (0 == numPercent) && (0 == numRelative))) { - Scale(aSize, numFixed, fixed, aNumSpecs, aValues); + Scale(aSize, numFixed, fixed.get(), aNumSpecs, aValues); return; } @@ -482,7 +482,7 @@ void nsHTMLFramesetFrame::CalculateRowCol(nsPresContext* aPresContext, // scale the percent sizes if they total too much (or too little and there aren't any relative) if ((percentTotal > percentMax) || ((percentTotal < percentMax) && (0 == numRelative))) { - Scale(percentMax, numPercent, percent, aNumSpecs, aValues); + Scale(percentMax, numPercent, percent.get(), aNumSpecs, aValues); return; } @@ -497,7 +497,7 @@ void nsHTMLFramesetFrame::CalculateRowCol(nsPresContext* aPresContext, // scale the relative sizes if they take up too much or too little if (relativeTotal != relativeMax) { - Scale(relativeMax, numRelative, relative, aNumSpecs, aValues); + Scale(relativeMax, numRelative, relative.get(), aNumSpecs, aValues); } } @@ -852,10 +852,10 @@ nsHTMLFramesetFrame::Reflow(nsPresContext* aPresContext, CalculateRowCol(aPresContext, width, mNumCols, colSpecs, mColSizes.get()); CalculateRowCol(aPresContext, height, mNumRows, rowSpecs, mRowSizes.get()); - nsAutoArrayPtr verBordersVis; // vertical borders visibility - nsAutoArrayPtr verBorderColors; - nsAutoArrayPtr horBordersVis; // horizontal borders visibility - nsAutoArrayPtr horBorderColors; + UniquePtr verBordersVis; // vertical borders visibility + UniquePtr verBorderColors; + UniquePtr horBordersVis; // horizontal borders visibility + UniquePtr horBorderColors; nscolor borderColor = GetBorderColor(); nsFrameborder frameborder = GetFrameBorder(); @@ -865,15 +865,15 @@ nsHTMLFramesetFrame::Reflow(nsPresContext* aPresContext, PR_STATIC_ASSERT(NS_MAX_FRAMESET_SPEC_COUNT < UINT_MAX / sizeof(bool)); PR_STATIC_ASSERT(NS_MAX_FRAMESET_SPEC_COUNT < UINT_MAX / sizeof(nscolor)); - verBordersVis = new bool[mNumCols]; - verBorderColors = new nscolor[mNumCols]; + verBordersVis = MakeUnique(mNumCols); + verBorderColors = MakeUnique(mNumCols); for (int verX = 0; verX < mNumCols; verX++) { verBordersVis[verX] = false; verBorderColors[verX] = NO_COLOR; } - horBordersVis = new bool[mNumRows]; - horBorderColors = new nscolor[mNumRows]; + horBordersVis = MakeUnique(mNumRows); + horBorderColors = MakeUnique(mNumRows); for (int horX = 0; horX < mNumRows; horX++) { horBordersVis[horX] = false; horBorderColors[horX] = NO_COLOR; diff --git a/layout/style/CSSVariableResolver.cpp b/layout/style/CSSVariableResolver.cpp index a79e2b29a65c..0d25b747bc4d 100644 --- a/layout/style/CSSVariableResolver.cpp +++ b/layout/style/CSSVariableResolver.cpp @@ -12,6 +12,7 @@ #include "CSSVariableDeclarations.h" #include "CSSVariableValues.h" #include "mozilla/PodOperations.h" +#include "mozilla/UniquePtr.h" #include namespace mozilla { @@ -25,7 +26,7 @@ class EnumerateVariableReferencesData public: explicit EnumerateVariableReferencesData(CSSVariableResolver& aResolver) : mResolver(aResolver) - , mReferences(new bool[aResolver.mVariables.Length()]) + , mReferences(MakeUnique(aResolver.mVariables.Length())) { } @@ -66,7 +67,7 @@ private: // true, it indicates that the variable we have called // EnumerateVariableReferences for has a reference to the variable with // that ID. - nsAutoArrayPtr mReferences; + const UniquePtr mReferences; // Whether the variable we have called EnumerateVariableReferences for // references a variable that does not exist in the resolver. diff --git a/layout/style/nsCSSRuleProcessor.cpp b/layout/style/nsCSSRuleProcessor.cpp index dd1a8b30776f..64e20b5e1a4b 100644 --- a/layout/style/nsCSSRuleProcessor.cpp +++ b/layout/style/nsCSSRuleProcessor.cpp @@ -3860,13 +3860,13 @@ nsCSSRuleProcessor::RefreshRuleCascade(nsPresContext* aPresContext) // Sort the hash table of per-weight linked lists by weight. uint32_t weightCount = data.mRulesByWeight.EntryCount(); - nsAutoArrayPtr weightArray(new PerWeightData[weightCount]); + auto weightArray = MakeUnique(weightCount); int32_t j = 0; for (auto iter = data.mRulesByWeight.Iter(); !iter.Done(); iter.Next()) { auto entry = static_cast(iter.Get()); weightArray[j++] = entry->data; } - NS_QuickSort(weightArray, weightCount, sizeof(PerWeightData), + NS_QuickSort(weightArray.get(), weightCount, sizeof(PerWeightData), CompareWeightData, nullptr); // Put things into the rule hash. From 1a625d9f042ba0bb30cc453bae2a7b1ebd4961e7 Mon Sep 17 00:00:00 2001 From: Jason Orendorff Date: Mon, 26 Oct 2015 16:33:59 -0500 Subject: [PATCH 050/113] Bug 1216623 - Part 1: Rename some loop variables to avoid conflicts with ES6 scoping rules. r=fitzgen, r=ttaubert, r=MattN, r=gps. --HG-- extra : commitid : 3eLC5U91GM8 extra : rebase_source : 0933f07c19c0ba87fc34ddd9beaff4bb89e7c690 extra : amend_source : 76fa887d541bfd9f6592b6a6113d76b4df82dea0 --- browser/components/sessionstore/SessionStore.jsm | 4 ++-- browser/components/uitour/UITour.jsm | 4 ++-- devtools/client/tilt/TiltWorkerCrafter.js | 3 ++- devtools/shared/heapsnapshot/CensusUtils.js | 4 ++-- services/metrics/dataprovider.jsm | 8 ++++---- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/browser/components/sessionstore/SessionStore.jsm b/browser/components/sessionstore/SessionStore.jsm index 7b35dc5f4774..5a78783c27df 100644 --- a/browser/components/sessionstore/SessionStore.jsm +++ b/browser/components/sessionstore/SessionStore.jsm @@ -2934,8 +2934,8 @@ var SessionStoreInternal = { // In case we didn't collect/receive data for any tabs yet we'll have to // fill the array with at least empty tabData objects until |_tPos| or // we'll end up with |null| entries. - for (let tab of Array.slice(tabbrowser.tabs, 0, tab._tPos)) { - let emptyState = {entries: [], lastAccessed: tab.lastAccessed}; + for (let otherTab of Array.slice(tabbrowser.tabs, 0, tab._tPos)) { + let emptyState = {entries: [], lastAccessed: otherTab.lastAccessed}; this._windows[window.__SSi].tabs.push(emptyState); } diff --git a/browser/components/uitour/UITour.jsm b/browser/components/uitour/UITour.jsm index e3d759c60f29..c91a996daaa0 100644 --- a/browser/components/uitour/UITour.jsm +++ b/browser/components/uitour/UITour.jsm @@ -2162,8 +2162,8 @@ if (AppConstants.MOZ_SERVICES_HEALTHREPORT) { _serializeJSONDaily: function(data) { let result = {_v: this.version }; - for (let [field, data] of data) { - result[field] = data; + for (let [field, value] of data) { + result[field] = value; } return result; diff --git a/devtools/client/tilt/TiltWorkerCrafter.js b/devtools/client/tilt/TiltWorkerCrafter.js index 8c224a23d618..da31b5abf884 100644 --- a/devtools/client/tilt/TiltWorkerCrafter.js +++ b/devtools/client/tilt/TiltWorkerCrafter.js @@ -265,7 +265,8 @@ self.random = { { let h, n = 0xefc8249d; - for (let i = 0, data = data.toString(), len = data.length; i < len; i++) { + data = data.toString(); + for (let i = 0, len = data.length; i < len; i++) { n += data.charCodeAt(i); h = 0.02519603282416938 * n; n = h >>> 0; diff --git a/devtools/shared/heapsnapshot/CensusUtils.js b/devtools/shared/heapsnapshot/CensusUtils.js index 050d96527ad0..f0fb77aa08bf 100644 --- a/devtools/shared/heapsnapshot/CensusUtils.js +++ b/devtools/shared/heapsnapshot/CensusUtils.js @@ -126,8 +126,8 @@ function recursiveWalk(breakdown, edge, report, visitor) { visitor.exit(breakdown, report, edge); } else { visitor.enter(breakdown, report, edge); - for (let { edge, referent, breakdown } of getReportEdges(breakdown, report)) { - recursiveWalk(breakdown, edge, referent, visitor); + for (let { edge, referent, breakdown: subBreakdown } of getReportEdges(breakdown, report)) { + recursiveWalk(subBreakdown, edge, referent, visitor); } visitor.exit(breakdown, report, edge); } diff --git a/services/metrics/dataprovider.jsm b/services/metrics/dataprovider.jsm index 9b8a8466f144..0cb5d7fcdf7c 100644 --- a/services/metrics/dataprovider.jsm +++ b/services/metrics/dataprovider.jsm @@ -370,7 +370,7 @@ Measurement.prototype = Object.freeze({ _serializeJSONSingular: function (data) { let result = {"_v": this.version}; - for (let [field, data] of data) { + for (let [field, value] of data) { // There could be legacy fields in storage we no longer care about. if (!this.shouldIncludeField(field)) { continue; @@ -381,7 +381,7 @@ Measurement.prototype = Object.freeze({ switch (type) { case this.storage.FIELD_LAST_NUMERIC: case this.storage.FIELD_LAST_TEXT: - result[field] = data[1]; + result[field] = value[1]; break; case this.storage.FIELD_DAILY_COUNTER: @@ -402,7 +402,7 @@ Measurement.prototype = Object.freeze({ _serializeJSONDay: function (data) { let result = {"_v": this.version}; - for (let [field, data] of data) { + for (let [field, value] of data) { if (!this.shouldIncludeField(field)) { continue; } @@ -415,7 +415,7 @@ Measurement.prototype = Object.freeze({ case this.storage.FIELD_DAILY_DISCRETE_TEXT: case this.storage.FIELD_DAILY_LAST_NUMERIC: case this.storage.FIELD_DAILY_LAST_TEXT: - result[field] = data; + result[field] = value; break; case this.storage.FIELD_LAST_NUMERIC: From 38d16faf668632015cc9b66592d4d5b62918a1e2 Mon Sep 17 00:00:00 2001 From: Jason Orendorff Date: Tue, 20 Oct 2015 11:52:01 -0500 Subject: [PATCH 051/113] Bug 1216623 - Part 2: In `for (let ...)` loops, evaluate initializers in the scope of the variables being initialized. r=Waldo. --HG-- extra : commitid : 7hDOQwrCy29 extra : rebase_source : 83dfe46c9e0109d50601ff3c3b9da2e0df2fe844 extra : amend_source : 6bdc771ff717ad82f251e2b58236edceeb6b0157 --- js/src/builtin/ReflectParse.cpp | 46 ++--- js/src/frontend/BytecodeEmitter.cpp | 74 +++++--- js/src/frontend/BytecodeEmitter.h | 2 +- js/src/frontend/FoldConstants.cpp | 2 - js/src/frontend/FullParseHandler.h | 4 - js/src/frontend/NameFunctions.cpp | 1 - js/src/frontend/ParseNode.cpp | 1 - js/src/frontend/ParseNode.h | 1 - js/src/frontend/Parser.cpp | 159 +++++------------- js/src/jit-test/tests/basic/bug646968-3.js | 8 +- js/src/jit-test/tests/basic/bug646968-4.js | 8 +- js/src/jit-test/tests/basic/bug646968-6.js | 16 ++ .../tests/basic/letLegacyForOfOrInScope.js | 5 - js/src/jit-test/tests/basic/testLet.js | 41 +++-- .../ecma_6/LexicalEnvironment/bug-1216623.js | 19 +++ .../ecma_6/LexicalEnvironment/for-loop.js | 6 +- .../destructuring-variable-declarations.js | 4 +- 17 files changed, 190 insertions(+), 207 deletions(-) create mode 100644 js/src/jit-test/tests/basic/bug646968-6.js delete mode 100644 js/src/jit-test/tests/basic/letLegacyForOfOrInScope.js create mode 100644 js/src/tests/ecma_6/LexicalEnvironment/bug-1216623.js diff --git a/js/src/builtin/ReflectParse.cpp b/js/src/builtin/ReflectParse.cpp index b50f8906b1ec..da397a5cb5b5 100644 --- a/js/src/builtin/ReflectParse.cpp +++ b/js/src/builtin/ReflectParse.cpp @@ -2491,8 +2491,9 @@ ASTSerializer::forInit(ParseNode* pn, MutableHandleValue dst) return true; } - return (pn->isKind(PNK_VAR)) - ? variableDeclaration(pn, false, dst) + bool lexical = pn->isKind(PNK_LET) || pn->isKind(PNK_CONST); + return (lexical || pn->isKind(PNK_VAR)) + ? variableDeclaration(pn, lexical, dst) : expression(pn, dst); } @@ -2641,32 +2642,31 @@ ASTSerializer::statement(ParseNode* pn, MutableHandleValue dst) if (!statement(pn->pn_right, &stmt)) return false; - if (head->isKind(PNK_FORIN)) { + if (head->isKind(PNK_FORIN) || head->isKind(PNK_FOROF)) { RootedValue var(cx); - return (!head->pn_kid1 - ? pattern(head->pn_kid2, &var) - : head->pn_kid1->isKind(PNK_LEXICALSCOPE) - ? variableDeclaration(head->pn_kid1->pn_expr, true, &var) - : variableDeclaration(head->pn_kid1, false, &var)) && - forIn(pn, head, var, stmt, dst); - } - - if (head->isKind(PNK_FOROF)) { - RootedValue var(cx); - return (!head->pn_kid1 - ? pattern(head->pn_kid2, &var) - : head->pn_kid1->isKind(PNK_LEXICALSCOPE) - ? variableDeclaration(head->pn_kid1->pn_expr, true, &var) - : variableDeclaration(head->pn_kid1, false, &var)) && - forOf(pn, head, var, stmt, dst); + if (!head->pn_kid1) { + if (!pattern(head->pn_kid2, &var)) + return false; + } else if (head->pn_kid1->isKind(PNK_LEXICALSCOPE)) { + if (!variableDeclaration(head->pn_kid1->pn_expr, true, &var)) + return false; + } else { + if (!variableDeclaration(head->pn_kid1, + head->pn_kid1->isKind(PNK_LET) || + head->pn_kid1->isKind(PNK_CONST), + &var)) + { + return false; + } + } + if (head->isKind(PNK_FORIN)) + return forIn(pn, head, var, stmt, dst); + return forOf(pn, head, var, stmt, dst); } RootedValue init(cx), test(cx), update(cx); - return forInit(head->pn_kid1 && !head->pn_kid1->isKind(PNK_FRESHENBLOCK) - ? head->pn_kid1 - : nullptr, - &init) && + return forInit(head->pn_kid1, &init) && optExpression(head->pn_kid2, &test) && optExpression(head->pn_kid3, &update) && builder.forStatement(init, test, update, stmt, &pn->pn_pos, dst); diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 81f0eab44e75..712ec2ac683c 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -2323,7 +2323,6 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) case PNK_FORIN: // by PNK_FOR case PNK_FOROF: // by PNK_FOR case PNK_FORHEAD: // by PNK_FOR - case PNK_FRESHENBLOCK: // by PNK_FOR case PNK_CLASSMETHOD: // by PNK_CLASS case PNK_CLASSNAMES: // by PNK_CLASS case PNK_CLASSMETHODLIST: // by PNK_CLASS @@ -5289,6 +5288,15 @@ BytecodeEmitter::emitIterator() bool BytecodeEmitter::emitForInOrOfVariables(ParseNode* pn, bool* letDecl) { + // ES6 specifies that loop variables get a fresh binding in each iteration. + // This is currently implemented for C-style for(;;) loops, but not + // for-in/of loops, though a similar approach should work. See bug 449811. + // + // In `for (let x in/of EXPR)`, ES6 specifies that EXPR is evaluated in a + // scope containing an uninitialized `x`. If EXPR accesses `x`, we should + // get a ReferenceError due to the TDZ violation. This is not yet + // implemented. See bug 1069480. + *letDecl = pn->isKind(PNK_LEXICALSCOPE); MOZ_ASSERT_IF(*letDecl, pn->isLexical()); @@ -5318,7 +5326,6 @@ BytecodeEmitter::emitForInOrOfVariables(ParseNode* pn, bool* letDecl) return true; } - bool BytecodeEmitter::emitForOf(StmtType type, ParseNode* pn, ptrdiff_t top) { @@ -5591,8 +5598,9 @@ BytecodeEmitter::emitForIn(ParseNode* pn, ptrdiff_t top) return true; } +/* C-style `for (init; cond; update) ...` loop. */ bool -BytecodeEmitter::emitNormalFor(ParseNode* pn, ptrdiff_t top) +BytecodeEmitter::emitCStyleFor(ParseNode* pn, ptrdiff_t top) { LoopStmtInfo stmtInfo(cx); pushLoopStatement(&stmtInfo, StmtType::FOR_LOOP, top); @@ -5600,28 +5608,48 @@ BytecodeEmitter::emitNormalFor(ParseNode* pn, ptrdiff_t top) ParseNode* forHead = pn->pn_left; ParseNode* forBody = pn->pn_right; - /* C-style for (init; cond; update) ... loop. */ + // If the head of this for-loop declared any lexical variables, the parser + // wrapped this PNK_FOR node in a PNK_LEXICALSCOPE representing the + // implicit scope of those variables. By the time we get here, we have + // already entered that scope. So far, so good. + // + // ### Scope freshening + // + // Each iteration of a `for (let V...)` loop creates a fresh loop variable + // binding for V, even if the loop is a C-style `for(;;)` loop: + // + // var funcs = []; + // for (let i = 0; i < 2; i++) + // funcs.push(function() { return i; }); + // assertEq(funcs[0](), 0); // the two closures capture... + // assertEq(funcs[1](), 1); // ...two different `i` bindings + // + // This is implemented by "freshening" the implicit block -- changing the + // scope chain to a fresh clone of the instantaneous block object -- each + // iteration, just before evaluating the "update" in for(;;) loops. + // + // No freshening occurs in `for (const ...;;)` as there's no point: you + // can't reassign consts. This is observable through the Debugger API. (The + // ES6 spec also skips cloning the environment in this case.) bool forLoopRequiresFreshening = false; if (ParseNode* init = forHead->pn_kid1) { - if (init->isKind(PNK_FRESHENBLOCK)) { - // The loop's init declaration was hoisted into an enclosing lexical - // scope node. Note that the block scope must be freshened each - // iteration. - forLoopRequiresFreshening = true; - } else { - emittingForInit = true; - if (!updateSourceCoordNotes(init->pn_pos.begin)) - return false; - if (!emitTree(init)) - return false; - emittingForInit = false; + forLoopRequiresFreshening = init->isKind(PNK_LET); - if (!init->isKind(PNK_VAR) && !init->isKind(PNK_LET) && !init->isKind(PNK_CONST)) { - // 'init' is an expression, not a declaration. emitTree left - // its value on the stack. - if (!emit1(JSOP_POP)) - return false; - } + // Emit the `init` clause, whether it's an expression or a variable + // declaration. (The loop variables were hoisted into an enclosing + // scope, but we still need to emit code for the initializers.) + emittingForInit = true; + if (!updateSourceCoordNotes(init->pn_pos.begin)) + return false; + if (!emitTree(init)) + return false; + emittingForInit = false; + + if (!init->isKind(PNK_VAR) && !init->isKind(PNK_LET) && !init->isKind(PNK_CONST)) { + // 'init' is an expression, not a declaration. emitTree left its + // value on the stack. + if (!emit1(JSOP_POP)) + return false; } } @@ -5749,7 +5777,7 @@ BytecodeEmitter::emitFor(ParseNode* pn, ptrdiff_t top) return emitForOf(StmtType::FOR_OF_LOOP, pn, top); MOZ_ASSERT(pn->pn_left->isKind(PNK_FORHEAD)); - return emitNormalFor(pn, top); + return emitCStyleFor(pn, top); } MOZ_NEVER_INLINE bool diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 2ce95dbb5ba5..1c2cff91777d 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -586,7 +586,7 @@ struct BytecodeEmitter bool emitFor(ParseNode* pn, ptrdiff_t top); bool emitForIn(ParseNode* pn, ptrdiff_t top); bool emitForInOrOfVariables(ParseNode* pn, bool* letDecl); - bool emitNormalFor(ParseNode* pn, ptrdiff_t top); + bool emitCStyleFor(ParseNode* pn, ptrdiff_t top); bool emitWhile(ParseNode* pn, ptrdiff_t top); bool emitBreak(PropertyName* label); diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp index accde97bc769..edeb85f57027 100644 --- a/js/src/frontend/FoldConstants.cpp +++ b/js/src/frontend/FoldConstants.cpp @@ -408,7 +408,6 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result) case PNK_FORIN: case PNK_FOROF: case PNK_FORHEAD: - case PNK_FRESHENBLOCK: case PNK_CLASSMETHOD: case PNK_CLASSMETHODLIST: case PNK_CLASSNAMES: @@ -1710,7 +1709,6 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser& parser, bo case PNK_GENERATOR: case PNK_EXPORT_BATCH_SPEC: case PNK_OBJECT_PROPERTY_NAME: - case PNK_FRESHENBLOCK: case PNK_POSHOLDER: MOZ_ASSERT(pn->isArity(PN_NULLARY)); return true; diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index bd045e559745..d6dd032efed5 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -571,10 +571,6 @@ class FullParseHandler return new_(kind, JSOP_NOP, pn1, pn2, pn3, pos); } - ParseNode* newFreshenBlock(const TokenPos& pos) { - return new_(PNK_FRESHENBLOCK, pos); - } - ParseNode* newSwitchStatement(uint32_t begin, ParseNode* discriminant, ParseNode* caseList) { TokenPos pos(begin, caseList->pn_pos.end); return new_(PNK_SWITCH, JSOP_NOP, pos, discriminant, caseList); diff --git a/js/src/frontend/NameFunctions.cpp b/js/src/frontend/NameFunctions.cpp index c226578e868f..667afaa271c7 100644 --- a/js/src/frontend/NameFunctions.cpp +++ b/js/src/frontend/NameFunctions.cpp @@ -374,7 +374,6 @@ class NameResolver case PNK_CONTINUE: case PNK_DEBUGGER: case PNK_EXPORT_BATCH_SPEC: - case PNK_FRESHENBLOCK: case PNK_OBJECT_PROPERTY_NAME: case PNK_POSHOLDER: MOZ_ASSERT(cur->isArity(PN_NULLARY)); diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp index 694600a62acd..cb35d11ed661 100644 --- a/js/src/frontend/ParseNode.cpp +++ b/js/src/frontend/ParseNode.cpp @@ -214,7 +214,6 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack) case PNK_DEBUGGER: case PNK_EXPORT_BATCH_SPEC: case PNK_OBJECT_PROPERTY_NAME: - case PNK_FRESHENBLOCK: case PNK_POSHOLDER: MOZ_ASSERT(pn->isArity(PN_NULLARY)); MOZ_ASSERT(!pn->isUsed(), "handle non-trivial cases separately"); diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index 2785f3e2a284..5d7771cd5d78 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -165,7 +165,6 @@ class PackedScopeCoordinate F(FORIN) \ F(FOROF) \ F(FORHEAD) \ - F(FRESHENBLOCK) \ F(ARGSBODY) \ F(SPREAD) \ F(MUTATEPROTO) \ diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 0d932ef1d4e8..7c05d3dfe1e6 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -4063,7 +4063,8 @@ Parser::pushLetScope(HandleStaticBlockObject blockObj, AutoPus template <> SyntaxParseHandler::Node -Parser::pushLetScope(HandleStaticBlockObject blockObj, AutoPushStmtInfoPC& stmt) +Parser::pushLetScope(HandleStaticBlockObject blockObj, + AutoPushStmtInfoPC& stmt) { JS_ALWAYS_FALSE(abortIfSyntaxParser()); return SyntaxParseHandler::NodeFailure; @@ -5187,19 +5188,37 @@ Parser::forStatement(YieldHandling yieldHandling) MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_AFTER_FOR); - /* - * True if we have 'for (var/let/const ...)'. - */ + // True if we have 'for (var/let/const ...)'. bool isForDecl = false; + // The next three variables are used to implement `for (let/const ...)`. + // + // We generate an implicit block, wrapping the whole loop, to store loop + // variables declared this way. Note that if the loop uses `for (var...)` + // instead, those variables go on some existing enclosing scope, so no + // implicit block scope is created. + // + // All three variables remain null/none if the loop is any other form. + // + // blockObj is the static block object for the implicit block scope. + RootedStaticBlockObject blockObj(context); + + // letStmt is the BLOCK StmtInfo for the implicit block. + // + // Caution: `letStmt.emplace()` creates some Rooted objects. Rooteds must + // be created/destroyed in FIFO order. Therefore adding a Rooted in this + // function, between this point and the .emplace() call below, would trip + // assertions. + Maybe letStmt; + + // The PNK_LEXICALSCOPE node containing blockObj's ObjectBox. + ParseNode* forLetImpliedBlock = nullptr; + // True if a 'let' token at the head is parsed as an identifier instead of // as starting a declaration. bool letIsIdentifier = false; - /* Non-null when isForDecl is true for a 'for (let ...)' statement. */ - RootedStaticBlockObject blockObj(context); - - /* Set to 'x' in 'for (x ;... ;...)' or 'for (x in ...)'. */ + // Set to 'x' in 'for (x; ...; ...)' or 'for (x in ...)'. ParseNode* pn1; TokenStream::Modifier modifier = TokenStream::Operand; @@ -5249,9 +5268,19 @@ Parser::forStatement(YieldHandling yieldHandling) // Initialize the enclosing scope manually for the call to // |variables| below. + blockObj = StaticBlockObject::create(context); + if (!blockObj) + return null(); blockObj->initEnclosingScopeFromParser(pc->innermostStaticScope()); + letStmt.emplace(*this, StmtType::BLOCK); + forLetImpliedBlock = pushLetScope(blockObj, *letStmt); + if (!forLetImpliedBlock) + return null(); + (*letStmt)->isForLetBlock = true; + + MOZ_ASSERT(CurrentLexicalStaticBlock(pc) == blockObj); pn1 = variables(yieldHandling, constDecl ? PNK_CONST : PNK_LET, InForInit, - nullptr, blockObj, DontHoistVars); + nullptr, blockObj, HoistVars); } else { pn1 = expr(InProhibited, yieldHandling, TripledotProhibited); } @@ -5269,60 +5298,10 @@ Parser::forStatement(YieldHandling yieldHandling) } MOZ_ASSERT_IF(isForDecl, pn1->isArity(PN_LIST)); - MOZ_ASSERT(!!blockObj == (isForDecl && (pn1->isOp(JSOP_DEFLET) || pn1->isOp(JSOP_DEFCONST)))); - - // If the head of a for-loop declares any lexical variables, we generate an - // implicit block to store them. We implement this by desugaring. These: - // - // for (let/const ; ; ) - // for (let in ) - // for (let of ) - // - // transform into roughly the same parse trees as these (using deprecated - // let-block syntax): - // - // let () { for (; ; ) } - // let () { for ( in ) } - // let () { for ( of ) } - // - // This desugaring is not ES6 compliant. Initializers in the head of a - // let-block are evaluated *outside* the scope of the variables being - // initialized. ES6 mandates that they be evaluated in the same scope, - // triggering used-before-initialization temporal dead zone errors as - // necessary. See bug 1216623 on scoping and bug 1069480 on TDZ. - // - // Additionally, in ES6, each iteration of a for-loop creates a fresh - // binding of the loop variables. For example: - // - // var funcs = []; - // for (let i = 0; i < 2; i++) - // funcs.push(function() { return i; }); - // assertEq(funcs[0](), 0); // the two closures capture... - // assertEq(funcs[1](), 1); // ...two different `i` bindings - // - // These semantics are implemented by "freshening" the implicit block -- - // changing the scope chain to a fresh clone of the instantaneous block - // object -- each iteration, just before evaluating the "update" in - // for(;;) loops. We don't implement this freshening for for-in/of loops - // yet: bug 449811. - // - // No freshening occurs in `for (const ...;;)` as there's no point: you - // can't reassign consts. This is observable through the Debugger API. (The - // ES6 spec also skips cloning the environment in this case.) - // - // If the for-loop head includes a lexical declaration, then we create an - // implicit block scope, and: - // - // * forLetImpliedBlock is the node for the implicit block scope. - // * forLetDecl is the node for the decl 'let/const '. - // - // Otherwise both are null. - ParseNode* forLetImpliedBlock = nullptr; - ParseNode* forLetDecl = nullptr; + MOZ_ASSERT(letStmt.isSome() == (isForDecl && (pn1->isOp(JSOP_DEFLET) || pn1->isOp(JSOP_DEFCONST)))); // If there's an |in| keyword here, it's a for-in loop, by dint of careful // parsing of |pn1|. - Maybe letStmt; /* used if blockObj != nullptr. */ ParseNode* pn2; /* forHead->pn_kid2 */ ParseNode* pn3; /* forHead->pn_kid3 */ ParseNodeKind headKind = PNK_FORHEAD; @@ -5393,7 +5372,7 @@ Parser::forStatement(YieldHandling yieldHandling) } } else { /* Not a declaration. */ - MOZ_ASSERT(!blockObj); + MOZ_ASSERT(!letStmt); pn2 = pn1; pn1 = nullptr; @@ -5408,27 +5387,10 @@ Parser::forStatement(YieldHandling yieldHandling) return null(); modifier = TokenStream::None; - if (blockObj) { - /* - * Now that the pn3 has been parsed, push the let scope. To hold - * the blockObj for the emitter, wrap the PNK_LEXICALSCOPE node - * created by pushLetScope around the for's initializer. This also - * serves to indicate the let-decl to the emitter. - */ - letStmt.emplace(*this, StmtType::BLOCK); - ParseNode* block = pushLetScope(blockObj, *letStmt); - if (!block) - return null(); - (*letStmt)->isForLetBlock = true; - block->pn_expr = pn1; - block->pn_pos = pn1->pn_pos; - pn1 = block; - } - if (isForDecl) { /* * pn2 is part of a declaration. Make a copy that can be passed to - * EmitAssignment. Take care to do this after pushLetScope. + * BytecodeEmitter::emitAssignment. */ pn2 = cloneLeftHandSide(pn2); if (!pn2) @@ -5450,47 +5412,13 @@ Parser::forStatement(YieldHandling yieldHandling) MOZ_ASSERT(headKind == PNK_FORHEAD); - if (blockObj) { + if (letStmt) { // Ensure here that the previously-unchecked assignment mandate for // const declarations holds. if (!checkForHeadConstInitializers(pn1)) { report(ParseError, false, nullptr, JSMSG_BAD_CONST_DECL); return null(); } - - // Desugar - // - // for (let INIT; TEST; UPDATE) STMT - // - // into - // - // let (INIT) { for (; TEST; UPDATE) STMT } - // - // to provide a block scope for INIT. - letStmt.emplace(*this, StmtType::BLOCK); - forLetImpliedBlock = pushLetScope(blockObj, *letStmt); - if (!forLetImpliedBlock) - return null(); - (*letStmt)->isForLetBlock = true; - - forLetDecl = pn1; - - // The above transformation isn't enough to implement |INIT| - // scoping, because each loop iteration must see separate bindings - // of |INIT|. We handle this by replacing the block on the scope - // chain with a new block, copying the old one's contents, each - // iteration. We supply a special PNK_FRESHENBLOCK node as the - // |let INIT| node for |for(let INIT;;)| loop heads to distinguish - // such nodes from *actual*, non-desugared use of the above syntax. - // (We don't do this for PNK_CONST nodes because the spec says no - // freshening happens -- observable with the Debugger API.) - if (pn1->isKind(PNK_CONST)) { - pn1 = nullptr; - } else { - pn1 = handler.newFreshenBlock(pn1->pn_pos); - if (!pn1) - return null(); - } } /* Parse the loop condition or null into pn2. */ @@ -5542,7 +5470,7 @@ Parser::forStatement(YieldHandling yieldHandling) if (forLetImpliedBlock) { forLetImpliedBlock->pn_expr = forLoop; forLetImpliedBlock->pn_pos = forLoop->pn_pos; - return handler.newLetBlock(forLetDecl, forLetImpliedBlock, forLoop->pn_pos); + return forLetImpliedBlock; } return forLoop; } @@ -8293,6 +8221,7 @@ Parser::comprehensionFor(GeneratorKind comprehensionKind) RootedStaticBlockObject blockObj(context, StaticBlockObject::create(context)); if (!blockObj) return null(); + // Initialize the enclosing scope manually for the call to |bind| // below, which is before the call to |pushLetScope|. blockObj->initEnclosingScopeFromParser(pc->innermostStaticScope()); diff --git a/js/src/jit-test/tests/basic/bug646968-3.js b/js/src/jit-test/tests/basic/bug646968-3.js index ee4ed9e85068..a02cb3f682cb 100644 --- a/js/src/jit-test/tests/basic/bug646968-3.js +++ b/js/src/jit-test/tests/basic/bug646968-3.js @@ -1,16 +1,16 @@ -var s, x = 0; +var s, v = "NOPE"; s = ''; -for (let x = x; x < 3; x++) +for (let v = 0, x = v; x < 3; x++) s += x; assertEq(s, '012'); s = ''; -for (let x = eval('x'); x < 3; x++) +for (let v = 0, x = eval('v'); x < 3; x++) s += x; assertEq(s, '012'); s = '' -for (let x = function () { with ({}) return x; }(); x < 3; x++) +for (let v = 0, x = function () { with ({}) return v; }(); x < 3; x++) s += x; assertEq(s, '012'); diff --git a/js/src/jit-test/tests/basic/bug646968-4.js b/js/src/jit-test/tests/basic/bug646968-4.js index ffb7ffed08a8..4d0e2c15fbb6 100644 --- a/js/src/jit-test/tests/basic/bug646968-4.js +++ b/js/src/jit-test/tests/basic/bug646968-4.js @@ -1,4 +1,8 @@ -var s = '', x = {a: 1, b: 2, c: 3}; +// Scoping: `x` in the head of a `for (let x...)` loop refers to the loop variable. + +// For now, this means it evaluates to undefined. It ought to throw a +// ReferenceError instead, but the TDZ isn't implemented here (bug 1069480). +var s = "", x = {a: 1, b: 2, c: 3}; for (let x in eval('x')) s += x; -assertEq(s, 'abc'); +assertEq(s, ""); diff --git a/js/src/jit-test/tests/basic/bug646968-6.js b/js/src/jit-test/tests/basic/bug646968-6.js new file mode 100644 index 000000000000..11b1fa1c18e5 --- /dev/null +++ b/js/src/jit-test/tests/basic/bug646968-6.js @@ -0,0 +1,16 @@ +// In `for (let x = EXPR; ;)`, if `x` appears within EXPR, it refers to the +// loop variable. Actually doing this is typically a TDZ error. + +load(libdir + "asserts.js"); + +assertThrowsInstanceOf(() => { + for (let x = x; null.foo; null.foo++) {} +}, ReferenceError); + +assertThrowsInstanceOf(() => { + for (let x = eval('x'); null.foo; null.foo++) {} +}, ReferenceError); + +assertThrowsInstanceOf(() => { + for (let x = function () { with ({}) return x; }(); null.foo; null.foo++) {} +}, ReferenceError); diff --git a/js/src/jit-test/tests/basic/letLegacyForOfOrInScope.js b/js/src/jit-test/tests/basic/letLegacyForOfOrInScope.js deleted file mode 100644 index d1c0764dc5d2..000000000000 --- a/js/src/jit-test/tests/basic/letLegacyForOfOrInScope.js +++ /dev/null @@ -1,5 +0,0 @@ -var x = "foobar"; -{ for (let x of x) assertEq(x.length, 1, "second x refers to outer x"); } - -var x = "foobar"; -{ for (let x in x) assertEq(x.length, 1, "second x refers to outer x"); } diff --git a/js/src/jit-test/tests/basic/testLet.js b/js/src/jit-test/tests/basic/testLet.js index a79c8a521e25..da00dec2dd73 100644 --- a/js/src/jit-test/tests/basic/testLet.js +++ b/js/src/jit-test/tests/basic/testLet.js @@ -3,7 +3,8 @@ var otherGlobal = newGlobal(); function test(str, arg, result) { arg = arg || 'ponies'; - result = result || 'ponies'; + if (arguments.length < 3) + result = 'ponies'; var fun = new Function('x', str); @@ -136,11 +137,7 @@ test('for (let y = 1;; ++y) {return x;}'); test('for (let y = 1; ++y;) {return x;}'); test('for (let [[a, [b, c]]] = [[x, []]];;) {return a;}'); test('var sum = 0;for (let y = x; y < 4; ++y) {sum += y;}return sum;', 1, 6); -test('var sum = 0;for (let x = x, y = 10; x < 4; ++x) {sum += x;}return sum;', 1, 6); -test('var sum = 0;for (let x = x; x < 4; ++x) {sum += x;}return x;', 1, 1); -test('var sum = 0;for (let x = eval("x"); x < 4; ++x) {sum += x;}return sum;', 1, 6); -test('var sum = 0;for (let x = x; eval("x") < 4; ++x) {sum += eval("x");}return sum;', 1, 6); -test('var sum = 0;for (let x = eval("x"); eval("x") < 4; ++x) {sum += eval("x");}return sum;', 1, 6); +test('var sum = 0;for (let x = 1; eval("x") < 4; ++x) {sum += eval("x");}return sum;', 1, 6); test('for (var y = 1;;) {return x;}'); test('for (var y = 1;; ++y) {return x;}'); test('for (var y = 1; ++y;) {return x;}'); @@ -151,8 +148,6 @@ test('var sum = 0;for (var X = x; X < 4; ++X) {sum += X;}return x;', 1, 1); test('var sum = 0;for (var X = eval("x"); X < 4; ++X) {sum += X;}return sum;', 1, 6); test('var sum = 0;for (var X = x; eval("X") < 4; ++X) {sum += eval("X");}return sum;', 1, 6); test('var sum = 0;for (var X = eval("x"); eval("X") < 4; ++X) {sum += eval("X");}return sum;', 1, 6); -test('try {for (let x = eval("throw x");;) {}} catch (e) {return e;}'); -test('try {for (let x = x + "s"; eval("throw x");) {}} catch (e) {return e;}', 'ponie'); test('for (let y = x;;) {let x;return y;}'); test('for (let y = x;;) {let y;return x;}'); test('for (let y;;) {let y;return x;}'); @@ -170,28 +165,31 @@ test('for (let i in x) {return x;}'); test('for (let i in x) {let y;return x;}'); test('for each (let [a, b] in x) {let y;return x;}'); test('for (let i in x) {let i = x;return i;}'); -test('for each (let [x, y] in x) {return x + y;}', [['ponies', '']]); -test('for each (let [{0: x, 1: y}, z] in x) {return x + y + z;}', [[['po','nies'], '']]); test('var s = "";for (let a in x) {for (let b in x) {s += a + b;}}return s;', [1,2], '00011011'); test('var res = "";for (let i in x) {res += x[i];}return res;'); test('var res = "";for (var i in x) {res += x[i];}return res;'); -test('for each (let {x: y, y: x} in [{x: x, y: x}]) {return y;}'); -test('for (let x in eval("x")) {return x;}', {ponies:true}); -test('for (let x in x) {return eval("x");}', {ponies:true}); -test('for (let x in eval("x")) {return eval("x");}', {ponies:true}); +isParseError('for ((let (x = {y: true}) x).y in eval("x")) {return eval("x");}'); test('for (let i in x) {break;}return x;'); test('for (let i in x) {break;}return eval("x");'); -test('for (let x in x) {break;}return x;'); -test('for (let x in x) {break;}return eval("x");'); test('a:for (let i in x) {for (let j in x) {break a;}}return x;'); test('a:for (let i in x) {for (let j in x) {break a;}}return eval("x");'); test('var j;for (let i in x) {j = i;break;}return j;', {ponies:true}); -test('try {for (let x in eval("throw x")) {}} catch (e) {return e;}'); -test('try {for each (let x in x) {eval("throw x");}} catch (e) {return e;}', ['ponies']); isParseError('for (let [x, x] in o) {}'); isParseError('for (let [x, y, x] in o) {}'); isParseError('for (let [x, [y, [x]]] in o) {}'); +// for(let ... in ...) scoping bugs (bug 1069480) +test('for each (let [x, y] in x) {return x + y;}', [['ponies', '']], undefined); +test('for each (let [{0: x, 1: y}, z] in x) {return x + y + z;}', [[['po','nies'], '']], undefined); +test('for (let x in eval("x")) {return x;}', {ponies:true}, undefined); +test('for (let x in x) {return eval("x");}', {ponies:true}, undefined); +test('for (let x in eval("x")) {return eval("x");}', {ponies:true}, undefined); +test('for (let x in x) {break;}return x;'); +test('for (let x in x) {break;}return eval("x");'); +test('try {for (let x in eval("throw x")) {}} catch (e) {return e;}', undefined, undefined); +test('try {for each (let x in x) {eval("throw x");}} catch (e) {return e;}', ['ponies'], undefined); +test('for each (let {x: y, y: x} in [{x: x, y: x}]) {return y;}', undefined, undefined); + // genexps test('return (i for (i in x)).next();', {ponies:true}); test('return (eval("i") for (i in x)).next();', {ponies:true}); @@ -222,6 +220,13 @@ isReferenceError('let {x} = {x:x};'); isReferenceError('switch (x) {case 3:let x;break;default:if (x === undefined) {return "ponies";}}'); isReferenceError('let x = function() {} ? x() : function() {}'); isReferenceError('(function() { let x = (function() { return x }()); }())'); +isReferenceError('var sum = 0;for (let x = x, y = 10; x < 4; ++x) {sum += x;}return sum;'); +isReferenceError('var sum = 0;for (let x = x; x < 4; ++x) {sum += x;}return x;'); +isReferenceError('var sum = 0;for (let x = eval("x"); x < 4; ++x) {sum += x;}return sum;'); +isReferenceError('var sum = 0;for (let x = x; eval("x") < 4; ++x) {sum += eval("x");}return sum;'); +isReferenceError('var sum = 0;for (let x = eval("x"); eval("x") < 4; ++x) {sum += eval("x");}return sum;'); +isReferenceError('for (let x = eval("throw x");;) {}'); +isReferenceError('for (let x = x + "s"; eval("throw x");) {}'); // redecl with function statements isParseError('let a; function a() {}'); diff --git a/js/src/tests/ecma_6/LexicalEnvironment/bug-1216623.js b/js/src/tests/ecma_6/LexicalEnvironment/bug-1216623.js new file mode 100644 index 000000000000..cbdbe9722e7b --- /dev/null +++ b/js/src/tests/ecma_6/LexicalEnvironment/bug-1216623.js @@ -0,0 +1,19 @@ +// Scoping in the head of for(let;;) statements. + +let x = 0; +for (let i = 0, a = () => i; i < 4; i++) { + assertEq(i, x++); + assertEq(a(), 0); +} +assertEq(x, 4); + +x = 11; +let q = 0; +for (let {[++q]: r} = [0, 11, 22], s = () => r; r < 13; r++) { + assertEq(r, x++); + assertEq(s(), 11); +} +assertEq(x, 13); +assertEq(q, 1); + +reportCompare(0, 0); diff --git a/js/src/tests/ecma_6/LexicalEnvironment/for-loop.js b/js/src/tests/ecma_6/LexicalEnvironment/for-loop.js index db36323488a1..8db10a4e0a7e 100644 --- a/js/src/tests/ecma_6/LexicalEnvironment/for-loop.js +++ b/js/src/tests/ecma_6/LexicalEnvironment/for-loop.js @@ -78,11 +78,7 @@ var outer = "OUTER V IGNORE"; var save; for (let outer = (save = function() { return outer; }); ; ) break; -assertEq(save(), "OUTER V IGNORE", - "this is actually a bug: fix for(;;) loops to evaluate init RHSes " + - "in the block scope containing all the LHS bindings!"); - - +assertEq(save(), save); var funcs = []; function t(i, name, expect) diff --git a/js/src/tests/js1_8_5/reflect-parse/destructuring-variable-declarations.js b/js/src/tests/js1_8_5/reflect-parse/destructuring-variable-declarations.js index 01f896d5784b..4df8682cb643 100644 --- a/js/src/tests/js1_8_5/reflect-parse/destructuring-variable-declarations.js +++ b/js/src/tests/js1_8_5/reflect-parse/destructuring-variable-declarations.js @@ -23,9 +23,9 @@ function testVarPatternCombinations(makePattSrc, makePattPatt) { assertStmt("for (var " + pattSrcs[i].join(",") + "; foo; bar);", forStmt(varDecl(pattPatts[i]), ident("foo"), ident("bar"), emptyStmt)); assertStmt("for (let " + pattSrcs[i].join(",") + "; foo; bar);", - letStmt(pattPatts[i], forStmt(null, ident("foo"), ident("bar"), emptyStmt))); + forStmt(letDecl(pattPatts[i]), ident("foo"), ident("bar"), emptyStmt)); assertStmt("for (const " + constSrcs[i].join(",") + "; foo; bar);", - letStmt(constPatts[i], forStmt(null, ident("foo"), ident("bar"), emptyStmt))); + forStmt(constDecl(constPatts[i]), ident("foo"), ident("bar"), emptyStmt)); } } From ba6beeb0c095eebe398116418f580b39e017d0be Mon Sep 17 00:00:00 2001 From: Chris Manchester Date: Tue, 3 Nov 2015 10:23:04 -0800 Subject: [PATCH 052/113] Bug 1220000 - Unconditionally include EXTRA_MDDEPEND_FILES so callers get what they expect. r=glandium GENERATED_FILES and accessible/xpcom/Makefile.in add to EXTRA_MDDEPEND_FILES, but for targets that run during export. Export doesn't include EXTRA_MDDEPEND_FILES, so none of them is ending up with correct dependencies. The EXTRA_EXPORT_MDDEPEND_FILES variable could be used for this purpose, but given the circumstances this variable is removed, and EXTRA_MDDEPEND_FILES is instead included unconditionally. --HG-- extra : commitid : 7daRRnxfkS0 --- config/rules.mk | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/config/rules.mk b/config/rules.mk index 138ef8f8ba37..ec9c01e0213b 100644 --- a/config/rules.mk +++ b/config/rules.mk @@ -1341,7 +1341,7 @@ endif # it. ifneq (,$(filter-out all chrome default export realchrome clean clobber clobber_all distclean realclean,$(MAKECMDGOALS))) -MDDEPEND_FILES := $(strip $(wildcard $(addprefix $(MDDEPDIR)/,$(EXTRA_MDDEPEND_FILES) $(addsuffix .pp,$(notdir $(sort $(OBJS) $(PROGOBJS) $(HOST_OBJS) $(HOST_PROGOBJS))))))) +MDDEPEND_FILES := $(strip $(wildcard $(addprefix $(MDDEPDIR)/,$(addsuffix .pp,$(notdir $(sort $(OBJS) $(PROGOBJS) $(HOST_OBJS) $(HOST_PROGOBJS))))))) ifneq (,$(MDDEPEND_FILES)) $(call include_deps,$(MDDEPEND_FILES)) @@ -1349,16 +1349,12 @@ endif endif - -ifneq (,$(filter export,$(MAKECMDGOALS))) -MDDEPEND_FILES := $(strip $(wildcard $(addprefix $(MDDEPDIR)/,$(EXTRA_EXPORT_MDDEPEND_FILES)))) +MDDEPEND_FILES := $(strip $(wildcard $(addprefix $(MDDEPDIR)/,$(EXTRA_MDDEPEND_FILES)))) ifneq (,$(MDDEPEND_FILES)) $(call include_deps,$(MDDEPEND_FILES)) endif -endif - ############################################################################# -include $(topsrcdir)/$(MOZ_BUILD_APP)/app-rules.mk From 8ffd9ff2ed6fcf6eaf8af92ff7468ec3bb187641 Mon Sep 17 00:00:00 2001 From: Chris Manchester Date: Tue, 3 Nov 2015 10:23:04 -0800 Subject: [PATCH 053/113] Bug 1218999 - Back out changeset 5f32b2bcfa43 (bug 1188468) in favor of a more efficient solution. r=glandium Bug 118468 landed an option for FileAvoidWrite to always write to an output file, whether or not the contents would be changed. This was to address a problem caused by not updating mtimes when building GENERATED_FILES, but undoes the purpose of FileAvoidWrite and isn't really necessary. This is addressed in a subsequent commit by unconditionally updating mtimes when processing GENERATED_FILES. --HG-- extra : commitid : AfOhgUstokq --- python/mozbuild/mozbuild/util.py | 40 +++++++++-------------- security/manager/ssl/tests/unit/pycert.py | 3 -- security/manager/ssl/tests/unit/pykey.py | 3 -- 3 files changed, 16 insertions(+), 30 deletions(-) diff --git a/python/mozbuild/mozbuild/util.py b/python/mozbuild/mozbuild/util.py index 8e3eddbdf3e1..e3fe05bb7442 100644 --- a/python/mozbuild/mozbuild/util.py +++ b/python/mozbuild/mozbuild/util.py @@ -109,25 +109,6 @@ def ensureParentDir(path): raise -def readFileContent(name, mode): - """Read the content of file, returns tuple (file existed, file content)""" - existed = False - old_content = None - try: - existing = open(name, mode) - existed = True - except IOError: - pass - else: - try: - old_content = existing.read() - except IOError: - pass - finally: - existing.close() - return existed, old_content - - class FileAvoidWrite(BytesIO): """File-like object that buffers output and only writes if content changed. @@ -146,7 +127,6 @@ class FileAvoidWrite(BytesIO): self._capture_diff = capture_diff self.diff = None self.mode = mode - self.force_update = False def write(self, buf): if isinstance(buf, unicode): @@ -166,11 +146,23 @@ class FileAvoidWrite(BytesIO): """ buf = self.getvalue() BytesIO.close(self) + existed = False + old_content = None - existed, old_content = readFileContent(self.name, self.mode) - if not self.force_update and old_content == buf: - assert existed - return existed, False + try: + existing = open(self.name, self.mode) + existed = True + except IOError: + pass + else: + try: + old_content = existing.read() + if old_content == buf: + return True, False + except IOError: + pass + finally: + existing.close() ensureParentDir(self.name) with open(self.name, 'w') as file: diff --git a/security/manager/ssl/tests/unit/pycert.py b/security/manager/ssl/tests/unit/pycert.py index 26afe509d569..e0616c3ced1a 100755 --- a/security/manager/ssl/tests/unit/pycert.py +++ b/security/manager/ssl/tests/unit/pycert.py @@ -612,9 +612,6 @@ class Certificate(object): def main(output, inputPath): with open(inputPath) as configStream: output.write(Certificate(configStream).toPEM()) - # Force updating the output file even if the content does not change - # so that we won't be called again simply because of the mtime. - output.force_update = True # When run as a standalone program, this will read a specification from # stdin and output the certificate as PEM to stdout. diff --git a/security/manager/ssl/tests/unit/pykey.py b/security/manager/ssl/tests/unit/pykey.py index 848358d8552f..d3c43deb4c22 100755 --- a/security/manager/ssl/tests/unit/pykey.py +++ b/security/manager/ssl/tests/unit/pykey.py @@ -699,9 +699,6 @@ def keyFromSpecification(specification): def main(output, inputPath): with open(inputPath) as configStream: output.write(keyFromSpecification(configStream.read().strip()).toPEM()) - # Force updating the output file even if the content does not change - # so that we won't be called again simply because of the mtime. - output.force_update = True # When run as a standalone program, this will read a specification from # stdin and output the certificate as PEM to stdout. From deb47f849b8f60163c79405cc3483b931a49aa94 Mon Sep 17 00:00:00 2001 From: Chris Manchester Date: Tue, 3 Nov 2015 10:23:04 -0800 Subject: [PATCH 054/113] Bug 1218999 - Update mtimes when building a GENERATED_FILES target, even when contents don't change. r=glandium When a make target is generated with FileAvoidWrite, this can cause targets to get rebuilt perpetually when a prerequisite is updated, because FileAvoidWrite will leave the target's mtime older than the prerequisite's when the target's contents are unchanged. To avoid this issue, GENERATED_FILES is modified to unconditionally update its target's mtime. --HG-- extra : commitid : 4k5e5rKtPZ2 --- python/mozbuild/mozbuild/action/file_generate.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/python/mozbuild/mozbuild/action/file_generate.py b/python/mozbuild/mozbuild/action/file_generate.py index 86c92031f472..f6e51b04ed1f 100644 --- a/python/mozbuild/mozbuild/action/file_generate.py +++ b/python/mozbuild/mozbuild/action/file_generate.py @@ -67,6 +67,15 @@ def main(argv): mk.dump(dep_file) # The script succeeded, so reset |ret| to indicate that. ret = None + # Even when our file's contents haven't changed, we want to update + # the file's mtime so make knows this target isn't still older than + # whatever prerequisite caused it to be built this time around. + try: + os.utime(args.output_file, None) + except: + print('Error processing file "{0}"'.format(args.output_file), + file=sys.stderr) + traceback.print_exc() except IOError as e: print('Error opening file "{0}"'.format(e.filename), file=sys.stderr) traceback.print_exc() From e7029d44aa692ba924f387c27547d7b5aa9ea571 Mon Sep 17 00:00:00 2001 From: Andrew Halberstadt Date: Thu, 29 Oct 2015 15:11:25 -0400 Subject: [PATCH 055/113] Bug 1219870 - [mozlog] ensure correct suite state when logging suite_start/suite_end via StructuredLogger.log_raw, r=chmanchester --HG-- extra : commitid : L5aVgE5euqR extra : rebase_source : 3b7f3b105503a7cc3e0ba8d8c15a85b18a06ce30 --- .../mozbase/mozlog/mozlog/structuredlog.py | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/testing/mozbase/mozlog/mozlog/structuredlog.py b/testing/mozbase/mozlog/mozlog/structuredlog.py index 9e9caaefd096..adabdc31eacf 100644 --- a/testing/mozbase/mozlog/mozlog/structuredlog.py +++ b/testing/mozbase/mozlog/mozlog/structuredlog.py @@ -187,6 +187,9 @@ class StructuredLogger(object): "expected" not in raw_data): del data["expected"] + if not self._ensure_suite_state(action, data): + return + self._handle_log(data) def _log_data(self, action, data=None): @@ -217,6 +220,21 @@ class StructuredLogger(object): all_data.update(data) return all_data + def _ensure_suite_state(self, action, data): + if action == 'suite_start': + if self._state.suite_started: + self.error("Got second suite_start message before suite_end. " + + "Logged with data: {}".format(json.dumps(data))) + return False + self._state.suite_started = True + elif action == 'suite_end': + if not self._state.suite_started: + self.error("Got suite_end message before suite_start. " + + "Logged with data: {}".format(json.dumps(data))) + return False + self._state.suite_started = False + return True + @log_action(List("tests", Unicode), Dict("run_info", default=None, optional=True), Dict("version_info", default=None, optional=True), @@ -229,24 +247,17 @@ class StructuredLogger(object): :param dict version_info: Optional target application version information provided by mozversion. :param dict device_info: Optional target device information provided by mozdevice. """ - if self._state.suite_started: - self.error("Got second suite_start message before suite_end. Logged with data %s" % - json.dumps(data)) + if not self._ensure_suite_state('suite_start', data): return - self._state.suite_started = True - self._log_data("suite_start", data) @log_action() def suite_end(self, data): """Log a suite_end message""" - if not self._state.suite_started: - self.error("Got suite_end message before suite_start.") + if not self._ensure_suite_state('suite_end', data): return - self._state.suite_started = False - self._log_data("suite_end") @log_action(TestId("test"), From 762a891a534a10772067df241f8fbd259a24b757 Mon Sep 17 00:00:00 2001 From: Shih-Chiang Chien Date: Tue, 3 Nov 2015 13:49:23 -0500 Subject: [PATCH 056/113] Bug 1220679 - replace AutoSafeJSContext with AutoJSAPI. r=bz. --- dom/base/nsContentPermissionHelper.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/dom/base/nsContentPermissionHelper.cpp b/dom/base/nsContentPermissionHelper.cpp index 1b1c50d027d2..650f85e3cc2a 100644 --- a/dom/base/nsContentPermissionHelper.cpp +++ b/dom/base/nsContentPermissionHelper.cpp @@ -32,6 +32,7 @@ #include "nsIDocument.h" #include "nsIDOMEvent.h" #include "nsWeakPtr.h" +#include "ScriptSettings.h" using mozilla::Unused; // using namespace mozilla::dom; @@ -650,7 +651,10 @@ nsContentPermissionRequestProxy::Allow(JS::HandleValue aChoices) for (uint32_t i = 0; i < mPermissionRequests.Length(); ++i) { nsCString type = mPermissionRequests[i].type(); - mozilla::AutoSafeJSContext cx; + AutoJSAPI jsapi; + jsapi.Init(); + + JSContext* cx = jsapi.cx(); JS::Rooted obj(cx, &aChoices.toObject()); JSAutoCompartment ac(cx, obj); @@ -658,10 +662,12 @@ nsContentPermissionRequestProxy::Allow(JS::HandleValue aChoices) if (!JS_GetProperty(cx, obj, type.BeginReading(), &val) || !val.isString()) { - // no setting for the permission type, skip it + // no setting for the permission type, clear exception and skip it + jsapi.ClearException(); } else { nsAutoJSString choice; if (!choice.init(cx, val)) { + jsapi.ClearException(); return NS_ERROR_FAILURE; } choices.AppendElement(PermissionChoice(type, choice)); From 2c86b2085014919732b25763588c46eccf0f99a3 Mon Sep 17 00:00:00 2001 From: Randell Jesup Date: Tue, 3 Nov 2015 13:51:32 -0500 Subject: [PATCH 057/113] Bug 1215769: use longer video clips when testing captureStream to avoid failure due to looping (bug 1215769) r=drno --- dom/media/test/manifest.js | 6 ++++++ .../tests/mochitest/test_peerConnection_capturedVideo.html | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/dom/media/test/manifest.js b/dom/media/test/manifest.js index beb8854dbde8..2477d29db280 100644 --- a/dom/media/test/manifest.js +++ b/dom/media/test/manifest.js @@ -58,6 +58,12 @@ var gVideoTests = [ { name:"bogus.duh", type:"bogus/duh" } ]; +// Temp hack for trackIDs and captureStream() -- bug 1215769 +var gLongerTests = [ + { name:"seek.webm", type:"video/webm", width:320, height:240, duration:3.966 }, + { name:"gizmo.mp4", type:"video/mp4", width:560, height:320, duration:5.56 }, +]; + // Used by test_progress to ensure we get the correct progress information // during resource download. var gProgressTests = [ diff --git a/dom/media/tests/mochitest/test_peerConnection_capturedVideo.html b/dom/media/tests/mochitest/test_peerConnection_capturedVideo.html index 9e9dd240b16d..0d723db4c479 100644 --- a/dom/media/tests/mochitest/test_peerConnection_capturedVideo.html +++ b/dom/media/tests/mochitest/test_peerConnection_capturedVideo.html @@ -14,7 +14,7 @@ createHTML({ title: "Captured video-only over peer connection", visible: true }).then(() => new Promise(resolve => { - manager.runTests(getPlayableVideos(gSmallTests), startTest); + manager.runTests(getPlayableVideos(gLongerTests), startTest); manager.onFinished = () => { // Tear down before SimpleTest.finish. if ("nsINetworkInterfaceListService" in SpecialPowers.Ci) { From 57f72c864a38a6ef8efd0bd06c7b5bad6e0954b0 Mon Sep 17 00:00:00 2001 From: A-deLuna Date: Tue, 3 Nov 2015 11:00:02 -0800 Subject: [PATCH 058/113] Bug 1220480 - Reference proper variable in Arch Linux bootstrapper; r=gps DONTBUILD (NPOTB) --HG-- extra : commitid : FF82L5ToIR1 extra : amend_source : 113a8d10ba1fdf385e2a1dac1aaae262f23b3568 --- python/mozboot/mozboot/archlinux.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/mozboot/mozboot/archlinux.py b/python/mozboot/mozboot/archlinux.py index 6929768cc97d..6422743f6651 100644 --- a/python/mozboot/mozboot/archlinux.py +++ b/python/mozboot/mozboot/archlinux.py @@ -81,7 +81,7 @@ class ArchlinuxBootstrapper(BaseBootstrapper): self.pacman_install(*self.SYSTEM_PACKAGES) def install_browser_packages(self): - self.aur_install(*self.AUR_BROWSER_PACKAGES) + self.aur_install(*self.BROWSER_AUR_PACKAGES) self.pacman_install(*self.BROWSER_PACKAGES) def install_mobile_android_packages(self): From c59018c14ac546adabe95d619230e77f0f5b2f61 Mon Sep 17 00:00:00 2001 From: Ben Kelly Date: Tue, 3 Nov 2015 11:20:56 -0800 Subject: [PATCH 059/113] Bug 1220007 P1 Allow ConsoleReportCollectors to flush to another collector. r=bz --- dom/base/ConsoleReportCollector.cpp | 21 +++++++++++++++++++++ dom/base/ConsoleReportCollector.h | 3 +++ dom/base/nsIConsoleReportCollector.h | 10 +++++++++- netwerk/protocol/http/HttpBaseChannel.cpp | 6 ++++++ netwerk/protocol/http/HttpBaseChannel.h | 3 +++ 5 files changed, 42 insertions(+), 1 deletion(-) diff --git a/dom/base/ConsoleReportCollector.cpp b/dom/base/ConsoleReportCollector.cpp index 462f7242c51f..05b8c755f8a0 100644 --- a/dom/base/ConsoleReportCollector.cpp +++ b/dom/base/ConsoleReportCollector.cpp @@ -79,6 +79,27 @@ ConsoleReportCollector::FlushConsoleReports(nsIDocument* aDocument) } } +void +ConsoleReportCollector::FlushConsoleReports(nsIConsoleReportCollector* aCollector) +{ + MOZ_ASSERT(aCollector); + + nsTArray reports; + + { + MutexAutoLock lock(mMutex); + mPendingReports.SwapElements(reports); + } + + for (uint32_t i = 0; i < reports.Length(); ++i) { + PendingReport& report = reports[i]; + aCollector->AddConsoleReport(report.mErrorFlags, report.mCategory, + report.mPropertiesFile, report.mSourceFileURI, + report.mLineNumber, report.mColumnNumber, + report.mMessageName, report.mStringParams); + } +} + ConsoleReportCollector::~ConsoleReportCollector() { } diff --git a/dom/base/ConsoleReportCollector.h b/dom/base/ConsoleReportCollector.h index 942563bfb2e0..ea07fa68344a 100644 --- a/dom/base/ConsoleReportCollector.h +++ b/dom/base/ConsoleReportCollector.h @@ -29,6 +29,9 @@ public: void FlushConsoleReports(nsIDocument* aDocument) override; + void + FlushConsoleReports(nsIConsoleReportCollector* aCollector) override; + private: ~ConsoleReportCollector(); diff --git a/dom/base/nsIConsoleReportCollector.h b/dom/base/nsIConsoleReportCollector.h index 0b140a5f18d1..7f22c4ff4c5d 100644 --- a/dom/base/nsIConsoleReportCollector.h +++ b/dom/base/nsIConsoleReportCollector.h @@ -66,7 +66,7 @@ public: aLineNumber, aColumnNumber, aMessageName, params); } - // Flush all pending reports to the console. + // Flush all pending reports to the console. Main thread only. // // aDocument An optional document representing where to flush the // reports. If provided, then the corresponding window's @@ -74,6 +74,14 @@ public: // go to the browser console. virtual void FlushConsoleReports(nsIDocument* aDocument) = 0; + + // Flush all pending reports to another collector. May be called from any + // thread. + // + // aCollector A required collector object that will effectively take + // ownership of our currently console reports. + virtual void + FlushConsoleReports(nsIConsoleReportCollector* aCollector) = 0; }; NS_DEFINE_STATIC_IID_ACCESSOR(nsIConsoleReportCollector, NS_NSICONSOLEREPORTCOLLECTOR_IID) diff --git a/netwerk/protocol/http/HttpBaseChannel.cpp b/netwerk/protocol/http/HttpBaseChannel.cpp index 850dbf87b9b9..3a8b39ea0896 100644 --- a/netwerk/protocol/http/HttpBaseChannel.cpp +++ b/netwerk/protocol/http/HttpBaseChannel.cpp @@ -2357,6 +2357,12 @@ HttpBaseChannel::FlushConsoleReports(nsIDocument* aDocument) mReportCollector->FlushConsoleReports(aDocument); } +void +HttpBaseChannel::FlushConsoleReports(nsIConsoleReportCollector* aCollector) +{ + mReportCollector->FlushConsoleReports(aCollector); +} + nsIPrincipal * HttpBaseChannel::GetURIPrincipal() { diff --git a/netwerk/protocol/http/HttpBaseChannel.h b/netwerk/protocol/http/HttpBaseChannel.h index 40ad33f88d0e..39db21e70773 100644 --- a/netwerk/protocol/http/HttpBaseChannel.h +++ b/netwerk/protocol/http/HttpBaseChannel.h @@ -247,6 +247,9 @@ public: void FlushConsoleReports(nsIDocument* aDocument) override; + void + FlushConsoleReports(nsIConsoleReportCollector* aCollector) override; + class nsContentEncodings : public nsIUTF8StringEnumerator { public: From 8267928bb3c024dee852f1d1c0a00392f5ad9d53 Mon Sep 17 00:00:00 2001 From: Ben Kelly Date: Tue, 3 Nov 2015 11:20:56 -0800 Subject: [PATCH 060/113] Bug 1220007 P2 Make InterceptedChannel's collect logs locally and only flush to nsIChannel on main thread r=bz --- CLOBBER | 2 +- dom/workers/ServiceWorkerEvents.cpp | 5 +-- modules/libjar/InterceptedJARChannel.cpp | 12 +++--- modules/libjar/InterceptedJARChannel.h | 3 -- .../base/nsINetworkInterceptController.idl | 22 +++++----- netwerk/protocol/http/InterceptedChannel.cpp | 43 ++++++++++--------- netwerk/protocol/http/InterceptedChannel.h | 9 ++-- 7 files changed, 46 insertions(+), 50 deletions(-) diff --git a/CLOBBER b/CLOBBER index 04652348f9b5..5630ff8575ec 100644 --- a/CLOBBER +++ b/CLOBBER @@ -22,4 +22,4 @@ # changes to stick? As of bug 928195, this shouldn't be necessary! Please # don't change CLOBBER for WebIDL changes any more. -Merge day clobber +Bug 1220007 Clobber since .idl changes not getting picked up on emulator-L in b2g-inbound diff --git a/dom/workers/ServiceWorkerEvents.cpp b/dom/workers/ServiceWorkerEvents.cpp index efb226352217..01683e494144 100644 --- a/dom/workers/ServiceWorkerEvents.cpp +++ b/dom/workers/ServiceWorkerEvents.cpp @@ -113,10 +113,7 @@ AsyncLog(nsIInterceptedChannel *aInterceptedChannel, const nsACString& aMessageName, const nsTArray& aParams) { MOZ_ASSERT(aInterceptedChannel); - // Since the intercepted channel is kept alive and paused while handling - // the FetchEvent, we are guaranteed the reporter is stable on the worker - // thread. - nsIConsoleReportCollector* reporter = + nsCOMPtr reporter = aInterceptedChannel->GetConsoleReportCollector(); if (reporter) { reporter->AddConsoleReport(nsIScriptError::errorFlag, diff --git a/modules/libjar/InterceptedJARChannel.cpp b/modules/libjar/InterceptedJARChannel.cpp index 832314534306..a47c26247939 100644 --- a/modules/libjar/InterceptedJARChannel.cpp +++ b/modules/libjar/InterceptedJARChannel.cpp @@ -123,6 +123,12 @@ InterceptedJARChannel::SetChannelInfo(mozilla::dom::ChannelInfo* aChannelInfo) return aChannelInfo->ResurrectInfoOnChannel(mChannel); } +NS_IMETHODIMP +InterceptedJARChannel::GetConsoleReportCollector(nsIConsoleReportCollector**) +{ + return NS_ERROR_NOT_AVAILABLE; +} + void InterceptedJARChannel::NotifyController() { @@ -146,9 +152,3 @@ InterceptedJARChannel::NotifyController() } mController = nullptr; } - -nsIConsoleReportCollector* -InterceptedJARChannel::GetConsoleReportCollector() const -{ - return nullptr; -} diff --git a/modules/libjar/InterceptedJARChannel.h b/modules/libjar/InterceptedJARChannel.h index 07a722b81ddc..f1dd2e78e7b4 100644 --- a/modules/libjar/InterceptedJARChannel.h +++ b/modules/libjar/InterceptedJARChannel.h @@ -53,9 +53,6 @@ public: NS_DECL_NSIINTERCEPTEDCHANNEL void NotifyController(); - - virtual nsIConsoleReportCollector* - GetConsoleReportCollector() const override; }; } // namespace net diff --git a/netwerk/base/nsINetworkInterceptController.idl b/netwerk/base/nsINetworkInterceptController.idl index 83f96ef225ea..3e9ff02018c3 100644 --- a/netwerk/base/nsINetworkInterceptController.idl +++ b/netwerk/base/nsINetworkInterceptController.idl @@ -7,6 +7,7 @@ #include "nsIContentPolicyBase.idl" interface nsIChannel; +interface nsIConsoleReportCollector; interface nsIOutputStream; interface nsIURI; @@ -28,7 +29,7 @@ class ChannelInfo; * which do not implement nsIChannel. */ -[scriptable, uuid(ea78e439-cc42-4b2d-a42b-85ab55a149d1)] +[scriptable, uuid(231bb567-90e1-4973-9728-7dab93ab29a8)] interface nsIInterceptedChannel : nsISupports { /** @@ -87,16 +88,17 @@ interface nsIInterceptedChannel : nsISupports [noscript] readonly attribute nsContentPolicyType internalContentPolicyType; + [noscript] + readonly attribute nsIConsoleReportCollector consoleReportCollector; + %{C++ - // Allow access to the inner channel as a ConsoleReportCollector off - // the main thread. Pure C++ method here to avoid requiring an - // AddRef() during QI. Callers should not save the returned pointer. - // May return nullptr. - // - // Note: Only safe to use OMT prior to resetInterception(), - // finishSynthesizedResponse(), and cancel(). - virtual nsIConsoleReportCollector* - GetConsoleReportCollector() const = 0; + already_AddRefed + GetConsoleReportCollector() + { + nsCOMPtr reporter; + GetConsoleReportCollector(getter_AddRefs(reporter)); + return reporter.forget(); + } %} }; diff --git a/netwerk/protocol/http/InterceptedChannel.cpp b/netwerk/protocol/http/InterceptedChannel.cpp index 0a9c723774c7..c36351955d52 100644 --- a/netwerk/protocol/http/InterceptedChannel.cpp +++ b/netwerk/protocol/http/InterceptedChannel.cpp @@ -13,6 +13,7 @@ #include "nsHttpChannel.h" #include "HttpChannelChild.h" #include "nsHttpResponseHead.h" +#include "mozilla/ConsoleReportCollector.h" #include "mozilla/dom/ChannelInfo.h" namespace mozilla { @@ -37,6 +38,7 @@ NS_IMPL_ISUPPORTS(InterceptedChannelBase, nsIInterceptedChannel) InterceptedChannelBase::InterceptedChannelBase(nsINetworkInterceptController* aController) : mController(aController) +, mReportCollector(new ConsoleReportCollector()) { } @@ -107,6 +109,15 @@ InterceptedChannelBase::DoSynthesizeHeader(const nsACString& aName, const nsACSt return NS_OK; } +NS_IMETHODIMP +InterceptedChannelBase::GetConsoleReportCollector(nsIConsoleReportCollector** aCollectorOut) +{ + MOZ_ASSERT(aCollectorOut); + nsCOMPtr ref = mReportCollector; + ref.forget(aCollectorOut); + return NS_OK; +} + InterceptedChannelChrome::InterceptedChannelChrome(nsHttpChannel* aChannel, nsINetworkInterceptController* aController, nsICacheEntry* aEntry) @@ -134,16 +145,6 @@ InterceptedChannelChrome::NotifyController() DoNotifyController(); } -nsIConsoleReportCollector* -InterceptedChannelChrome::GetConsoleReportCollector() const -{ - // The ConsoleReportCollector should only be used when the inner channel is - // stable. Nothing should try to use it once we return to the main thread - // and clear the inner channel. - MOZ_ASSERT(mChannel); - return mChannel; -} - NS_IMETHODIMP InterceptedChannelChrome::GetChannel(nsIChannel** aChannel) { @@ -158,6 +159,8 @@ InterceptedChannelChrome::ResetInterception() return NS_ERROR_NOT_AVAILABLE; } + mReportCollector->FlushConsoleReports(mChannel); + mSynthesizedCacheEntry->AsyncDoom(nullptr); mSynthesizedCacheEntry = nullptr; @@ -200,6 +203,8 @@ InterceptedChannelChrome::FinishSynthesizedResponse(const nsACString& aFinalURLS return NS_ERROR_NOT_AVAILABLE; } + mReportCollector->FlushConsoleReports(mChannel); + EnsureSynthesizedResponse(); // If the synthesized response is a redirect, then we want to respect @@ -273,6 +278,8 @@ InterceptedChannelChrome::Cancel(nsresult aStatus) return NS_ERROR_FAILURE; } + mReportCollector->FlushConsoleReports(mChannel); + // we need to use AsyncAbort instead of Cancel since there's no active pump // to cancel which will provide OnStart/OnStopRequest to the channel. nsresult rv = mChannel->AsyncAbort(aStatus); @@ -322,16 +329,6 @@ InterceptedChannelContent::NotifyController() DoNotifyController(); } -nsIConsoleReportCollector* -InterceptedChannelContent::GetConsoleReportCollector() const -{ - // The ConsoleReportCollector should only be used when the inner channel is - // stable. Nothing should try to use it once we return to the main thread - // and clear the inner channel. - MOZ_ASSERT(mChannel); - return mChannel; -} - NS_IMETHODIMP InterceptedChannelContent::GetChannel(nsIChannel** aChannel) { @@ -346,6 +343,8 @@ InterceptedChannelContent::ResetInterception() return NS_ERROR_NOT_AVAILABLE; } + mReportCollector->FlushConsoleReports(mChannel); + mResponseBody = nullptr; mSynthesizedInput = nullptr; @@ -381,6 +380,8 @@ InterceptedChannelContent::FinishSynthesizedResponse(const nsACString& aFinalURL return NS_ERROR_NOT_AVAILABLE; } + mReportCollector->FlushConsoleReports(mChannel); + EnsureSynthesizedResponse(); nsCOMPtr originalURI; @@ -420,6 +421,8 @@ InterceptedChannelContent::Cancel(nsresult aStatus) return NS_ERROR_FAILURE; } + mReportCollector->FlushConsoleReports(mChannel); + // we need to use AsyncAbort instead of Cancel since there's no active pump // to cancel which will provide OnStart/OnStopRequest to the channel. nsresult rv = mChannel->AsyncAbort(aStatus); diff --git a/netwerk/protocol/http/InterceptedChannel.h b/netwerk/protocol/http/InterceptedChannel.h index 229d56705741..f12b2e99d6f4 100644 --- a/netwerk/protocol/http/InterceptedChannel.h +++ b/netwerk/protocol/http/InterceptedChannel.h @@ -36,6 +36,8 @@ protected: // Response head for use when synthesizing Maybe> mSynthesizedResponseHead; + nsCOMPtr mReportCollector; + void EnsureSynthesizedResponse(); void DoNotifyController(); nsresult DoSynthesizeStatus(uint16_t aStatus, const nsACString& aReason); @@ -52,6 +54,7 @@ public: NS_DECL_ISUPPORTS NS_IMETHOD GetResponseBody(nsIOutputStream** aOutput) override; + NS_IMETHOD GetConsoleReportCollector(nsIConsoleReportCollector** aCollectorOut) override; }; class InterceptedChannelChrome : public InterceptedChannelBase @@ -82,9 +85,6 @@ public: NS_IMETHOD GetInternalContentPolicyType(nsContentPolicyType *aInternalContentPolicyType) override; virtual void NotifyController() override; - - virtual nsIConsoleReportCollector* - GetConsoleReportCollector() const override; }; class InterceptedChannelContent : public InterceptedChannelBase @@ -113,9 +113,6 @@ public: NS_IMETHOD GetInternalContentPolicyType(nsContentPolicyType *aInternalContentPolicyType) override; virtual void NotifyController() override; - - virtual nsIConsoleReportCollector* - GetConsoleReportCollector() const override; }; } // namespace net From bc977a5380d8adfe8ff3c46812b3b0740e21c570 Mon Sep 17 00:00:00 2001 From: Kartikaya Gupta Date: Tue, 3 Nov 2015 14:21:40 -0500 Subject: [PATCH 061/113] Bug 1141884 - Rename ResetInputState and make it only apply to touch events. r=botond In particular, we want to prevent requesting a snap in the scenario that a wheel block gets prevent-defaulted. In general the function is really only relevant for touch input and there's no need to run it for other types of input. --HG-- extra : commitid : oBr4VmShHv --- gfx/layers/apz/src/AsyncPanZoomController.cpp | 5 +---- gfx/layers/apz/src/AsyncPanZoomController.h | 4 ++-- gfx/layers/apz/src/InputQueue.cpp | 6 ++++-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/gfx/layers/apz/src/AsyncPanZoomController.cpp b/gfx/layers/apz/src/AsyncPanZoomController.cpp index ce20f82f78c6..2c43a0841a5c 100644 --- a/gfx/layers/apz/src/AsyncPanZoomController.cpp +++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp @@ -3351,11 +3351,8 @@ AsyncPanZoomController::CurrentPanGestureBlock() } void -AsyncPanZoomController::ResetInputState() +AsyncPanZoomController::ResetTouchInputState() { - // This may be called during non-touch input blocks as well. We send - // a fake cancel touch event here but on the assumption that none of the - // code in GEL assumes a CurrentTouchBlock() MultiTouchInput cancel(MultiTouchInput::MULTITOUCH_CANCEL, 0, TimeStamp::Now(), 0); RefPtr listener = GetGestureEventListener(); if (listener) { diff --git a/gfx/layers/apz/src/AsyncPanZoomController.h b/gfx/layers/apz/src/AsyncPanZoomController.h index a4d46d450e5a..cfa1840d4034 100644 --- a/gfx/layers/apz/src/AsyncPanZoomController.h +++ b/gfx/layers/apz/src/AsyncPanZoomController.h @@ -834,9 +834,9 @@ public: bool ArePointerEventsConsumable(TouchBlockState* aBlock, uint32_t aTouchPoints); /** - * Clear internal state relating to input handling. + * Clear internal state relating to touch input handling. */ - void ResetInputState(); + void ResetTouchInputState(); private: void CancelAnimationAndGestureState(); diff --git a/gfx/layers/apz/src/InputQueue.cpp b/gfx/layers/apz/src/InputQueue.cpp index dad0363e32ca..2ffe88315d00 100644 --- a/gfx/layers/apz/src/InputQueue.cpp +++ b/gfx/layers/apz/src/InputQueue.cpp @@ -651,7 +651,9 @@ InputQueue::ProcessInputBlocks() { curBlock->DropEvents(); } else if (curBlock->IsDefaultPrevented()) { curBlock->DropEvents(); - target->ResetInputState(); + if (curBlock->AsTouchBlock()) { + target->ResetTouchInputState(); + } } else { UpdateActiveApzc(curBlock->GetTargetApzc()); curBlock->HandleEvents(); @@ -677,7 +679,7 @@ void InputQueue::UpdateActiveApzc(const RefPtr& aNewActive) { if (mLastActiveApzc && mLastActiveApzc != aNewActive && mTouchCounter.GetActiveTouchCount() > 0) { - mLastActiveApzc->ResetInputState(); + mLastActiveApzc->ResetTouchInputState(); } mLastActiveApzc = aNewActive; } From 2a13672d4b1597c4210f41a89a8e3b3d6778d111 Mon Sep 17 00:00:00 2001 From: Kartikaya Gupta Date: Tue, 3 Nov 2015 14:21:40 -0500 Subject: [PATCH 062/113] Bug 1141884 - Handle wheel events on the main thread if the frame has snapping. r=dvander,mstange --HG-- extra : commitid : Jw0Kdhy4ob6 --- dom/events/EventStateManager.cpp | 8 ++++++++ layout/base/nsDisplayList.cpp | 6 ++++++ layout/base/nsLayoutUtils.cpp | 12 ++++++++++++ layout/base/nsLayoutUtils.h | 6 ++++++ 4 files changed, 32 insertions(+) diff --git a/dom/events/EventStateManager.cpp b/dom/events/EventStateManager.cpp index c35493b67fd9..c9ea91a63721 100644 --- a/dom/events/EventStateManager.cpp +++ b/dom/events/EventStateManager.cpp @@ -3119,6 +3119,14 @@ EventStateManager::PostHandleEvent(nsPresContext* aPresContext, if (pluginFrame) { MOZ_ASSERT(pluginFrame->WantsToHandleWheelEventAsDefaultAction()); action = WheelPrefs::ACTION_SEND_TO_PLUGIN; + } else if (nsLayoutUtils::IsScrollFrameWithSnapping(frameToScroll)) { + // If the target has scroll-snapping points then we want to handle + // the wheel event on the main thread even if we have APZ enabled. Do + // so and let the APZ know that it should ignore this event. + if (wheelEvent->mFlags.mHandledByAPZ) { + wheelEvent->mFlags.mDefaultPrevented = true; + } + action = WheelPrefs::GetInstance()->ComputeActionFor(wheelEvent); } else if (wheelEvent->mFlags.mHandledByAPZ) { action = WheelPrefs::ACTION_NONE; } else { diff --git a/layout/base/nsDisplayList.cpp b/layout/base/nsDisplayList.cpp index e8bd59fdb49c..9250d8fba219 100644 --- a/layout/base/nsDisplayList.cpp +++ b/layout/base/nsDisplayList.cpp @@ -3281,6 +3281,12 @@ nsDisplayLayerEventRegions::AddFrame(nsDisplayListBuilder* aBuilder, if (pluginFrame && pluginFrame->WantsToHandleWheelEventAsDefaultAction()) { mDispatchToContentHitRegion.Or(mDispatchToContentHitRegion, borderBox); } + } else if (gfxPlatform::GetPlatform()->SupportsApzWheelInput() && + nsLayoutUtils::IsScrollFrameWithSnapping(aFrame->GetParent())) { + // If the frame is the inner content of a scrollable frame with snap-points + // then we want to handle wheel events for it on the main thread. Add it to + // the d-t-c region so that APZ waits for the main thread. + mDispatchToContentHitRegion.Or(mDispatchToContentHitRegion, borderBox); } // Touch action region diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index 007e7867730d..8b1255a1f52e 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -8676,3 +8676,15 @@ nsLayoutUtils::GetSelectionBoundingRect(Selection* aSel) return res; } + +/* static */ bool +nsLayoutUtils::IsScrollFrameWithSnapping(nsIFrame* aFrame) +{ + nsIScrollableFrame* sf = do_QueryFrame(aFrame); + if (!sf) { + return false; + } + ScrollbarStyles styles = sf->GetScrollbarStyles(); + return styles.mScrollSnapTypeY != NS_STYLE_SCROLL_SNAP_TYPE_NONE || + styles.mScrollSnapTypeX != NS_STYLE_SCROLL_SNAP_TYPE_NONE; +} diff --git a/layout/base/nsLayoutUtils.h b/layout/base/nsLayoutUtils.h index 9b2ce653025b..3b4416dda690 100644 --- a/layout/base/nsLayoutUtils.h +++ b/layout/base/nsLayoutUtils.h @@ -2773,6 +2773,12 @@ public: * @param aSel Selection to check */ static nsRect GetSelectionBoundingRect(mozilla::dom::Selection* aSel); + + /** + * Returns true if the given frame is a scrollframe and it has snap points. + */ + static bool IsScrollFrameWithSnapping(nsIFrame* aFrame); + private: static uint32_t sFontSizeInflationEmPerLine; static uint32_t sFontSizeInflationMinTwips; From 91c354b0d426b01bcc300ff82481d45b04987f72 Mon Sep 17 00:00:00 2001 From: Kartikaya Gupta Date: Tue, 3 Nov 2015 14:21:40 -0500 Subject: [PATCH 063/113] Bug 1141884 - Trigger compositor smooth scrolling to snap points when APZ is enabled. r=mstange,kip --HG-- extra : commitid : 9TdFTY6HI0a --- dom/events/EventStateManager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dom/events/EventStateManager.cpp b/dom/events/EventStateManager.cpp index c9ea91a63721..5544060d22f2 100644 --- a/dom/events/EventStateManager.cpp +++ b/dom/events/EventStateManager.cpp @@ -2557,6 +2557,8 @@ EventStateManager::DoScrollText(nsIScrollableFrame* aScrollableFrame, case WidgetWheelEvent::SCROLL_DEFAULT: if (isDeltaModePixel) { mode = nsIScrollableFrame::NORMAL; + } else if (aEvent->mFlags.mHandledByAPZ) { + mode = nsIScrollableFrame::SMOOTH_MSD; } else { mode = nsIScrollableFrame::SMOOTH; } From 0f1d97ed61138dd147969acefe1a09fbe8a7a98f Mon Sep 17 00:00:00 2001 From: Benoit Girard Date: Tue, 3 Nov 2015 14:21:35 -0500 Subject: [PATCH 064/113] Bug 1220853 - Notify wheel transaction of mouse move when using async dragging. r=kats --HG-- extra : commitid : Aw8xxcgcHKF --- gfx/layers/apz/src/APZCTreeManager.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/gfx/layers/apz/src/APZCTreeManager.cpp b/gfx/layers/apz/src/APZCTreeManager.cpp index b99796f26751..333f946f0ead 100644 --- a/gfx/layers/apz/src/APZCTreeManager.cpp +++ b/gfx/layers/apz/src/APZCTreeManager.cpp @@ -1051,6 +1051,11 @@ APZCTreeManager::ProcessMouseEvent(WidgetMouseEventBase& aEvent, ScrollableLayerGuid* aOutTargetGuid, uint64_t* aOutInputBlockId) { + MOZ_ASSERT(NS_IsMainThread()); + + // Note, we call this before having transformed the reference point. + UpdateWheelTransaction(aEvent); + MouseInput input(aEvent); input.mOrigin = ScreenPoint(aEvent.refPoint.x, aEvent.refPoint.y); From f6a65703600c76b04879d548350b574ad0205cc5 Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Tue, 3 Nov 2015 21:45:32 +0100 Subject: [PATCH 065/113] Bug 1215099 part 1 - [css-grid] Backout bug 1206703. r=dholbert --- layout/generic/nsGridContainerFrame.h | 7 +-- .../css-grid/grid-abspos-items-001-ref.html | 12 ++-- .../css-grid/grid-abspos-items-002-ref.html | 8 +-- .../css-grid/grid-abspos-items-011-ref.html | 54 ---------------- .../css-grid/grid-abspos-items-011.html | 61 ------------------- layout/reftests/css-grid/reftest.list | 1 - 6 files changed, 13 insertions(+), 130 deletions(-) delete mode 100644 layout/reftests/css-grid/grid-abspos-items-011-ref.html delete mode 100644 layout/reftests/css-grid/grid-abspos-items-011.html diff --git a/layout/generic/nsGridContainerFrame.h b/layout/generic/nsGridContainerFrame.h index d2ebb0021d00..3113037b18fd 100644 --- a/layout/generic/nsGridContainerFrame.h +++ b/layout/generic/nsGridContainerFrame.h @@ -233,15 +233,14 @@ protected: }; /** - * Return aLine if it's inside the aMin..aMax range (inclusive), otherwise - * return kAutoLine. If the range is empty (aMin == aMax, i.e. there are - * no tracks in the grid) then aLine is outside. + * Return aLine if it's inside the aMin..aMax range (inclusive), + * otherwise return kAutoLine. */ static int32_t AutoIfOutside(int32_t aLine, int32_t aMin, int32_t aMax) { MOZ_ASSERT(aMin <= aMax); - if (aLine < aMin || aLine > aMax || aMin == aMax) { + if (aLine < aMin || aLine > aMax) { return kAutoLine; } return aLine; diff --git a/layout/reftests/css-grid/grid-abspos-items-001-ref.html b/layout/reftests/css-grid/grid-abspos-items-001-ref.html index f4a05afe7c6a..0fc6afed8d48 100644 --- a/layout/reftests/css-grid/grid-abspos-items-001-ref.html +++ b/layout/reftests/css-grid/grid-abspos-items-001-ref.html @@ -163,17 +163,17 @@ span {
-a -b -c -d +a +b +c +d
- +
- +
diff --git a/layout/reftests/css-grid/grid-abspos-items-002-ref.html b/layout/reftests/css-grid/grid-abspos-items-002-ref.html index c031bd734b22..f084d630ca73 100644 --- a/layout/reftests/css-grid/grid-abspos-items-002-ref.html +++ b/layout/reftests/css-grid/grid-abspos-items-002-ref.html @@ -164,10 +164,10 @@ span {
-a -b -c -d +a +b +c +d
diff --git a/layout/reftests/css-grid/grid-abspos-items-011-ref.html b/layout/reftests/css-grid/grid-abspos-items-011-ref.html deleted file mode 100644 index 92e540563e26..000000000000 --- a/layout/reftests/css-grid/grid-abspos-items-011-ref.html +++ /dev/null @@ -1,54 +0,0 @@ - - - - - CSS Grid Test: abs pos areas in empty grid - - - - - -There should be no red areas. -
- -
-
-
-
- -
-
-
-
-
-
- -
-
-
-
- -
- - - diff --git a/layout/reftests/css-grid/grid-abspos-items-011.html b/layout/reftests/css-grid/grid-abspos-items-011.html deleted file mode 100644 index cc514b7a4b1d..000000000000 --- a/layout/reftests/css-grid/grid-abspos-items-011.html +++ /dev/null @@ -1,61 +0,0 @@ - - - - - CSS Grid Test: abs pos areas in empty grid - - - - - - - -There should be no red areas. -
- -
-
-
-
- -
-
-
-
-
-
- -
-
-
-
- -
- - - diff --git a/layout/reftests/css-grid/reftest.list b/layout/reftests/css-grid/reftest.list index 0571ed64943a..3efcc4028f17 100644 --- a/layout/reftests/css-grid/reftest.list +++ b/layout/reftests/css-grid/reftest.list @@ -21,7 +21,6 @@ fails == grid-whitespace-handling-1b.xhtml grid-whitespace-handling-1-ref.xhtml == grid-abspos-items-008.html grid-abspos-items-008-ref.html == grid-abspos-items-009.html grid-abspos-items-009-ref.html == grid-abspos-items-010.html grid-abspos-items-010-ref.html -== grid-abspos-items-011.html grid-abspos-items-011-ref.html == grid-order-abspos-items-001.html grid-order-abspos-items-001-ref.html == grid-order-placement-auto-001.html grid-order-placement-auto-001-ref.html == grid-order-placement-definite-001.html grid-order-placement-definite-001-ref.html From b264df1571897807860a284a1fd495f153ab24ff Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Tue, 3 Nov 2015 21:45:32 +0100 Subject: [PATCH 066/113] Bug 1215099 part 2 - [css-grid] An empty grid should still have one explicit grid line in each axis. r=dholbert --- layout/generic/nsGridContainerFrame.cpp | 7 +- .../css-grid/grid-abspos-items-011-ref.html | 82 ++++++++++++++++ .../css-grid/grid-abspos-items-011.html | 97 +++++++++++++++++++ layout/reftests/css-grid/reftest.list | 1 + 4 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 layout/reftests/css-grid/grid-abspos-items-011-ref.html create mode 100644 layout/reftests/css-grid/grid-abspos-items-011.html diff --git a/layout/generic/nsGridContainerFrame.cpp b/layout/generic/nsGridContainerFrame.cpp index 775b4cd3d066..192a3b21c850 100644 --- a/layout/generic/nsGridContainerFrame.cpp +++ b/layout/generic/nsGridContainerFrame.cpp @@ -808,7 +808,12 @@ IsNameWithStartSuffix(const nsString& aString, uint32_t* aIndex) static nscoord GridLinePosition(uint32_t aLine, const nsTArray& aTrackSizes) { - MOZ_ASSERT(aTrackSizes.Length() > 0, "There are no lines in this grid"); + if (aTrackSizes.Length() == 0) { + // https://drafts.csswg.org/css-grid/#grid-definition + // "... the explicit grid still contains one grid line in each axis." + MOZ_ASSERT(aLine == 0, "We should only resolve line 1 in an empty grid"); + return nscoord(0); + } MOZ_ASSERT(aLine <= aTrackSizes.Length(), "aTrackSizes is too small"); if (aLine == aTrackSizes.Length()) { const TrackSize& sz = aTrackSizes[aLine - 1]; diff --git a/layout/reftests/css-grid/grid-abspos-items-011-ref.html b/layout/reftests/css-grid/grid-abspos-items-011-ref.html new file mode 100644 index 000000000000..55ddb6da8b2b --- /dev/null +++ b/layout/reftests/css-grid/grid-abspos-items-011-ref.html @@ -0,0 +1,82 @@ + + + + + CSS Grid Test: abs pos areas in empty grid + + + + + +There should be no red areas. +
+ +
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ +
+ +
+
+ + +
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ +
+
+ + + diff --git a/layout/reftests/css-grid/grid-abspos-items-011.html b/layout/reftests/css-grid/grid-abspos-items-011.html new file mode 100644 index 000000000000..5706998fefdb --- /dev/null +++ b/layout/reftests/css-grid/grid-abspos-items-011.html @@ -0,0 +1,97 @@ + + + + + CSS Grid Test: abs pos areas in empty grid + + + + + + + +There should be no red areas. +
+ +
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ +
+ +
+
+ + + + +
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ +
+
+ + + diff --git a/layout/reftests/css-grid/reftest.list b/layout/reftests/css-grid/reftest.list index 3efcc4028f17..0571ed64943a 100644 --- a/layout/reftests/css-grid/reftest.list +++ b/layout/reftests/css-grid/reftest.list @@ -21,6 +21,7 @@ fails == grid-whitespace-handling-1b.xhtml grid-whitespace-handling-1-ref.xhtml == grid-abspos-items-008.html grid-abspos-items-008-ref.html == grid-abspos-items-009.html grid-abspos-items-009-ref.html == grid-abspos-items-010.html grid-abspos-items-010-ref.html +== grid-abspos-items-011.html grid-abspos-items-011-ref.html == grid-order-abspos-items-001.html grid-order-abspos-items-001-ref.html == grid-order-placement-auto-001.html grid-order-placement-auto-001-ref.html == grid-order-placement-definite-001.html grid-order-placement-definite-001-ref.html From 97a104a94224d6efbe31cebc9abeac7b3cfd6ba0 Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Tue, 3 Nov 2015 21:45:32 +0100 Subject: [PATCH 067/113] Bug 1211260 - Implement the new Grid Placement Conflict Handling: "If the placement for a grid item contains two lines, and the start line is further end-ward than the end line, swap the two lines." r=dholbert https://drafts.csswg.org/css-grid/#grid-placement-errors --- layout/generic/nsGridContainerFrame.cpp | 12 +++++--- .../css-grid/grid-abspos-items-001-ref.html | 2 +- .../css-grid/grid-abspos-items-002-ref.html | 2 +- ...rid-placement-abspos-implicit-001-ref.html | 4 +-- ...d-placement-definite-implicit-002-ref.html | 30 +++++++++---------- 5 files changed, 27 insertions(+), 23 deletions(-) diff --git a/layout/generic/nsGridContainerFrame.cpp b/layout/generic/nsGridContainerFrame.cpp index 192a3b21c850..aeaca39f3057 100644 --- a/layout/generic/nsGridContainerFrame.cpp +++ b/layout/generic/nsGridContainerFrame.cpp @@ -1414,12 +1414,16 @@ nsGridContainerFrame::ResolveLineRange( // range has a HypotheticalEnd <= kMaxLine. // http://dev.w3.org/csswg/css-grid/#overlarge-grids r.second = std::min(r.second, nsStyleGridLine::kMaxLine - 1); - } else if (r.second <= r.first) { + } else { // http://dev.w3.org/csswg/css-grid/#grid-placement-errors - if (MOZ_UNLIKELY(r.first == nsStyleGridLine::kMaxLine)) { - r.first = nsStyleGridLine::kMaxLine - 1; + if (r.second < r.first) { + Swap(r.first, r.second); + } else if (r.first == r.second) { + if (MOZ_UNLIKELY(r.first == nsStyleGridLine::kMaxLine)) { + r.first = nsStyleGridLine::kMaxLine - 1; + } + r.second = r.first + 1; // XXX subgrid explicit size instead of 1? } - r.second = r.first + 1; // XXX subgrid explicit size instead of 1? } return LineRange(r.first, r.second); } diff --git a/layout/reftests/css-grid/grid-abspos-items-001-ref.html b/layout/reftests/css-grid/grid-abspos-items-001-ref.html index 0fc6afed8d48..a03613445016 100644 --- a/layout/reftests/css-grid/grid-abspos-items-001-ref.html +++ b/layout/reftests/css-grid/grid-abspos-items-001-ref.html @@ -48,7 +48,7 @@ body,html { color:black; background:white; font-size:16px; padding:0; margin:0; width: 112px; height: 82px; } .d { - left: 1px; top: 27px; + left: 1px; top: 20px; width: 5px; height: 1px; } .e { diff --git a/layout/reftests/css-grid/grid-abspos-items-002-ref.html b/layout/reftests/css-grid/grid-abspos-items-002-ref.html index f084d630ca73..9be7f8adf5f2 100644 --- a/layout/reftests/css-grid/grid-abspos-items-002-ref.html +++ b/layout/reftests/css-grid/grid-abspos-items-002-ref.html @@ -49,7 +49,7 @@ body,html { color:black; background:white; font-size:16px; padding:0; margin:0; width: 112px; height: 82px; } .d { - left: 1px; top: 27px; + left: 1px; top: 20px; width: 5px; height: 1px; } .e { diff --git a/layout/reftests/css-grid/grid-placement-abspos-implicit-001-ref.html b/layout/reftests/css-grid/grid-placement-abspos-implicit-001-ref.html index ae720880b799..c01334985b70 100644 --- a/layout/reftests/css-grid/grid-placement-abspos-implicit-001-ref.html +++ b/layout/reftests/css-grid/grid-placement-abspos-implicit-001-ref.html @@ -45,7 +45,7 @@ body,html { color:black; background:white; font-size:16px; padding:0; margin:0; width: 51px; height: 2px; } .d { - left: 1px; top: 18px; + left: 1px; top: 11px; width: 212px; height: 1px; } .e { @@ -98,7 +98,7 @@ span {
-d +d
diff --git a/layout/reftests/css-grid/grid-placement-definite-implicit-002-ref.html b/layout/reftests/css-grid/grid-placement-definite-implicit-002-ref.html index 0476ef29eafe..30f764f4e94e 100644 --- a/layout/reftests/css-grid/grid-placement-definite-implicit-002-ref.html +++ b/layout/reftests/css-grid/grid-placement-definite-implicit-002-ref.html @@ -27,15 +27,15 @@ span { box-sizing: border-box; } -.XeN { left: 100px; } -.XsN { width: 80px; } .XsN ~ span { top:20px; left:60px; } +.XeN { left: 20px; width: 80px; } +.XsN { width: 80px; } .XsN ~ span { top:20px; left:60px; } .NeX { left: 20px; width: 80px; } -.NsX { left: 20px; } -.XeA { left: 100px; } +.NsX { width: 80px; } .NsX ~ span { left: 60px; top: 20px; } +.XeA { width: 100px; } .XeA ~ span { top: 20px; } .XsA { width: 60px; } .XsA ~ span { left: 60px; } .XsA2 { width: 80px; } .XsA2 ~ span { left: 60px; top: 20px; } .XsA3 { width: 120px; } .XsA3 ~ span { left: 60px; top: 20px; } -.AsX { } .AsX ~ span { top: 20px; } +.AsX { width: 60px;} .AsX ~ span { left: 60px; } .xsN { } .xsN ~ span { left: 20px; } .x2sN { width: 40px; } .x2sN ~ span { left: 40px; } .xsN2 { width: 40px; } .xsN2 ~ span { left: 20px; top: 20px; } @@ -44,18 +44,18 @@ span { .aXe { left: 20px; width: 60px; } .xXe { width: 100px; } .xXe ~ span { left: 20px; top: 20px; } -.AXe { width: 80px; } .AXe ~ span { top: 20px; } +.AXe { width: 80px; } .AXe ~ span { top: 20px; } .A2Xe { left: 20px; width: 60px; } .XXe { left: 60px; } .XX3e { left: 60px; width: 40px; } -.XbXe { width: 100px; } .XbXe ~ span { top: 20px; left: 40px; } +.XbXe { width: 100px; } .XbXe ~ span { top: 20px; left: 40px; } .XX0b { } .XX0b ~ span { left: 60px; } .XX1b { } .XX1b ~ span { left: 60px; } .XX2b { width: 40px; } .XX2b ~ span { left: 60px; } .XbN1 { width: 60px; } .XbN1 ~ span { left: 60px; } .XbN2 { width: 80px; } .XbN2 ~ span { top: 20px; left: 60px; } -.Xbb { } .Xbb ~ span { left: 60px; } -.Xee { left: 100px; } +.Xbb { } .Xbb ~ span { left: 80px; } +.Xee { left: 60px; width:40px; } .nX2s { width: 40px; } .nX2s ~ span { left: 80px; } .nXs { width: 40px; } .nXs ~ span { left: 60px; } .nXe { left: 20px; width: 40px; } @@ -69,11 +69,11 @@ span { .Xea2 { left: 60px; width: 40px; } .Xea3 { left: 60px; width: 60px; } .Xsa { } .Xsa ~ span { left: 20px; } -.Xsa2 { width: 40px; } .Xsa2 ~ span { left: 20px; top: 20px; } -.Xsa4 { width: 100px; } .Xsa4 ~ span { left: 20px; top: 20px; } +.Xsa2 { width: 40px; } .Xsa2 ~ span { left: 20px; top: 20px; } +.Xsa4 { width: 100px; } .Xsa4 ~ span { left: 20px; top: 20px; } .Xs2a { width: 40px; } .Xs2a ~ span { left: 40px; } -.Xs2a2 { width: 60px; } .Xs2a2 ~ span { left: 40px; top: 20px;} -.Xs2a4 { width: 120px; } .Xs2a4 ~ span { left: 40px; top: 20px; } +.Xs2a2 { width: 60px; } .Xs2a2 ~ span { left: 40px; top: 20px; } +.Xs2a4 { width: 120px; } .Xs2a4 ~ span { left: 40px; top: 20px; } .Xs3a { width: 60px; } .Xs3a ~ span { left: 60px; } .Xs3a2 { width: 80px; } .Xs3a2 ~ span { left: 60px; top: 20px; } .Xs3a4 { width: 140px; } .Xs3a4 ~ span { left: 60px; top: 20px; } @@ -88,8 +88,8 @@ span { .Aa { } .Aa ~ span { top: 20px; } .A2a { left: 20px; width: 40px; } .Aa3 { width: 80px; } .Aa3 ~ span { top: 20px; } -.AXs { } .AXs ~ span { top: 20px; } -.A2Xs { left: 20px; } +.AXs { width: 40px; } .AXs ~ span { left:40px; } +.A2Xs { width: 60px; } .A2Xs ~ span { left: 40px; top:20px; } ._Xs { } ._Xs ~ span { left: 60px; } ._Xe { left: 60px; } ._xe { } ._xe ~ span { top: 20px; } From 9f977fa5fc897ae7f862e018c43ab535cdbb39dd Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Tue, 3 Nov 2015 21:45:32 +0100 Subject: [PATCH 068/113] Bug 1215182 - [css-grid] Make our "Implicit Named Areas" detection match the spec. r=dholbert --- layout/generic/nsGridContainerFrame.cpp | 29 +++++++------------------ 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/layout/generic/nsGridContainerFrame.cpp b/layout/generic/nsGridContainerFrame.cpp index aeaca39f3057..ac808f4784e5 100644 --- a/layout/generic/nsGridContainerFrame.cpp +++ b/layout/generic/nsGridContainerFrame.cpp @@ -1165,33 +1165,22 @@ nsGridContainerFrame::AddImplicitNamedAreas( const nsTArray>& aLineNameLists) { // http://dev.w3.org/csswg/css-grid/#implicit-named-areas - // XXX this just checks x-start .. x-end in one dimension and there's - // no other error checking. A few wrong cases (maybe): - // (x-start x-end) - // (x-start) 0 (x-start) 0 (x-end) - // (x-end) 0 (x-start) 0 (x-end) - // (x-start) 0 (x-end) 0 (x-start) 0 (x-end) + // Note: recording these names for fast lookup later is just an optimization. const uint32_t len = std::min(aLineNameLists.Length(), size_t(nsStyleGridLine::kMaxLine)); nsTHashtable currentStarts; ImplicitNamedAreas* areas = GetImplicitNamedAreas(); for (uint32_t i = 0; i < len; ++i) { - const nsTArray& names(aLineNameLists[i]); - const uint32_t jLen = names.Length(); - for (uint32_t j = 0; j < jLen; ++j) { - const nsString& name = names[j]; + for (const nsString& name : aLineNameLists[i]) { uint32_t index; - if (::IsNameWithStartSuffix(name, &index)) { - currentStarts.PutEntry(nsDependentSubstring(name, 0, index)); - } else if (::IsNameWithEndSuffix(name, &index)) { + if (::IsNameWithStartSuffix(name, &index) || + ::IsNameWithEndSuffix(name, &index)) { nsDependentSubstring area(name, 0, index); - if (currentStarts.Contains(area)) { - if (!areas) { - areas = new ImplicitNamedAreas; - Properties().Set(ImplicitNamedAreasProperty(), areas); - } - areas->PutEntry(area); + if (!areas) { + areas = new ImplicitNamedAreas; + Properties().Set(ImplicitNamedAreasProperty(), areas); } + areas->PutEntry(area); } } } @@ -1251,8 +1240,6 @@ nsGridContainerFrame::ResolveLine( lineName.AppendLiteral("-end"); implicitLine = area ? area->*aAreaEnd : 0; } - // XXX must Implicit Named Areas have all four lines? - // http://dev.w3.org/csswg/css-grid/#implicit-named-areas line = ::FindNamedLine(lineName, &aNth, aFromIndex, implicitLine, aLineNameList); } From 426c64bebe454b7bec6de7e19778a26eaafa8cd9 Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Tue, 3 Nov 2015 21:45:32 +0100 Subject: [PATCH 069/113] Bug 1215182 - Reftests. --- ...lacement-implicit-named-areas-001-ref.html | 157 +++++++++++++++++ ...id-placement-implicit-named-areas-001.html | 159 ++++++++++++++++++ layout/reftests/css-grid/reftest.list | 1 + 3 files changed, 317 insertions(+) create mode 100644 layout/reftests/css-grid/grid-placement-implicit-named-areas-001-ref.html create mode 100644 layout/reftests/css-grid/grid-placement-implicit-named-areas-001.html diff --git a/layout/reftests/css-grid/grid-placement-implicit-named-areas-001-ref.html b/layout/reftests/css-grid/grid-placement-implicit-named-areas-001-ref.html new file mode 100644 index 000000000000..a1c63b396526 --- /dev/null +++ b/layout/reftests/css-grid/grid-placement-implicit-named-areas-001-ref.html @@ -0,0 +1,157 @@ + + + + + Reference: implicit named areas + + + + + +
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+ +
+ + + +
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+ +
+ + + diff --git a/layout/reftests/css-grid/grid-placement-implicit-named-areas-001.html b/layout/reftests/css-grid/grid-placement-implicit-named-areas-001.html new file mode 100644 index 000000000000..8f73dfb9b688 --- /dev/null +++ b/layout/reftests/css-grid/grid-placement-implicit-named-areas-001.html @@ -0,0 +1,159 @@ + + + + + CSS Grid Test: implicit named areas + + + + + + + +
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+ +
+ + + +
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+ +
+ + + diff --git a/layout/reftests/css-grid/reftest.list b/layout/reftests/css-grid/reftest.list index 0571ed64943a..32dee3187a30 100644 --- a/layout/reftests/css-grid/reftest.list +++ b/layout/reftests/css-grid/reftest.list @@ -10,6 +10,7 @@ fails == grid-whitespace-handling-1b.xhtml grid-whitespace-handling-1-ref.xhtml == grid-placement-auto-row-dense-001.html grid-placement-auto-row-dense-001-ref.html == grid-placement-auto-col-sparse-001.html grid-placement-auto-col-sparse-001-ref.html == grid-placement-auto-col-dense-001.html grid-placement-auto-col-dense-001-ref.html +== grid-placement-implicit-named-areas-001.html grid-placement-implicit-named-areas-001-ref.html == grid-track-sizing-001.html grid-track-sizing-001-ref.html == grid-abspos-items-001.html grid-abspos-items-001-ref.html == grid-abspos-items-002.html grid-abspos-items-002-ref.html From d05caaac365d06797ea5108da71cfb32bf3ac8df Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Tue, 3 Nov 2015 21:45:32 +0100 Subject: [PATCH 070/113] Bug 1215957 - Start at the end of the explicit grid also when matching plain negative number lines. r=dholbert --- layout/generic/nsGridContainerFrame.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/layout/generic/nsGridContainerFrame.cpp b/layout/generic/nsGridContainerFrame.cpp index ac808f4784e5..4498b002f872 100644 --- a/layout/generic/nsGridContainerFrame.cpp +++ b/layout/generic/nsGridContainerFrame.cpp @@ -1312,7 +1312,8 @@ nsGridContainerFrame::ResolveLineRangeHelper( return LinePair(kAutoLine, 1); // XXX subgrid explicit size instead of 1? } - auto end = ResolveLine(aEnd, aEnd.mInteger, 0, aLineNameList, aAreaStart, + uint32_t from = aEnd.mInteger < 0 ? aExplicitGridEnd : 0; + auto end = ResolveLine(aEnd, aEnd.mInteger, from, aLineNameList, aAreaStart, aAreaEnd, aExplicitGridEnd, eLineRangeSideEnd, aStyle); int32_t span = aStart.mInteger == 0 ? 1 : aStart.mInteger; @@ -1345,9 +1346,10 @@ nsGridContainerFrame::ResolveLineRangeHelper( return LinePair(start, 1); // XXX subgrid explicit size instead of 1? } } else { - start = ResolveLine(aStart, aStart.mInteger, 0, aLineNameList, aAreaStart, - aAreaEnd, aExplicitGridEnd, eLineRangeSideStart, - aStyle); + uint32_t from = aStart.mInteger < 0 ? aExplicitGridEnd : 0; + start = ResolveLine(aStart, aStart.mInteger, from, aLineNameList, + aAreaStart, aAreaEnd, aExplicitGridEnd, + eLineRangeSideStart, aStyle); if (aEnd.IsAuto()) { // A "definite line / auto" should resolve the auto to 'span 1'. // The error handling in ResolveLineRange will make that happen and also @@ -1356,14 +1358,14 @@ nsGridContainerFrame::ResolveLineRangeHelper( } } - uint32_t from = 0; + uint32_t from; int32_t nth = aEnd.mInteger == 0 ? 1 : aEnd.mInteger; if (aEnd.mHasSpan) { if (MOZ_UNLIKELY(start < 0)) { if (aEnd.mLineName.IsEmpty()) { return LinePair(start, start + nth); } - // Fall through and start searching from the start of the grid (from=0). + from = 0; } else { if (start >= int32_t(aExplicitGridEnd)) { // The start is at or after the last explicit line, thus all lines @@ -1372,6 +1374,8 @@ nsGridContainerFrame::ResolveLineRangeHelper( } from = start; } + } else { + from = aEnd.mInteger < 0 ? aExplicitGridEnd : 0; } auto end = ResolveLine(aEnd, nth, from, aLineNameList, aAreaStart, aAreaEnd, aExplicitGridEnd, eLineRangeSideEnd, aStyle); From cad3e35d69018ecd08280644f756773bc8da22db Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Tue, 3 Nov 2015 21:45:32 +0100 Subject: [PATCH 071/113] Bug 1215957 - Reftests. --- ...grid-placement-negative-lines-001-ref.html | 116 ++++++++++++++++++ .../grid-placement-negative-lines-001.html | 110 +++++++++++++++++ layout/reftests/css-grid/reftest.list | 1 + 3 files changed, 227 insertions(+) create mode 100644 layout/reftests/css-grid/grid-placement-negative-lines-001-ref.html create mode 100644 layout/reftests/css-grid/grid-placement-negative-lines-001.html diff --git a/layout/reftests/css-grid/grid-placement-negative-lines-001-ref.html b/layout/reftests/css-grid/grid-placement-negative-lines-001-ref.html new file mode 100644 index 000000000000..6de1935e449c --- /dev/null +++ b/layout/reftests/css-grid/grid-placement-negative-lines-001-ref.html @@ -0,0 +1,116 @@ + + + + + Reference: Placement involving negative line numbers + + + + + +
+grid-template-columns: 60px [A] 60px 60px;
+grid-auto-columns: 40px;
+
+ +
grid-column-start:
+
+ +-1 +4 +-2 +-3 +-4 +-5 +A -1 +B -1 +A -2 +A -3 +A -4 +A -5 +A +B +A 1 +A 2 +A 3 +A 4 +A 5 +
+ +
grid-column-end:
+
+ +-1 +4 +-2 +-3 +-4 +-5 +A -1 +B -1 +A -2 +A -3 +A -4 +A -5 +A +B +A 1 +A 2 +A 3 +A 4 +A 5 +
+ +
grid-column: / span A 2
+
+ +-1 +4 +-2 +-3 +-4 +-5 +A -1 +B -1 +A -2 +A -3 +A -4 +A -5 +A +B +A 1 +A 2 +A 3 +A 4 +A 5 +
+ + + diff --git a/layout/reftests/css-grid/grid-placement-negative-lines-001.html b/layout/reftests/css-grid/grid-placement-negative-lines-001.html new file mode 100644 index 000000000000..3c99efa4d8db --- /dev/null +++ b/layout/reftests/css-grid/grid-placement-negative-lines-001.html @@ -0,0 +1,110 @@ + + + + + CSS Grid Test: Placement involving negative line numbers + + + + + + + +
+grid-template-columns: 60px [A] 60px 60px;
+grid-auto-columns: 40px;
+
+ +
grid-column-start:
+
+ +-1 +4 +-2 +-3 +-4 +-5 +A -1 +B -1 +A -2 +A -3 +A -4 +A -5 +A +B +A 1 +A 2 +A 3 +A 4 +A 5 +
+ +
grid-column-end:
+
+ +-1 +4 +-5 +-4 +-3 +-2 +A -1 +B -1 +A -2 +A -3 +A -4 +A -5 +A +B +A 1 +A 2 +A 3 +A 4 +A 5 +
+ +
grid-column: / span A 2
+
+ +-1 +4 +-2 +-3 +-4 +-5 +A -1 +B -1 +A -2 +A -3 +A -4 +A -5 +A +B +A 1 +A 2 +A 3 +A 4 +A 5 +
+ + + diff --git a/layout/reftests/css-grid/reftest.list b/layout/reftests/css-grid/reftest.list index 32dee3187a30..0575dfb5367a 100644 --- a/layout/reftests/css-grid/reftest.list +++ b/layout/reftests/css-grid/reftest.list @@ -6,6 +6,7 @@ fails == grid-whitespace-handling-1b.xhtml grid-whitespace-handling-1-ref.xhtml == grid-placement-definite-001.html grid-placement-definite-001-ref.html == grid-placement-definite-002.html grid-placement-definite-002-ref.html == grid-placement-definite-003.html grid-placement-definite-003-ref.html +== grid-placement-negative-lines-001.html grid-placement-negative-lines-001-ref.html == grid-placement-auto-row-sparse-001.html grid-placement-auto-row-sparse-001-ref.html == grid-placement-auto-row-dense-001.html grid-placement-auto-row-dense-001-ref.html == grid-placement-auto-col-sparse-001.html grid-placement-auto-col-sparse-001-ref.html From a113aae1a56595fcc4615bdc4605c99603853f2c Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Tue, 3 Nov 2015 21:45:33 +0100 Subject: [PATCH 072/113] Bug 1163435 part 1 - [css-grid][css-flexbox] Propagate an explicit CB width/height to the reflow state to resolve percentage lengths for grid items properly. Resolve percent against the size in the same axis for abs.pos. children too. r=dholbert Grid items are supposed to use the size of their 'grid area', not their grid container, as their containing block. --- layout/generic/nsGridContainerFrame.cpp | 5 +++-- layout/generic/nsHTMLReflowState.cpp | 7 ++++--- layout/generic/nsHTMLReflowState.h | 6 +++--- layout/generic/nsIFrame.h | 1 + layout/generic/nsIFrameInlines.h | 18 +++++++++++------- 5 files changed, 22 insertions(+), 15 deletions(-) diff --git a/layout/generic/nsGridContainerFrame.cpp b/layout/generic/nsGridContainerFrame.cpp index 4498b002f872..8c96cb966b96 100644 --- a/layout/generic/nsGridContainerFrame.cpp +++ b/layout/generic/nsGridContainerFrame.cpp @@ -2799,12 +2799,13 @@ nsGridContainerFrame::ReflowChildren(GridReflowState& aState, } WritingMode childWM = child->GetWritingMode(); LogicalSize childCBSize = cb.Size(wm).ConvertTo(childWM, wm); + LogicalSize percentBasis(childCBSize); // XXX temporary workaround to avoid being INCOMPLETE until we have // support for fragmentation (bug 1144096) childCBSize.BSize(childWM) = NS_UNCONSTRAINEDSIZE; Maybe childRS; // Maybe<> so we can reuse the space - childRS.emplace(pc, *aState.mReflowState, child, childCBSize); + childRS.emplace(pc, *aState.mReflowState, child, childCBSize, &percentBasis); // We need the width of the child before we can correctly convert // the writing-mode of its origin, so we reflow at (0, 0) using a dummy // containerSize, and then pass the correct position to FinishReflowChild. @@ -2835,7 +2836,7 @@ nsGridContainerFrame::ReflowChildren(GridReflowState& aState, NS_FRAME_NO_MOVE_FRAME | NS_FRAME_NO_SIZE_VIEW); childSize.reset(); // In reverse declaration order since it runs childRS.reset(); // destructors. - childRS.emplace(pc, *aState.mReflowState, child, childCBSize); + childRS.emplace(pc, *aState.mReflowState, child, childCBSize, &percentBasis); if ((alignResize && alignResize.value() == eLogicalAxisBlock) || (justifyResize && justifyResize.value() == eLogicalAxisBlock)) { childRS->SetComputedBSize(newContentSize.BSize(childWM)); diff --git a/layout/generic/nsHTMLReflowState.cpp b/layout/generic/nsHTMLReflowState.cpp index dc191660d193..1002a067d309 100644 --- a/layout/generic/nsHTMLReflowState.cpp +++ b/layout/generic/nsHTMLReflowState.cpp @@ -2036,8 +2036,8 @@ IsSideCaption(nsIFrame* aFrame, const nsStyleDisplay* aStyleDisplay, captionSide == NS_STYLE_CAPTION_SIDE_RIGHT; } -// Flex items resolve block-axis percentage margin & padding against the flex -// container's block-size (which is the containing block block-size). +// Flex/grid items resolve block-axis percentage margin & padding against the +// containing block block-size (also for abs/fixed-pos child frames). // For everything else: the CSS21 spec requires that margin and padding // percentage values are calculated with respect to the inline-size of the // containing block, even for margin & padding in the block axis. @@ -2047,7 +2047,8 @@ OffsetPercentBasis(const nsIFrame* aFrame, const LogicalSize& aContainingBlockSize) { LogicalSize offsetPercentBasis = aContainingBlockSize; - if (!aFrame->IsFlexOrGridItem()) { + if (MOZ_LIKELY(!aFrame->GetParent() || + !aFrame->GetParent()->IsFlexOrGridContainer())) { offsetPercentBasis.BSize(aWM) = offsetPercentBasis.ISize(aWM); } else if (offsetPercentBasis.BSize(aWM) == NS_AUTOHEIGHT) { offsetPercentBasis.BSize(aWM) = 0; diff --git a/layout/generic/nsHTMLReflowState.h b/layout/generic/nsHTMLReflowState.h index 1ab628e7d378..b802255607d9 100644 --- a/layout/generic/nsHTMLReflowState.h +++ b/layout/generic/nsHTMLReflowState.h @@ -652,9 +652,9 @@ public: * @param aFrame The frame for whose reflow state is being constructed. * @param aAvailableSpace See comments for availableHeight and availableWidth * members. - * @param aContainingBlockSize An optional size, in app units, that - * is used by absolute positioning code to override default containing - * block sizes. + * @param aContainingBlockSize An optional size, in app units, specifying + * the containing block size to use instead of the default which is + * to use the aAvailableSpace. * @param aFlags A set of flags used for additional boolean parameters (see * below). */ diff --git a/layout/generic/nsIFrame.h b/layout/generic/nsIFrame.h index 7438b6813eaa..1019138248bb 100644 --- a/layout/generic/nsIFrame.h +++ b/layout/generic/nsIFrame.h @@ -2953,6 +2953,7 @@ NS_PTR_TO_INT32(frame->Properties().Get(nsIFrame::ParagraphDepthProperty())) * Is this a flex or grid item? (i.e. a non-abs-pos child of a flex/grid container) */ inline bool IsFlexOrGridItem() const; + inline bool IsFlexOrGridContainer() const; /** * @return true if this frame is used as a table caption. diff --git a/layout/generic/nsIFrameInlines.h b/layout/generic/nsIFrameInlines.h index e2872b2a384c..d29df4e4c34d 100644 --- a/layout/generic/nsIFrameInlines.h +++ b/layout/generic/nsIFrameInlines.h @@ -19,16 +19,20 @@ nsIFrame::IsFlexItem() const !(GetStateBits() & NS_FRAME_OUT_OF_FLOW); } +bool +nsIFrame::IsFlexOrGridContainer() const +{ + nsIAtom* t = GetType(); + return t == nsGkAtoms::flexContainerFrame || + t == nsGkAtoms::gridContainerFrame; +} + bool nsIFrame::IsFlexOrGridItem() const { - if (GetParent()) { - nsIAtom* t = GetParent()->GetType(); - return (t == nsGkAtoms::flexContainerFrame || - t == nsGkAtoms::gridContainerFrame) && - !(GetStateBits() & NS_FRAME_OUT_OF_FLOW); - } - return false; + return !(GetStateBits() & NS_FRAME_OUT_OF_FLOW) && + GetParent() && + GetParent()->IsFlexOrGridContainer(); } bool From 759f29e87ff4ea2991d13a6f8211794135986821 Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Tue, 3 Nov 2015 21:45:33 +0100 Subject: [PATCH 073/113] Bug 1163435 part 2 - tests. --- .../grid-item-sizing-percent-001-ref.html | 94 ++++++++++++++++++ .../grid-item-sizing-percent-001.html | 96 +++++++++++++++++++ .../css-grid/grid-item-sizing-px-001.html | 94 ++++++++++++++++++ layout/reftests/css-grid/reftest.list | 2 + 4 files changed, 286 insertions(+) create mode 100644 layout/reftests/css-grid/grid-item-sizing-percent-001-ref.html create mode 100644 layout/reftests/css-grid/grid-item-sizing-percent-001.html create mode 100644 layout/reftests/css-grid/grid-item-sizing-px-001.html diff --git a/layout/reftests/css-grid/grid-item-sizing-percent-001-ref.html b/layout/reftests/css-grid/grid-item-sizing-percent-001-ref.html new file mode 100644 index 000000000000..3a7f2d6bb0e1 --- /dev/null +++ b/layout/reftests/css-grid/grid-item-sizing-percent-001-ref.html @@ -0,0 +1,94 @@ + + + + Reference 001 + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + diff --git a/layout/reftests/css-grid/grid-item-sizing-percent-001.html b/layout/reftests/css-grid/grid-item-sizing-percent-001.html new file mode 100644 index 000000000000..cc5948968ca9 --- /dev/null +++ b/layout/reftests/css-grid/grid-item-sizing-percent-001.html @@ -0,0 +1,96 @@ + + + + CSS Test: Testing grid item percent sizes + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + diff --git a/layout/reftests/css-grid/grid-item-sizing-px-001.html b/layout/reftests/css-grid/grid-item-sizing-px-001.html new file mode 100644 index 000000000000..a004365fc2cf --- /dev/null +++ b/layout/reftests/css-grid/grid-item-sizing-px-001.html @@ -0,0 +1,94 @@ + + + + CSS Test: Testing grid item 'px' sizes + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + diff --git a/layout/reftests/css-grid/reftest.list b/layout/reftests/css-grid/reftest.list index 0575dfb5367a..97b24b25df18 100644 --- a/layout/reftests/css-grid/reftest.list +++ b/layout/reftests/css-grid/reftest.list @@ -36,6 +36,8 @@ pref(layout.css.vertical-text.enabled,true) == rtl-grid-placement-auto-row-spars pref(layout.css.vertical-text.enabled,true) == vlr-grid-placement-auto-row-sparse-001.html vlr-grid-placement-auto-row-sparse-001-ref.html pref(layout.css.vertical-text.enabled,true) == vrl-grid-placement-auto-row-sparse-001.html vrl-grid-placement-auto-row-sparse-001-ref.html == grid-relpos-items-001.html grid-relpos-items-001-ref.html +== grid-item-sizing-percent-001.html grid-item-sizing-percent-001-ref.html +== grid-item-sizing-px-001.html grid-item-sizing-percent-001-ref.html == grid-item-dir-001.html grid-item-dir-001-ref.html fuzzy-if(winWidget,70,130) fuzzy-if(cocoaWidget,85,180) == grid-col-max-sizing-max-content-001.html grid-col-max-sizing-max-content-001-ref.html fuzzy-if(winWidget,70,130) fuzzy-if(cocoaWidget,85,180) == grid-col-max-sizing-max-content-002.html grid-col-max-sizing-max-content-002-ref.html From a09faf69989455b459b6b80aa70a280c41ace934 Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Tue, 3 Nov 2015 21:45:33 +0100 Subject: [PATCH 074/113] Bug 1176775 part 1 - [css-grid] Implement "Implied Minimum Size of Grid Items" (special min-width/height:auto behavior). r=dholbert --- layout/base/nsLayoutUtils.cpp | 74 +++++++++++++++++++------ layout/base/nsLayoutUtils.h | 19 ++++++- layout/generic/nsFrame.cpp | 24 ++++++-- layout/generic/nsGridContainerFrame.cpp | 51 ++++++++++++----- 4 files changed, 129 insertions(+), 39 deletions(-) diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index 8b1255a1f52e..56b4be0d7c2f 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -4546,10 +4546,15 @@ nsLayoutUtils::IntrinsicForAxis(PhysicalAxis aAxis, const nsStylePosition* stylePos = aFrame->StylePosition(); uint8_t boxSizing = stylePos->mBoxSizing; - const nsStyleCoord& styleISize = - horizontalAxis ? stylePos->mWidth : stylePos->mHeight; const nsStyleCoord& styleMinISize = horizontalAxis ? stylePos->mMinWidth : stylePos->mMinHeight; + const nsStyleCoord& styleISize = + (aFlags & MIN_INTRINSIC_ISIZE) ? styleMinISize : + (horizontalAxis ? stylePos->mWidth : stylePos->mHeight); + MOZ_ASSERT(!(aFlags & MIN_INTRINSIC_ISIZE) || + styleISize.GetUnit() == eStyleUnit_Auto || + styleISize.GetUnit() == eStyleUnit_Enumerated, + "should only use MIN_INTRINSIC_ISIZE for intrinsic values"); const nsStyleCoord& styleMaxISize = horizontalAxis ? stylePos->mMaxWidth : stylePos->mMaxHeight; @@ -4765,9 +4770,9 @@ nsLayoutUtils::MinSizeContributionForAxis(PhysicalAxis aAxis, IntrinsicISizeType aType, uint32_t aFlags) { - NS_PRECONDITION(aFrame, "null frame"); - NS_PRECONDITION(aFrame->GetParent(), - "MinSizeContributionForAxis called on frame not in tree"); + MOZ_ASSERT(aFrame); + MOZ_ASSERT(aFrame->IsFlexOrGridItem(), + "only grid/flex items have this behavior currently"); #ifdef DEBUG_INTRINSIC_WIDTH nsFrame::IndentBy(stderr, gNoiseIndent); @@ -4777,6 +4782,46 @@ nsLayoutUtils::MinSizeContributionForAxis(PhysicalAxis aAxis, aWM.IsVertical() ? "vertical" : "horizontal"); #endif + const nsStylePosition* const stylePos = aFrame->StylePosition(); + const nsStyleCoord* style = aAxis == eAxisHorizontal ? &stylePos->mMinWidth + : &stylePos->mMinHeight; + nscoord minSize; + nscoord* fixedMinSize = nullptr; + auto minSizeUnit = style->GetUnit(); + if (minSizeUnit == eStyleUnit_Auto) { + if (aFrame->StyleDisplay()->mOverflowX == NS_STYLE_OVERFLOW_VISIBLE) { + style = aAxis == eAxisHorizontal ? &stylePos->mWidth + : &stylePos->mHeight; + if (GetAbsoluteCoord(*style, minSize)) { + // We have a definite width/height. This is the "specified size" in: + // https://drafts.csswg.org/css-grid/#min-size-auto + fixedMinSize = &minSize; + } + // XXX the "transferred size" piece is missing (bug 1218178) + } else { + // min-[width|height]:auto with overflow != visible computes to zero. + minSize = 0; + fixedMinSize = &minSize; + } + } else if (GetAbsoluteCoord(*style, minSize)) { + fixedMinSize = &minSize; + } else if (minSizeUnit != eStyleUnit_Enumerated) { + MOZ_ASSERT(style->HasPercent()); + minSize = 0; + fixedMinSize = &minSize; + } + + if (!fixedMinSize) { + // Let the caller deal with the "content size" cases. +#ifdef DEBUG_INTRINSIC_WIDTH + nsFrame::IndentBy(stderr, gNoiseIndent); + static_cast(aFrame)->ListTag(stderr); + printf_stderr(" %s min-isize is indefinite.\n", + aType == MIN_ISIZE ? "min" : "pref"); +#endif + return NS_UNCONSTRAINEDSIZE; + } + // If aFrame is a container for font size inflation, then shrink // wrapping inside of it should not apply font size inflation. AutoMaybeDisableFontInflation an(aFrame); @@ -4788,18 +4833,13 @@ nsLayoutUtils::MinSizeContributionForAxis(PhysicalAxis aAxis, : aFrame->IntrinsicBSizeOffsets(); nscoord result = 0; nscoord min = 0; - const nsStylePosition* stylePos = aFrame->StylePosition(); - uint8_t boxSizing = stylePos->mBoxSizing; - const nsStyleCoord& style = aAxis == eAxisHorizontal ? stylePos->mMinWidth - : stylePos->mMinHeight; - nscoord minSize; - nscoord* fixedMinSize = nullptr; - if (GetAbsoluteCoord(style, minSize)) { - fixedMinSize = &minSize; - } - result = AddIntrinsicSizeOffset(aRC, aFrame, offsets, aType, boxSizing, - result, min, style, fixedMinSize, - style, fixedMinSize, style, aFlags, aAxis); + + const nsStyleCoord& maxISize = + aAxis == eAxisHorizontal ? stylePos->mMaxWidth : stylePos->mMaxHeight; + result = AddIntrinsicSizeOffset(aRC, aFrame, offsets, aType, + stylePos->mBoxSizing, + result, min, *style, fixedMinSize, + *style, nullptr, maxISize, aFlags, aAxis); #ifdef DEBUG_INTRINSIC_WIDTH nsFrame::IndentBy(stderr, gNoiseIndent); diff --git a/layout/base/nsLayoutUtils.h b/layout/base/nsLayoutUtils.h index 3b4416dda690..f4cfcfa742a2 100644 --- a/layout/base/nsLayoutUtils.h +++ b/layout/base/nsLayoutUtils.h @@ -1324,10 +1324,13 @@ public: * variations if that's what matches aAxis) and its padding, border and margin * in the corresponding dimension. */ - enum IntrinsicISizeType { MIN_ISIZE, PREF_ISIZE }; + enum class IntrinsicISizeType { MIN_ISIZE, PREF_ISIZE }; + static const auto MIN_ISIZE = IntrinsicISizeType::MIN_ISIZE; + static const auto PREF_ISIZE = IntrinsicISizeType::PREF_ISIZE; enum { IGNORE_PADDING = 0x01, BAIL_IF_REFLOW_NEEDED = 0x02, // returns NS_INTRINSIC_WIDTH_UNKNOWN if so + MIN_INTRINSIC_ISIZE = 0x04, // use min-width/height instead of width/height }; static nscoord IntrinsicForAxis(mozilla::PhysicalAxis aAxis, nsRenderingContext* aRenderingContext, @@ -1343,10 +1346,20 @@ public: uint32_t aFlags = 0); /** - * Get the contribution of aFrame for the given physical axis. + * Get the definite size contribution of aFrame for the given physical axis. * This considers the child's 'min-width' property (or 'min-height' if the * given axis is vertical), and its padding, border, and margin in the - * corresponding dimension. + * corresponding dimension. If the 'min-' property is 'auto' (and 'overflow' + * is 'visible') and the corresponding 'width'/'height' is definite it returns + * the "specified / transferred size" for: + * https://drafts.csswg.org/css-grid/#min-size-auto + * Note that any percentage in 'width'/'height' makes it count as indefinite. + * If the 'min-' property is 'auto' and 'overflow' is not 'visible', then it + * calculates the result as if the 'min-' computed value is zero. + * Otherwise, return NS_UNCONSTRAINEDSIZE. + * + * @note this behavior is specific to Grid/Flexbox (currently) so aFrame + * should be a grid/flex item. */ static nscoord MinSizeContributionForAxis(mozilla::PhysicalAxis aAxis, nsRenderingContext* aRC, diff --git a/layout/generic/nsFrame.cpp b/layout/generic/nsFrame.cpp index 7a7408e2f713..754397fabd3b 100644 --- a/layout/generic/nsFrame.cpp +++ b/layout/generic/nsFrame.cpp @@ -4300,6 +4300,9 @@ nsFrame::ComputeSize(nsRenderingContext *aRenderingContext, const LogicalSize& aPadding, ComputeSizeFlags aFlags) { + MOZ_ASSERT(GetIntrinsicRatio() == nsSize(0,0), + "Please override this method and call " + "nsLayoutUtils::ComputeSizeWithIntrinsicDimensions instead."); LogicalSize result = ComputeAutoSize(aRenderingContext, aWM, aCBSize, aAvailableISize, aMargin, aBorder, aPadding, @@ -4321,9 +4324,12 @@ nsFrame::ComputeSize(nsRenderingContext *aRenderingContext, const nsStyleCoord* inlineStyleCoord = &stylePos->ISize(aWM); const nsStyleCoord* blockStyleCoord = &stylePos->BSize(aWM); - bool isFlexItem = IsFlexItem(); + nsIAtom* parentFrameType = GetParent() ? GetParent()->GetType() : nullptr; + bool isGridItem = (parentFrameType == nsGkAtoms::gridContainerFrame && + !(GetStateBits() & NS_FRAME_OUT_OF_FLOW)); + bool isFlexItem = (parentFrameType == nsGkAtoms::flexContainerFrame && + !(GetStateBits() & NS_FRAME_OUT_OF_FLOW)); bool isInlineFlexItem = false; - if (isFlexItem) { // Flex items use their "flex-basis" property in place of their main-size // property (e.g. "width") for sizing purposes, *unless* they have @@ -4365,14 +4371,14 @@ nsFrame::ComputeSize(nsRenderingContext *aRenderingContext, *inlineStyleCoord); } - const nsStyleCoord& maxISizeCoord = stylePos->MaxISize(aWM); - // Flex items ignore their min & max sizing properties in their // flex container's main-axis. (Those properties get applied later in // the flexbox algorithm.) + const nsStyleCoord& maxISizeCoord = stylePos->MaxISize(aWM); + nscoord maxISize = NS_UNCONSTRAINEDSIZE; if (maxISizeCoord.GetUnit() != eStyleUnit_None && !(isFlexItem && isInlineFlexItem)) { - nscoord maxISize = + maxISize = nsLayoutUtils::ComputeISizeValue(aRenderingContext, this, aCBSize.ISize(aWM), boxSizingAdjust.ISize(aWM), boxSizingToMarginEdgeISize, maxISizeCoord); @@ -4380,7 +4386,6 @@ nsFrame::ComputeSize(nsRenderingContext *aRenderingContext, } const nsStyleCoord& minISizeCoord = stylePos->MinISize(aWM); - nscoord minISize; if (minISizeCoord.GetUnit() != eStyleUnit_Auto && !(isFlexItem && isInlineFlexItem)) { @@ -4388,6 +4393,13 @@ nsFrame::ComputeSize(nsRenderingContext *aRenderingContext, nsLayoutUtils::ComputeISizeValue(aRenderingContext, this, aCBSize.ISize(aWM), boxSizingAdjust.ISize(aWM), boxSizingToMarginEdgeISize, minISizeCoord); + } else if (MOZ_UNLIKELY(isGridItem)) { + // This implements "Implied Minimum Size of Grid Items". + // https://drafts.csswg.org/css-grid/#min-size-auto + minISize = std::min(maxISize, GetMinISize(aRenderingContext)); + if (inlineStyleCoord->IsCoordPercentCalcUnit()) { + minISize = std::min(minISize, result.ISize(aWM)); + } } else { // Treat "min-width: auto" as 0. // NOTE: Technically, "auto" is supposed to behave like "min-content" on diff --git a/layout/generic/nsGridContainerFrame.cpp b/layout/generic/nsGridContainerFrame.cpp index 8c96cb966b96..05e981dbb738 100644 --- a/layout/generic/nsGridContainerFrame.cpp +++ b/layout/generic/nsGridContainerFrame.cpp @@ -1938,15 +1938,6 @@ nsGridContainerFrame::Tracks::Initialize( } } -static nscoord -MinSize(nsIFrame* aChild, nsRenderingContext* aRC, WritingMode aCBWM, - LogicalAxis aAxis, nsLayoutUtils::IntrinsicISizeType aConstraint) -{ - PhysicalAxis axis(aCBWM.PhysicalAxis(aAxis)); - return nsLayoutUtils::MinSizeContributionForAxis(axis, aRC, aChild, - aConstraint); -} - /** * Return the [min|max]-content contribution of aChild to its parent (i.e. * the child's margin-box) in aAxis. @@ -1957,11 +1948,12 @@ ContentContribution(nsIFrame* aChild, nsRenderingContext* aRC, WritingMode aCBWM, LogicalAxis aAxis, - nsLayoutUtils::IntrinsicISizeType aConstraint) + nsLayoutUtils::IntrinsicISizeType aConstraint, + uint32_t aFlags = 0) { PhysicalAxis axis(aCBWM.PhysicalAxis(aAxis)); nscoord size = nsLayoutUtils::IntrinsicForAxis(axis, aRC, aChild, aConstraint, - nsLayoutUtils::BAIL_IF_REFLOW_NEEDED); + aFlags | nsLayoutUtils::BAIL_IF_REFLOW_NEEDED); if (size == NS_INTRINSIC_WIDTH_UNKNOWN) { // We need to reflow the child to find its BSize contribution. WritingMode wm = aChild->GetWritingMode(); @@ -2025,6 +2017,39 @@ MaxContentContribution(nsIFrame* aChild, nsLayoutUtils::PREF_ISIZE); } +static nscoord +MinSize(nsIFrame* aChild, + const nsHTMLReflowState* aRS, + nsRenderingContext* aRC, + WritingMode aCBWM, + LogicalAxis aAxis) +{ + PhysicalAxis axis(aCBWM.PhysicalAxis(aAxis)); + const nsStylePosition* stylePos = aChild->StylePosition(); + const nsStyleCoord& style = axis == eAxisHorizontal ? stylePos->mMinWidth + : stylePos->mMinHeight; + // https://drafts.csswg.org/css-grid/#min-size-auto + // This calculates the min-content contribution from either a definite + // min-width (or min-height depending on aAxis), or the "specified / + // transferred size" for min-width:auto if overflow == visible (as min-width:0 + // otherwise), or NS_UNCONSTRAINEDSIZE for other min-width intrinsic values + // (which results in always taking the "content size" part below). + nscoord sz = + nsLayoutUtils::MinSizeContributionForAxis(axis, aRC, aChild, + nsLayoutUtils::MIN_ISIZE); + auto unit = style.GetUnit(); + if (unit == eStyleUnit_Enumerated || + (unit == eStyleUnit_Auto && + aChild->StyleDisplay()->mOverflowX == NS_STYLE_OVERFLOW_VISIBLE)) { + // Now calculate the "content size" part and return whichever is smaller. + MOZ_ASSERT(unit != eStyleUnit_Enumerated || sz == NS_UNCONSTRAINEDSIZE); + sz = std::min(sz, ContentContribution(aChild, aRS, aRC, aCBWM, aAxis, + nsLayoutUtils::MIN_ISIZE, + nsLayoutUtils::MIN_INTRINSIC_ISIZE)); + } + return sz; +} + void nsGridContainerFrame::Tracks::CalculateSizes( GridReflowState& aState, @@ -2112,7 +2137,7 @@ nsGridContainerFrame::Tracks::ResolveIntrinsicSizeStep1( const nsHTMLReflowState* rs = aState.mReflowState; nsRenderingContext* rc = &aState.mRenderingContext; if (sz.mState & TrackSize::eAutoMinSizing) { - nscoord s = MinSize(aGridItem, rc, wm, mAxis, aConstraint); + nscoord s = MinSize(aGridItem, rs, rc, wm, mAxis); sz.mBase = std::max(sz.mBase, s); } else if ((sz.mState & TrackSize::eMinContentMinSizing) || (aConstraint == nsLayoutUtils::MIN_ISIZE && @@ -2217,7 +2242,7 @@ nsGridContainerFrame::Tracks::ResolveIntrinsicSize( stateBitsPerSpan[span] |= state; nscoord minSize = 0; if (state & (flexMin | TrackSize::eIntrinsicMinSizing)) { // for 2.1 - minSize = MinSize(child, rc, wm, mAxis, aConstraint); + minSize = MinSize(child, aState.mReflowState, rc, wm, mAxis); } nscoord minContent = 0; if (state & (flexMin | TrackSize::eMinOrMaxContentMinSizing | // for 2.2 From 59d2bd18d3fd4e9c45bd1d9c353fb23e6f8771fd Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Tue, 3 Nov 2015 21:45:33 +0100 Subject: [PATCH 075/113] Bug 1176775 part 2 - [css-grid] Testcases for 'auto' min-sizing and intrinsic 'min-width|height'. --- ...grid-auto-min-sizing-definite-001-ref.html | 111 +++++++++++++ .../grid-auto-min-sizing-definite-001.html | 81 +++++++++ ...rid-auto-min-sizing-intrinsic-001-ref.html | 154 +++++++++++++++++ .../grid-auto-min-sizing-intrinsic-001.html | 148 +++++++++++++++++ ...rid-auto-min-sizing-intrinsic-002-ref.html | 155 ++++++++++++++++++ .../grid-auto-min-sizing-intrinsic-002.html | 150 +++++++++++++++++ ...rid-auto-min-sizing-intrinsic-003-ref.html | 66 ++++++++ .../grid-auto-min-sizing-intrinsic-003.html | 66 ++++++++ ...rid-auto-min-sizing-intrinsic-004-ref.html | 75 +++++++++ .../grid-auto-min-sizing-intrinsic-004.html | 75 +++++++++ .../grid-auto-min-sizing-percent-001-ref.html | 136 +++++++++++++++ .../grid-auto-min-sizing-percent-001.html | 109 ++++++++++++ .../grid-placement-auto-implicit-001.html | 1 + .../grid-placement-definite-implicit-001.html | 1 + layout/reftests/css-grid/reftest.list | 6 + .../reftests/css-grid/support/lime-25x1.png | Bin 0 -> 3676 bytes 16 files changed, 1334 insertions(+) create mode 100644 layout/reftests/css-grid/grid-auto-min-sizing-definite-001-ref.html create mode 100644 layout/reftests/css-grid/grid-auto-min-sizing-definite-001.html create mode 100644 layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-001-ref.html create mode 100644 layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-001.html create mode 100644 layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-002-ref.html create mode 100644 layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-002.html create mode 100644 layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-003-ref.html create mode 100644 layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-003.html create mode 100644 layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-004-ref.html create mode 100644 layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-004.html create mode 100644 layout/reftests/css-grid/grid-auto-min-sizing-percent-001-ref.html create mode 100644 layout/reftests/css-grid/grid-auto-min-sizing-percent-001.html create mode 100644 layout/reftests/css-grid/support/lime-25x1.png diff --git a/layout/reftests/css-grid/grid-auto-min-sizing-definite-001-ref.html b/layout/reftests/css-grid/grid-auto-min-sizing-definite-001-ref.html new file mode 100644 index 000000000000..58aba6fb5eb9 --- /dev/null +++ b/layout/reftests/css-grid/grid-auto-min-sizing-definite-001-ref.html @@ -0,0 +1,111 @@ + + + + + Reference: Testing 'auto' min-sizing with definite min-width/height + + + + + +
horizontal container, horizontal item
+
+
+
+
+
horizontal container, vertical item
+
+
+
+
+ + + diff --git a/layout/reftests/css-grid/grid-auto-min-sizing-definite-001.html b/layout/reftests/css-grid/grid-auto-min-sizing-definite-001.html new file mode 100644 index 000000000000..38a577195c2c --- /dev/null +++ b/layout/reftests/css-grid/grid-auto-min-sizing-definite-001.html @@ -0,0 +1,81 @@ + + + + + CSS Grid Test: 'auto' min-sizing with definite min-width/height + + + + + + + +
horizontal container, horizontal item
+
+
+
+
+
horizontal container, vertical item
+
+
+
+
+ + + diff --git a/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-001-ref.html b/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-001-ref.html new file mode 100644 index 000000000000..9f527f8157e1 --- /dev/null +++ b/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-001-ref.html @@ -0,0 +1,154 @@ + + + + + Reference: auto min-sizing with intrinsic min-width + + + + + + +
+ +
+
+ +
+
+ +
+ + + +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+ +
+
+ +
+
+ +
+ + + + + + + diff --git a/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-001.html b/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-001.html new file mode 100644 index 000000000000..095cc1b5b04b --- /dev/null +++ b/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-001.html @@ -0,0 +1,148 @@ + + + + + CSS Grid Test: auto min-sizing with intrinsic min-width + + + + + + + + +
+ +
+
+ +
+
+ +
+ + +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+ +
+
+ +
+
+ +
+ + + + + + diff --git a/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-002-ref.html b/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-002-ref.html new file mode 100644 index 000000000000..ca7244a20df3 --- /dev/null +++ b/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-002-ref.html @@ -0,0 +1,155 @@ + + + + + Reference: 'auto' min-sizing with intrinsic min-width and overflow:hidden + + + + + + +
+ +
+
+ +
+
+ +
+ + +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+ +
+
+ +
+
+ +
+ + + + + + + diff --git a/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-002.html b/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-002.html new file mode 100644 index 000000000000..c15afec94418 --- /dev/null +++ b/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-002.html @@ -0,0 +1,150 @@ + + + + + CSS Grid Test: 'auto' min-sizing with intrinsic min-width and overflow:hidden + + + + + + + + +
+ +
+
+ +
+
+ +
+ + +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+ +
+
+ +
+
+ +
+ + + + + + + diff --git a/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-003-ref.html b/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-003-ref.html new file mode 100644 index 000000000000..cb7ca9ea5a3a --- /dev/null +++ b/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-003-ref.html @@ -0,0 +1,66 @@ + + + + +Reference: min-width|min-height:auto + + + + + +
+ a + IAmReallyWideAndTheBorderShouldSurroundMe +
+ +
The border shouldn't shrink-wrap the wide text below, due to definite "width" values:
+
+ a + IAmReallyWideButIHaveADefiniteWidthSoIOverflow + c + SameHereeeeeeeeeeeeeeeeeeeeeeeeeeee + SameHereeeeeeeeeeeeeeeeeeeeeeeeeeee + SameHereeeeeeeeeeeeeeeeeeeeeeeeeeee +
+ +
Now the same tests for 'height':
+ +
+ a + IAmReallyTall + c + d +
+ +The border shouldn't shrink-wrap the wide text below, due to definite "height" values: +
+ a + IAmReallyTall + c + SameHere + SameHere +
+ + + diff --git a/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-003.html b/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-003.html new file mode 100644 index 000000000000..63cd48fff998 --- /dev/null +++ b/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-003.html @@ -0,0 +1,66 @@ + + + + +CSS Grid Test: min-width|min-height:auto + + + + + + + +
+ a + IAmReallyWideAndTheBorderShouldSurroundMe +
+ +
The border shouldn't shrink-wrap the wide text below, due to definite "width" values:
+
+ a + IAmReallyWideButIHaveADefiniteWidthSoIOverflow + c + SameHereeeeeeeeeeeeeeeeeeeeeeeeeeee + SameHereeeeeeeeeeeeeeeeeeeeeeeeeeee + SameHereeeeeeeeeeeeeeeeeeeeeeeeeeee +
+ +
Now the same tests for 'height':
+ +
+ a + IAmReallyTall + c + d +
+ +The border shouldn't shrink-wrap the wide text below, due to definite "height" values: +
+ a + IAmReallyTall + c + SameHere + SameHere +
+ + + diff --git a/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-004-ref.html b/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-004-ref.html new file mode 100644 index 000000000000..ddd050a7590e --- /dev/null +++ b/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-004-ref.html @@ -0,0 +1,75 @@ + + + + +CSS Grid Test: min-width|min-height:auto w. vertical writing-mode + + + + + +
+ a + IAmReallyWideAndTheBorderShouldSurroundMe +
+ +
The border shouldn't shrink-wrap the wide text below, due to definite "height" values:
+
+ a + IAmReallyWideButIHaveADefiniteHeightSoIOverflow + c + SameHereeeeeeeeeeeeeeeeeeeeeeeeeeee + SameHereeeeeeeeeeeeeeeeeeeeeeeeeeee + SameHereeeeeeeeeeeeeeeeeeeeeeeeeeee +
+ +
Now the same tests for 'width':
+ +
+ a + IAmReallyTall + c + d +
+ +
The border shouldn't shrink-wrap the wide text below, due to definite "width" values:
+
+ a + IAmReallyTall + c + SameHere + SameHere +
+ + + diff --git a/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-004.html b/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-004.html new file mode 100644 index 000000000000..35874df27f9c --- /dev/null +++ b/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-004.html @@ -0,0 +1,75 @@ + + + + +CSS Grid Test: min-width|min-height:auto w. vertical writing-mode + + + + + + + +
+ a + IAmReallyWideAndTheBorderShouldSurroundMe +
+ +
The border shouldn't shrink-wrap the wide text below, due to definite "height" values:
+
+ a + IAmReallyWideButIHaveADefiniteHeightSoIOverflow + c + SameHereeeeeeeeeeeeeeeeeeeeeeeeeeee + SameHereeeeeeeeeeeeeeeeeeeeeeeeeeee + SameHereeeeeeeeeeeeeeeeeeeeeeeeeeee +
+ +
Now the same tests for 'width':
+ +
+ a + IAmReallyTall + c + d +
+ +
The border shouldn't shrink-wrap the wide text below, due to definite "width" values:
+
+ a + IAmReallyTall + c + SameHere + SameHere +
+ + + diff --git a/layout/reftests/css-grid/grid-auto-min-sizing-percent-001-ref.html b/layout/reftests/css-grid/grid-auto-min-sizing-percent-001-ref.html new file mode 100644 index 000000000000..10f0f79f39ef --- /dev/null +++ b/layout/reftests/css-grid/grid-auto-min-sizing-percent-001-ref.html @@ -0,0 +1,136 @@ + + + + + Reference: Testing 'auto' min-sizing with percentage sizes + + + + + + + + +
no border/padding/margin'border-left:20px''padding-left:10%'
+ + + + + + + diff --git a/layout/reftests/css-grid/grid-auto-min-sizing-percent-001.html b/layout/reftests/css-grid/grid-auto-min-sizing-percent-001.html new file mode 100644 index 000000000000..6854ad71f2c7 --- /dev/null +++ b/layout/reftests/css-grid/grid-auto-min-sizing-percent-001.html @@ -0,0 +1,109 @@ + + + + + CSS Grid Test: Testing 'auto' min-sizing with percentage sizes + + + + + + + + + + +
no border/padding/margin'border-left:20px''padding-left:10%'
+ + + + + + + diff --git a/layout/reftests/css-grid/grid-placement-auto-implicit-001.html b/layout/reftests/css-grid/grid-placement-auto-implicit-001.html index 13c3f914fbc1..c14bf06cc5f2 100644 --- a/layout/reftests/css-grid/grid-placement-auto-implicit-001.html +++ b/layout/reftests/css-grid/grid-placement-auto-implicit-001.html @@ -35,6 +35,7 @@ body,html { color:black; background:white; font-size:12px; padding:0; margin:0; span { border: 1px solid; line-height: 18px; + min-width: 0; } diff --git a/layout/reftests/css-grid/grid-placement-definite-implicit-001.html b/layout/reftests/css-grid/grid-placement-definite-implicit-001.html index 147458370189..d19cbdff81e5 100644 --- a/layout/reftests/css-grid/grid-placement-definite-implicit-001.html +++ b/layout/reftests/css-grid/grid-placement-definite-implicit-001.html @@ -30,6 +30,7 @@ body,html { color:black; background:white; font-size:12px; padding:0; margin:0; span { border: 1px solid; line-height: 18px; + min-width: 0; } diff --git a/layout/reftests/css-grid/reftest.list b/layout/reftests/css-grid/reftest.list index 97b24b25df18..1b3f2b054a6c 100644 --- a/layout/reftests/css-grid/reftest.list +++ b/layout/reftests/css-grid/reftest.list @@ -42,6 +42,12 @@ pref(layout.css.vertical-text.enabled,true) == vrl-grid-placement-auto-row-spars fuzzy-if(winWidget,70,130) fuzzy-if(cocoaWidget,85,180) == grid-col-max-sizing-max-content-001.html grid-col-max-sizing-max-content-001-ref.html fuzzy-if(winWidget,70,130) fuzzy-if(cocoaWidget,85,180) == grid-col-max-sizing-max-content-002.html grid-col-max-sizing-max-content-002-ref.html == grid-min-max-content-sizing-001.html grid-min-max-content-sizing-001-ref.html +== grid-auto-min-sizing-definite-001.html grid-auto-min-sizing-definite-001-ref.html +== grid-auto-min-sizing-intrinsic-001.html grid-auto-min-sizing-intrinsic-001-ref.html +== grid-auto-min-sizing-intrinsic-002.html grid-auto-min-sizing-intrinsic-002-ref.html +== grid-auto-min-sizing-intrinsic-003.html grid-auto-min-sizing-intrinsic-003-ref.html +== grid-auto-min-sizing-intrinsic-004.html grid-auto-min-sizing-intrinsic-004-ref.html +== grid-auto-min-sizing-percent-001.html grid-auto-min-sizing-percent-001-ref.html == grid-track-intrinsic-sizing-001.html grid-track-intrinsic-sizing-001-ref.html == grid-track-intrinsic-sizing-002.html grid-track-intrinsic-sizing-002-ref.html == grid-track-intrinsic-sizing-003.html grid-track-intrinsic-sizing-003-ref.html diff --git a/layout/reftests/css-grid/support/lime-25x1.png b/layout/reftests/css-grid/support/lime-25x1.png new file mode 100644 index 0000000000000000000000000000000000000000..31e1c4087a51ec433be9a8c2d716348b55edc571 GIT binary patch literal 3676 zcmZ{m2{@GN`^N`U_NB6?nq)~aV@+hnG8iLSLe0oF!(cE*$dJaC<=C=kDP+l-r6{5y zJMlBgmI}$1JxkW)H=NEn{r%7Hy{>orKHtxCKllAU_jSEthWZG07C{yO0Kksa(Y#8# z3(!tFCVJX6ha;{50ANwa!{LTVI2>f?Mzq5_;{X61_O$yX6XQYNP%1@{^7g1ONOV>3 zHZ$nmB^@51Q5e$SkUk_x%v1A_Hun*UYqln@=@6m9Hd@@QcLT8l%*$McE%ip4S`$N& zPRxtN3M<2@JFV!E!KuoDLHkAWKsCVdMqot~eIjsr#a zdeqJih}h7i@BKl6rljQSw3M$cfc!bJ`PShV^ssQ8NUP0%M8 zmp2Q@?gHPeCQ8VL$jI_$GG{#z9&0LfUdx+2U(=qPBsQKYi0%@2C7&KyV>p~DI+`5` z%rs9H{cN9ohY?3HL8byG*3b6zY&_HhNOPxHbxw(8I)}+>aSJSjEz<*-5}K4=xp72n zDmkPBymIZw!+~K)Z0?bPWc`%=v`IE2DpS=}}iJ*zQU5`Ii#!Ytf0K z2`LV-^Tk5*^XUFbht0adk(0j0JJ7b<+onrmbJTS8$aM6G^V|~%g%_|UV)o!fd2OO8 zpn`B%ZDRC^!6FE-d_Yx>t_;HOiID=V8nLX9P){<1XPZ~&eu@EaFTmU>|fZw_aQMnBhaGbV4iy5U7`hGuwJ3|E}m z=3B9R{&RN=GvS8N)iWM~)l3$5w;nOR1<4=Yp(6(!Nv7|?u)hbq@EPO-u{^aWa} zF&sI^Uq3d8>lcnH1&45mUoTu-DM4UxTxCEJ|n0A7W zG{_qTq6(9%)65Jgnq*xv z1Q9Qnw30N|UL0Q(>y$W~2wyX;z~_Gm=| zG#IIsnw5zC1DlEs!`}U(&}=07DlQQ$QeI=2&dCukBokZb8yF|Lg=t4bHVjp*3S|Op(pjX;a%n{=_=(ybnE{dY&QAz34 zG%NYt@gJcxWz}J~tF|1!H_kw3;ssL$YXt=aO$C$WTNDIRT~gaqCsL>6TPqyQSf2yU zCd{UuTS_OUEOeUKiIztwWfqsr=Sg+La)yea#WwlnDEn;v@~&eRao`(@>Xqh|#v~1c z%McXAs?=yP6K0fAoRL$9sJr3!=Ji|92kQv+2le@x;hA-qbWHZB&6&d3qV99qX4#io zF50}f8NLJ?yCGH^Db%!Pm3=~^pJ?|#QfgFb zyOZtxVDmgHGy)Zo#yiAoB{?r?EHx{63N3*?gd$YTlx;PBXi9amupzgGIF8wrcjXRE zJ})ki8u6@~CtpgnC>!Ff(XXMdajXe%i*3s=LPNbmDIDwXJv`6W1d;oW%zhoM>RO5% zV-)uhf7*l08%aZVNp!JyWwWyJedFtu;&|(#p7X?4sjh9!wb)28)6n ztM#lcB11MXJM=rATSF@Y-=?+(whe$6flrvfvg81@f!7#etg_5U58VPb*3;G31fGJt zvl21y<)zp70rw16VOC(X;MsEL2TRI75u5X_GWGKHA_1kaTAJ{t8lC92cWsD>DBjDw zB<*%YvsNGCik6R7lGf!Ulq9-*TszV%5|j(&J$0PNN5s>uVEx_D<d`mPojiW(#0b}J*s+F!3B8h4tCyq+;6Qv$=yu#x%lWCOC5sK* zk62(*(6gX!&7>xaFdJkEk_S0PZ=nAC^Q~uA%K_sB2{8%T5^@FI1^>YZRcuuBXozCM z%g$TKLB%PMM)_=Mc=a26--79}EVb@fy|eCUyPD*CKlQ%rM2b8Ry^CfocYHQ%kHBTT zG3q=rxjLRY?$U9heWiWdSHVt!bj!Zh^JmD9dW8iij^g0{67!EQ^+^`>)uZ2QXQK%q zg3jQ#4(*O8-0=~Y@ZnygM@D6})(+*1lPwxAHH=0nQe8JVoSU6_oacw^%8MO8CJ9@1 zf5&!w3IAds`YCR91WmraIn=opG*5ZF_IR80*^`O%X=u871(kwDTMoW0`D{@<(Yc(p zTv>B{uJ#({#!YKZe6($Jg>3#aif*YRqAzbIXC^=}I(3pV<5M}G_M!FTK-~zzuZJ#* zp_vhOl3VDNU)#E&Q?WXs8S`oMNh5VOYo@x+-p|TUb6#gDmWl{QJZ^1>zSVcBcR(>; zG2uc(R9wr`v74>LozLIlKF5lt4dt?5ZU8TyR$BVfx^ZeTolRxXJDglNExwbn_TsHs zxLIcZr~a@PIT!FgdOjo5{yk|Yx=u)`KG?mp8o8E|TK>lVGBvh~=ivjT43h7%MoaKg zL>OPF@^uebb;bC*w34!t;f{c$Qp=^6ZUrAH+doW`itJ02EH07L$dvhzov8kkHR<=Q z=6vzf&ld@lY1i_d`(wrTY7dd8wk)p?5q zvdU;*+CL?;s}9;70AM|~chUh;AMyhL40(7Hln3gHp0W**0LIu7u{f|7!IkC)093q` zX^#Y)2L|LtaCUK5_JRuUJCte9don~Av~Tfnf(oOq7=qwLHylU-EDx3zhOvM^AQd-T zJLRjI+P`qx8&ufA!^2e>0wIw|V3Hh|=w=U*QBqQZNXtTGWu<5iDR*xd4~&c+7pN6~PFdjrV6C%+WW{CH~ITQ9?L2_VO@Gtgv$8Ydn9?Ax8 zcpR<3du4>t?Egp-t?v}c#pqB9FPxE=zqpx zin71L{4O2@Q2z$}F8&7WeF)|KN})9I1f0u$F0!;_{&nCW2q!ud-Hcr^Hn_cH_L1MD zgYNw>=zqfeg&fp>aVn6#t^C{8{ Date: Tue, 3 Nov 2015 21:45:33 +0100 Subject: [PATCH 076/113] Bug 1000592 - Enable CSS Grid by default in non-release channels. r=dholbert --- modules/libpref/init/all.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 2b27904ae638..65b9b7ebbd2a 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -2357,7 +2357,11 @@ pref("layout.css.variables.enabled", true); pref("layout.css.overflow-clip-box.enabled", false); // Is support for CSS grid enabled? +#ifdef RELEASE_BUILD pref("layout.css.grid.enabled", false); +#else +pref("layout.css.grid.enabled", true); +#endif // Is support for CSS contain enabled? pref("layout.css.contain.enabled", false); From fe5445d03059a8fe478f678d3c5bb152c93065cf Mon Sep 17 00:00:00 2001 From: Jeff Walden Date: Tue, 3 Nov 2015 13:03:26 -0800 Subject: [PATCH 077/113] Bug 1220693 - Make mozilla::Atomic work even on compilers that don't have . r=froydnj --HG-- extra : rebase_source : 2eb167a1b86a1a93527761a9541ea30ad213785a --- mfbt/Atomics.h | 101 +++++++++++++++++++++++++++---------- mfbt/tests/TestAtomics.cpp | 42 +++++++++++++++ 2 files changed, 115 insertions(+), 28 deletions(-) diff --git a/mfbt/Atomics.h b/mfbt/Atomics.h index ced98ade8422..6293344dc8da 100644 --- a/mfbt/Atomics.h +++ b/mfbt/Atomics.h @@ -320,6 +320,12 @@ struct AtomicIntrinsics { }; +template +struct ToStorageTypeArgument +{ + static T convert (T aT) { return aT; } +}; + } // namespace detail } // namespace mozilla @@ -384,60 +390,81 @@ struct Barrier static void afterStore() { __sync_synchronize(); } }; +template::value> +struct AtomicStorageType +{ + // For non-enums, just use the type directly. + typedef T Type; +}; + +template +struct AtomicStorageType + : Conditional +{ + static_assert(sizeof(T) == 4 || sizeof(T) == 8, + "wrong type computed in conditional above"); +}; + template struct IntrinsicMemoryOps { - static T load(const T& aPtr) + typedef typename AtomicStorageType::Type ValueType; + + static T load(const ValueType& aPtr) { Barrier::beforeLoad(); - T val = aPtr; + T val = T(aPtr); Barrier::afterLoad(); return val; } - static void store(T& aPtr, T aVal) + static void store(ValueType& aPtr, T aVal) { Barrier::beforeStore(); - aPtr = aVal; + aPtr = ValueType(aVal); Barrier::afterStore(); } - static T exchange(T& aPtr, T aVal) + static T exchange(ValueType& aPtr, T aVal) { // __sync_lock_test_and_set is only an acquire barrier; loads and stores // can't be moved up from after to before it, but they can be moved down // from before to after it. We may want a stricter ordering, so we need // an explicit barrier. Barrier::beforeStore(); - return __sync_lock_test_and_set(&aPtr, aVal); + return T(__sync_lock_test_and_set(&aPtr, ValueType(aVal))); } - static bool compareExchange(T& aPtr, T aOldVal, T aNewVal) + static bool compareExchange(ValueType& aPtr, T aOldVal, T aNewVal) { - return __sync_bool_compare_and_swap(&aPtr, aOldVal, aNewVal); + return __sync_bool_compare_and_swap(&aPtr, ValueType(aOldVal), ValueType(aNewVal)); } }; -template +template struct IntrinsicAddSub + : public IntrinsicMemoryOps { - typedef T ValueType; + typedef IntrinsicMemoryOps Base; + typedef typename Base::ValueType ValueType; - static T add(T& aPtr, T aVal) + static T add(ValueType& aPtr, T aVal) { - return __sync_fetch_and_add(&aPtr, aVal); + return T(__sync_fetch_and_add(&aPtr, ValueType(aVal))); } - static T sub(T& aPtr, T aVal) + static T sub(ValueType& aPtr, T aVal) { - return __sync_fetch_and_sub(&aPtr, aVal); + return T(__sync_fetch_and_sub(&aPtr, ValueType(aVal))); } }; -template -struct IntrinsicAddSub +template +struct IntrinsicAddSub + : public IntrinsicMemoryOps { - typedef T* ValueType; + typedef IntrinsicMemoryOps Base; + typedef typename Base::ValueType ValueType; /* * The reinterpret_casts are needed so that @@ -459,16 +486,18 @@ struct IntrinsicAddSub } }; -template -struct IntrinsicIncDec : public IntrinsicAddSub +template +struct IntrinsicIncDec : public IntrinsicAddSub { - static T inc(T& aPtr) { return IntrinsicAddSub::add(aPtr, 1); } - static T dec(T& aPtr) { return IntrinsicAddSub::sub(aPtr, 1); } + typedef IntrinsicAddSub Base; + typedef typename Base::ValueType ValueType; + + static T inc(ValueType& aPtr) { return Base::add(aPtr, 1); } + static T dec(ValueType& aPtr) { return Base::sub(aPtr, 1); } }; template -struct AtomicIntrinsics : public IntrinsicMemoryOps, - public IntrinsicIncDec +struct AtomicIntrinsics : public IntrinsicIncDec { static T or_( T& aPtr, T aVal) { return __sync_fetch_and_or(&aPtr, aVal); } static T xor_(T& aPtr, T aVal) { return __sync_fetch_and_xor(&aPtr, aVal); } @@ -476,11 +505,24 @@ struct AtomicIntrinsics : public IntrinsicMemoryOps, }; template -struct AtomicIntrinsics : public IntrinsicMemoryOps, - public IntrinsicIncDec +struct AtomicIntrinsics : public IntrinsicIncDec { }; +template::value> +struct ToStorageTypeArgument +{ + typedef typename AtomicStorageType::Type ResultType; + + static ResultType convert (T aT) { return ResultType(aT); } +}; + +template +struct ToStorageTypeArgument +{ + static T convert (T aT) { return aT; } +}; + } // namespace detail } // namespace mozilla @@ -500,11 +542,14 @@ class AtomicBase protected: typedef typename detail::AtomicIntrinsics Intrinsics; - typename Intrinsics::ValueType mValue; + typedef typename Intrinsics::ValueType ValueType; + ValueType mValue; public: MOZ_CONSTEXPR AtomicBase() : mValue() {} - explicit MOZ_CONSTEXPR AtomicBase(T aInit) : mValue(aInit) {} + explicit MOZ_CONSTEXPR AtomicBase(T aInit) + : mValue(ToStorageTypeArgument::convert(aInit)) + {} // Note: we can't provide operator T() here because Atomic inherits // from AtomcBase with T=uint32_t and not T=bool. If we implemented @@ -691,7 +736,7 @@ public: MOZ_CONSTEXPR Atomic() : Base() {} explicit MOZ_CONSTEXPR Atomic(T aInit) : Base(aInit) {} - operator T() const { return Base::Intrinsics::load(Base::mValue); } + operator T() const { return T(Base::Intrinsics::load(Base::mValue)); } using Base::operator=; diff --git a/mfbt/tests/TestAtomics.cpp b/mfbt/tests/TestAtomics.cpp index 07696dbd9388..e23b3d51716c 100644 --- a/mfbt/tests/TestAtomics.cpp +++ b/mfbt/tests/TestAtomics.cpp @@ -168,6 +168,44 @@ TestEnumWithOrdering() A(atomic == EnumType_3, "CAS should have changed atomic's value."); } +enum class EnumClass : uint32_t +{ + Value0 = 0, + Value1 = 1, + Value2 = 2, + Value3 = 3 +}; + +template +static void +TestEnumClassWithOrdering() +{ + Atomic atomic(EnumClass::Value2); + A(atomic == EnumClass::Value2, "Atomic variable did not initialize"); + + // Test assignment + EnumClass result; + result = (atomic = EnumClass::Value3); + A(atomic == EnumClass::Value3, "Atomic assignment failed"); + A(result == EnumClass::Value3, "Atomic assignment returned the wrong value"); + + // Test exchange. + atomic = EnumClass::Value1; + result = atomic.exchange(EnumClass::Value2); + A(atomic == EnumClass::Value2, "Atomic exchange did not work"); + A(result == EnumClass::Value1, "Atomic exchange returned the wrong value"); + + // Test CAS. + atomic = EnumClass::Value1; + bool boolResult = atomic.compareExchange(EnumClass::Value0, EnumClass::Value2); + A(!boolResult, "CAS should have returned false."); + A(atomic == EnumClass::Value1, "CAS shouldn't have done anything."); + + boolResult = atomic.compareExchange(EnumClass::Value1, EnumClass::Value3); + A(boolResult, "CAS should have succeeded."); + A(atomic == EnumClass::Value3, "CAS should have changed atomic's value."); +} + template static void TestBoolWithOrdering() @@ -222,6 +260,10 @@ TestEnum() TestEnumWithOrdering(); TestEnumWithOrdering(); TestEnumWithOrdering(); + + TestEnumClassWithOrdering(); + TestEnumClassWithOrdering(); + TestEnumClassWithOrdering(); } static void From 0ca14bd2fba4cd5752fb0167c7b6adad570f7175 Mon Sep 17 00:00:00 2001 From: Jeff Walden Date: Tue, 3 Nov 2015 13:03:50 -0800 Subject: [PATCH 078/113] Bug 1220693 - Lazily trigger ICU default-time-zone recreation only at the instant where the new default time zone is going to be used, rather than every time the time zone might have changed, as apparently ICU's default-time-zone computation is a lot of work. r=till --HG-- extra : rebase_source : 11f83baea8a68a15f3a92076d0f23f913d1d0993 --- js/src/builtin/Intl.cpp | 21 +++++++++++++++++++++ js/src/vm/DateTime.cpp | 15 ++++++++++++--- js/src/vm/DateTime.h | 26 ++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 3 deletions(-) diff --git a/js/src/builtin/Intl.cpp b/js/src/builtin/Intl.cpp index 55c3b2277707..8444ed3fc4c9 100644 --- a/js/src/builtin/Intl.cpp +++ b/js/src/builtin/Intl.cpp @@ -1928,6 +1928,27 @@ NewUDateFormat(JSContext* cx, HandleObject dateTimeFormat) UErrorCode status = U_ZERO_ERROR; + if (!uTimeZone) { +#if ENABLE_INTL_API && defined(ICU_TZ_HAS_RECREATE_DEFAULT) + // JS::ResetTimeZone() recomputes the JS language's LocalTZA value. It + // *should* also recreate ICU's default time zone (used for formatting + // when no time zone has been specified), but this operation is slow. + // Doing it eagerly introduces a perf regression -- see bug 1220693. + // Therefore we perform it lazily, responding to the value of a global + // atomic variable that records whether ICU's default time zone is + // accurate. Baroque, but it's the only way to get the job done. + // + // Beware: this is kosher *only* if every place using ICU's default + // time zone performs the atomic compare-exchange and possible + // recreation song and dance routine here. + if (js::DefaultTimeZoneStatus.compareExchange(IcuTimeZoneStatus::NeedsUpdate, + IcuTimeZoneStatus::Updating)) + { + icu::TimeZone::recreateDefault(); + } +#endif + } + // If building with ICU headers before 50.1, use UDAT_IGNORE instead of // UDAT_PATTERN. UDateFormat* df = diff --git a/js/src/vm/DateTime.cpp b/js/src/vm/DateTime.cpp index 4dd6d408e58d..57614a1386a7 100644 --- a/js/src/vm/DateTime.cpp +++ b/js/src/vm/DateTime.cpp @@ -6,6 +6,8 @@ #include "vm/DateTime.h" +#include "mozilla/Atomics.h" + #include #include "jsutil.h" @@ -23,6 +25,9 @@ js::DateTimeInfo::instance; /* static */ mozilla::Atomic js::DateTimeInfo::AcquireLock::spinLock; +/* extern */ mozilla::Atomic +js::DefaultTimeZoneStatus; + static bool ComputeLocalTime(time_t local, struct tm* ptm) { @@ -312,7 +317,11 @@ JS::ResetTimeZone() { js::DateTimeInfo::updateTimeZoneAdjustment(); -#if ENABLE_INTL_API && defined(ICU_TZ_HAS_RECREATE_DEFAULT) - icu::TimeZone::recreateDefault(); -#endif + // Trigger lazy recreation of ICU's default time zone, if needed and not + // already being performed. (If it's already being performed, behavior + // will be safe but racy.) See also Intl.cpp:NewUDateFormat which performs + // the recreation. Note that if new places observing ICU's default time + // zone are added, they'll need to do the same things NewUDateFormat does. + js::DefaultTimeZoneStatus.compareExchange(js::IcuTimeZoneStatus::Valid, + js::IcuTimeZoneStatus::NeedsUpdate); } diff --git a/js/src/vm/DateTime.h b/js/src/vm/DateTime.h index 5c68ca13d07e..968bf76e5ac5 100644 --- a/js/src/vm/DateTime.h +++ b/js/src/vm/DateTime.h @@ -200,6 +200,32 @@ class DateTimeInfo void sanityCheck(); }; +enum class IcuTimeZoneStatus : uint32_t +{ + // ICU's current default time zone is accurate. + Valid = 0, + + // ICU's current default time zone may not be consistent with + // DateTimeInfo::localTZA(). + NeedsUpdate, + + // We're in the middle of recreating ICU's default time zone. + Updating +}; + +// The user's current time zone adjustment and time zone are computed in two +// places: via DateTimeInfo::localTZA(), and via ICU. The mechanisms are, +// unfortunately, separate: both must be triggered to respond to a time zone +// change. +// +// Updating ICU's default time zone is a relatively slow operation. If we +// perform it exactly when we update DateTimeInfo::localTZA(), we regress perf +// per bug 1220693. Instead, we defer updating ICU until we actually need the +// data. We record needs-update status in this global (necessarily atomic) +// variable. +extern mozilla::Atomic +DefaultTimeZoneStatus; + } /* namespace js */ #endif /* vm_DateTime_h */ From d860fa7191f245e803d338ec469949043885e816 Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Tue, 3 Nov 2015 22:33:16 +0100 Subject: [PATCH 079/113] Bug 1211260 - [css-grid] Follow-up: address a code readability nit that I missed. r=dholbert --- layout/generic/nsGridContainerFrame.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/layout/generic/nsGridContainerFrame.cpp b/layout/generic/nsGridContainerFrame.cpp index 05e981dbb738..884e44c22c5c 100644 --- a/layout/generic/nsGridContainerFrame.cpp +++ b/layout/generic/nsGridContainerFrame.cpp @@ -1407,7 +1407,7 @@ nsGridContainerFrame::ResolveLineRange( r.second = std::min(r.second, nsStyleGridLine::kMaxLine - 1); } else { // http://dev.w3.org/csswg/css-grid/#grid-placement-errors - if (r.second < r.first) { + if (r.first > r.second) { Swap(r.first, r.second); } else if (r.first == r.second) { if (MOZ_UNLIKELY(r.first == nsStyleGridLine::kMaxLine)) { From 02611daf0f865bf451a6d358c53d43e8dd69289f Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Tue, 3 Nov 2015 22:33:16 +0100 Subject: [PATCH 080/113] No bug - [css-grid] Add a small fuzz factor to make this reftest pass on Windows. r=me --- layout/reftests/css-grid/reftest.list | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/layout/reftests/css-grid/reftest.list b/layout/reftests/css-grid/reftest.list index 1b3f2b054a6c..8c3b75a2b096 100644 --- a/layout/reftests/css-grid/reftest.list +++ b/layout/reftests/css-grid/reftest.list @@ -42,7 +42,7 @@ pref(layout.css.vertical-text.enabled,true) == vrl-grid-placement-auto-row-spars fuzzy-if(winWidget,70,130) fuzzy-if(cocoaWidget,85,180) == grid-col-max-sizing-max-content-001.html grid-col-max-sizing-max-content-001-ref.html fuzzy-if(winWidget,70,130) fuzzy-if(cocoaWidget,85,180) == grid-col-max-sizing-max-content-002.html grid-col-max-sizing-max-content-002-ref.html == grid-min-max-content-sizing-001.html grid-min-max-content-sizing-001-ref.html -== grid-auto-min-sizing-definite-001.html grid-auto-min-sizing-definite-001-ref.html +fuzzy-if(winWidget,1,36) == grid-auto-min-sizing-definite-001.html grid-auto-min-sizing-definite-001-ref.html == grid-auto-min-sizing-intrinsic-001.html grid-auto-min-sizing-intrinsic-001-ref.html == grid-auto-min-sizing-intrinsic-002.html grid-auto-min-sizing-intrinsic-002-ref.html == grid-auto-min-sizing-intrinsic-003.html grid-auto-min-sizing-intrinsic-003-ref.html From 17b7d5be31cd880cab1c50f2af07b9056c7aa8cc Mon Sep 17 00:00:00 2001 From: Nathan Froyd Date: Wed, 28 Oct 2015 12:16:33 -0400 Subject: [PATCH 081/113] Bug 1219310 - part 1 - ask the prefs file for its size directly; r=njn Calling nsIInputStream::Available on nsIFileInputStreams is relatively expensive, as it requires three system calls: one to determine the current file position, one to seek to the end file position, and one to rewind to the previous file position. We can do better by asking the file for its size directly, prior to opening the stream. This only requires one system call, stat, and is thus superior--at least in considering the number of system calls. --- modules/libpref/Preferences.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/libpref/Preferences.cpp b/modules/libpref/Preferences.cpp index d25e6214ea6d..a8760d6d51d5 100644 --- a/modules/libpref/Preferences.cpp +++ b/modules/libpref/Preferences.cpp @@ -1000,8 +1000,8 @@ static nsresult openPrefFile(nsIFile* aFile) if (NS_FAILED(rv)) return rv; - uint64_t fileSize64; - rv = inStr->Available(&fileSize64); + int64_t fileSize64; + rv = aFile->GetFileSize(&fileSize64); if (NS_FAILED(rv)) return rv; NS_ENSURE_TRUE(fileSize64 <= UINT32_MAX, NS_ERROR_FILE_TOO_BIG); From 65fd1f778309cd4ae0b1a803f3f6c148cde433dc Mon Sep 17 00:00:00 2001 From: Nathan Froyd Date: Wed, 28 Oct 2015 12:19:03 -0400 Subject: [PATCH 082/113] Bug 1219310 - part 2 - keep track of how much pref file we have read; r=njn Looking at a preference file read with strace typically looks like: open("...", O_RDONLY) = X ... read(X, "...", SIZE) = SIZE read(X, "...", SIZE) = 0 ... There's no reason to call Read() and make another syscall to determine there's no data left for reading. We can keep track of how much we've read at minimal cost and thus determine for ourselves when we are done. --- modules/libpref/Preferences.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/libpref/Preferences.cpp b/modules/libpref/Preferences.cpp index a8760d6d51d5..4b3c06fa3e4b 100644 --- a/modules/libpref/Preferences.cpp +++ b/modules/libpref/Preferences.cpp @@ -1017,6 +1017,7 @@ static nsresult openPrefFile(nsIFile* aFile) // Read is not guaranteed to return a buf the size of fileSize, // but usually will. nsresult rv2 = NS_OK; + uint32_t offset = 0; for (;;) { uint32_t amtRead = 0; rv = inStr->Read((char*)fileBuffer, fileSize, &amtRead); @@ -1024,6 +1025,10 @@ static nsresult openPrefFile(nsIFile* aFile) break; if (!PREF_ParseBuf(&ps, fileBuffer, amtRead)) rv2 = NS_ERROR_FILE_CORRUPTED; + offset += amtRead; + if (offset == fileSize) { + break; + } } PREF_FinalizeParseState(&ps); From a46d212a29c1acf194e333662465c247a5c33f5e Mon Sep 17 00:00:00 2001 From: Jordan Lund Date: Tue, 3 Nov 2015 14:02:56 -0800 Subject: [PATCH 083/113] Bug 1220765 - 43.0b1 build1 fennec repacks failing to upload and submit to taskcluster, DONTBUILD r=rail, a=releases --HG-- extra : amend_source : 7368e06aa2c7509159e69d17a7def6872299eaf7 --- .../configs/single_locale/release_mozilla-beta_android_api_11.py | 1 + .../configs/single_locale/release_mozilla-beta_android_api_9.py | 1 + .../single_locale/release_mozilla-release_android_api_11.py | 1 + .../single_locale/release_mozilla-release_android_api_9.py | 1 + 4 files changed, 4 insertions(+) diff --git a/testing/mozharness/configs/single_locale/release_mozilla-beta_android_api_11.py b/testing/mozharness/configs/single_locale/release_mozilla-beta_android_api_11.py index b11c5c89d0e1..3114399cf13c 100644 --- a/testing/mozharness/configs/single_locale/release_mozilla-beta_android_api_11.py +++ b/testing/mozharness/configs/single_locale/release_mozilla-beta_android_api_11.py @@ -6,6 +6,7 @@ EN_US_BINARY_URL = "http://archive.mozilla.org/pub/mobile/candidates/%(version)s HG_SHARE_BASE_DIR = "/builds/hg-shared" config = { + "stage_product": "mobile", "log_name": "single_locale", "objdir": OBJDIR, "is_automation": True, diff --git a/testing/mozharness/configs/single_locale/release_mozilla-beta_android_api_9.py b/testing/mozharness/configs/single_locale/release_mozilla-beta_android_api_9.py index b032e4b60e42..5651684c847e 100644 --- a/testing/mozharness/configs/single_locale/release_mozilla-beta_android_api_9.py +++ b/testing/mozharness/configs/single_locale/release_mozilla-beta_android_api_9.py @@ -6,6 +6,7 @@ EN_US_BINARY_URL = "http://archive.mozilla.org/pub/mobile/candidates/%(version)s HG_SHARE_BASE_DIR = "/builds/hg-shared" config = { + "stage_product": "mobile", "log_name": "single_locale", "objdir": OBJDIR, "is_automation": True, diff --git a/testing/mozharness/configs/single_locale/release_mozilla-release_android_api_11.py b/testing/mozharness/configs/single_locale/release_mozilla-release_android_api_11.py index 8e300b237f81..7da75cb38581 100644 --- a/testing/mozharness/configs/single_locale/release_mozilla-release_android_api_11.py +++ b/testing/mozharness/configs/single_locale/release_mozilla-release_android_api_11.py @@ -6,6 +6,7 @@ EN_US_BINARY_URL = "http://archive.mozilla.org/pub/mobile/candidates/%(version)s HG_SHARE_BASE_DIR = "/builds/hg-shared" config = { + "stage_product": "mobile", "log_name": "single_locale", "objdir": OBJDIR, "is_automation": True, diff --git a/testing/mozharness/configs/single_locale/release_mozilla-release_android_api_9.py b/testing/mozharness/configs/single_locale/release_mozilla-release_android_api_9.py index 0c18ea8a1693..3bcd914f8745 100644 --- a/testing/mozharness/configs/single_locale/release_mozilla-release_android_api_9.py +++ b/testing/mozharness/configs/single_locale/release_mozilla-release_android_api_9.py @@ -6,6 +6,7 @@ EN_US_BINARY_URL = "http://archive.mozilla.org/pub/mobile/candidates/%(version)s HG_SHARE_BASE_DIR = "/builds/hg-shared" config = { + "stage_product": "mobile", "log_name": "single_locale", "objdir": OBJDIR, "is_automation": True, From b5a83e754ed5d3c60185b50d5b289de5652abcc8 Mon Sep 17 00:00:00 2001 From: Bobby Holley Date: Tue, 3 Nov 2015 09:28:33 -0800 Subject: [PATCH 084/113] Bug 1220682 - Clear exceptions on single-arg init. r=bz --- dom/base/nsJSUtils.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/dom/base/nsJSUtils.cpp b/dom/base/nsJSUtils.cpp index ea2ad69a0567..f0e567f44cea 100644 --- a/dom/base/nsJSUtils.cpp +++ b/dom/base/nsJSUtils.cpp @@ -329,6 +329,12 @@ JSObject* GetDefaultScopeFromJSContext(JSContext *cx) bool nsAutoJSString::init(const JS::Value &v) { - return init(nsContentUtils::RootingCxForThread(), v); + JSContext* cx = nsContentUtils::RootingCxForThread(); + if (!init(nsContentUtils::RootingCxForThread(), v)) { + JS_ClearPendingException(cx); + return false; + } + + return true; } From 84b37ed6eaf70a2feeab2e43b4bd2dea5463df72 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Tue, 3 Nov 2015 14:49:46 -0800 Subject: [PATCH 085/113] Bug 1214058: Part 1 - Add a simplified JSON-based add-on update protocol. r=Mossop --HG-- extra : commitid : kx59XtC26O extra : rebase_source : 95f8fa8f13be16f2353ea3dcaa230a92563e6af6 --- modules/libpref/init/all.js | 1 + toolkit/mozapps/extensions/AddonManager.jsm | 16 + .../internal/AddonUpdateChecker.jsm | 248 ++++++++-- .../extensions/internal/XPIProvider.jsm | 2 +- .../test/xpcshell/data/test_updatecheck.json | 327 +++++++++++++ .../test/xpcshell/data/test_updatecheck.rdf | 2 +- .../extensions/test/xpcshell/head_addons.js | 66 ++- .../test/xpcshell/test_json_updatecheck.js | 373 ++++++++++++++ .../test/xpcshell/test_updatecheck.js | 462 ++++++++---------- .../test/xpcshell/xpcshell-shared.ini | 1 + 10 files changed, 1190 insertions(+), 308 deletions(-) create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.json create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_json_updatecheck.js diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 65b9b7ebbd2a..001a6d9570c5 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -4402,6 +4402,7 @@ pref("xpinstall.whitelist.required", true); pref("xpinstall.signatures.required", false); pref("extensions.alwaysUnpack", false); pref("extensions.minCompatiblePlatformVersion", "2.0"); +pref("extensions.webExtensionsMinPlatformVersion", "42.0a1"); pref("network.buffer.cache.count", 24); pref("network.buffer.cache.size", 32768); diff --git a/toolkit/mozapps/extensions/AddonManager.jsm b/toolkit/mozapps/extensions/AddonManager.jsm index 0864cfbec3de..184bf3b1f3cf 100644 --- a/toolkit/mozapps/extensions/AddonManager.jsm +++ b/toolkit/mozapps/extensions/AddonManager.jsm @@ -42,6 +42,8 @@ const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS"; const PREF_SELECTED_LOCALE = "general.useragent.locale"; const UNKNOWN_XPCOM_ABI = "unknownABI"; +const PREF_MIN_WEBEXT_PLATFORM_VERSION = "extensions.webExtensionsMinPlatformVersion"; + const UPDATE_REQUEST_VERSION = 2; const CATEGORY_UPDATE_PARAMS = "extension-update-params"; @@ -663,6 +665,7 @@ var gCheckUpdateSecurity = gCheckUpdateSecurityDefault; var gUpdateEnabled = true; var gAutoUpdateDefault = true; var gHotfixID = null; +var gWebExtensionsMinPlatformVersion = null; var gShutdownBarrier = null; var gRepoShutdownState = ""; var gShutdownInProgress = false; @@ -947,6 +950,11 @@ var AddonManagerInternal = { } catch (e) {} Services.prefs.addObserver(PREF_EM_HOTFIX_ID, this, false); + try { + gWebExtensionsMinPlatformVersion = Services.prefs.getCharPref(PREF_MIN_WEBEXT_PLATFORM_VERSION); + } catch (e) {} + Services.prefs.addObserver(PREF_MIN_WEBEXT_PLATFORM_VERSION, this, false); + let defaultProvidersEnabled = true; try { defaultProvidersEnabled = Services.prefs.getBoolPref(PREF_DEFAULT_PROVIDERS_ENABLED); @@ -1377,6 +1385,10 @@ var AddonManagerInternal = { } break; } + case PREF_MIN_WEBEXT_PLATFORM_VERSION: { + gWebExtensionsMinPlatformVersion = Services.prefs.getCharPref(PREF_MIN_WEBEXT_PLATFORM_VERSION); + break; + } } }, @@ -2894,6 +2906,10 @@ this.AddonManagerPrivate = { safeCall(listener.onUpdateFinished.bind(listener), addon); } }, + + get webExtensionsMinPlatformVersion() { + return gWebExtensionsMinPlatformVersion; + }, }; /** diff --git a/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm b/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm index f6becf98fb43..a567eb678de0 100644 --- a/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm +++ b/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm @@ -31,6 +31,8 @@ Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate", + "resource://gre/modules/AddonManager.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository", "resource://gre/modules/addons/AddonRepository.jsm"); @@ -217,6 +219,48 @@ RDFSerializer.prototype = { } } +/** + * Sanitizes the update URL in an update item, as returned by + * parseRDFManifest and parseJSONManifest. Ensures that: + * + * - The URL is secure, or secured by a strong enough hash. + * - The security principal of the update manifest has permission to + * load the URL. + * + * @param aUpdate + * The update item to sanitize. + * @param aRequest + * The XMLHttpRequest used to load the manifest. + * @param aHashPattern + * The regular expression used to validate the update hash. + * @param aHashString + * The human-readable string specifying which hash functions + * are accepted. + */ +function sanitizeUpdateURL(aUpdate, aRequest, aHashPattern, aHashString) { + if (aUpdate.updateURL) { + let scriptSecurity = Services.scriptSecurityManager; + let principal = scriptSecurity.getChannelURIPrincipal(aRequest.channel); + try { + // This logs an error on failure, so no need to log it a second time + scriptSecurity.checkLoadURIStrWithPrincipal(principal, aUpdate.updateURL, + scriptSecurity.DISALLOW_SCRIPT); + } catch (e) { + delete aUpdate.updateURL; + return; + } + + if (AddonManager.checkUpdateSecurity && + !aUpdate.updateURL.startsWith("https:") && + !aHashPattern.test(aUpdate.updateHash)) { + logger.warn(`Update link ${aUpdate.updateURL} is not secure and is not verified ` + + `by a strong enough hash (needs to be ${aHashString}).`); + delete aUpdate.updateURL; + delete aUpdate.updateHash; + } + } +} + /** * Parses an RDF style update manifest into an array of update objects. * @@ -226,10 +270,17 @@ RDFSerializer.prototype = { * An optional update key for the add-on * @param aRequest * The XMLHttpRequest that has retrieved the update manifest + * @param aManifestData + * The pre-parsed manifest, as a bare XML DOM document * @return an array of update objects * @throws if the update manifest is invalid in any way */ -function parseRDFManifest(aId, aUpdateKey, aRequest) { +function parseRDFManifest(aId, aUpdateKey, aRequest, aManifestData) { + if (aManifestData.documentElement.namespaceURI != PREFIX_NS_RDF) { + throw Components.Exception("Update manifest had an unrecognised namespace: " + xml.documentElement.namespaceURI); + return; + } + function EM_R(aProp) { return gRDF.GetResource(PREFIX_NS_EM + aProp); } @@ -366,20 +417,136 @@ function parseRDFManifest(aId, aUpdateKey, aRequest) { targetApplications: [appEntry] }; - if (result.updateURL && AddonManager.checkUpdateSecurity && - result.updateURL.substring(0, 6) != "https:" && - (!result.updateHash || result.updateHash.substring(0, 3) != "sha")) { - logger.warn("updateLink " + result.updateURL + " is not secure and is not verified" + - " by a strong enough hash (needs to be sha1 or stronger)."); - delete result.updateURL; - delete result.updateHash; - } + // The JSON update protocol requires an SHA-2 hash. RDF still + // supports SHA-1, for compatibility reasons. + sanitizeUpdateURL(result, aRequest, /^sha/, "sha1 or stronger"); + results.push(result); } } return results; } +/** + * Parses an JSON update manifest into an array of update objects. + * + * @param aId + * The ID of the add-on being checked for updates + * @param aUpdateKey + * An optional update key for the add-on + * @param aRequest + * The XMLHttpRequest that has retrieved the update manifest + * @param aManifestData + * The pre-parsed manifest, as a JSON object tree + * @return an array of update objects + * @throws if the update manifest is invalid in any way + */ +function parseJSONManifest(aId, aUpdateKey, aRequest, aManifestData) { + if (aUpdateKey) + throw Components.Exception("Update keys are not supported for JSON update manifests"); + + let TYPE_CHECK = { + "array": val => Array.isArray(val), + "object": val => val && typeof val == "object" && !Array.isArray(val), + }; + + function getProperty(aObj, aProperty, aType, aDefault = undefined) { + if (!(aProperty in aObj)) + return aDefault; + + let value = aObj[aProperty]; + + let matchesType = aType in TYPE_CHECK ? TYPE_CHECK[aType](value) : typeof value == aType; + if (!matchesType) + throw Components.Exception(`Update manifest property '${aProperty}' has incorrect type (expected ${aType})`); + + return value; + } + + function getRequiredProperty(aObj, aProperty, aType) { + let value = getProperty(aObj, aProperty, aType); + if (value === undefined) + throw Components.Exception(`Update manifest is missing a required ${aProperty} property.`); + return value; + } + + let manifest = aManifestData; + + if (!TYPE_CHECK["object"](manifest)) + throw Components.Exception("Root element of update manifest must be a JSON object literal"); + + // The set of add-ons this manifest has updates for + let addons = getRequiredProperty(manifest, "addons", "object"); + + // The entry for this particular add-on + let addon = getProperty(addons, aId, "object"); + + // A missing entry doesn't count as a failure, just as no avialable update + // information + if (!addon) { + logger.warn("Update manifest did not contain an entry for " + aId); + return []; + } + + // The list of available updates + let updates = getProperty(addon, "updates", "array", []); + + let results = []; + + for (let update of updates) { + let version = getRequiredProperty(update, "version", "string"); + + logger.debug(`Found an update entry for ${aId} version ${version}`); + + let applications = getProperty(update, "applications", "object", + { gecko: {} }); + + // "gecko" is currently the only supported application entry. If + // it's missing, skip this update. + if (!("gecko" in applications)) + continue; + + let app = getProperty(applications, "gecko", "object"); + + let appEntry = { + id: TOOLKIT_ID, + minVersion: getProperty(app, "strict_min_version", "string", + AddonManagerPrivate.webExtensionsMinPlatformVersion), + maxVersion: "*", + }; + + let result = { + id: aId, + version: version, + multiprocessCompatible: getProperty(update, "multiprocess_compatible", "boolean", true), + updateURL: getProperty(update, "update_link", "string"), + updateHash: getProperty(update, "update_hash", "string"), + updateInfoURL: getProperty(update, "update_info_url", "string"), + strictCompatibility: false, + targetApplications: [appEntry], + }; + + if ("strict_max_version" in app) { + if ("advisory_max_version" in app) { + logger.warn("Ignoring 'advisory_max_version' update manifest property for " + + aId + " property since 'strict_max_version' also present"); + } + + appEntry.maxVersion = getProperty(app, "strict_max_version", "string"); + result.strictCompatibility = appEntry.maxVersion != "*"; + } else if ("advisory_max_version" in app) { + appEntry.maxVersion = getProperty(app, "advisory_max_version", "string"); + } + + // The JSON update protocol requires an SHA-2 hash. RDF still + // supports SHA-1, for compatibility reasons. + sanitizeUpdateURL(result, aRequest, /^sha(256|512):/, "sha256 or sha512"); + + results.push(result); + } + return results; +} + /** * Starts downloading an update manifest and then passes it to an appropriate * parser to convert to an array of update objects @@ -415,7 +582,7 @@ function UpdateParser(aId, aUpdateKey, aUrl, aObserver) { this.request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE; // Prevent the request from writing to cache. this.request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING; - this.request.overrideMimeType("text/xml"); + this.request.overrideMimeType("text/plain"); this.request.setRequestHeader("Moz-XPI-Update", "1", true); this.request.timeout = TIMEOUT; var self = this; @@ -474,41 +641,50 @@ UpdateParser.prototype = { return; } - let xml = request.responseXML; - if (!xml || xml.documentElement.namespaceURI == XMLURI_PARSE_ERROR) { - logger.warn("Update manifest was not valid XML"); + // Detect the manifest type by first attempting to parse it as + // JSON, and falling back to parsing it as XML if that fails. + let parser; + try { + try { + let json = JSON.parse(request.responseText); + + parser = () => parseJSONManifest(this.id, this.updateKey, request, json); + } catch (e if e instanceof SyntaxError) { + let domParser = Cc["@mozilla.org/xmlextras/domparser;1"].createInstance(Ci.nsIDOMParser); + let xml = domParser.parseFromString(request.responseText, "text/xml"); + + if (xml.documentElement.namespaceURI == XMLURI_PARSE_ERROR) + throw new Error("Update manifest was not valid XML or JSON"); + + parser = () => parseRDFManifest(this.id, this.updateKey, request, xml); + } + } catch (e) { + logger.warn("onUpdateCheckComplete failed to determine manifest type"); + this.notifyError(AddonUpdateChecker.ERROR_UNKNOWN_FORMAT); + return; + } + + let results; + try { + results = parser(); + } + catch (e) { + logger.warn("onUpdateCheckComplete failed to parse update manifest", e); this.notifyError(AddonUpdateChecker.ERROR_PARSE_ERROR); return; } - // We currently only know about RDF update manifests - if (xml.documentElement.namespaceURI == PREFIX_NS_RDF) { - let results = null; - + if ("onUpdateCheckComplete" in this.observer) { try { - results = parseRDFManifest(this.id, this.updateKey, request); + this.observer.onUpdateCheckComplete(results); } catch (e) { - logger.warn("onUpdateCheckComplete failed to parse RDF manifest", e); - this.notifyError(AddonUpdateChecker.ERROR_PARSE_ERROR); - return; + logger.warn("onUpdateCheckComplete notification failed", e); } - if ("onUpdateCheckComplete" in this.observer) { - try { - this.observer.onUpdateCheckComplete(results); - } - catch (e) { - logger.warn("onUpdateCheckComplete notification failed", e); - } - } - else { - logger.warn("onUpdateCheckComplete may not properly cancel", new Error("stack marker")); - } - return; } - - logger.warn("Update manifest had an unrecognised namespace: " + xml.documentElement.namespaceURI); - this.notifyError(AddonUpdateChecker.ERROR_UNKNOWN_FORMAT); + else { + logger.warn("onUpdateCheckComplete may not properly cancel", new Error("stack marker")); + } }, /** diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index 0d5ed412f3e8..6a8a8a9d0647 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -906,7 +906,7 @@ function loadManifestFromWebManifest(aStream) { addon.targetApplications = [{ id: TOOLKIT_ID, - minVersion: "42a1", + minVersion: AddonManagerPrivate.webExtensionsMinPlatformVersion, maxVersion: "*", }]; diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.json b/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.json new file mode 100644 index 000000000000..811e50158ea8 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.json @@ -0,0 +1,327 @@ +{ + "addons": { + "updatecheck1@tests.mozilla.org": { + "updates": [ + { + "version": "1.0", + "update_link": "https://localhost:4444/addons/test1.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + }, + { + "_comment_": "This update is incompatible and so should not be considered a valid update", + "version": "2.0", + "update_link": "https://localhost:4444/addons/test2.xpi", + "applications": { + "gecko": { + "strict_min_version": "2", + "strict_max_version": "2" + } + } + }, + { + "version": "3.0", + "update_link": "https://localhost:4444/addons/test3.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + }, + { + "version": "2.0", + "update_link": "https://localhost:4444/addons/test2.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "2" + } + } + }, + { + "_comment_": "This update is incompatible and so should not be considered a valid update", + "version": "4.0", + "update_link": "https://localhost:4444/addons/test4.xpi", + "applications": { + "gecko": { + "strict_min_version": "2", + "strict_max_version": "2" + } + } + } + ] + }, + + "test_bug378216_5@tests.mozilla.org": { + "_comment_": "An update which expects a signature. It will fail since signatures are ", + "_comment_": "supported in this format.", + "_comment_": "The updateLink will also be ignored since it is not secure and there ", + "_comment_": "is no updateHash.", + + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:4444/broken.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + } + ] + }, + + "test_bug378216_5@tests.mozilla.org": { + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:4444/broken.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + } + ] + }, + + "test_bug378216_7@tests.mozilla.org": { + "_comment_": "An update which expects a signature. It will fail since signatures are ", + "_comment_": "supported in this format.", + "_comment_": "The updateLink will also be ignored since it is not secure ", + "_comment_": "and there is no updateHash.", + + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:4444/broken.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "2" + } + } + } + ] + }, + + "test_bug378216_8@tests.mozilla.org": { + "_comment_": "The updateLink will be ignored since it is not secure and ", + "_comment_": "there is no updateHash.", + + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:4444/broken.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + } + ] + }, + + "test_bug378216_9@tests.mozilla.org": { + "_comment_": "The updateLink will used since there is an updateHash to verify it.", + + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:4444/broken.xpi", + "update_hash": "sha256:78fc1d2887eda35b4ad2e3a0b60120ca271ce6e6", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + } + ] + }, + + "test_bug378216_10@tests.mozilla.org": { + "_comment_": "The updateLink will used since it is a secure URL.", + + "updates": [ + { + "version": "2.0", + "update_link": "https://localhost:4444/broken.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + } + ] + }, + + "test_bug378216_11@tests.mozilla.org": { + "_comment_": "The updateLink will used since it is a secure URL.", + + "updates": [ + { + "version": "2.0", + "update_link": "https://localhost:4444/broken.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + } + ] + }, + + "test_bug378216_12@tests.mozilla.org": { + "_comment_": "The updateLink will not be used since the updateHash ", + "_comment_": "verifying it is not strong enough.", + + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:4444/broken.xpi", + "update_hash": "sha1:78fc1d2887eda35b4ad2e3a0b60120ca271ce6e6", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + } + ] + }, + + "test_bug378216_13@tests.mozilla.org": { + "_comment_": "An update with a weak hash. The updateLink will used since it is ", + "_comment_": "a secure URL.", + + "updates": [ + { + "version": "2.0", + "update_link": "https://localhost:4444/broken.xpi", + "update_hash": "sha1:78fc1d2887eda35b4ad2e3a0b60120ca271ce6e6", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + } + ] + }, + + "_comment_": "There should be no information present for test_bug378216_14", + + "test_bug378216_15@tests.mozilla.org": { + "_comment_": "Invalid update JSON", + + "updates": "foo" + }, + + "ignore-compat@tests.mozilla.org": { + "_comment_": "Various updates available - one is not compatible, but compatibility checking is disabled", + + "updates": [ + { + "version": "1.0", + "update_link": "https://localhost:4444/addons/test1.xpi", + "applications": { + "gecko": { + "strict_min_version": "0.1", + "advisory_max_version": "0.2" + } + } + }, + { + "version": "2.0", + "update_link": "https://localhost:4444/addons/test2.xpi", + "applications": { + "gecko": { + "strict_min_version": "0.5", + "advisory_max_version": "0.6" + } + } + }, + { + "_comment_": "Update for future app versions - should never be compatible", + "version": "3.0", + "update_link": "https://localhost:4444/addons/test3.xpi", + "applications": { + "gecko": { + "strict_min_version": "2", + "advisory_max_version": "3" + } + } + } + ] + }, + + "compat-override@tests.mozilla.org": { + "_comment_": "Various updates available - one is not compatible, but compatibility checking is disabled", + + "updates": [ + { + "_comment_": "Has compatibility override, but it doesn't match this app version", + "version": "1.0", + "update_link": "https://localhost:4444/addons/test1.xpi", + "applications": { + "gecko": { + "strict_min_version": "0.1", + "advisory_max_version": "0.2" + } + } + }, + { + "_comment_": "Has compatibility override, so is incompaible", + "version": "2.0", + "update_link": "https://localhost:4444/addons/test2.xpi", + "applications": { + "gecko": { + "strict_min_version": "0.5", + "advisory_max_version": "0.6" + } + } + }, + { + "_comment_": "Update for future app versions - should never be compatible", + "version": "3.0", + "update_link": "https://localhost:4444/addons/test3.xpi", + "applications": { + "gecko": { + "strict_min_version": "2", + "advisory_max_version": "3" + } + } + } + ] + }, + + "compat-strict-optin@tests.mozilla.org": { + "_comment_": "Opt-in to strict compatibility checking", + + "updates": [ + { + "version": "1.0", + "update_link": "https://localhost:4444/addons/test1.xpi", + "_comment_": "strictCompatibility: true", + "applications": { + "gecko": { + "strict_min_version": "0.1", + "strict_max_version": "0.2" + } + } + } + ] + } + } +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.rdf b/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.rdf index 93c82886a65b..c5d97ada0d53 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.rdf +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.rdf @@ -236,7 +236,7 @@ A90eF5zy - diff --git a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js index a5e6ba3d1a37..d8f3b7959095 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js +++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js @@ -1542,7 +1542,7 @@ if ("nsIWindowsRegKey" in AM_Ci) { * This is a mock nsIWindowsRegistry implementation. It only implements the * methods that the extension manager requires. */ - function MockWindowsRegKey() { + var MockWindowsRegKey = function MockWindowsRegKey() { } MockWindowsRegKey.prototype = { @@ -1723,6 +1723,30 @@ do_register_cleanup(function addon_cleanup() { } catch (e) {} }); +/** + * Creates a new HttpServer for testing, and begins listening on the + * specified port. Automatically shuts down the server when the test + * unit ends. + * + * @param port + * The port to listen on. If omitted, listen on a random + * port. The latter is the preferred behavior. + * + * @return HttpServer + */ +function createHttpServer(port = -1) { + let server = new HttpServer(); + server.start(port); + + do_register_cleanup(() => { + return new Promise(resolve => { + server.stop(resolve); + }); + }); + + return server; +} + /** * Handler function that responds with the interpolated * static file associated to the URL specified by request.path. @@ -1912,3 +1936,43 @@ function promiseFindAddonUpdates(addon, reason = AddonManager.UPDATE_WHEN_PERIOD }, reason); }); } + +/** + * Monitors console output for the duration of a task, and returns a promise + * which resolves to a tuple containing a list of all console messages + * generated during the task's execution, and the result of the task itself. + * + * @param {function} aTask + * The task to run while monitoring console output. May be + * either a generator function, per Task.jsm, or an ordinary + * function which returns promose. + * @return {Promise<[Array, *]>} + */ +var promiseConsoleOutput = Task.async(function*(aTask) { + const DONE = "=== xpcshell test console listener done ==="; + + let listener, messages = []; + let awaitListener = new Promise(resolve => { + listener = msg => { + if (msg == DONE) { + resolve(); + } else { + msg instanceof Components.interfaces.nsIScriptError; + messages.push(msg); + } + } + }); + + Services.console.registerListener(listener); + try { + let result = yield aTask(); + + Services.console.logStringMessage(DONE); + yield awaitListener; + + return { messages, result }; + } + finally { + Services.console.unregisterListener(listener); + } +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_json_updatecheck.js b/toolkit/mozapps/extensions/test/xpcshell/test_json_updatecheck.js new file mode 100644 index 000000000000..2e8cc6a972c1 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_json_updatecheck.js @@ -0,0 +1,373 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +"use strict"; + +// This verifies that AddonUpdateChecker works correctly for JSON +// update manifests, particularly for behavior which does not +// cleanly overlap with RDF manifests. + +const TOOLKIT_ID = "toolkit@mozilla.org"; +const TOOLKIT_MINVERSION = "42.0a1"; + +createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "42.0a2", "42.0a2"); + +Components.utils.import("resource://gre/modules/addons/AddonUpdateChecker.jsm"); +Components.utils.import("resource://testing-common/httpd.js"); + +let testserver = createHttpServer(); +gPort = testserver.identity.primaryPort; + +let gUpdateManifests = {}; + +function mapManifest(aPath, aManifestData) { + gUpdateManifests[aPath] = aManifestData; + testserver.registerPathHandler(aPath, serveManifest); +} + +function serveManifest(request, response) { + let manifest = gUpdateManifests[request.path]; + + response.setHeader("Content-Type", manifest.contentType, false); + response.write(manifest.data); +} + +const extensionsDir = gProfD.clone(); +extensionsDir.append("extensions"); + + +function checkUpdates(aData) { + // Registers JSON update manifest for it with the testing server, + // checks for updates, and yields the list of updates on + // success. + + let extension = aData.manifestExtension || "json"; + + let path = `/updates/${aData.id}.${extension}`; + let updateUrl = `http://localhost:${gPort}${path}` + + let addonData = {}; + if ("updates" in aData) + addonData.updates = aData.updates; + + let manifestJSON = { + "addons": { + [aData.id]: addonData + } + }; + + mapManifest(path.replace(/\?.*/, ""), + { data: JSON.stringify(manifestJSON), + contentType: aData.contentType || "application/json" }); + + + return new Promise((resolve, reject) => { + AddonUpdateChecker.checkForUpdates(aData.id, aData.updateKey, updateUrl, { + onUpdateCheckComplete: resolve, + + onUpdateCheckError: function(status) { + reject(new Error("Update check failed with status " + status)); + } + }); + }); +} + + +add_task(function* test_default_values() { + // Checks that the appropriate defaults are used for omitted values. + + startupManager(); + + let updates = yield checkUpdates({ + id: "updatecheck-defaults@tests.mozilla.org", + version: "0.1", + updates: [{ + version: "0.2" + }] + }); + + equal(updates.length, 1); + let update = updates[0]; + + equal(update.targetApplications.length, 1); + let targetApp = update.targetApplications[0]; + + equal(targetApp.id, TOOLKIT_ID); + equal(targetApp.minVersion, TOOLKIT_MINVERSION); + equal(targetApp.maxVersion, "*"); + + equal(update.version, "0.2"); + equal(update.multiprocessCompatible, true, "multiprocess_compatible flag"); + equal(update.strictCompatibility, false, "inferred strictConpatibility flag"); + equal(update.updateURL, null, "updateURL"); + equal(update.updateHash, null, "updateHash"); + equal(update.updateInfoURL, null, "updateInfoURL"); + + // If there's no applications property, we default to using one + // containing "gecko". If there is an applications property, but + // it doesn't contain "gecko", the update is skipped. + updates = yield checkUpdates({ + id: "updatecheck-defaults@tests.mozilla.org", + version: "0.1", + updates: [{ + version: "0.2", + applications: { "foo": {} } + }] + }); + + equal(updates.length, 0); + + // Updates property is also optional. No updates, but also no error. + updates = yield checkUpdates({ + id: "updatecheck-defaults@tests.mozilla.org", + version: "0.1", + }); + + equal(updates.length, 0); +}); + + +add_task(function* test_explicit_values() { + // Checks that the appropriate explicit values are used when + // provided. + + let updates = yield checkUpdates({ + id: "updatecheck-explicit@tests.mozilla.org", + version: "0.1", + updates: [{ + version: "0.2", + update_link: "https://example.com/foo.xpi", + update_hash: "sha256:0", + update_info_url: "https://example.com/update_info.html", + multiprocess_compatible: false, + applications: { + gecko: { + strict_min_version: "42.0a2.xpcshell", + strict_max_version: "43.xpcshell" + } + } + }] + }); + + equal(updates.length, 1); + let update = updates[0]; + + equal(update.targetApplications.length, 1); + let targetApp = update.targetApplications[0]; + + equal(targetApp.id, TOOLKIT_ID); + equal(targetApp.minVersion, "42.0a2.xpcshell"); + equal(targetApp.maxVersion, "43.xpcshell"); + + equal(update.version, "0.2"); + equal(update.multiprocessCompatible, false, "multiprocess_compatible flag"); + equal(update.strictCompatibility, true, "inferred strictCompatibility flag"); + equal(update.updateURL, "https://example.com/foo.xpi", "updateURL"); + equal(update.updateHash, "sha256:0", "updateHash"); + equal(update.updateInfoURL, "https://example.com/update_info.html", "updateInfoURL"); +}); + + +add_task(function* test_secure_hashes() { + // Checks that only secure hash functions are accepted for + // non-secure update URLs. + + let hashFunctions = ["sha512", + "sha256", + "sha1", + "md5", + "md4", + "xxx"]; + + let updateItems = hashFunctions.map((hash, idx) => ({ + version: `0.${idx}`, + update_link: `http://localhost:${gPort}/updates/${idx}-${hash}.xpi`, + update_hash: `${hash}:08ac852190ecd81f40a514ea9299fe9143d9ab5e296b97e73fb2a314de49648a`, + })); + + let { messages, result: updates } = yield promiseConsoleOutput(() => { + return checkUpdates({ + id: "updatecheck-hashes@tests.mozilla.org", + version: "0.1", + updates: updateItems + }); + }); + + equal(updates.length, hashFunctions.length); + + updates = updates.filter(update => update.updateHash || update.updateURL); + equal(updates.length, 2, "expected number of update hashes were accepted"); + + ok(updates[0].updateHash.startsWith("sha512:"), "sha512 hash is present"); + ok(updates[0].updateURL); + + ok(updates[1].updateHash.startsWith("sha256:"), "sha256 hash is present"); + ok(updates[1].updateURL); + + messages = messages.filter(msg => /Update link.*not secure.*strong enough hash \(needs to be sha256 or sha512\)/.test(msg.message)); + equal(messages.length, hashFunctions.length - 2, "insecure hashes generated the expected warning"); +}); + + +add_task(function* test_strict_compat() { + // Checks that strict compatibility is enabled for strict max + // versions other than "*", but not for advisory max versions. + // Also, ensure that strict max versions take precedence over + // advisory versions. + + let { messages, result: updates } = yield promiseConsoleOutput(() => { + return checkUpdates({ + id: "updatecheck-strict@tests.mozilla.org", + version: "0.1", + updates: [ + { version: "0.2", + applications: { gecko: { strict_max_version: "*" } } }, + { version: "0.3", + applications: { gecko: { strict_max_version: "43" } } }, + { version: "0.4", + applications: { gecko: { advisory_max_version: "43" } } }, + { version: "0.5", + applications: { gecko: { advisory_max_version: "43", + strict_max_version: "44" } } }, + ] + }); + }); + + equal(updates.length, 4, "all update items accepted"); + + equal(updates[0].targetApplications[0].maxVersion, "*"); + equal(updates[0].strictCompatibility, false); + + equal(updates[1].targetApplications[0].maxVersion, "43"); + equal(updates[1].strictCompatibility, true); + + equal(updates[2].targetApplications[0].maxVersion, "43"); + equal(updates[2].strictCompatibility, false); + + equal(updates[3].targetApplications[0].maxVersion, "44"); + equal(updates[3].strictCompatibility, true); + + messages = messages.filter(msg => /Ignoring 'advisory_max_version'.*'strict_max_version' also present/.test(msg.message)); + equal(messages.length, 1, "mix of advisory_max_version and strict_max_version generated the expected warning"); +}); + + +add_task(function* test_update_url_security() { + // Checks that update links to privileged URLs are not accepted. + + let { messages, result: updates } = yield promiseConsoleOutput(() => { + return checkUpdates({ + id: "updatecheck-security@tests.mozilla.org", + version: "0.1", + updates: [ + { version: "0.2", + update_link: "chrome://browser/content/browser.xul", + update_hash: "sha256:08ac852190ecd81f40a514ea9299fe9143d9ab5e296b97e73fb2a314de49648a" }, + { version: "0.3", + update_link: "http://example.com/update.xpi", + update_hash: "sha256:18ac852190ecd81f40a514ea9299fe9143d9ab5e296b97e73fb2a314de49648a" }, + ] + }); + }); + + equal(updates.length, 2, "both updates were processed"); + equal(updates[0].updateURL, null, "privileged update URL was removed"); + equal(updates[1].updateURL, "http://example.com/update.xpi", "safe update URL was accepted"); + + messages = messages.filter(msg => /http:\/\/localhost.*\/updates\/.*may not load or link to chrome:/.test(msg.message)); + equal(messages.length, 1, "privileged upate URL generated the expected console message"); +}); + + +add_task(function* test_no_update_key() { + // Checks that updates fail when an update key has been specified. + + let { messages } = yield promiseConsoleOutput(function* () { + yield Assert.rejects( + checkUpdates({ + id: "updatecheck-updatekey@tests.mozilla.org", + version: "0.1", + updateKey: "ayzzx=", + updates: [ + { version: "0.2" }, + { version: "0.3" }, + ] + }), + null, "updated expected to fail"); + }); + + messages = messages.filter(msg => /Update keys are not supported for JSON update manifests/.test(msg.message)); + equal(messages.length, 1, "got expected update-key-unsupported error"); +}); + + +add_task(function* test_type_detection() { + // Checks that JSON update manifests are detected correctly + // regardless of extension or MIME type. + + let tests = [ + { contentType: "application/json", + extension: "json", + valid: true }, + { contentType: "application/json", + extension: "php", + valid: true }, + { contentType: "text/plain", + extension: "json", + valid: true }, + { contentType: "application/octet-stream", + extension: "json", + valid: true }, + { contentType: "text/plain", + extension: "json?foo=bar", + valid: true }, + { contentType: "text/plain", + extension: "php", + valid: true }, + { contentType: "text/plain", + extension: "rdf", + valid: true }, + { contentType: "application/json", + extension: "rdf", + valid: true }, + { contentType: "text/xml", + extension: "json", + valid: true }, + { contentType: "application/rdf+xml", + extension: "json", + valid: true }, + ]; + + for (let [i, test] of tests.entries()) { + let { messages } = yield promiseConsoleOutput(function *() { + let id = `updatecheck-typedetection-${i}@tests.mozilla.org`; + let updates; + try { + updates = yield checkUpdates({ + id: id, + version: "0.1", + contentType: test.contentType, + manifestExtension: test.extension, + updates: [{ version: "0.2" }] + }); + } catch (e) { + ok(!test.valid, "update manifest correctly detected as RDF"); + return; + } + + ok(test.valid, "update manifest correctly detected as JSON"); + equal(updates.length, 1, "correct number of updates"); + equal(updates[0].id, id, "update is for correct extension"); + }); + + if (test.valid) { + // Make sure we don't get any XML parsing errors from the + // XMLHttpRequest machinery. + ok(!messages.some(msg => /not well-formed/.test(msg.message)), + "expect XMLHttpRequest not to attempt XML parsing"); + } + + messages = messages.filter(msg => /Update manifest was not valid XML/.test(msg.message)); + equal(messages.length, !test.valid, "expected number of XML parsing errors"); + } +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_updatecheck.js b/toolkit/mozapps/extensions/test/xpcshell/test_updatecheck.js index 9d251933aab5..9a2f6a01dbf4 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_updatecheck.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_updatecheck.js @@ -7,52 +7,47 @@ Components.utils.import("resource://gre/modules/addons/AddonUpdateChecker.jsm"); Components.utils.import("resource://testing-common/httpd.js"); -var testserver; -function run_test() { - createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); +var testserver = createHttpServer(4444); +testserver.registerDirectory("/data/", do_get_file("data")); - // Create and configure the HTTP server. - testserver = new HttpServer(); - testserver.registerDirectory("/data/", do_get_file("data")); - testserver.start(4444); +function checkUpdates(aId, aUpdateKey, aUpdateFile) { + return new Promise((resolve, reject) => { + AddonUpdateChecker.checkForUpdates(aId, aUpdateKey, `http://localhost:4444/data/${aUpdateFile}`, { + onUpdateCheckComplete: resolve, - do_test_pending(); - run_test_1(); -} - -function end_test() { - testserver.stop(do_test_finished); -} - -// Test that a basic update check returns the expected available updates -function run_test_1() { - AddonUpdateChecker.checkForUpdates("updatecheck1@tests.mozilla.org", null, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - check_test_1(updates); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } + onUpdateCheckError: function(status) { + let error = new Error("Update check failed with status " + status); + error.status = status; + reject(error); + } + }); }); } -function check_test_1(updates) { - do_check_eq(updates.length, 5); - let update = AddonUpdateChecker.getNewestCompatibleUpdate(updates); - do_check_neq(update, null); - do_check_eq(update.version, 3); - update = AddonUpdateChecker.getCompatibilityUpdate(updates, "2"); - do_check_neq(update, null); - do_check_eq(update.version, 2); - do_check_eq(update.targetApplications[0].minVersion, 1); - do_check_eq(update.targetApplications[0].maxVersion, 2); +function run_test() { + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1"); - run_test_2(); + run_next_test(); } +// Test that a basic update check returns the expected available updates +add_task(function* () { + for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { + let updates = yield checkUpdates("updatecheck1@tests.mozilla.org", null, file); + + equal(updates.length, 5); + let update = AddonUpdateChecker.getNewestCompatibleUpdate(updates); + notEqual(update, null); + equal(update.version, "3.0"); + update = AddonUpdateChecker.getCompatibilityUpdate(updates, "2"); + notEqual(update, null); + equal(update.version, "2.0"); + equal(update.targetApplications[0].minVersion, "1"); + equal(update.targetApplications[0].maxVersion, "2"); + } +}); + /* * Tests that the security checks are applied correctly * @@ -73,240 +68,169 @@ var updateKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDK426erD/H3XtsjvaB5+PJqbh "NyeP6i4LuUYjTURnn7Yw/IgzyIJ2oKsYa32RuxAyteqAWqPT/J63wBixIeCxmysf" + "awB/zH4KaPiY3vnrzQIDAQAB"; -function run_test_2() { - AddonUpdateChecker.checkForUpdates("test_bug378216_5@tests.mozilla.org", - updateKey, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_throw("Expected the update check to fail"); - }, +add_task(function* () { + for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { + try { + yield checkUpdates("test_bug378216_5@tests.mozilla.org", + updateKey, file); + throw "Expected the update check to fail"; + } catch (e) {} + } +}); - onUpdateCheckError: function(status) { - run_test_3(); +add_task(function* () { + for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { + try { + yield checkUpdates("test_bug378216_7@tests.mozilla.org", + updateKey, file); + + throw "Expected the update check to fail"; + } catch (e) {} + } +}); + +add_task(function* () { + // Make sure that the JSON manifest is rejected when an update key is + // required, but perform the remaining tests which aren't expected to fail + // because of the update key, without requiring one for the JSON variant. + + try { + let updates = yield checkUpdates("test_bug378216_8@tests.mozilla.org", + updateKey, "test_updatecheck.json"); + + throw "Expected the update check to fail"; + } catch(e) {} + + for (let [file, key] of [["test_updatecheck.rdf", updateKey], + ["test_updatecheck.json", null]]) { + let updates = yield checkUpdates("test_bug378216_8@tests.mozilla.org", + key, file); + equal(updates.length, 1); + ok(!("updateURL" in updates[0])); + } +}); + +add_task(function* () { + for (let [file, key] of [["test_updatecheck.rdf", updateKey], + ["test_updatecheck.json", null]]) { + let updates = yield checkUpdates("test_bug378216_9@tests.mozilla.org", + key, file); + equal(updates.length, 1); + equal(updates[0].version, "2.0"); + ok("updateURL" in updates[0]); + } +}); + +add_task(function* () { + for (let [file, key] of [["test_updatecheck.rdf", updateKey], + ["test_updatecheck.json", null]]) { + let updates = yield checkUpdates("test_bug378216_10@tests.mozilla.org", + key, file); + equal(updates.length, 1); + equal(updates[0].version, "2.0"); + ok("updateURL" in updates[0]); + } +}); + +add_task(function* () { + for (let [file, key] of [["test_updatecheck.rdf", updateKey], + ["test_updatecheck.json", null]]) { + let updates = yield checkUpdates("test_bug378216_11@tests.mozilla.org", + key, file); + equal(updates.length, 1); + equal(updates[0].version, "2.0"); + ok("updateURL" in updates[0]); + } +}); + +add_task(function* () { + for (let [file, key] of [["test_updatecheck.rdf", updateKey], + ["test_updatecheck.json", null]]) { + let updates = yield checkUpdates("test_bug378216_12@tests.mozilla.org", + key, file); + equal(updates.length, 1); + do_check_false("updateURL" in updates[0]); + } +}); + +add_task(function* () { + for (let [file, key] of [["test_updatecheck.rdf", updateKey], + ["test_updatecheck.json", null]]) { + let updates = yield checkUpdates("test_bug378216_13@tests.mozilla.org", + key, file); + equal(updates.length, 1); + equal(updates[0].version, "2.0"); + ok("updateURL" in updates[0]); + } +}); + +add_task(function* () { + for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { + let updates = yield checkUpdates("test_bug378216_14@tests.mozilla.org", + null, file); + equal(updates.length, 0); + } +}); + +add_task(function* () { + for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { + try { + yield checkUpdates("test_bug378216_15@tests.mozilla.org", + null, file); + + throw "Update check should have failed"; + } catch (e) { + equal(e.status, AddonUpdateChecker.ERROR_PARSE_ERROR); } - }); -} + } +}); -function run_test_3() { - AddonUpdateChecker.checkForUpdates("test_bug378216_7@tests.mozilla.org", - updateKey, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_throw("Expected the update check to fail"); - }, +add_task(function* () { + for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { + let updates = yield checkUpdates("ignore-compat@tests.mozilla.org", + null, file); + equal(updates.length, 3); + let update = AddonUpdateChecker.getNewestCompatibleUpdate( + updates, null, null, true); + notEqual(update, null); + equal(update.version, 2); + } +}); - onUpdateCheckError: function(status) { - run_test_4(); - } - }); -} +add_task(function* () { + for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { + let updates = yield checkUpdates("compat-override@tests.mozilla.org", + null, file); + equal(updates.length, 3); + let overrides = [{ + type: "incompatible", + minVersion: 1, + maxVersion: 2, + appID: "xpcshell@tests.mozilla.org", + appMinVersion: 0.1, + appMaxVersion: 0.2 + }, { + type: "incompatible", + minVersion: 2, + maxVersion: 2, + appID: "xpcshell@tests.mozilla.org", + appMinVersion: 1, + appMaxVersion: 2 + }]; + let update = AddonUpdateChecker.getNewestCompatibleUpdate( + updates, null, null, true, false, overrides); + notEqual(update, null); + equal(update.version, 1); + } +}); -function run_test_4() { - AddonUpdateChecker.checkForUpdates("test_bug378216_8@tests.mozilla.org", - updateKey, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 1); - do_check_false("updateURL" in updates[0]); - run_test_5(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} - -function run_test_5() { - AddonUpdateChecker.checkForUpdates("test_bug378216_9@tests.mozilla.org", - updateKey, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 1); - do_check_eq(updates[0].version, "2.0"); - do_check_true("updateURL" in updates[0]); - run_test_6(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} - -function run_test_6() { - AddonUpdateChecker.checkForUpdates("test_bug378216_10@tests.mozilla.org", - updateKey, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 1); - do_check_eq(updates[0].version, "2.0"); - do_check_true("updateURL" in updates[0]); - run_test_7(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} - -function run_test_7() { - AddonUpdateChecker.checkForUpdates("test_bug378216_11@tests.mozilla.org", - updateKey, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 1); - do_check_eq(updates[0].version, "2.0"); - do_check_true("updateURL" in updates[0]); - run_test_8(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} - -function run_test_8() { - AddonUpdateChecker.checkForUpdates("test_bug378216_12@tests.mozilla.org", - updateKey, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 1); - do_check_false("updateURL" in updates[0]); - run_test_9(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} - -function run_test_9() { - AddonUpdateChecker.checkForUpdates("test_bug378216_13@tests.mozilla.org", - updateKey, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 1); - do_check_eq(updates[0].version, "2.0"); - do_check_true("updateURL" in updates[0]); - run_test_10(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} - -function run_test_10() { - AddonUpdateChecker.checkForUpdates("test_bug378216_14@tests.mozilla.org", - null, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 0); - run_test_11(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} - -function run_test_11() { - AddonUpdateChecker.checkForUpdates("test_bug378216_15@tests.mozilla.org", - null, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_throw("Update check should have failed"); - }, - - onUpdateCheckError: function(status) { - do_check_eq(status, AddonUpdateChecker.ERROR_PARSE_ERROR); - run_test_12(); - } - }); -} - -function run_test_12() { - AddonUpdateChecker.checkForUpdates("ignore-compat@tests.mozilla.org", - null, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 3); - let update = AddonUpdateChecker.getNewestCompatibleUpdate(updates, - null, - null, - true); - do_check_neq(update, null); - do_check_eq(update.version, 2); - run_test_13(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} - -function run_test_13() { - AddonUpdateChecker.checkForUpdates("compat-override@tests.mozilla.org", - null, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 3); - let overrides = [{ - type: "incompatible", - minVersion: 1, - maxVersion: 2, - appID: "xpcshell@tests.mozilla.org", - appMinVersion: 0.1, - appMaxVersion: 0.2 - }, { - type: "incompatible", - minVersion: 2, - maxVersion: 2, - appID: "xpcshell@tests.mozilla.org", - appMinVersion: 1, - appMaxVersion: 2 - }]; - let update = AddonUpdateChecker.getNewestCompatibleUpdate(updates, - null, - null, - true, - false, - overrides); - do_check_neq(update, null); - do_check_eq(update.version, 1); - run_test_14(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} - -function run_test_14() { - AddonUpdateChecker.checkForUpdates("compat-strict-optin@tests.mozilla.org", - null, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 1); - let update = AddonUpdateChecker.getNewestCompatibleUpdate(updates, - null, - null, - true, - false); - do_check_eq(update, null); - end_test(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} +add_task(function* () { + for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { + let updates = yield checkUpdates("compat-strict-optin@tests.mozilla.org", + null, file); + equal(updates.length, 1); + let update = AddonUpdateChecker.getNewestCompatibleUpdate( + updates, null, null, true, false); + equal(update, null); + } +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini index f8b9972cf289..fdd2dd9ac356 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini +++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini @@ -279,6 +279,7 @@ skip-if = os == "android" # Bug 676992: test consistently hangs on Android skip-if = os == "android" run-sequentially = Uses hardcoded ports in xpi files. +[test_json_updatecheck.js] [test_updateid.js] # Bug 676992: test consistently hangs on Android skip-if = os == "android" From 110d21abaa47e3acc77ebc5aa0ef6d47f4ec7d53 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Mon, 19 Oct 2015 09:18:42 -0700 Subject: [PATCH 086/113] Bug 1214058: Part 2 - Run add-on update tests against comparable JSON and RDF manifests. r=Mossop I tried to keep the changes to existing tests as minimal as possible. There were a few exceptions, though: * test_update_ignorecompat.js was completely broken. I couldn't figure out why it was suddenly failing after I changed it to use `add_test`, and it turned out that it had been failing all along, but in a way that the harness didn't pick up. * I changed most of the `do_throw` in update callbacks to `ok(false` because it took me about an hour to figure out where the test was failing when I hit one of them. * I made some changes to sync `test_update.js` and `test_update_ignorecompat.js` where one appeared to have been changed without updating the other. * I made `promiseFindAddonUpdates` a bit more generic, because I was planning to convert most of `test_update.js` to use it, rather than nested callbacks. I changed my mind a quarter of the way through, but decided to keep the changes, since they'll probably be useful elsewhere. --HG-- extra : commitid : 84oLUw4ZPOg extra : rebase_source : 2bd6c921c6b677e4d487d0ee9c33b1130163bc39 --- .../test/xpcshell/data/test_update.json | 215 ++ .../extensions/test/xpcshell/head_addons.js | 48 +- .../xpcshell/test_AddonRepository_cache.js | 7 +- .../test/xpcshell/test_blocklistchange.js | 9 +- .../test/xpcshell/test_bug542391.js | 13 +- .../test/xpcshell/test_bug570173.js | 82 +- .../test/xpcshell/test_metadata_update.js | 15 +- .../test/xpcshell/test_signed_install.js | 9 +- .../extensions/test/xpcshell/test_update.js | 2132 +++++++++-------- .../test/xpcshell/test_update_ignorecompat.js | 144 +- .../test/xpcshell/test_update_strictcompat.js | 1862 +++++++------- 11 files changed, 2410 insertions(+), 2126 deletions(-) create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_update.json diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_update.json b/toolkit/mozapps/extensions/test/xpcshell/data/test_update.json new file mode 100644 index 000000000000..027a9b2333f8 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_update.json @@ -0,0 +1,215 @@ +{ + "addons": { + "addon1@tests.mozilla.org": { + "updates": [ + { + "version": "1.0", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_min_version": "1" + } + } + }, + { + "version": "1.0", + "applications": { + "gecko": { + "strict_min_version": "2", + "strict_min_version": "2" + } + } + }, + { + "version": "2.0", + "update_link": "http://localhost:%PORT%/addons/test_update.xpi", + "update_info_url": "http://example.com/updateInfo.xhtml", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_min_version": "1" + } + } + } + ] + }, + + "addon2@tests.mozilla.org": { + "updates": [ + { + "version": "1.0", + "applications": { + "gecko": { + "strict_min_version": "0", + "advisory_max_version": "1" + } + } + } + ] + }, + + "addon2@tests.mozilla.org": { + "updates": [ + { + "version": "1.0", + "applications": { + "gecko": { + "strict_min_version": "0", + "advisory_max_version": "1" + } + } + } + ] + }, + + "addon3@tests.mozilla.org": { + "updates": [ + { + "version": "1.0", + "applications": { + "gecko": { + "strict_min_version": "3", + "advisory_max_version": "3" + } + } + } + ] + }, + + "addon4@tests.mozilla.org": { + "updates": [ + { + "version": "5.0", + "applications": { + "gecko": { + "strict_min_version": "0", + "advisory_max_version": "0" + } + } + } + ] + }, + + "addon7@tests.mozilla.org": { + "updates": [ + { + "version": "1.0", + "applications": { + "gecko": { + "strict_min_version": "0", + "advisory_max_version": "1" + } + } + } + ] + }, + + "addon8@tests.mozilla.org": { + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:%PORT%/addons/test_update8.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "advisory_max_version": "1" + } + } + } + ] + }, + + "addon9@tests.mozilla.org": { + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:%PORT%/addons/test_update9_2.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "advisory_max_version": "1" + } + } + }, + { + "_comment_": "Incompatible when strict compatibility is enabled", + "version": "3.0", + "update_link": "http://localhost:%PORT%/addons/test_update9_3.xpi", + "applications": { + "gecko": { + "strict_min_version": "0.9", + "advisory_max_version": "0.9" + } + } + }, + { + "_comment_": "Incompatible due to compatibility override", + "version": "4.0", + "update_link": "http://localhost:%PORT%/addons/test_update9_4.xpi", + "applications": { + "gecko": { + "strict_min_version": "0.9", + "advisory_max_version": "0.9" + } + } + }, + { + "_comment_": "Addon for future version of app", + "version": "4.0", + "update_link": "http://localhost:%PORT%/addons/test_update9_5.xpi", + "applications": { + "gecko": { + "strict_min_version": "5", + "advisory_max_version": "6" + } + } + } + ] + }, + + "addon10@tests.mozilla.org": { + "updates": [ + { + "version": "1.0", + "update_link": "http://localhost:%PORT%/addons/test_update10.xpi", + "applications": { + "gecko": { + "strict_min_version": "0.1", + "advisory_max_version": "0.4" + } + } + } + ] + }, + + "addon11@tests.mozilla.org": { + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:%PORT%/addons/test_update11.xpi", + "applications": { + "gecko": { + "strict_min_version": "0.1", + "strict_max_version": "0.2" + } + } + } + ] + }, + + "addon12@tests.mozilla.org": { + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:%PORT%/addons/test_update12.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "advisory_max_version": "1" + } + } + } + ] + } + } +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js index d8f3b7959095..27bca41f0f26 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js +++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js @@ -1920,18 +1920,48 @@ function promiseAddonByID(aId) { */ function promiseFindAddonUpdates(addon, reason = AddonManager.UPDATE_WHEN_PERIODIC_UPDATE) { return new Promise((resolve, reject) => { + let result = {}; addon.findUpdates({ - install: null, - - onUpdateAvailable: function(addon, install) { - this.install = install; + onNoCompatibilityUpdateAvailable: function(addon2) { + if ("compatibilityUpdate" in result) { + do_throw("Saw multiple compatibility update events"); + } + equal(addon, addon2); + addon.compatibilityUpdate = false; }, - onUpdateFinished: function(addon, error) { - if (error == AddonManager.UPDATE_STATUS_NO_ERROR) - resolve(this.install); - else - reject(error); + onCompatibilityUpdateAvailable: function(addon2) { + if ("compatibilityUpdate" in result) { + do_throw("Saw multiple compatibility update events"); + } + equal(addon, addon2); + addon.compatibilityUpdate = true; + }, + + onNoUpdateAvailable: function(addon2) { + if ("updateAvailable" in result) { + do_throw("Saw multiple update available events"); + } + equal(addon, addon2); + result.updateAvailable = false; + }, + + onUpdateAvailable: function(addon2, install) { + if ("updateAvailable" in result) { + do_throw("Saw multiple update available events"); + } + equal(addon, addon2); + result.updateAvailable = install; + }, + + onUpdateFinished: function(addon2, error) { + equal(addon, addon2); + if (error == AddonManager.UPDATE_STATUS_NO_ERROR) { + resolve(result); + } else { + result.error = error; + reject(result); + } } }, reason); }); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js b/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js index b2c0f59013e2..2ba21a8a213d 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js @@ -505,9 +505,8 @@ add_task(function* setup() { yield promiseInstallAllFiles(ADDON_FILES); yield promiseRestartManager(); - gServer = new HttpServer(); + gServer = createHttpServer(PORT); gServer.registerDirectory("/data/", do_get_file("data")); - gServer.start(PORT); }); // Tests AddonRepository.cacheEnabled @@ -704,7 +703,3 @@ add_task(function* run_test_17() { let aAddons = yield promiseAddonsByIDs(ADDON_IDS); check_results(aAddons, WITH_EXTENSION_CACHE); }); - -add_task(function* end_test() { - yield new Promise((resolve, reject) => gServer.stop(resolve)); -}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js b/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js index 989c3e4ceead..7113f7cfd543 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js @@ -41,8 +41,7 @@ Cu.import("resource://testing-common/MockRegistrar.jsm"); Services.prefs.setBoolPref("extensions.checkUpdateSecurity", false) Cu.import("resource://testing-common/httpd.js"); -var testserver = new HttpServer(); -testserver.start(-1); +var testserver = createHttpServer(); gPort = testserver.identity.primaryPort; // register static files with server and interpolate port numbers in them @@ -1305,9 +1304,3 @@ add_task(function* run_local_install_test() { check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); }); - -add_task(function* shutdown_httpserver() { - yield new Promise((resolve, reject) => { - testserver.stop(resolve); - }); -}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug542391.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug542391.js index 7c274d90aefd..67b50d16190e 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug542391.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug542391.js @@ -296,10 +296,9 @@ add_task(function* init() { }, profileDir); // Create and configure the HTTP server. - testserver = new HttpServer(); + testserver = createHttpServer(4444); testserver.registerDirectory("/data/", do_get_file("data")); testserver.registerDirectory("/addons/", do_get_file("addons")); - testserver.start(4444); startupManager(); @@ -464,13 +463,3 @@ add_task(function* run_test_6() { "override1x2-1x3@tests.mozilla.org"]); check_state_v1_2(addons); }); - -add_task(function* cleanup() { - return new Promise((resolve, reject) => { - testserver.stop(resolve); - }); -}); - -function run_test() { - run_next_test(); -} diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug570173.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug570173.js index 70de3b426d26..c859c474c711 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug570173.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug570173.js @@ -16,65 +16,47 @@ function run_test() { createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); // Create and configure the HTTP server. - testserver = new HttpServer(); + testserver = createHttpServer(); testserver.registerDirectory("/data/", do_get_file("data")); testserver.registerDirectory("/addons/", do_get_file("addons")); - testserver.start(-1); gPort = testserver.identity.primaryPort; - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_missing.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - startupManager(); - - do_test_pending(); - run_test_1(); -} - -function end_test() { - testserver.stop(do_test_finished); + run_next_test(); } // Verify that an update check returns the correct errors. -function run_test_1() { - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { - do_check_neq(a1, null); - do_check_eq(a1.version, "1.0"); +add_task(function* () { + for (let manifestType of ["rdf", "json"]) { + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: `http://localhost:${gPort}/data/test_missing.${manifestType}`, + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + bootstrap: "true", + }, profileDir); - let sawCompat = false; - let sawUpdate = false; - a1.findUpdates({ - onNoCompatibilityUpdateAvailable: function(addon) { - sawCompat = true; - }, + yield promiseRestartManager(); - onCompatibilityUpdateAvailable: function(addon) { - do_throw("Should not have seen a compatibility update"); - }, + let addon = yield promiseAddonByID("addon1@tests.mozilla.org"); - onNoUpdateAvailable: function(addon) { - sawUpdate = true; - }, + ok(addon); + ok(addon.updateURL.endsWith(manifestType)); + equal(addon.version, "1.0"); - onUpdateAvailable: function(addon, install) { - do_throw("Should not have seen an update"); - }, + // We're expecting an error, so resolve when the promise is rejected. + let update = yield promiseFindAddonUpdates(addon, AddonManager.UPDATE_WHEN_USER_REQUESTED) + .catch(Promise.resolve); - onUpdateFinished: function(addon, error) { - do_check_true(sawCompat); - do_check_true(sawUpdate); - do_check_eq(error, AddonManager.UPDATE_STATUS_DOWNLOAD_ERROR); - end_test(); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); - }); -} + ok(!update.compatibilityUpdate, "not expecting a compatibility update"); + ok(!update.updateAvailable, "not expecting a compatibility update"); + + equal(update.error, AddonManager.UPDATE_STATUS_DOWNLOAD_ERROR); + + addon.uninstall(); + } +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_metadata_update.js b/toolkit/mozapps/extensions/test/xpcshell/test_metadata_update.js index 78d03e88cd2a..962e0c8cc633 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_metadata_update.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_metadata_update.js @@ -69,10 +69,9 @@ add_task(function* checkFirstMetadata() { Services.prefs.setBoolPref(PREF_EM_SHOW_MISMATCH_UI, true); // Create and configure the HTTP server. - testserver = new HttpServer(); + testserver = createHttpServer(); testserver.registerDirectory("/data/", do_get_file("data")); testserver.registerDirectory("/addons/", do_get_file("addons")); - testserver.start(-1); gPort = testserver.identity.primaryPort; const BASE_URL = "http://localhost:" + gPort; const GETADDONS_RESULTS = BASE_URL + "/data/test_AddonRepository_cache.xml"; @@ -159,15 +158,3 @@ add_task(function* upgrade_young_pref_lastupdate() { yield promiseRestartManager("2"); do_check_false(WindowWatcher.expected); }); - - - -add_task(function* cleanup() { - return new Promise((resolve, reject) => { - testserver.stop(resolve); - }); -}); - -function run_test() { - run_next_test(); -} diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_signed_install.js b/toolkit/mozapps/extensions/test/xpcshell/test_signed_install.js index 8c7f491d1452..392a9b7a2476 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_signed_install.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_signed_install.js @@ -16,8 +16,7 @@ const WORKING = "signed_bootstrap_1.xpi"; const ID = "test@tests.mozilla.org"; Components.utils.import("resource://testing-common/httpd.js"); -var gServer = new HttpServer(); -gServer.start(4444); +var gServer = createHttpServer(4444); // Creates an add-on with a broken signature by changing an existing file function createBrokenAddonModify(file) { @@ -137,7 +136,8 @@ function* test_update_broken(file, expectedError) { serveUpdateRDF(file.leafName); let addon = yield promiseAddonByID(ID); - let install = yield promiseFindAddonUpdates(addon); + let update = yield promiseFindAddonUpdates(addon); + let install = update.updateAvailable; yield promiseCompleteAllInstalls([install]); do_check_eq(install.state, AddonManager.STATE_DOWNLOAD_FAILED); @@ -158,7 +158,8 @@ function* test_update_working(file, expectedSignedState) { serveUpdateRDF(file.leafName); let addon = yield promiseAddonByID(ID); - let install = yield promiseFindAddonUpdates(addon); + let update = yield promiseFindAddonUpdates(addon); + let install = update.updateAvailable; yield promiseCompleteAllInstalls([install]); do_check_eq(install.state, AddonManager.STATE_INSTALLED); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_update.js b/toolkit/mozapps/extensions/test/xpcshell/test_update.js index f410382c2d2e..07e8c37128c6 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_update.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_update.js @@ -24,10 +24,10 @@ const PARAMS = "?%REQ_VERSION%/%ITEM_ID%/%ITEM_VERSION%/%ITEM_MAXAPPVERSION%/" + var gInstallDate; Components.utils.import("resource://testing-common/httpd.js"); -var testserver = new HttpServer(); -testserver.start(-1); +var testserver = createHttpServer(); gPort = testserver.identity.primaryPort; mapFile("/data/test_update.rdf", testserver); +mapFile("/data/test_update.json", testserver); mapFile("/data/test_update.xml", testserver); testserver.registerDirectory("/addons/", do_get_file("addons")); @@ -37,386 +37,1160 @@ profileDir.append("extensions"); var originalSyncGUID; function run_test() { - createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1"); + Services.prefs.setBoolPref(PREF_MATCH_OS_LOCALE, false); Services.prefs.setCharPref(PREF_SELECTED_LOCALE, "fr-FR"); - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon2@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0", - maxVersion: "0" - }], - name: "Test Addon 2", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon3@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "5", - maxVersion: "5" - }], - name: "Test Addon 3", - }, profileDir); - - startupManager(); - - do_test_pending(); - run_test_1(); + run_next_test(); } -function end_test() { - testserver.stop(do_test_finished); -} +let testParams = [ + { updateFile: "test_update.rdf", + appId: "xpcshell@tests.mozilla.org" }, + { updateFile: "test_update.json", + appId: "toolkit@mozilla.org" }, +]; -// Verify that an update is available and can be installed. -function run_test_1() { - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { - do_check_neq(a1, null); - do_check_eq(a1.version, "1.0"); - do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DEFAULT); - do_check_eq(a1.releaseNotesURI, null); - do_check_true(a1.foreignInstall); - do_check_neq(a1.syncGUID, null); +for (let test of testParams) { + let { updateFile, appId } = test; - originalSyncGUID = a1.syncGUID; - a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT; + add_test(function run_test() { + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); - prepare_test({ - "addon1@tests.mozilla.org": [ - ["onPropertyChanged", ["applyBackgroundUpdates"]] - ] + writeInstallRDFForExtension({ + id: "addon2@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0", + maxVersion: "0" + }], + name: "Test Addon 2", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon3@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "5", + maxVersion: "5" + }], + name: "Test Addon 3", + }, profileDir); + + startupManager(); + + run_next_test(); + }); + + // Verify that an update is available and can be installed. + let check_test_1; + add_test(function run_test_1() { + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + do_check_neq(a1, null); + do_check_eq(a1.version, "1.0"); + do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DEFAULT); + do_check_eq(a1.releaseNotesURI, null); + do_check_true(a1.foreignInstall); + do_check_neq(a1.syncGUID, null); + + originalSyncGUID = a1.syncGUID; + a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT; + + prepare_test({ + "addon1@tests.mozilla.org": [ + ["onPropertyChanged", ["applyBackgroundUpdates"]] + ] + }); + a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + check_test_completed(); + + a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + + prepare_test({}, [ + "onNewInstall", + ]); + + a1.findUpdates({ + onNoCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should not have seen onNoCompatibilityUpdateAvailable notification"); + }, + + onUpdateAvailable: function(addon, install) { + ensure_test_completed(); + + AddonManager.getAllInstalls(function(aInstalls) { + do_check_eq(aInstalls.length, 1); + do_check_eq(aInstalls[0], install); + + do_check_eq(addon, a1); + do_check_eq(install.name, addon.name); + do_check_eq(install.version, "2.0"); + do_check_eq(install.state, AddonManager.STATE_AVAILABLE); + do_check_eq(install.existingAddon, addon); + do_check_eq(install.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); + + // Verify that another update check returns the same AddonInstall + a1.findUpdates({ + onNoCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should not have seen onNoCompatibilityUpdateAvailable notification"); + }, + + onUpdateAvailable: function(newAddon, newInstall) { + AddonManager.getAllInstalls(function(aInstalls) { + do_check_eq(aInstalls.length, 1); + do_check_eq(aInstalls[0], install); + do_check_eq(newAddon, addon); + do_check_eq(newInstall, install); + + prepare_test({}, [ + "onDownloadStarted", + "onDownloadEnded", + ], check_test_1); + install.install(); + }); + }, + + onNoUpdateAvailable: function(addon) { + ok(false, "Should not have seen onNoUpdateAvailable notification"); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); + }, + + onNoUpdateAvailable: function(addon) { + ok(false, "Should not have seen onNoUpdateAvailable notification"); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); }); - a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; - check_test_completed(); + }); - a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + let run_test_2; + check_test_1 = (install) => { + ensure_test_completed(); + do_check_eq(install.state, AddonManager.STATE_DOWNLOADED); + run_test_2(install); + return false; + }; - prepare_test({}, [ - "onNewInstall", - ]); - - a1.findUpdates({ + // Continue installing the update. + let check_test_2; + run_test_2 = (install) => { + // Verify that another update check returns no new update + install.existingAddon.findUpdates({ onNoCompatibilityUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoCompatibilityUpdateAvailable notification"); + ok(false, "Should not have seen onNoCompatibilityUpdateAvailable notification"); }, onUpdateAvailable: function(addon, install) { - ensure_test_completed(); + ok(false, "Should find no available update when one is already downloading"); + }, + onNoUpdateAvailable: function(addon) { AddonManager.getAllInstalls(function(aInstalls) { do_check_eq(aInstalls.length, 1); do_check_eq(aInstalls[0], install); - do_check_eq(addon, a1); - do_check_eq(install.name, addon.name); - do_check_eq(install.version, "2.0"); - do_check_eq(install.state, AddonManager.STATE_AVAILABLE); - do_check_eq(install.existingAddon, addon); - do_check_eq(install.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); - - // Verify that another update check returns the same AddonInstall - a1.findUpdates({ - onNoCompatibilityUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoCompatibilityUpdateAvailable notification"); - }, - - onUpdateAvailable: function(newAddon, newInstall) { - AddonManager.getAllInstalls(function(aInstalls) { - do_check_eq(aInstalls.length, 1); - do_check_eq(aInstalls[0], install); - do_check_eq(newAddon, addon); - do_check_eq(newInstall, install); - - prepare_test({}, [ - "onDownloadStarted", - "onDownloadEnded", - ], check_test_1); - install.install(); - }); - }, - - onNoUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoUpdateAvailable notification"); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + prepare_test({ + "addon1@tests.mozilla.org": [ + "onInstalling" + ] + }, [ + "onInstallStarted", + "onInstallEnded", + ], check_test_2); + install.install(); }); - }, - - onNoUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoUpdateAvailable notification"); } }, AddonManager.UPDATE_WHEN_USER_REQUESTED); - }); -} + }; -function check_test_1(install) { - ensure_test_completed(); - do_check_eq(install.state, AddonManager.STATE_DOWNLOADED); - run_test_2(install); - return false; -} + check_test_2 = () => { + ensure_test_completed(); -// Continue installing the update. -function run_test_2(install) { - // Verify that another update check returns no new update - install.existingAddon.findUpdates({ - onNoCompatibilityUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoCompatibilityUpdateAvailable notification"); - }, + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(olda1) { + do_check_neq(olda1, null); + do_check_eq(olda1.version, "1.0"); + do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); - onUpdateAvailable: function(addon, install) { - do_throw("Should find no available update when one is already downloading"); - }, + shutdownManager(); - onNoUpdateAvailable: function(addon) { - AddonManager.getAllInstalls(function(aInstalls) { - do_check_eq(aInstalls.length, 1); - do_check_eq(aInstalls[0], install); + startupManager(); - prepare_test({ - "addon1@tests.mozilla.org": [ - "onInstalling" - ] - }, [ - "onInstallStarted", - "onInstallEnded", - ], check_test_2); - install.install(); + do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); + + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + do_check_neq(a1, null); + do_check_eq(a1.version, "2.0"); + do_check_true(isExtensionInAddonsList(profileDir, a1.id)); + do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DISABLE); + do_check_eq(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); + do_check_true(a1.foreignInstall); + do_check_neq(a1.syncGUID, null); + do_check_eq(originalSyncGUID, a1.syncGUID); + + a1.uninstall(); + run_next_test(); }); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); -} + })); + }; -function check_test_2() { - ensure_test_completed(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(olda1) { - do_check_neq(olda1, null); - do_check_eq(olda1.version, "1.0"); - do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); + // Check that an update check finds compatibility updates and applies them + let check_test_3; + add_test(function run_test_3() { + restartManager(); - shutdownManager(); + AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { + do_check_neq(a2, null); + do_check_true(a2.isActive); + do_check_true(a2.isCompatible); + do_check_false(a2.appDisabled); + do_check_true(a2.isCompatibleWith("0", "0")); - startupManager(); + a2.findUpdates({ + onCompatibilityUpdateAvailable: function(addon) { + do_check_true(a2.isCompatible); + do_check_false(a2.appDisabled); + do_check_true(a2.isActive); + }, - do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); + onUpdateAvailable: function(addon, install) { + ok(false, "Should not have seen an available update"); + }, + onNoUpdateAvailable: function(addon) { + do_check_eq(addon, a2); + do_execute_soon(check_test_3); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); + }); + + check_test_3 = () => { + restartManager(); + AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { + do_check_neq(a2, null); + do_check_true(a2.isActive); + do_check_true(a2.isCompatible); + do_check_false(a2.appDisabled); + a2.uninstall(); + + run_next_test(); + }); + } + + // Checks that we see no compatibility information when there is none. + add_test(function run_test_4() { + AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { + do_check_neq(a3, null); + do_check_false(a3.isActive); + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + do_check_true(a3.isCompatibleWith("5", "5")); + do_check_false(a3.isCompatibleWith("2", "2")); + + a3.findUpdates({ + sawUpdate: false, + onCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should not have seen compatibility information"); + }, + + onNoCompatibilityUpdateAvailable: function(addon) { + this.sawUpdate = true; + }, + + onUpdateAvailable: function(addon, install) { + ok(false, "Should not have seen an available update"); + }, + + onNoUpdateAvailable: function(addon) { + do_check_true(this.sawUpdate); + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); + }); + + // Checks that compatibility info for future apps are detected but don't make + // the item compatibile. + let check_test_5; + add_test(function run_test_5() { + AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { + do_check_neq(a3, null); + do_check_false(a3.isActive); + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + do_check_true(a3.isCompatibleWith("5", "5")); + do_check_false(a3.isCompatibleWith("2", "2")); + + a3.findUpdates({ + sawUpdate: false, + onCompatibilityUpdateAvailable: function(addon) { + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + do_check_false(a3.isActive); + this.sawUpdate = true; + }, + + onNoCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should have seen some compatibility information"); + }, + + onUpdateAvailable: function(addon, install) { + ok(false, "Should not have seen an available update"); + }, + + onNoUpdateAvailable: function(addon) { + do_check_true(this.sawUpdate); + do_execute_soon(check_test_5); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED, "3.0", "3.0"); + }); + }); + + check_test_5 = () => { + restartManager(); + AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { + do_check_neq(a3, null); + do_check_false(a3.isActive); + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + + a3.uninstall(); + run_next_test(); + }); + } + + // Test that background update checks work + let continue_test_6; + add_test(function run_test_6() { + restartManager(); + + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + restartManager(); + + prepare_test({}, [ + "onNewInstall", + "onDownloadStarted", + "onDownloadEnded" + ], continue_test_6); + + AddonManagerInternal.backgroundUpdateCheck(); + }); + + let check_test_6; + continue_test_6 = (install) => { + do_check_neq(install.existingAddon, null); + do_check_eq(install.existingAddon.id, "addon1@tests.mozilla.org"); + + prepare_test({ + "addon1@tests.mozilla.org": [ + "onInstalling" + ] + }, [ + "onInstallStarted", + "onInstallEnded", + ], callback_soon(check_test_6)); + } + + check_test_6 = (install) => { + do_check_eq(install.existingAddon.pendingUpgrade.install, install); + + restartManager(); AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { do_check_neq(a1, null); do_check_eq(a1.version, "2.0"); - do_check_true(isExtensionInAddonsList(profileDir, a1.id)); - do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DISABLE); do_check_eq(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); - do_check_true(a1.foreignInstall); - do_check_neq(a1.syncGUID, null); - do_check_eq(originalSyncGUID, a1.syncGUID); - a1.uninstall(); - do_execute_soon(run_test_3); + run_next_test(); }); - })); -} + } + // Verify the parameter escaping in update urls. + add_test(function run_test_8() { + restartManager(); -// Check that an update check finds compatibility updates and applies them -function run_test_3() { - restartManager(); + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "5.0", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "2" + }], + name: "Test Addon 1", + }, profileDir); - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { - do_check_neq(a2, null); - do_check_true(a2.isActive); - do_check_true(a2.isCompatible); - do_check_false(a2.appDisabled); - do_check_true(a2.isCompatibleWith("0")); + writeInstallRDFForExtension({ + id: "addon2@tests.mozilla.org", + version: "67.0.5b1", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: "toolkit@mozilla.org", + minVersion: "0", + maxVersion: "3" + }], + name: "Test Addon 2", + }, profileDir); - a2.findUpdates({ - onCompatibilityUpdateAvailable: function(addon) { - do_check_true(a2.isCompatible); - do_check_false(a2.appDisabled); - do_check_true(a2.isActive); + writeInstallRDFForExtension({ + id: "addon3@tests.mozilla.org", + version: "1.3+", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "0", + maxVersion: "0" + }, { + id: "toolkit@mozilla.org", + minVersion: "0", + maxVersion: "3" + }], + name: "Test Addon 3", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon4@tests.mozilla.org", + version: "0.5ab6", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "5" + }], + name: "Test Addon 4", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon5@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 5", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon6@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 6", + }, profileDir); + + restartManager(); + + AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(a2) { + a2.userDisabled = true; + restartManager(); + + testserver.registerPathHandler("/data/param_test.rdf", function(request, response) { + do_check_neq(request.queryString, ""); + let [req_version, item_id, item_version, + item_maxappversion, item_status, + app_id, app_version, current_app_version, + app_os, app_abi, app_locale, update_type] = + request.queryString.split("/").map(a => decodeURIComponent(a)); + + do_check_eq(req_version, "2"); + + switch(item_id) { + case "addon1@tests.mozilla.org": + do_check_eq(item_version, "5.0"); + do_check_eq(item_maxappversion, "2"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "97"); + break; + case "addon2@tests.mozilla.org": + do_check_eq(item_version, "67.0.5b1"); + do_check_eq(item_maxappversion, "3"); + do_check_eq(item_status, "userDisabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "49"); + break; + case "addon3@tests.mozilla.org": + do_check_eq(item_version, "1.3+"); + do_check_eq(item_maxappversion, "0"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "112"); + break; + case "addon4@tests.mozilla.org": + do_check_eq(item_version, "0.5ab6"); + do_check_eq(item_maxappversion, "5"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "2"); + do_check_eq(update_type, "98"); + break; + case "addon5@tests.mozilla.org": + do_check_eq(item_version, "1.0"); + do_check_eq(item_maxappversion, "1"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "35"); + break; + case "addon6@tests.mozilla.org": + do_check_eq(item_version, "1.0"); + do_check_eq(item_maxappversion, "1"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "99"); + break; + default: + ok(false, "Update request for unexpected add-on " + item_id); + } + + do_check_eq(app_id, "xpcshell@tests.mozilla.org"); + do_check_eq(current_app_version, "1"); + do_check_eq(app_os, "XPCShell"); + do_check_eq(app_abi, "noarch-spidermonkey"); + do_check_eq(app_locale, "fr-FR"); + + request.setStatusLine(null, 500, "Server Error"); + }); + + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon2@tests.mozilla.org", + "addon3@tests.mozilla.org", + "addon4@tests.mozilla.org", + "addon5@tests.mozilla.org", + "addon6@tests.mozilla.org"], + function([a1, a2, a3, a4, a5, a6]) { + let count = 6; + + function next_test() { + a1.uninstall(); + a2.uninstall(); + a3.uninstall(); + a4.uninstall(); + a5.uninstall(); + a6.uninstall(); + + restartManager(); + run_next_test(); + } + + let compatListener = { + onUpdateFinished: function(addon, error) { + if (--count == 0) + do_execute_soon(next_test); + } + }; + + let updateListener = { + onUpdateAvailable: function(addon, update) { + // Dummy so the update checker knows we care about new versions + }, + + onUpdateFinished: function(addon, error) { + if (--count == 0) + do_execute_soon(next_test); + } + }; + + a1.findUpdates(updateListener, AddonManager.UPDATE_WHEN_USER_REQUESTED); + a2.findUpdates(compatListener, AddonManager.UPDATE_WHEN_ADDON_INSTALLED); + a3.findUpdates(updateListener, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); + a4.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "2"); + a5.findUpdates(compatListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); + a6.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); + }); + })); + }); + + // Tests that if an install.rdf claims compatibility then the add-on will be + // seen as compatible regardless of what the update.rdf says. + add_test(function run_test_9() { + writeInstallRDFForExtension({ + id: "addon4@tests.mozilla.org", + version: "5.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + + restartManager(); + + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + do_check_true(a4.isActive, "addon4 is active"); + do_check_true(a4.isCompatible, "addon4 is compatible"); + + run_next_test(); + }); + }); + + // Tests that a normal update check won't decrease a targetApplication's + // maxVersion. + add_test(function run_test_10() { + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + a4.findUpdates({ + onUpdateFinished: function(addon) { + do_check_true(addon.isCompatible, "addon4 is compatible"); + + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); + }); + }); + + // Tests that an update check for a new application will decrease a + // targetApplication's maxVersion. + add_test(function run_test_11() { + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + a4.findUpdates({ + onUpdateFinished: function(addon) { + do_check_true(addon.isCompatible, "addon4 is not compatible"); + + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); + }); + }); + + // Check that the decreased maxVersion applied and disables the add-on + add_test(function run_test_12() { + restartManager(); + + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + do_check_true(a4.isActive); + do_check_true(a4.isCompatible); + + a4.uninstall(); + run_next_test(); + }); + }); + + // Tests that a compatibility update is passed to the listener when there is + // compatibility info for the current version of the app but not for the + // version of the app that the caller requested an update check for, when + // strict compatibility checking is disabled. + let check_test_13; + add_test(function run_test_13() { + restartManager(); + + // Not initially compatible but the update check will make it compatible + writeInstallRDFForExtension({ + id: "addon7@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0", + maxVersion: "0" + }], + name: "Test Addon 7", + }, profileDir); + restartManager(); + + AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { + do_check_neq(a7, null); + do_check_true(a7.isActive); + do_check_true(a7.isCompatible); + do_check_false(a7.appDisabled); + do_check_true(a7.isCompatibleWith("0", "0")); + + a7.findUpdates({ + sawUpdate: false, + onNoCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should have seen compatibility information"); + }, + + onUpdateAvailable: function(addon, install) { + ok(false, "Should not have seen an available update"); + }, + + onUpdateFinished: function(addon) { + do_check_true(addon.isCompatible); + do_execute_soon(check_test_13); + } + }, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "3.0", "3.0"); + }); + }); + + check_test_13 = () => { + restartManager(); + AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { + do_check_neq(a7, null); + do_check_true(a7.isActive); + do_check_true(a7.isCompatible); + do_check_false(a7.appDisabled); + + a7.uninstall(); + run_next_test(); + }); + } + + // Test that background update checks doesn't update an add-on that isn't + // allowed to update automatically. + let check_test_14; + add_test(function run_test_14() { + restartManager(); + + // Have an add-on there that will be updated so we see some events from it + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon8@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 8", + }, profileDir); + restartManager(); + + AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { + a8.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + + // The background update check will find updates for both add-ons but only + // proceed to install one of them. + AddonManager.addInstallListener({ + onNewInstall: function(aInstall) { + let id = aInstall.existingAddon.id; + ok((id == "addon1@tests.mozilla.org" || id == "addon8@tests.mozilla.org"), + "Saw unexpected onNewInstall for " + id); + }, + + onDownloadStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadFailed: function(aInstall) { + ok(false, "Should not have seen onDownloadFailed event"); + }, + + onDownloadCancelled: function(aInstall) { + ok(false, "Should not have seen onDownloadCancelled event"); + }, + + onInstallStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onInstallEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + do_check_eq(aInstall.existingAddon.pendingUpgrade.install, aInstall); + + do_execute_soon(check_test_14); + }, + + onInstallFailed: function(aInstall) { + ok(false, "Should not have seen onInstallFailed event"); + }, + + onInstallCancelled: function(aInstall) { + ok(false, "Should not have seen onInstallCancelled event"); + }, + }); + + AddonManagerInternal.backgroundUpdateCheck(); + }); + }); + + check_test_14 = () => { + restartManager(); + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon8@tests.mozilla.org"], function([a1, a8]) { + do_check_neq(a1, null); + do_check_eq(a1.version, "2.0"); + a1.uninstall(); + + do_check_neq(a8, null); + do_check_eq(a8.version, "1.0"); + a8.uninstall(); + + run_next_test(); + }); + } + + // Test that background update checks doesn't update an add-on that is + // pending uninstall + let check_test_15; + add_test(function run_test_15() { + restartManager(); + + // Have an add-on there that will be updated so we see some events from it + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon8@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 8", + }, profileDir); + restartManager(); + + AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { + a8.uninstall(); + do_check_false(hasFlag(a8.permissions, AddonManager.PERM_CAN_UPGRADE)); + + // The background update check will find updates for both add-ons but only + // proceed to install one of them. + AddonManager.addInstallListener({ + onNewInstall: function(aInstall) { + let id = aInstall.existingAddon.id; + ok((id == "addon1@tests.mozilla.org" || id == "addon8@tests.mozilla.org"), + "Saw unexpected onNewInstall for " + id); + }, + + onDownloadStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadFailed: function(aInstall) { + ok(false, "Should not have seen onDownloadFailed event"); + }, + + onDownloadCancelled: function(aInstall) { + ok(false, "Should not have seen onDownloadCancelled event"); + }, + + onInstallStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onInstallEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + do_execute_soon(check_test_15); + }, + + onInstallFailed: function(aInstall) { + ok(false, "Should not have seen onInstallFailed event"); + }, + + onInstallCancelled: function(aInstall) { + ok(false, "Should not have seen onInstallCancelled event"); + }, + }); + + AddonManagerInternal.backgroundUpdateCheck(); + }); + }); + + check_test_15 = () => { + restartManager(); + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon8@tests.mozilla.org"], function([a1, a8]) { + do_check_neq(a1, null); + do_check_eq(a1.version, "2.0"); + a1.uninstall(); + + do_check_eq(a8, null); + + run_next_test(); + }); + } + + add_test(function run_test_16() { + restartManager(); + + restartManager(); + + let url = "http://localhost:" + gPort + "/addons/test_install2_1.xpi"; + AddonManager.getInstallForURL(url, function(aInstall) { + aInstall.addListener({ + onInstallEnded: function() { + do_execute_soon(function install_2_1_ended() { + restartManager(); + + AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a1) { + do_check_neq(a1.syncGUID, null); + let oldGUID = a1.syncGUID; + + let url = "http://localhost:" + gPort + "/addons/test_install2_2.xpi"; + AddonManager.getInstallForURL(url, function(aInstall) { + aInstall.addListener({ + onInstallEnded: function() { + do_execute_soon(function install_2_2_ended() { + restartManager(); + + AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { + do_check_neq(a2.syncGUID, null); + do_check_eq(oldGUID, a2.syncGUID); + + a2.uninstall(); + run_next_test(); + }); + }); + } + }); + aInstall.install(); + }, "application/x-xpinstall"); + }); + }); + } + }); + aInstall.install(); + }, "application/x-xpinstall"); + }); + + // Test that the update check correctly observes the + // extensions.strictCompatibility pref and compatibility overrides. + add_test(function run_test_17() { + restartManager(); + + writeInstallRDFForExtension({ + id: "addon9@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 9", + }, profileDir); + restartManager(); + + AddonManager.addInstallListener({ + onNewInstall: function(aInstall) { + equal(aInstall.existingAddon.id, "addon9@tests.mozilla.org", + "Saw unexpected onNewInstall for " + aInstall.existingAddon.id); + do_check_eq(aInstall.version, "3.0"); }, - - onUpdateAvailable: function(addon, install) { - do_throw("Should not have seen an available update"); - }, - - onNoUpdateAvailable: function(addon) { - do_check_eq(addon, a2); - do_execute_soon(check_test_3); + onDownloadFailed: function(aInstall) { + AddonManager.getAddonByID("addon9@tests.mozilla.org", function(a9) { + a9.uninstall(); + run_next_test(); + }); } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); + + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, + "http://localhost:" + gPort + "/data/test_update.xml"); + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS_PERFORMANCE, + "http://localhost:" + gPort + "/data/test_update.xml"); + Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); + + AddonManagerInternal.backgroundUpdateCheck(); }); -} -function check_test_3() { - restartManager(); - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { - do_check_neq(a2, null); - do_check_true(a2.isActive); - do_check_true(a2.isCompatible); - do_check_false(a2.appDisabled); - a2.uninstall(); + // Tests that compatibility updates are applied to addons when the updated + // compatibility data wouldn't match with strict compatibility enabled. + add_test(function run_test_18() { + restartManager(); + writeInstallRDFForExtension({ + id: "addon10@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 10", + }, profileDir); + restartManager(); - run_test_4(); + AddonManager.getAddonByID("addon10@tests.mozilla.org", function(a10) { + do_check_neq(a10, null); + + a10.findUpdates({ + onNoCompatibilityUpdateAvailable: function() { + ok(false, "Should have seen compatibility information"); + }, + + onUpdateAvailable: function() { + ok(false, "Should not have seen an available update"); + }, + + onUpdateFinished: function() { + a10.uninstall(); + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); }); -} -// Checks that we see no compatibility information when there is none. -function run_test_4() { - AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { - do_check_neq(a3, null); - do_check_false(a3.isActive); - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); - do_check_true(a3.isCompatibleWith("5")); - do_check_false(a3.isCompatibleWith("2")); + // Test that the update check correctly observes when an addon opts-in to + // strict compatibility checking. + add_test(function run_test_19() { + restartManager(); + writeInstallRDFForExtension({ + id: "addon11@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 11", + }, profileDir); + restartManager(); - a3.findUpdates({ - sawUpdate: false, - onCompatibilityUpdateAvailable: function(addon) { - do_throw("Should not have seen compatibility information"); - }, + AddonManager.getAddonByID("addon11@tests.mozilla.org", function(a11) { + do_check_neq(a11, null); - onNoCompatibilityUpdateAvailable: function(addon) { - this.sawUpdate = true; - }, + a11.findUpdates({ + onCompatibilityUpdateAvailable: function() { + ok(false, "Should have not have seen compatibility information"); + }, - onUpdateAvailable: function(addon, install) { - do_throw("Should not have seen an available update"); - }, + onUpdateAvailable: function() { + ok(false, "Should not have seen an available update"); + }, - onNoUpdateAvailable: function(addon) { - do_check_true(this.sawUpdate); - run_test_5(); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + onUpdateFinished: function() { + a11.uninstall(); + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); }); -} -// Checks that compatibility info for future apps are detected but don't make -// the item compatibile. -function run_test_5() { - AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { - do_check_neq(a3, null); - do_check_false(a3.isActive); - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); - do_check_true(a3.isCompatibleWith("5")); - do_check_false(a3.isCompatibleWith("2")); + // Test that the update succeeds when the update.rdf URN contains a type prefix + // different from the add-on type + let continue_test_20; + add_test(function run_test_20() { + restartManager(); + writeInstallRDFForExtension({ + id: "addon12@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 12", + }, profileDir); + restartManager(); - a3.findUpdates({ - sawUpdate: false, - onCompatibilityUpdateAvailable: function(addon) { - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); - do_check_false(a3.isActive); - this.sawUpdate = true; - }, + prepare_test({}, [ + "onNewInstall", + "onDownloadStarted", + "onDownloadEnded" + ], continue_test_20); - onNoCompatibilityUpdateAvailable: function(addon) { - do_throw("Should have seen some compatibility information"); - }, - - onUpdateAvailable: function(addon, install) { - do_throw("Should not have seen an available update"); - }, - - onNoUpdateAvailable: function(addon) { - do_check_true(this.sawUpdate); - do_execute_soon(check_test_5); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED, "3.0"); + AddonManagerPrivate.backgroundUpdateCheck(); }); -} -function check_test_5() { - restartManager(); - AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { - do_check_neq(a3, null); - do_check_false(a3.isActive); - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); + let check_test_20; + continue_test_20 = (install) => { + do_check_neq(install.existingAddon, null); + do_check_eq(install.existingAddon.id, "addon12@tests.mozilla.org"); - a3.uninstall(); - do_execute_soon(run_test_6); - }); -} + prepare_test({ + "addon12@tests.mozilla.org": [ + "onInstalling" + ] + }, [ + "onInstallStarted", + "onInstallEnded", + ], callback_soon(check_test_20)); + } -// Test that background update checks work -function run_test_6() { - restartManager(); + check_test_20 = (install) => { + do_check_eq(install.existingAddon.pendingUpgrade.install, install); - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - restartManager(); + restartManager(); + AddonManager.getAddonByID("addon12@tests.mozilla.org", function(a12) { + do_check_neq(a12, null); + do_check_eq(a12.version, "2.0"); + do_check_eq(a12.type, "extension"); + a12.uninstall(); - prepare_test({}, [ - "onNewInstall", - "onDownloadStarted", - "onDownloadEnded" - ], continue_test_6); + do_execute_soon(() => { + restartManager(); + run_next_test() + }); + }); + } - AddonManagerInternal.backgroundUpdateCheck(); -} + add_task(function cleanup() { + let addons = yield new Promise(resolve => { + AddonManager.getAddonsByTypes(["extension"], resolve); + }); -function continue_test_6(install) { - do_check_neq(install.existingAddon, null); - do_check_eq(install.existingAddon.id, "addon1@tests.mozilla.org"); + for (let addon of addons) + addon.uninstall(); - prepare_test({ - "addon1@tests.mozilla.org": [ - "onInstalling" - ] - }, [ - "onInstallStarted", - "onInstallEnded", - ], callback_soon(check_test_6)); -} + yield promiseRestartManager(); -function check_test_6(install) { - do_check_eq(install.existingAddon.pendingUpgrade.install, install); + shutdownManager(); - restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { - do_check_neq(a1, null); - do_check_eq(a1.version, "2.0"); - do_check_eq(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); - a1.uninstall(); - do_execute_soon(run_test_7); + yield new Promise(do_execute_soon); }); } // Test that background update checks work for lightweight themes -function run_test_7() { - restartManager(); +add_test(function run_test_7() { + startupManager(); LightweightThemeManager.currentTheme = { id: "1", @@ -484,7 +1258,7 @@ function run_test_7() { AddonManagerInternal.backgroundUpdateCheck(); }); -} +}); function check_test_7() { AddonManager.getAddonByID("1@personas.mozilla.org", function(p1) { @@ -501,13 +1275,13 @@ function check_test_7() { gInstallDate = p1.installDate.getTime(); - run_test_7_cache(); + run_next_test(); }); } // Test that background update checks for lightweight themes do not use the cache // The update body from test 7 shouldn't be used since the cache should be bypassed. -function run_test_7_cache() { +add_test(function () { // XXX The lightweight theme manager strips non-https updateURLs so hack it // back in. let themes = JSON.parse(Services.prefs.getCharPref("lightweightThemes.usedThemes")); @@ -550,7 +1324,7 @@ function run_test_7_cache() { AddonManagerInternal.backgroundUpdateCheck(); }); -} +}); function check_test_7_cache() { AddonManager.getAddonByID("1@personas.mozilla.org", function(p1) { @@ -571,740 +1345,6 @@ function check_test_7_cache() { do_check_eq(p1.installDate.getTime(), gInstallDate); do_check_true(p1.installDate.getTime() < p1.updateDate.getTime()); - do_execute_soon(run_test_8); - }); -} - -// Verify the parameter escaping in update urls. -function run_test_8() { - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "5.0", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "2" - }], - name: "Test Addon 1", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon2@tests.mozilla.org", - version: "67.0.5b1", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "toolkit@mozilla.org", - minVersion: "0", - maxVersion: "3" - }], - name: "Test Addon 2", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon3@tests.mozilla.org", - version: "1.3+", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0", - maxVersion: "0" - }, { - id: "toolkit@mozilla.org", - minVersion: "0", - maxVersion: "3" - }], - name: "Test Addon 3", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon4@tests.mozilla.org", - version: "0.5ab6", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "5" - }], - name: "Test Addon 4", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon5@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 5", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon6@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 6", - }, profileDir); - - restartManager(); - - AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(a2) { - a2.userDisabled = true; - restartManager(); - - testserver.registerPathHandler("/data/param_test.rdf", function(request, response) { - do_check_neq(request.queryString, ""); - let [req_version, item_id, item_version, - item_maxappversion, item_status, - app_id, app_version, current_app_version, - app_os, app_abi, app_locale, update_type] = - request.queryString.split("/").map(a => decodeURIComponent(a)); - - do_check_eq(req_version, "2"); - - switch(item_id) { - case "addon1@tests.mozilla.org": - do_check_eq(item_version, "5.0"); - do_check_eq(item_maxappversion, "2"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "97"); - break; - case "addon2@tests.mozilla.org": - do_check_eq(item_version, "67.0.5b1"); - do_check_eq(item_maxappversion, "3"); - do_check_eq(item_status, "userDisabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "49"); - break; - case "addon3@tests.mozilla.org": - do_check_eq(item_version, "1.3+"); - do_check_eq(item_maxappversion, "0"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "112"); - break; - case "addon4@tests.mozilla.org": - do_check_eq(item_version, "0.5ab6"); - do_check_eq(item_maxappversion, "5"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "2"); - do_check_eq(update_type, "98"); - break; - case "addon5@tests.mozilla.org": - do_check_eq(item_version, "1.0"); - do_check_eq(item_maxappversion, "1"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "35"); - break; - case "addon6@tests.mozilla.org": - do_check_eq(item_version, "1.0"); - do_check_eq(item_maxappversion, "1"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "99"); - break; - default: - do_throw("Update request for unexpected add-on " + item_id); - } - - do_check_eq(app_id, "xpcshell@tests.mozilla.org"); - do_check_eq(current_app_version, "1"); - do_check_eq(app_os, "XPCShell"); - do_check_eq(app_abi, "noarch-spidermonkey"); - do_check_eq(app_locale, "fr-FR"); - - request.setStatusLine(null, 500, "Server Error"); - }); - - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon2@tests.mozilla.org", - "addon3@tests.mozilla.org", - "addon4@tests.mozilla.org", - "addon5@tests.mozilla.org", - "addon6@tests.mozilla.org"], - function([a1, a2, a3, a4, a5, a6]) { - let count = 6; - - function run_next_test() { - a1.uninstall(); - a2.uninstall(); - a3.uninstall(); - a4.uninstall(); - a5.uninstall(); - a6.uninstall(); - - restartManager(); - run_test_9(); - } - - let compatListener = { - onUpdateFinished: function(addon, error) { - if (--count == 0) - do_execute_soon(run_next_test); - } - }; - - let updateListener = { - onUpdateAvailable: function(addon, update) { - // Dummy so the update checker knows we care about new versions - }, - - onUpdateFinished: function(addon, error) { - if (--count == 0) - do_execute_soon(run_next_test); - } - }; - - a1.findUpdates(updateListener, AddonManager.UPDATE_WHEN_USER_REQUESTED); - a2.findUpdates(compatListener, AddonManager.UPDATE_WHEN_ADDON_INSTALLED); - a3.findUpdates(updateListener, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); - a4.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "2"); - a5.findUpdates(compatListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); - a6.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); - }); - })); -} - -// Tests that if an install.rdf claims compatibility then the add-on will be -// seen as compatible regardless of what the update.rdf says. -function run_test_9() { - writeInstallRDFForExtension({ - id: "addon4@tests.mozilla.org", - version: "5.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - restartManager(); - - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - do_check_true(a4.isActive); - do_check_true(a4.isCompatible); - - run_test_10(); - }); -} - -// Tests that a normal update check won't decrease a targetApplication's -// maxVersion. -function run_test_10() { - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - a4.findUpdates({ - onUpdateFinished: function(addon) { - do_check_true(addon.isCompatible); - - run_test_11(); - } - }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); - }); -} - -// Tests that an update check for a new application will decrease a -// targetApplication's maxVersion. -function run_test_11() { - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - a4.findUpdates({ - onUpdateFinished: function(addon) { - do_check_true(addon.isCompatible); - - do_execute_soon(run_test_12); - } - }, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); - }); -} - -// Check that the decreased maxVersion applied and disables the add-on -function run_test_12() { - restartManager(); - - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - do_check_true(a4.isActive); - do_check_true(a4.isCompatible); - - a4.uninstall(); - do_execute_soon(run_test_13); - }); -} - -// Tests that a compatibility update is passed to the listener when there is -// compatibility info for the current version of the app but not for the -// version of the app that the caller requested an update check for, when -// strict compatibility checking is disabled. -function run_test_13() { - restartManager(); - - // Not initially compatible but the update check will make it compatible - writeInstallRDFForExtension({ - id: "addon7@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0", - maxVersion: "0" - }], - name: "Test Addon 7", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { - do_check_neq(a7, null); - do_check_true(a7.isActive); - do_check_true(a7.isCompatible); - do_check_false(a7.appDisabled); - do_check_true(a7.isCompatibleWith("0")); - - a7.findUpdates({ - sawUpdate: false, - onNoCompatibilityUpdateAvailable: function(addon) { - do_throw("Should have seen compatibility information"); - }, - - onUpdateAvailable: function(addon, install) { - do_throw("Should not have seen an available update"); - }, - - onUpdateFinished: function(addon) { - do_check_true(addon.isCompatible); - do_execute_soon(check_test_13); - } - }, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "3.0"); - }); -} - -function check_test_13() { - restartManager(); - AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { - do_check_neq(a7, null); - do_check_true(a7.isActive); - do_check_true(a7.isCompatible); - do_check_false(a7.appDisabled); - - a7.uninstall(); - do_execute_soon(run_test_14); - }); -} - -// Test that background update checks doesn't update an add-on that isn't -// allowed to update automatically. -function run_test_14() { - restartManager(); - - // Have an add-on there that will be updated so we see some events from it - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon8@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 8", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { - a8.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; - - // The background update check will find updates for both add-ons but only - // proceed to install one of them. - AddonManager.addInstallListener({ - onNewInstall: function(aInstall) { - if (aInstall.existingAddon.id != "addon1@tests.mozilla.org" && - aInstall.existingAddon.id != "addon8@tests.mozilla.org") - do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); - }, - - onDownloadStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadFailed: function(aInstall) { - do_throw("Should not have seen onDownloadFailed event"); - }, - - onDownloadCancelled: function(aInstall) { - do_throw("Should not have seen onDownloadCancelled event"); - }, - - onInstallStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onInstallEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - do_check_eq(aInstall.existingAddon.pendingUpgrade.install, aInstall); - - do_execute_soon(check_test_14); - }, - - onInstallFailed: function(aInstall) { - do_throw("Should not have seen onInstallFailed event"); - }, - - onInstallCancelled: function(aInstall) { - do_throw("Should not have seen onInstallCancelled event"); - }, - }); - - AddonManagerInternal.backgroundUpdateCheck(); - }); -} - -function check_test_14() { - restartManager(); - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon8@tests.mozilla.org"], function([a1, a8]) { - do_check_neq(a1, null); - do_check_eq(a1.version, "2.0"); - a1.uninstall(); - - do_check_neq(a8, null); - do_check_eq(a8.version, "1.0"); - a8.uninstall(); - - do_execute_soon(run_test_15); - }); -} - -// Test that background update checks doesn't update an add-on that is -// pending uninstall -function run_test_15() { - restartManager(); - - // Have an add-on there that will be updated so we see some events from it - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon8@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 8", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { - a8.uninstall(); - do_check_false(hasFlag(a8.permissions, AddonManager.PERM_CAN_UPGRADE)); - - // The background update check will find updates for both add-ons but only - // proceed to install one of them. - AddonManager.addInstallListener({ - onNewInstall: function(aInstall) { - if (aInstall.existingAddon.id != "addon1@tests.mozilla.org" && - aInstall.existingAddon.id != "addon8@tests.mozilla.org") - do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); - }, - - onDownloadStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadFailed: function(aInstall) { - do_throw("Should not have seen onDownloadFailed event"); - }, - - onDownloadCancelled: function(aInstall) { - do_throw("Should not have seen onDownloadCancelled event"); - }, - - onInstallStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onInstallEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - do_execute_soon(check_test_15); - }, - - onInstallFailed: function(aInstall) { - do_throw("Should not have seen onInstallFailed event"); - }, - - onInstallCancelled: function(aInstall) { - do_throw("Should not have seen onInstallCancelled event"); - }, - }); - - AddonManagerInternal.backgroundUpdateCheck(); - }); -} - -function check_test_15() { - restartManager(); - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon8@tests.mozilla.org"], function([a1, a8]) { - do_check_neq(a1, null); - do_check_eq(a1.version, "2.0"); - a1.uninstall(); - - do_check_eq(a8, null); - - do_execute_soon(run_test_16); - }); -} - -function run_test_16() { - restartManager(); - - restartManager(); - - let url = "http://localhost:" + gPort + "/addons/test_install2_1.xpi"; - AddonManager.getInstallForURL(url, function(aInstall) { - aInstall.addListener({ - onInstallEnded: function() { - do_execute_soon(function install_2_1_ended() { - restartManager(); - - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a1) { - do_check_neq(a1.syncGUID, null); - let oldGUID = a1.syncGUID; - - let url = "http://localhost:" + gPort + "/addons/test_install2_2.xpi"; - AddonManager.getInstallForURL(url, function(aInstall) { - aInstall.addListener({ - onInstallEnded: function() { - do_execute_soon(function install_2_2_ended() { - restartManager(); - - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { - do_check_neq(a2.syncGUID, null); - do_check_eq(oldGUID, a2.syncGUID); - - a2.uninstall(); - do_execute_soon(run_test_17); - }); - }); - } - }); - aInstall.install(); - }, "application/x-xpinstall"); - }); - }); - } - }); - aInstall.install(); - }, "application/x-xpinstall"); -} - -// Test that the update check correctly observes the -// extensions.strictCompatibility pref and compatibility overrides. -function run_test_17() { - restartManager(); - - writeInstallRDFForExtension({ - id: "addon9@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0.1", - maxVersion: "0.2" - }], - name: "Test Addon 9", - }, profileDir); - restartManager(); - - AddonManager.addInstallListener({ - onNewInstall: function(aInstall) { - if (aInstall.existingAddon.id != "addon9@tests.mozilla.org") - do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); - do_check_eq(aInstall.version, "3.0"); - }, - onDownloadFailed: function(aInstall) { - AddonManager.getAddonByID("addon9@tests.mozilla.org", function(a9) { - a9.uninstall(); - do_execute_soon(run_test_18); - }); - } - }); - - Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, - "http://localhost:" + gPort + "/data/test_update.xml"); - Services.prefs.setCharPref(PREF_GETADDONS_BYIDS_PERFORMANCE, - "http://localhost:" + gPort + "/data/test_update.xml"); - Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); - - AddonManagerInternal.backgroundUpdateCheck(); -} - -// Tests that compatibility updates are applied to addons when the updated -// compatibility data wouldn't match with strict compatibility enabled. -function run_test_18() { - restartManager(); - writeInstallRDFForExtension({ - id: "addon10@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0.1", - maxVersion: "0.2" - }], - name: "Test Addon 10", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon10@tests.mozilla.org", function(a10) { - do_check_neq(a10, null); - - a10.findUpdates({ - onNoCompatibilityUpdateAvailable: function() { - do_throw("Should have seen compatibility information"); - }, - - onUpdateAvailable: function() { - do_throw("Should not have seen an available update"); - }, - - onUpdateFinished: function() { - a10.uninstall(); - do_execute_soon(run_test_19); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); - }); -} - -// Test that the update check correctly observes when an addon opts-in to -// strict compatibility checking. -function run_test_19() { - restartManager(); - writeInstallRDFForExtension({ - id: "addon11@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0.1", - maxVersion: "0.2" - }], - name: "Test Addon 11", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon11@tests.mozilla.org", function(a11) { - do_check_neq(a11, null); - - a11.findUpdates({ - onCompatibilityUpdateAvailable: function() { - do_throw("Should have not have seen compatibility information"); - }, - - onUpdateAvailable: function() { - do_throw("Should not have seen an available update"); - }, - - onUpdateFinished: function() { - a11.uninstall(); - do_execute_soon(run_test_20); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); - }); -} - -// Test that the update succeeds when the update.rdf URN contains a type prefix -// different from the add-on type -function run_test_20() { - restartManager(); - writeInstallRDFForExtension({ - id: "addon12@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 12", - }, profileDir); - restartManager(); - - prepare_test({}, [ - "onNewInstall", - "onDownloadStarted", - "onDownloadEnded" - ], continue_test_20); - - AddonManagerPrivate.backgroundUpdateCheck(); -} - -function continue_test_20(install) { - do_check_neq(install.existingAddon, null); - do_check_eq(install.existingAddon.id, "addon12@tests.mozilla.org"); - - prepare_test({ - "addon12@tests.mozilla.org": [ - "onInstalling" - ] - }, [ - "onInstallStarted", - "onInstallEnded", - ], callback_soon(check_test_20)); -} - -function check_test_20(install) { - do_check_eq(install.existingAddon.pendingUpgrade.install, install); - - restartManager(); - AddonManager.getAddonByID("addon12@tests.mozilla.org", function(a12) { - do_check_neq(a12, null); - do_check_eq(a12.version, "2.0"); - do_check_eq(a12.type, "extension"); - a12.uninstall(); - - do_execute_soon(() => { - restartManager(); - end_test(); - }); + run_next_test(); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_update_ignorecompat.js b/toolkit/mozapps/extensions/test/xpcshell/test_update_ignorecompat.js index 672594088fb3..16fa3a4c18b3 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_update_ignorecompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_update_ignorecompat.js @@ -9,12 +9,13 @@ const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled"; // The test extension uses an insecure update url. Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false); +Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, false); Components.utils.import("resource://testing-common/httpd.js"); -var testserver = new HttpServer(); -testserver.start(-1); +var testserver = createHttpServer(); gPort = testserver.identity.primaryPort; mapFile("/data/test_update.rdf", testserver); +mapFile("/data/test_update.json", testserver); mapFile("/data/test_update.xml", testserver); testserver.registerDirectory("/addons/", do_get_file("addons")); @@ -22,77 +23,86 @@ const profileDir = gProfD.clone(); profileDir.append("extensions"); -function run_test() { - createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); +createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1"); - run_test_1(); -} +let testParams = [ + { updateFile: "test_update.rdf", + appId: "xpcshell@tests.mozilla.org" }, + { updateFile: "test_update.json", + appId: "toolkit@mozilla.org" }, +]; -// Test that the update check correctly observes the -// extensions.strictCompatibility pref and compatibility overrides. -function run_test_1() { - writeInstallRDFForExtension({ - id: "addon9@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0.1", - maxVersion: "0.2" - }], - name: "Test Addon 9", - }, profileDir); - restartManager(); +for (let test of testParams) { + let { updateFile, appId } = test; - AddonManager.addInstallListener({ - onNewInstall: function(aInstall) { - if (aInstall.existingAddon.id != "addon9@tests.mozilla.org") - do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); - do_check_eq(aInstall.version, "4.0"); - }, - onDownloadFailed: function(aInstall) { - do_execute_soon(run_test_2); - } - }); + // Test that the update check correctly observes the + // extensions.strictCompatibility pref and compatibility overrides. + add_test(function () { + writeInstallRDFForExtension({ + id: "addon9@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 9", + }, profileDir); - Services.prefs.setCharPref(PREF_GETADDONS_BYIDS_PERFORMANCE, - "http://localhost:" + gPort + "/data/test_update.xml"); - Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); + restartManager(); - AddonManagerInternal.backgroundUpdateCheck(); -} - -// Test that the update check correctly observes when an addon opts-in to -// strict compatibility checking. -function run_test_2() { - writeInstallRDFForExtension({ - id: "addon11@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0.1", - maxVersion: "0.2" - }], - name: "Test Addon 11", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon11@tests.mozilla.org", function(a11) { - do_check_neq(a11, null); - - a11.findUpdates({ - onCompatibilityUpdateAvailable: function() { - do_throw("Should have not have seen compatibility information"); + AddonManager.addInstallListener({ + onNewInstall: function(aInstall) { + if (aInstall.existingAddon.id != "addon9@tests.mozilla.org") + do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); + do_check_eq(aInstall.version, "4.0"); }, - - onNoUpdateAvailable: function() { - do_throw("Should have seen an available update"); - }, - - onUpdateFinished: function() { - end_test(); + onDownloadFailed: function(aInstall) { + run_next_test(); } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); + + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS_PERFORMANCE, + "http://localhost:" + gPort + "/data/" + updateFile); + Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); + + AddonManagerInternal.backgroundUpdateCheck(); + }); + + // Test that the update check correctly observes when an addon opts-in to + // strict compatibility checking. + add_test(function () { + writeInstallRDFForExtension({ + id: "addon11@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 11", + }, profileDir); + + restartManager(); + + AddonManager.getAddonByID("addon11@tests.mozilla.org", function(a11) { + do_check_neq(a11, null); + + a11.findUpdates({ + onCompatibilityUpdateAvailable: function() { + do_throw("Should not have seen compatibility information"); + }, + + onUpdateAvailable: function() { + do_throw("Should not have seen an available update"); + }, + + onUpdateFinished: function() { + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js b/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js index 3c56f9adb84a..3fc16a0f7e15 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js @@ -9,7 +9,8 @@ const PREF_SELECTED_LOCALE = "general.useragent.locale"; const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled"; // The test extension uses an insecure update url. -Services.prefs.setBoolPref("extensions.checkUpdateSecurity", false); +Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false); +Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, true); // This test requires lightweight themes update to be enabled even if the app // doesn't support lightweight themes. Services.prefs.setBoolPref("lightweightThemes.update.enabled", true); @@ -23,10 +24,10 @@ const PARAMS = "?%REQ_VERSION%/%ITEM_ID%/%ITEM_VERSION%/%ITEM_MAXAPPVERSION%/" + var gInstallDate; Components.utils.import("resource://testing-common/httpd.js"); -var testserver = new HttpServer(); -testserver.start(-1); +var testserver = createHttpServer(); gPort = testserver.identity.primaryPort; mapFile("/data/test_update.rdf", testserver); +mapFile("/data/test_update.json", testserver); mapFile("/data/test_update.xml", testserver); testserver.registerDirectory("/addons/", do_get_file("addons")); @@ -34,383 +35,1013 @@ const profileDir = gProfD.clone(); profileDir.append("extensions"); function run_test() { - createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1"); + Services.prefs.setBoolPref(PREF_MATCH_OS_LOCALE, false); Services.prefs.setCharPref(PREF_SELECTED_LOCALE, "fr-FR"); - Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, true); - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon2@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0", - maxVersion: "0" - }], - name: "Test Addon 2", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon3@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "5", - maxVersion: "5" - }], - name: "Test Addon 3", - }, profileDir); - - startupManager(); - - do_test_pending(); - run_test_1(); + run_next_test(); } -function end_test() { - Services.prefs.clearUserPref(PREF_EM_STRICT_COMPATIBILITY); +let testParams = [ + { updateFile: "test_update.rdf", + appId: "xpcshell@tests.mozilla.org" }, + { updateFile: "test_update.json", + appId: "toolkit@mozilla.org" }, +]; - testserver.stop(do_test_finished); -} +for (let test of testParams) { + let { updateFile, appId } = test; -// Verify that an update is available and can be installed. -function run_test_1() { - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { - do_check_neq(a1, null); - do_check_eq(a1.version, "1.0"); - do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DEFAULT); - do_check_eq(a1.releaseNotesURI, null); + add_test(function run_test() { + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); - a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT; + writeInstallRDFForExtension({ + id: "addon2@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0", + maxVersion: "0" + }], + name: "Test Addon 2", + }, profileDir); - prepare_test({ - "addon1@tests.mozilla.org": [ - ["onPropertyChanged", ["applyBackgroundUpdates"]] - ] + writeInstallRDFForExtension({ + id: "addon3@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "5", + maxVersion: "5" + }], + name: "Test Addon 3", + }, profileDir); + + startupManager(); + + run_next_test(); + }); + + // Verify that an update is available and can be installed. + let check_test_1; + add_test(function run_test_1() { + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + do_check_neq(a1, null); + do_check_eq(a1.version, "1.0"); + do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DEFAULT); + do_check_eq(a1.releaseNotesURI, null); + + a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT; + + prepare_test({ + "addon1@tests.mozilla.org": [ + ["onPropertyChanged", ["applyBackgroundUpdates"]] + ] + }); + a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + check_test_completed(); + + a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + + prepare_test({}, [ + "onNewInstall", + ]); + + a1.findUpdates({ + onNoCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should not have seen onNoCompatibilityUpdateAvailable notification"); + }, + + onUpdateAvailable: function(addon, install) { + ensure_test_completed(); + + AddonManager.getAllInstalls(function(aInstalls) { + do_check_eq(aInstalls.length, 1); + do_check_eq(aInstalls[0], install); + + do_check_eq(addon, a1); + do_check_eq(install.name, addon.name); + do_check_eq(install.version, "2.0"); + do_check_eq(install.state, AddonManager.STATE_AVAILABLE); + do_check_eq(install.existingAddon, addon); + do_check_eq(install.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); + + // Verify that another update check returns the same AddonInstall + a1.findUpdates({ + onNoCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should not have seen onNoCompatibilityUpdateAvailable notification"); + }, + + onUpdateAvailable: function(newAddon, newInstall) { + AddonManager.getAllInstalls(function(aInstalls) { + do_check_eq(aInstalls.length, 1); + do_check_eq(aInstalls[0], install); + do_check_eq(newAddon, addon); + do_check_eq(newInstall, install); + + prepare_test({}, [ + "onDownloadStarted", + "onDownloadEnded", + ], check_test_1); + install.install(); + }); + }, + + onNoUpdateAvailable: function(addon) { + ok(false, "Should not have seen onNoUpdateAvailable notification"); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); + }, + + onNoUpdateAvailable: function(addon) { + ok(false, "Should not have seen onNoUpdateAvailable notification"); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); }); - a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; - check_test_completed(); + }); - a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + let run_test_2; + check_test_1 = (install) => { + ensure_test_completed(); + do_check_eq(install.state, AddonManager.STATE_DOWNLOADED); + run_test_2(install); + return false; + }; - prepare_test({}, [ - "onNewInstall", - ]); - - a1.findUpdates({ + // Continue installing the update. + let check_test_2; + run_test_2 = (install) => { + // Verify that another update check returns no new update + install.existingAddon.findUpdates({ onNoCompatibilityUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoCompatibilityUpdateAvailable notification"); + ok(false, "Should not have seen onNoCompatibilityUpdateAvailable notification"); }, onUpdateAvailable: function(addon, install) { - ensure_test_completed(); + ok(false, "Should find no available update when one is already downloading"); + }, + onNoUpdateAvailable: function(addon) { AddonManager.getAllInstalls(function(aInstalls) { do_check_eq(aInstalls.length, 1); do_check_eq(aInstalls[0], install); - do_check_eq(addon, a1); - do_check_eq(install.name, addon.name); - do_check_eq(install.version, "2.0"); - do_check_eq(install.state, AddonManager.STATE_AVAILABLE); - do_check_eq(install.existingAddon, addon); - do_check_eq(install.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); - - // Verify that another update check returns the same AddonInstall - a1.findUpdates({ - onNoCompatibilityUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoCompatibilityUpdateAvailable notification"); - }, - - onUpdateAvailable: function(newAddon, newInstall) { - AddonManager.getAllInstalls(function(aInstalls) { - do_check_eq(aInstalls.length, 1); - do_check_eq(aInstalls[0], install); - do_check_eq(newAddon, addon); - do_check_eq(newInstall, install); - - prepare_test({}, [ - "onDownloadStarted", - "onDownloadEnded", - ], check_test_1); - install.install(); - }); - }, - - onNoUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoUpdateAvailable notification"); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + prepare_test({ + "addon1@tests.mozilla.org": [ + "onInstalling" + ] + }, [ + "onInstallStarted", + "onInstallEnded", + ], check_test_2); + install.install(); }); - }, - - onNoUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoUpdateAvailable notification"); } }, AddonManager.UPDATE_WHEN_USER_REQUESTED); - }); -} + }; -function check_test_1(install) { - ensure_test_completed(); - do_check_eq(install.state, AddonManager.STATE_DOWNLOADED); - run_test_2(install); - return false; -} + check_test_2 = () => { + ensure_test_completed(); -// Continue installing the update. -function run_test_2(install) { - // Verify that another update check returns no new update - install.existingAddon.findUpdates({ - onNoCompatibilityUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoCompatibilityUpdateAvailable notification"); - }, + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(olda1) { + do_check_neq(olda1, null); + do_check_eq(olda1.version, "1.0"); + do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); - onUpdateAvailable: function(addon, install) { - do_throw("Should find no available update when one is already downloading"); - }, + shutdownManager(); - onNoUpdateAvailable: function(addon) { - AddonManager.getAllInstalls(function(aInstalls) { - do_check_eq(aInstalls.length, 1); - do_check_eq(aInstalls[0], install); + startupManager(); - prepare_test({ - "addon1@tests.mozilla.org": [ - "onInstalling" - ] - }, [ - "onInstallStarted", - "onInstallEnded", - ], check_test_2); - install.install(); + do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); + + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + do_check_neq(a1, null); + do_check_eq(a1.version, "2.0"); + do_check_true(isExtensionInAddonsList(profileDir, a1.id)); + do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DISABLE); + do_check_eq(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); + + a1.uninstall(); + run_next_test(); }); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); -} + })); + }; -function check_test_2() { - ensure_test_completed(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(olda1) { - do_check_neq(olda1, null); - do_check_eq(olda1.version, "1.0"); - do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); + // Check that an update check finds compatibility updates and applies them + let check_test_3; + add_test(function run_test_3() { + restartManager(); - shutdownManager(); + AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { + do_check_neq(a2, null); + do_check_false(a2.isActive); + do_check_false(a2.isCompatible); + do_check_true(a2.appDisabled); + do_check_true(a2.isCompatibleWith("0", "0")); - startupManager(); + a2.findUpdates({ + onCompatibilityUpdateAvailable: function(addon) { + do_check_true(a2.isCompatible); + do_check_false(a2.appDisabled); + do_check_false(a2.isActive); + }, - do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); + onUpdateAvailable: function(addon, install) { + ok(false, "Should not have seen an available update"); + }, + onNoUpdateAvailable: function(addon) { + do_check_eq(addon, a2); + do_execute_soon(check_test_3); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); + }); + + check_test_3 = () => { + restartManager(); + AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { + do_check_neq(a2, null); + do_check_true(a2.isActive); + do_check_true(a2.isCompatible); + do_check_false(a2.appDisabled); + a2.uninstall(); + + run_next_test(); + }); + } + + // Checks that we see no compatibility information when there is none. + add_test(function run_test_4() { + AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { + do_check_neq(a3, null); + do_check_false(a3.isActive); + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + do_check_true(a3.isCompatibleWith("5", "5")); + do_check_false(a3.isCompatibleWith("2", "2")); + + a3.findUpdates({ + sawUpdate: false, + onCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should not have seen compatibility information"); + }, + + onNoCompatibilityUpdateAvailable: function(addon) { + this.sawUpdate = true; + }, + + onUpdateAvailable: function(addon, install) { + ok(false, "Should not have seen an available update"); + }, + + onNoUpdateAvailable: function(addon) { + do_check_true(this.sawUpdate); + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); + }); + + // Checks that compatibility info for future apps are detected but don't make + // the item compatibile. + let check_test_5; + add_test(function run_test_5() { + AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { + do_check_neq(a3, null); + do_check_false(a3.isActive); + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + do_check_true(a3.isCompatibleWith("5", "5")); + do_check_false(a3.isCompatibleWith("2", "2")); + + a3.findUpdates({ + sawUpdate: false, + onCompatibilityUpdateAvailable: function(addon) { + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + do_check_false(a3.isActive); + this.sawUpdate = true; + }, + + onNoCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should have seen some compatibility information"); + }, + + onUpdateAvailable: function(addon, install) { + ok(false, "Should not have seen an available update"); + }, + + onNoUpdateAvailable: function(addon) { + do_check_true(this.sawUpdate); + do_execute_soon(check_test_5); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED, "3.0", "3.0"); + }); + }); + + check_test_5 = () => { + restartManager(); + AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { + do_check_neq(a3, null); + do_check_false(a3.isActive); + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + + a3.uninstall(); + run_next_test(); + }); + } + + // Test that background update checks work + let continue_test_6; + add_test(function run_test_6() { + restartManager(); + + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + restartManager(); + + prepare_test({}, [ + "onNewInstall", + "onDownloadStarted", + "onDownloadEnded" + ], continue_test_6); + + AddonManagerInternal.backgroundUpdateCheck(); + }); + + let check_test_6; + continue_test_6 = (install) => { + do_check_neq(install.existingAddon, null); + do_check_eq(install.existingAddon.id, "addon1@tests.mozilla.org"); + + prepare_test({ + "addon1@tests.mozilla.org": [ + "onInstalling" + ] + }, [ + "onInstallStarted", + "onInstallEnded", + ], callback_soon(check_test_6)); + } + + check_test_6 = (install) => { + do_check_eq(install.existingAddon.pendingUpgrade.install, install); + + restartManager(); AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { do_check_neq(a1, null); do_check_eq(a1.version, "2.0"); - do_check_true(isExtensionInAddonsList(profileDir, a1.id)); - do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DISABLE); do_check_eq(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); - a1.uninstall(); - do_execute_soon(run_test_3); + run_next_test(); }); - })); -} + } + // Verify the parameter escaping in update urls. + add_test(function run_test_8() { + restartManager(); -// Check that an update check finds compatibility updates and applies them -function run_test_3() { - restartManager(); + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "5.0", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "2" + }], + name: "Test Addon 1", + }, profileDir); - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { - do_check_neq(a2, null); - do_check_false(a2.isActive); - do_check_false(a2.isCompatible); - do_check_true(a2.appDisabled); - do_check_true(a2.isCompatibleWith("0")); + writeInstallRDFForExtension({ + id: "addon2@tests.mozilla.org", + version: "67.0.5b1", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: "toolkit@mozilla.org", + minVersion: "0", + maxVersion: "3" + }], + name: "Test Addon 2", + }, profileDir); - a2.findUpdates({ - onCompatibilityUpdateAvailable: function(addon) { - do_check_true(a2.isCompatible); - do_check_false(a2.appDisabled); - do_check_false(a2.isActive); + writeInstallRDFForExtension({ + id: "addon3@tests.mozilla.org", + version: "1.3+", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "0", + maxVersion: "0" + }, { + id: "toolkit@mozilla.org", + minVersion: "0", + maxVersion: "3" + }], + name: "Test Addon 3", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon4@tests.mozilla.org", + version: "0.5ab6", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "5" + }], + name: "Test Addon 4", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon5@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 5", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon6@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 6", + }, profileDir); + + restartManager(); + + AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(a2) { + a2.userDisabled = true; + restartManager(); + + testserver.registerPathHandler("/data/param_test.rdf", function(request, response) { + do_check_neq(request.queryString, ""); + let [req_version, item_id, item_version, + item_maxappversion, item_status, + app_id, app_version, current_app_version, + app_os, app_abi, app_locale, update_type] = + request.queryString.split("/").map(a => decodeURIComponent(a)); + + do_check_eq(req_version, "2"); + + switch(item_id) { + case "addon1@tests.mozilla.org": + do_check_eq(item_version, "5.0"); + do_check_eq(item_maxappversion, "2"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "97"); + break; + case "addon2@tests.mozilla.org": + do_check_eq(item_version, "67.0.5b1"); + do_check_eq(item_maxappversion, "3"); + do_check_eq(item_status, "userDisabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "49"); + break; + case "addon3@tests.mozilla.org": + do_check_eq(item_version, "1.3+"); + do_check_eq(item_maxappversion, "0"); + do_check_eq(item_status, "userEnabled,incompatible"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "112"); + break; + case "addon4@tests.mozilla.org": + do_check_eq(item_version, "0.5ab6"); + do_check_eq(item_maxappversion, "5"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "2"); + do_check_eq(update_type, "98"); + break; + case "addon5@tests.mozilla.org": + do_check_eq(item_version, "1.0"); + do_check_eq(item_maxappversion, "1"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "35"); + break; + case "addon6@tests.mozilla.org": + do_check_eq(item_version, "1.0"); + do_check_eq(item_maxappversion, "1"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "99"); + break; + default: + ok(false, "Update request for unexpected add-on " + item_id); + } + + do_check_eq(app_id, "xpcshell@tests.mozilla.org"); + do_check_eq(current_app_version, "1"); + do_check_eq(app_os, "XPCShell"); + do_check_eq(app_abi, "noarch-spidermonkey"); + do_check_eq(app_locale, "fr-FR"); + + request.setStatusLine(null, 500, "Server Error"); + }); + + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon2@tests.mozilla.org", + "addon3@tests.mozilla.org", + "addon4@tests.mozilla.org", + "addon5@tests.mozilla.org", + "addon6@tests.mozilla.org"], + function([a1, a2, a3, a4, a5, a6]) { + let count = 6; + + function next_test() { + a1.uninstall(); + a2.uninstall(); + a3.uninstall(); + a4.uninstall(); + a5.uninstall(); + a6.uninstall(); + + restartManager(); + run_next_test(); + } + + let compatListener = { + onUpdateFinished: function(addon, error) { + if (--count == 0) + do_execute_soon(next_test); + } + }; + + let updateListener = { + onUpdateAvailable: function(addon, update) { + // Dummy so the update checker knows we care about new versions + }, + + onUpdateFinished: function(addon, error) { + if (--count == 0) + do_execute_soon(next_test); + } + }; + + a1.findUpdates(updateListener, AddonManager.UPDATE_WHEN_USER_REQUESTED); + a2.findUpdates(compatListener, AddonManager.UPDATE_WHEN_ADDON_INSTALLED); + a3.findUpdates(updateListener, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); + a4.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "2"); + a5.findUpdates(compatListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); + a6.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); + }); + })); + }); + + // Tests that if an install.rdf claims compatibility then the add-on will be + // seen as compatible regardless of what the update.rdf says. + add_test(function run_test_9() { + writeInstallRDFForExtension({ + id: "addon4@tests.mozilla.org", + version: "5.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + + restartManager(); + + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + do_check_true(a4.isActive, "addon4 is active"); + do_check_true(a4.isCompatible, "addon4 is compatible"); + + run_next_test(); + }); + }); + + // Tests that a normal update check won't decrease a targetApplication's + // maxVersion. + add_test(function run_test_10() { + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + a4.findUpdates({ + onUpdateFinished: function(addon) { + do_check_true(addon.isCompatible, "addon4 is compatible"); + + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); + }); + }); + + // Tests that an update check for a new application will decrease a + // targetApplication's maxVersion. + add_test(function run_test_11() { + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + a4.findUpdates({ + onUpdateFinished: function(addon) { + do_check_false(addon.isCompatible, "addon4 is compatible"); + + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); + }); + }); + + // Check that the decreased maxVersion applied and disables the add-on + add_test(function run_test_12() { + restartManager(); + + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + do_check_false(a4.isActive, "addon4 is active"); + do_check_false(a4.isCompatible, "addon4 is compatible"); + + a4.uninstall(); + run_next_test(); + }); + }); + + // Tests that no compatibility update is passed to the listener when there is + // compatibility info for the current version of the app but not for the + // version of the app that the caller requested an update check for. + let check_test_13; + add_test(function run_test_13() { + restartManager(); + + // Not initially compatible but the update check will make it compatible + writeInstallRDFForExtension({ + id: "addon7@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0", + maxVersion: "0" + }], + name: "Test Addon 7", + }, profileDir); + restartManager(); + + AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { + do_check_neq(a7, null); + do_check_false(a7.isActive); + do_check_false(a7.isCompatible); + do_check_true(a7.appDisabled); + do_check_true(a7.isCompatibleWith("0", "0")); + + a7.findUpdates({ + sawUpdate: false, + onCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should not have seen compatibility information"); + }, + + onUpdateAvailable: function(addon, install) { + ok(false, "Should not have seen an available update"); + }, + + onUpdateFinished: function(addon) { + do_check_true(addon.isCompatible); + do_execute_soon(check_test_13); + } + }, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "3.0", "3.0"); + }); + }); + + check_test_13 = () => { + restartManager(); + AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { + do_check_neq(a7, null); + do_check_true(a7.isActive); + do_check_true(a7.isCompatible); + do_check_false(a7.appDisabled); + + a7.uninstall(); + run_next_test(); + }); + } + + // Test that background update checks doesn't update an add-on that isn't + // allowed to update automatically. + let check_test_14; + add_test(function run_test_14() { + restartManager(); + + // Have an add-on there that will be updated so we see some events from it + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon8@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 8", + }, profileDir); + restartManager(); + + AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { + a8.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + + // The background update check will find updates for both add-ons but only + // proceed to install one of them. + AddonManager.addInstallListener({ + onNewInstall: function(aInstall) { + let id = aInstall.existingAddon.id; + ok((id == "addon1@tests.mozilla.org" || id == "addon8@tests.mozilla.org"), + "Saw unexpected onNewInstall for " + id); + }, + + onDownloadStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadFailed: function(aInstall) { + ok(false, "Should not have seen onDownloadFailed event"); + }, + + onDownloadCancelled: function(aInstall) { + ok(false, "Should not have seen onDownloadCancelled event"); + }, + + onInstallStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onInstallEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + do_check_eq(aInstall.existingAddon.pendingUpgrade.install, aInstall); + + do_execute_soon(check_test_14); + }, + + onInstallFailed: function(aInstall) { + ok(false, "Should not have seen onInstallFailed event"); + }, + + onInstallCancelled: function(aInstall) { + ok(false, "Should not have seen onInstallCancelled event"); + }, + }); + + AddonManagerInternal.backgroundUpdateCheck(); + }); + }); + + check_test_14 = () => { + restartManager(); + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon8@tests.mozilla.org"], function([a1, a8]) { + do_check_neq(a1, null); + do_check_eq(a1.version, "2.0"); + a1.uninstall(); + + do_check_neq(a8, null); + do_check_eq(a8.version, "1.0"); + a8.uninstall(); + + run_next_test(); + }); + } + + // Test that background update checks doesn't update an add-on that is + // pending uninstall + let check_test_15; + add_test(function run_test_15() { + restartManager(); + + // Have an add-on there that will be updated so we see some events from it + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon8@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 8", + }, profileDir); + restartManager(); + + AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { + a8.uninstall(); + do_check_false(hasFlag(a8.permissions, AddonManager.PERM_CAN_UPGRADE)); + + // The background update check will find updates for both add-ons but only + // proceed to install one of them. + AddonManager.addInstallListener({ + onNewInstall: function(aInstall) { + let id = aInstall.existingAddon.id; + ok((id == "addon1@tests.mozilla.org" || id == "addon8@tests.mozilla.org"), + "Saw unexpected onNewInstall for " + id); + }, + + onDownloadStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadFailed: function(aInstall) { + ok(false, "Should not have seen onDownloadFailed event"); + }, + + onDownloadCancelled: function(aInstall) { + ok(false, "Should not have seen onDownloadCancelled event"); + }, + + onInstallStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onInstallEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + do_execute_soon(check_test_15); + }, + + onInstallFailed: function(aInstall) { + ok(false, "Should not have seen onInstallFailed event"); + }, + + onInstallCancelled: function(aInstall) { + ok(false, "Should not have seen onInstallCancelled event"); + }, + }); + + AddonManagerInternal.backgroundUpdateCheck(); + }); + }); + + check_test_15 = () => { + restartManager(); + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon8@tests.mozilla.org"], function([a1, a8]) { + do_check_neq(a1, null); + do_check_eq(a1.version, "2.0"); + a1.uninstall(); + + do_check_eq(a8, null); + + run_next_test(); + }); + } + + // Test that the update check correctly observes the + // extensions.strictCompatibility pref and compatibility overrides. + add_test(function run_test_17() { + restartManager(); + + writeInstallRDFForExtension({ + id: "addon9@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 9", + }, profileDir); + restartManager(); + + AddonManager.addInstallListener({ + onNewInstall: function(aInstall) { + equal(aInstall.existingAddon.id, "addon9@tests.mozilla.org", + "Saw unexpected onNewInstall for " + aInstall.existingAddon.id); + do_check_eq(aInstall.version, "2.0"); }, - - onUpdateAvailable: function(addon, install) { - do_throw("Should not have seen an available update"); - }, - - onNoUpdateAvailable: function(addon) { - do_check_eq(addon, a2); - do_execute_soon(check_test_3); + onDownloadFailed: function(aInstall) { + AddonManager.getAddonByID("addon9@tests.mozilla.org", function(a9) { + a9.uninstall(); + run_next_test(); + }); } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); + + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, + "http://localhost:" + gPort + "/data/test_update.xml"); + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS_PERFORMANCE, + "http://localhost:" + gPort + "/data/test_update.xml"); + Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); + + AddonManagerInternal.backgroundUpdateCheck(); }); -} -function check_test_3() { - restartManager(); - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { - do_check_neq(a2, null); - do_check_true(a2.isActive); - do_check_true(a2.isCompatible); - do_check_false(a2.appDisabled); - a2.uninstall(); + // Test that the update check correctly observes when an addon opts-in to + // strict compatibility checking. + add_test(function run_test_19() { + restartManager(); + writeInstallRDFForExtension({ + id: "addon11@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 11", + }, profileDir); + restartManager(); - run_test_4(); + AddonManager.getAddonByID("addon11@tests.mozilla.org", function(a11) { + do_check_neq(a11, null); + + a11.findUpdates({ + onCompatibilityUpdateAvailable: function() { + ok(false, "Should have not have seen compatibility information"); + }, + + onUpdateAvailable: function() { + ok(false, "Should not have seen an available update"); + }, + + onUpdateFinished: function() { + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); }); -} -// Checks that we see no compatibility information when there is none. -function run_test_4() { - AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { - do_check_neq(a3, null); - do_check_false(a3.isActive); - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); - do_check_true(a3.isCompatibleWith("5")); - do_check_false(a3.isCompatibleWith("2")); + add_task(function cleanup() { + let addons = yield new Promise(resolve => { + AddonManager.getAddonsByTypes(["extension"], resolve); + }); - a3.findUpdates({ - sawUpdate: false, - onCompatibilityUpdateAvailable: function(addon) { - do_throw("Should not have seen compatibility information"); - }, + for (let addon of addons) + addon.uninstall(); - onNoCompatibilityUpdateAvailable: function(addon) { - this.sawUpdate = true; - }, + yield promiseRestartManager(); - onUpdateAvailable: function(addon, install) { - do_throw("Should not have seen an available update"); - }, + shutdownManager(); - onNoUpdateAvailable: function(addon) { - do_check_true(this.sawUpdate); - run_test_5(); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); - }); -} - -// Checks that compatibility info for future apps are detected but don't make -// the item compatibile. -function run_test_5() { - AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { - do_check_neq(a3, null); - do_check_false(a3.isActive); - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); - do_check_true(a3.isCompatibleWith("5")); - do_check_false(a3.isCompatibleWith("2")); - - a3.findUpdates({ - sawUpdate: false, - onCompatibilityUpdateAvailable: function(addon) { - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); - do_check_false(a3.isActive); - this.sawUpdate = true; - }, - - onNoCompatibilityUpdateAvailable: function(addon) { - do_throw("Should have seen some compatibility information"); - }, - - onUpdateAvailable: function(addon, install) { - do_throw("Should not have seen an available update"); - }, - - onNoUpdateAvailable: function(addon) { - do_check_true(this.sawUpdate); - do_execute_soon(check_test_5); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED, "3.0"); - }); -} - -function check_test_5() { - restartManager(); - AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { - do_check_neq(a3, null); - do_check_false(a3.isActive); - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); - - a3.uninstall(); - do_execute_soon(run_test_6); - }); -} - -// Test that background update checks work -function run_test_6() { - restartManager(); - - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - restartManager(); - - prepare_test({}, [ - "onNewInstall", - "onDownloadStarted", - "onDownloadEnded" - ], continue_test_6); - - AddonManagerInternal.backgroundUpdateCheck(); -} - -function continue_test_6(install) { - do_check_neq(install.existingAddon, null); - do_check_eq(install.existingAddon.id, "addon1@tests.mozilla.org"); - - prepare_test({ - "addon1@tests.mozilla.org": [ - "onInstalling" - ] - }, [ - "onInstallStarted", - "onInstallEnded", - ], callback_soon(check_test_6)); -} - -function check_test_6(install) { - do_check_eq(install.existingAddon.pendingUpgrade.install, install); - - restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { - do_check_neq(a1, null); - do_check_eq(a1.version, "2.0"); - do_check_eq(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); - a1.uninstall(); - do_execute_soon(run_test_7); + yield new Promise(do_execute_soon); }); } // Test that background update checks work for lightweight themes -function run_test_7() { - restartManager(); +add_test(function run_test_7() { + startupManager(); LightweightThemeManager.currentTheme = { id: "1", @@ -474,7 +1105,7 @@ function run_test_7() { AddonManagerInternal.backgroundUpdateCheck(); }); -} +}); function check_test_7() { AddonManager.getAddonByID("1@personas.mozilla.org", function(p1) { @@ -491,595 +1122,6 @@ function check_test_7() { gInstallDate = p1.installDate.getTime(); - do_execute_soon(run_test_8); - }); -} - -// Verify the parameter escaping in update urls. -function run_test_8() { - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "5.0", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "2" - }], - name: "Test Addon 1", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon2@tests.mozilla.org", - version: "67.0.5b1", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "toolkit@mozilla.org", - minVersion: "0", - maxVersion: "3" - }], - name: "Test Addon 2", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon3@tests.mozilla.org", - version: "1.3+", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0", - maxVersion: "0" - }, { - id: "toolkit@mozilla.org", - minVersion: "0", - maxVersion: "3" - }], - name: "Test Addon 3", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon4@tests.mozilla.org", - version: "0.5ab6", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "5" - }], - name: "Test Addon 4", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon5@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 5", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon6@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 6", - }, profileDir); - - restartManager(); - - AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(a2) { - a2.userDisabled = true; - restartManager(); - - testserver.registerPathHandler("/data/param_test.rdf", function(request, response) { - do_check_neq(request.queryString, ""); - let [req_version, item_id, item_version, - item_maxappversion, item_status, - app_id, app_version, current_app_version, - app_os, app_abi, app_locale, update_type] = - request.queryString.split("/").map(a => decodeURIComponent(a)); - - do_check_eq(req_version, "2"); - - switch(item_id) { - case "addon1@tests.mozilla.org": - do_check_eq(item_version, "5.0"); - do_check_eq(item_maxappversion, "2"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "97"); - break; - case "addon2@tests.mozilla.org": - do_check_eq(item_version, "67.0.5b1"); - do_check_eq(item_maxappversion, "3"); - do_check_eq(item_status, "userDisabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "49"); - break; - case "addon3@tests.mozilla.org": - do_check_eq(item_version, "1.3+"); - do_check_eq(item_maxappversion, "0"); - do_check_eq(item_status, "userEnabled,incompatible"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "112"); - break; - case "addon4@tests.mozilla.org": - do_check_eq(item_version, "0.5ab6"); - do_check_eq(item_maxappversion, "5"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "2"); - do_check_eq(update_type, "98"); - break; - case "addon5@tests.mozilla.org": - do_check_eq(item_version, "1.0"); - do_check_eq(item_maxappversion, "1"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "35"); - break; - case "addon6@tests.mozilla.org": - do_check_eq(item_version, "1.0"); - do_check_eq(item_maxappversion, "1"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "99"); - break; - default: - do_throw("Update request for unexpected add-on " + item_id); - } - - do_check_eq(app_id, "xpcshell@tests.mozilla.org"); - do_check_eq(current_app_version, "1"); - do_check_eq(app_os, "XPCShell"); - do_check_eq(app_abi, "noarch-spidermonkey"); - do_check_eq(app_locale, "fr-FR"); - - request.setStatusLine(null, 500, "Server Error"); - }); - - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon2@tests.mozilla.org", - "addon3@tests.mozilla.org", - "addon4@tests.mozilla.org", - "addon5@tests.mozilla.org", - "addon6@tests.mozilla.org"], - function([a1, a2, a3, a4, a5, a6]) { - let count = 6; - - function run_next_test() { - a1.uninstall(); - a2.uninstall(); - a3.uninstall(); - a4.uninstall(); - a5.uninstall(); - a6.uninstall(); - - restartManager(); - run_test_9(); - } - - let compatListener = { - onUpdateFinished: function(addon, error) { - if (--count == 0) - do_execute_soon(run_next_test); - } - }; - - let updateListener = { - onUpdateAvailable: function(addon, update) { - // Dummy so the update checker knows we care about new versions - }, - - onUpdateFinished: function(addon, error) { - if (--count == 0) - do_execute_soon(run_next_test); - } - }; - - a1.findUpdates(updateListener, AddonManager.UPDATE_WHEN_USER_REQUESTED); - a2.findUpdates(compatListener, AddonManager.UPDATE_WHEN_ADDON_INSTALLED); - a3.findUpdates(updateListener, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); - a4.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "2"); - a5.findUpdates(compatListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); - a6.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); - }); - })); -} - -// Tests that if an install.rdf claims compatibility then the add-on will be -// seen as compatible regardless of what the update.rdf says. -function run_test_9() { - writeInstallRDFForExtension({ - id: "addon4@tests.mozilla.org", - version: "5.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - restartManager(); - - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - do_check_true(a4.isActive); - do_check_true(a4.isCompatible); - - run_test_10(); - }); -} - -// Tests that a normal update check won't decrease a targetApplication's -// maxVersion. -function run_test_10() { - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - a4.findUpdates({ - onUpdateFinished: function(addon) { - do_check_true(addon.isCompatible); - - run_test_11(); - } - }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); - }); -} - -// Tests that an update check for a new application will decrease a -// targetApplication's maxVersion. -function run_test_11() { - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - a4.findUpdates({ - onUpdateFinished: function(addon) { - do_check_false(addon.isCompatible); - - do_execute_soon(run_test_12); - } - }, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); - }); -} - -// Check that the decreased maxVersion applied and disables the add-on -function run_test_12() { - restartManager(); - - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - do_check_false(a4.isActive); - do_check_false(a4.isCompatible); - - a4.uninstall(); - do_execute_soon(run_test_13); - }); -} - -// Tests that no compatibility update is passed to the listener when there is -// compatibility info for the current version of the app but not for the -// version of the app that the caller requested an update check for. -function run_test_13() { - restartManager(); - - // Not initially compatible but the update check will make it compatible - writeInstallRDFForExtension({ - id: "addon7@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0", - maxVersion: "0" - }], - name: "Test Addon 7", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { - do_check_neq(a7, null); - do_check_false(a7.isActive); - do_check_false(a7.isCompatible); - do_check_true(a7.appDisabled); - do_check_true(a7.isCompatibleWith("0")); - - a7.findUpdates({ - sawUpdate: false, - onCompatibilityUpdateAvailable: function(addon) { - do_throw("Should have not have seen compatibility information"); - }, - - onUpdateAvailable: function(addon, install) { - do_throw("Should not have seen an available update"); - }, - - onUpdateFinished: function(addon) { - do_check_true(addon.isCompatible); - do_execute_soon(check_test_13); - } - }, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "3.0"); - }); -} - -function check_test_13() { - restartManager(); - AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { - do_check_neq(a7, null); - do_check_true(a7.isActive); - do_check_true(a7.isCompatible); - do_check_false(a7.appDisabled); - - a7.uninstall(); - do_execute_soon(run_test_14); - }); -} - -// Test that background update checks doesn't update an add-on that isn't -// allowed to update automatically. -function run_test_14() { - restartManager(); - - // Have an add-on there that will be updated so we see some events from it - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon8@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 8", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { - a8.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; - - // The background update check will find updates for both add-ons but only - // proceed to install one of them. - AddonManager.addInstallListener({ - onNewInstall: function(aInstall) { - if (aInstall.existingAddon.id != "addon1@tests.mozilla.org" && - aInstall.existingAddon.id != "addon8@tests.mozilla.org") - do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); - }, - - onDownloadStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadFailed: function(aInstall) { - do_throw("Should not have seen onDownloadFailed event"); - }, - - onDownloadCancelled: function(aInstall) { - do_throw("Should not have seen onDownloadCancelled event"); - }, - - onInstallStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onInstallEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - do_check_eq(aInstall.existingAddon.pendingUpgrade.install, aInstall); - do_execute_soon(check_test_14); - }, - - onInstallFailed: function(aInstall) { - do_throw("Should not have seen onInstallFailed event"); - }, - - onInstallCancelled: function(aInstall) { - do_throw("Should not have seen onInstallCancelled event"); - }, - }); - - AddonManagerInternal.backgroundUpdateCheck(); - }); -} - -function check_test_14() { - restartManager(); - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon8@tests.mozilla.org"], function([a1, a8]) { - do_check_neq(a1, null); - do_check_eq(a1.version, "2.0"); - a1.uninstall(); - - do_check_neq(a8, null); - do_check_eq(a8.version, "1.0"); - a8.uninstall(); - - do_execute_soon(run_test_15); - }); -} - -// Test that background update checks doesn't update an add-on that is -// pending uninstall -function run_test_15() { - restartManager(); - - // Have an add-on there that will be updated so we see some events from it - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon8@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 8", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { - a8.uninstall(); - do_check_false(hasFlag(a8.permissions, AddonManager.PERM_CAN_UPGRADE)); - - // The background update check will find updates for both add-ons but only - // proceed to install one of them. - AddonManager.addInstallListener({ - onNewInstall: function(aInstall) { - if (aInstall.existingAddon.id != "addon1@tests.mozilla.org" && - aInstall.existingAddon.id != "addon8@tests.mozilla.org") - do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); - }, - - onDownloadStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadFailed: function(aInstall) { - do_throw("Should not have seen onDownloadFailed event"); - }, - - onDownloadCancelled: function(aInstall) { - do_throw("Should not have seen onDownloadCancelled event"); - }, - - onInstallStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onInstallEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - do_execute_soon(check_test_15); - }, - - onInstallFailed: function(aInstall) { - do_throw("Should not have seen onInstallFailed event"); - }, - - onInstallCancelled: function(aInstall) { - do_throw("Should not have seen onInstallCancelled event"); - }, - }); - - AddonManagerInternal.backgroundUpdateCheck(); - }); -} - -function check_test_15() { - restartManager(); - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon8@tests.mozilla.org"], function([a1, a8]) { - do_check_neq(a1, null); - do_check_eq(a1.version, "2.0"); - a1.uninstall(); - - do_check_eq(a8, null); - - do_execute_soon(run_test_16); - }); -} - -// Test that the update check correctly observes the -// extensions.strictCompatibility pref and compatibility overrides. -function run_test_16() { - restartManager(); - - writeInstallRDFForExtension({ - id: "addon9@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0.1", - maxVersion: "0.2" - }], - name: "Test Addon 9", - }, profileDir); - restartManager(); - - AddonManager.addInstallListener({ - onNewInstall: function(aInstall) { - if (aInstall.existingAddon.id != "addon9@tests.mozilla.org") - do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); - do_check_eq(aInstall.version, "2.0"); - }, - onDownloadFailed: function(aInstall) { - do_execute_soon(run_test_17); - } - }); - - Services.prefs.setCharPref(PREF_GETADDONS_BYIDS_PERFORMANCE, - "http://localhost:" + gPort + "/data/test_update.xml"); - Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); - - AddonManagerInternal.backgroundUpdateCheck(); -} - -// Test that the update check correctly observes when an addon opts-in to -// strict compatibility checking. -function run_test_17() { - - writeInstallRDFForExtension({ - id: "addon11@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0.1", - maxVersion: "0.2" - }], - name: "Test Addon 11", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon11@tests.mozilla.org", function(a11) { - do_check_neq(a11, null); - - a11.findUpdates({ - onCompatibilityUpdateAvailable: function() { - do_throw("Should have not have seen compatibility information"); - }, - - onUpdateAvailable: function() { - do_throw("Should not have seen an available update"); - }, - - onUpdateFinished: function() { - do_execute_soon(end_test); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + run_next_test(); }); } From 7a3e94be6135feb5687cdefc9270898965cd56a3 Mon Sep 17 00:00:00 2001 From: Wes Kocher Date: Tue, 3 Nov 2015 16:06:23 -0800 Subject: [PATCH 087/113] Backed out 2 changesets (bug 1214058) for xpcshell bustage Backed out changeset 90e625ac70b2 (bug 1214058) Backed out changeset a4d5d63a03ef (bug 1214058) --- modules/libpref/init/all.js | 1 - toolkit/mozapps/extensions/AddonManager.jsm | 16 - .../internal/AddonUpdateChecker.jsm | 248 +- .../extensions/internal/XPIProvider.jsm | 2 +- .../test/xpcshell/data/test_update.json | 215 -- .../test/xpcshell/data/test_updatecheck.json | 327 --- .../test/xpcshell/data/test_updatecheck.rdf | 2 +- .../extensions/test/xpcshell/head_addons.js | 114 +- .../xpcshell/test_AddonRepository_cache.js | 7 +- .../test/xpcshell/test_blocklistchange.js | 9 +- .../test/xpcshell/test_bug542391.js | 13 +- .../test/xpcshell/test_bug570173.js | 82 +- .../test/xpcshell/test_json_updatecheck.js | 373 --- .../test/xpcshell/test_metadata_update.js | 15 +- .../test/xpcshell/test_signed_install.js | 9 +- .../extensions/test/xpcshell/test_update.js | 2206 ++++++++--------- .../test/xpcshell/test_update_ignorecompat.js | 138 +- .../test/xpcshell/test_update_strictcompat.js | 1906 +++++++------- .../test/xpcshell/test_updatecheck.js | 464 ++-- .../test/xpcshell/xpcshell-shared.ini | 1 - 20 files changed, 2491 insertions(+), 3657 deletions(-) delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_update.json delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.json delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_json_updatecheck.js diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 001a6d9570c5..65b9b7ebbd2a 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -4402,7 +4402,6 @@ pref("xpinstall.whitelist.required", true); pref("xpinstall.signatures.required", false); pref("extensions.alwaysUnpack", false); pref("extensions.minCompatiblePlatformVersion", "2.0"); -pref("extensions.webExtensionsMinPlatformVersion", "42.0a1"); pref("network.buffer.cache.count", 24); pref("network.buffer.cache.size", 32768); diff --git a/toolkit/mozapps/extensions/AddonManager.jsm b/toolkit/mozapps/extensions/AddonManager.jsm index 184bf3b1f3cf..0864cfbec3de 100644 --- a/toolkit/mozapps/extensions/AddonManager.jsm +++ b/toolkit/mozapps/extensions/AddonManager.jsm @@ -42,8 +42,6 @@ const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS"; const PREF_SELECTED_LOCALE = "general.useragent.locale"; const UNKNOWN_XPCOM_ABI = "unknownABI"; -const PREF_MIN_WEBEXT_PLATFORM_VERSION = "extensions.webExtensionsMinPlatformVersion"; - const UPDATE_REQUEST_VERSION = 2; const CATEGORY_UPDATE_PARAMS = "extension-update-params"; @@ -665,7 +663,6 @@ var gCheckUpdateSecurity = gCheckUpdateSecurityDefault; var gUpdateEnabled = true; var gAutoUpdateDefault = true; var gHotfixID = null; -var gWebExtensionsMinPlatformVersion = null; var gShutdownBarrier = null; var gRepoShutdownState = ""; var gShutdownInProgress = false; @@ -950,11 +947,6 @@ var AddonManagerInternal = { } catch (e) {} Services.prefs.addObserver(PREF_EM_HOTFIX_ID, this, false); - try { - gWebExtensionsMinPlatformVersion = Services.prefs.getCharPref(PREF_MIN_WEBEXT_PLATFORM_VERSION); - } catch (e) {} - Services.prefs.addObserver(PREF_MIN_WEBEXT_PLATFORM_VERSION, this, false); - let defaultProvidersEnabled = true; try { defaultProvidersEnabled = Services.prefs.getBoolPref(PREF_DEFAULT_PROVIDERS_ENABLED); @@ -1385,10 +1377,6 @@ var AddonManagerInternal = { } break; } - case PREF_MIN_WEBEXT_PLATFORM_VERSION: { - gWebExtensionsMinPlatformVersion = Services.prefs.getCharPref(PREF_MIN_WEBEXT_PLATFORM_VERSION); - break; - } } }, @@ -2906,10 +2894,6 @@ this.AddonManagerPrivate = { safeCall(listener.onUpdateFinished.bind(listener), addon); } }, - - get webExtensionsMinPlatformVersion() { - return gWebExtensionsMinPlatformVersion; - }, }; /** diff --git a/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm b/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm index a567eb678de0..f6becf98fb43 100644 --- a/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm +++ b/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm @@ -31,8 +31,6 @@ Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate", - "resource://gre/modules/AddonManager.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository", "resource://gre/modules/addons/AddonRepository.jsm"); @@ -219,48 +217,6 @@ RDFSerializer.prototype = { } } -/** - * Sanitizes the update URL in an update item, as returned by - * parseRDFManifest and parseJSONManifest. Ensures that: - * - * - The URL is secure, or secured by a strong enough hash. - * - The security principal of the update manifest has permission to - * load the URL. - * - * @param aUpdate - * The update item to sanitize. - * @param aRequest - * The XMLHttpRequest used to load the manifest. - * @param aHashPattern - * The regular expression used to validate the update hash. - * @param aHashString - * The human-readable string specifying which hash functions - * are accepted. - */ -function sanitizeUpdateURL(aUpdate, aRequest, aHashPattern, aHashString) { - if (aUpdate.updateURL) { - let scriptSecurity = Services.scriptSecurityManager; - let principal = scriptSecurity.getChannelURIPrincipal(aRequest.channel); - try { - // This logs an error on failure, so no need to log it a second time - scriptSecurity.checkLoadURIStrWithPrincipal(principal, aUpdate.updateURL, - scriptSecurity.DISALLOW_SCRIPT); - } catch (e) { - delete aUpdate.updateURL; - return; - } - - if (AddonManager.checkUpdateSecurity && - !aUpdate.updateURL.startsWith("https:") && - !aHashPattern.test(aUpdate.updateHash)) { - logger.warn(`Update link ${aUpdate.updateURL} is not secure and is not verified ` + - `by a strong enough hash (needs to be ${aHashString}).`); - delete aUpdate.updateURL; - delete aUpdate.updateHash; - } - } -} - /** * Parses an RDF style update manifest into an array of update objects. * @@ -270,17 +226,10 @@ function sanitizeUpdateURL(aUpdate, aRequest, aHashPattern, aHashString) { * An optional update key for the add-on * @param aRequest * The XMLHttpRequest that has retrieved the update manifest - * @param aManifestData - * The pre-parsed manifest, as a bare XML DOM document * @return an array of update objects * @throws if the update manifest is invalid in any way */ -function parseRDFManifest(aId, aUpdateKey, aRequest, aManifestData) { - if (aManifestData.documentElement.namespaceURI != PREFIX_NS_RDF) { - throw Components.Exception("Update manifest had an unrecognised namespace: " + xml.documentElement.namespaceURI); - return; - } - +function parseRDFManifest(aId, aUpdateKey, aRequest) { function EM_R(aProp) { return gRDF.GetResource(PREFIX_NS_EM + aProp); } @@ -417,136 +366,20 @@ function parseRDFManifest(aId, aUpdateKey, aRequest, aManifestData) { targetApplications: [appEntry] }; - // The JSON update protocol requires an SHA-2 hash. RDF still - // supports SHA-1, for compatibility reasons. - sanitizeUpdateURL(result, aRequest, /^sha/, "sha1 or stronger"); - + if (result.updateURL && AddonManager.checkUpdateSecurity && + result.updateURL.substring(0, 6) != "https:" && + (!result.updateHash || result.updateHash.substring(0, 3) != "sha")) { + logger.warn("updateLink " + result.updateURL + " is not secure and is not verified" + + " by a strong enough hash (needs to be sha1 or stronger)."); + delete result.updateURL; + delete result.updateHash; + } results.push(result); } } return results; } -/** - * Parses an JSON update manifest into an array of update objects. - * - * @param aId - * The ID of the add-on being checked for updates - * @param aUpdateKey - * An optional update key for the add-on - * @param aRequest - * The XMLHttpRequest that has retrieved the update manifest - * @param aManifestData - * The pre-parsed manifest, as a JSON object tree - * @return an array of update objects - * @throws if the update manifest is invalid in any way - */ -function parseJSONManifest(aId, aUpdateKey, aRequest, aManifestData) { - if (aUpdateKey) - throw Components.Exception("Update keys are not supported for JSON update manifests"); - - let TYPE_CHECK = { - "array": val => Array.isArray(val), - "object": val => val && typeof val == "object" && !Array.isArray(val), - }; - - function getProperty(aObj, aProperty, aType, aDefault = undefined) { - if (!(aProperty in aObj)) - return aDefault; - - let value = aObj[aProperty]; - - let matchesType = aType in TYPE_CHECK ? TYPE_CHECK[aType](value) : typeof value == aType; - if (!matchesType) - throw Components.Exception(`Update manifest property '${aProperty}' has incorrect type (expected ${aType})`); - - return value; - } - - function getRequiredProperty(aObj, aProperty, aType) { - let value = getProperty(aObj, aProperty, aType); - if (value === undefined) - throw Components.Exception(`Update manifest is missing a required ${aProperty} property.`); - return value; - } - - let manifest = aManifestData; - - if (!TYPE_CHECK["object"](manifest)) - throw Components.Exception("Root element of update manifest must be a JSON object literal"); - - // The set of add-ons this manifest has updates for - let addons = getRequiredProperty(manifest, "addons", "object"); - - // The entry for this particular add-on - let addon = getProperty(addons, aId, "object"); - - // A missing entry doesn't count as a failure, just as no avialable update - // information - if (!addon) { - logger.warn("Update manifest did not contain an entry for " + aId); - return []; - } - - // The list of available updates - let updates = getProperty(addon, "updates", "array", []); - - let results = []; - - for (let update of updates) { - let version = getRequiredProperty(update, "version", "string"); - - logger.debug(`Found an update entry for ${aId} version ${version}`); - - let applications = getProperty(update, "applications", "object", - { gecko: {} }); - - // "gecko" is currently the only supported application entry. If - // it's missing, skip this update. - if (!("gecko" in applications)) - continue; - - let app = getProperty(applications, "gecko", "object"); - - let appEntry = { - id: TOOLKIT_ID, - minVersion: getProperty(app, "strict_min_version", "string", - AddonManagerPrivate.webExtensionsMinPlatformVersion), - maxVersion: "*", - }; - - let result = { - id: aId, - version: version, - multiprocessCompatible: getProperty(update, "multiprocess_compatible", "boolean", true), - updateURL: getProperty(update, "update_link", "string"), - updateHash: getProperty(update, "update_hash", "string"), - updateInfoURL: getProperty(update, "update_info_url", "string"), - strictCompatibility: false, - targetApplications: [appEntry], - }; - - if ("strict_max_version" in app) { - if ("advisory_max_version" in app) { - logger.warn("Ignoring 'advisory_max_version' update manifest property for " + - aId + " property since 'strict_max_version' also present"); - } - - appEntry.maxVersion = getProperty(app, "strict_max_version", "string"); - result.strictCompatibility = appEntry.maxVersion != "*"; - } else if ("advisory_max_version" in app) { - appEntry.maxVersion = getProperty(app, "advisory_max_version", "string"); - } - - // The JSON update protocol requires an SHA-2 hash. RDF still - // supports SHA-1, for compatibility reasons. - sanitizeUpdateURL(result, aRequest, /^sha(256|512):/, "sha256 or sha512"); - - results.push(result); - } - return results; -} - /** * Starts downloading an update manifest and then passes it to an appropriate * parser to convert to an array of update objects @@ -582,7 +415,7 @@ function UpdateParser(aId, aUpdateKey, aUrl, aObserver) { this.request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE; // Prevent the request from writing to cache. this.request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING; - this.request.overrideMimeType("text/plain"); + this.request.overrideMimeType("text/xml"); this.request.setRequestHeader("Moz-XPI-Update", "1", true); this.request.timeout = TIMEOUT; var self = this; @@ -641,50 +474,41 @@ UpdateParser.prototype = { return; } - // Detect the manifest type by first attempting to parse it as - // JSON, and falling back to parsing it as XML if that fails. - let parser; - try { - try { - let json = JSON.parse(request.responseText); - - parser = () => parseJSONManifest(this.id, this.updateKey, request, json); - } catch (e if e instanceof SyntaxError) { - let domParser = Cc["@mozilla.org/xmlextras/domparser;1"].createInstance(Ci.nsIDOMParser); - let xml = domParser.parseFromString(request.responseText, "text/xml"); - - if (xml.documentElement.namespaceURI == XMLURI_PARSE_ERROR) - throw new Error("Update manifest was not valid XML or JSON"); - - parser = () => parseRDFManifest(this.id, this.updateKey, request, xml); - } - } catch (e) { - logger.warn("onUpdateCheckComplete failed to determine manifest type"); - this.notifyError(AddonUpdateChecker.ERROR_UNKNOWN_FORMAT); - return; - } - - let results; - try { - results = parser(); - } - catch (e) { - logger.warn("onUpdateCheckComplete failed to parse update manifest", e); + let xml = request.responseXML; + if (!xml || xml.documentElement.namespaceURI == XMLURI_PARSE_ERROR) { + logger.warn("Update manifest was not valid XML"); this.notifyError(AddonUpdateChecker.ERROR_PARSE_ERROR); return; } - if ("onUpdateCheckComplete" in this.observer) { + // We currently only know about RDF update manifests + if (xml.documentElement.namespaceURI == PREFIX_NS_RDF) { + let results = null; + try { - this.observer.onUpdateCheckComplete(results); + results = parseRDFManifest(this.id, this.updateKey, request); } catch (e) { - logger.warn("onUpdateCheckComplete notification failed", e); + logger.warn("onUpdateCheckComplete failed to parse RDF manifest", e); + this.notifyError(AddonUpdateChecker.ERROR_PARSE_ERROR); + return; } + if ("onUpdateCheckComplete" in this.observer) { + try { + this.observer.onUpdateCheckComplete(results); + } + catch (e) { + logger.warn("onUpdateCheckComplete notification failed", e); + } + } + else { + logger.warn("onUpdateCheckComplete may not properly cancel", new Error("stack marker")); + } + return; } - else { - logger.warn("onUpdateCheckComplete may not properly cancel", new Error("stack marker")); - } + + logger.warn("Update manifest had an unrecognised namespace: " + xml.documentElement.namespaceURI); + this.notifyError(AddonUpdateChecker.ERROR_UNKNOWN_FORMAT); }, /** diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index 6a8a8a9d0647..0d5ed412f3e8 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -906,7 +906,7 @@ function loadManifestFromWebManifest(aStream) { addon.targetApplications = [{ id: TOOLKIT_ID, - minVersion: AddonManagerPrivate.webExtensionsMinPlatformVersion, + minVersion: "42a1", maxVersion: "*", }]; diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_update.json b/toolkit/mozapps/extensions/test/xpcshell/data/test_update.json deleted file mode 100644 index 027a9b2333f8..000000000000 --- a/toolkit/mozapps/extensions/test/xpcshell/data/test_update.json +++ /dev/null @@ -1,215 +0,0 @@ -{ - "addons": { - "addon1@tests.mozilla.org": { - "updates": [ - { - "version": "1.0", - "applications": { - "gecko": { - "strict_min_version": "1", - "strict_min_version": "1" - } - } - }, - { - "version": "1.0", - "applications": { - "gecko": { - "strict_min_version": "2", - "strict_min_version": "2" - } - } - }, - { - "version": "2.0", - "update_link": "http://localhost:%PORT%/addons/test_update.xpi", - "update_info_url": "http://example.com/updateInfo.xhtml", - "applications": { - "gecko": { - "strict_min_version": "1", - "strict_min_version": "1" - } - } - } - ] - }, - - "addon2@tests.mozilla.org": { - "updates": [ - { - "version": "1.0", - "applications": { - "gecko": { - "strict_min_version": "0", - "advisory_max_version": "1" - } - } - } - ] - }, - - "addon2@tests.mozilla.org": { - "updates": [ - { - "version": "1.0", - "applications": { - "gecko": { - "strict_min_version": "0", - "advisory_max_version": "1" - } - } - } - ] - }, - - "addon3@tests.mozilla.org": { - "updates": [ - { - "version": "1.0", - "applications": { - "gecko": { - "strict_min_version": "3", - "advisory_max_version": "3" - } - } - } - ] - }, - - "addon4@tests.mozilla.org": { - "updates": [ - { - "version": "5.0", - "applications": { - "gecko": { - "strict_min_version": "0", - "advisory_max_version": "0" - } - } - } - ] - }, - - "addon7@tests.mozilla.org": { - "updates": [ - { - "version": "1.0", - "applications": { - "gecko": { - "strict_min_version": "0", - "advisory_max_version": "1" - } - } - } - ] - }, - - "addon8@tests.mozilla.org": { - "updates": [ - { - "version": "2.0", - "update_link": "http://localhost:%PORT%/addons/test_update8.xpi", - "applications": { - "gecko": { - "strict_min_version": "1", - "advisory_max_version": "1" - } - } - } - ] - }, - - "addon9@tests.mozilla.org": { - "updates": [ - { - "version": "2.0", - "update_link": "http://localhost:%PORT%/addons/test_update9_2.xpi", - "applications": { - "gecko": { - "strict_min_version": "1", - "advisory_max_version": "1" - } - } - }, - { - "_comment_": "Incompatible when strict compatibility is enabled", - "version": "3.0", - "update_link": "http://localhost:%PORT%/addons/test_update9_3.xpi", - "applications": { - "gecko": { - "strict_min_version": "0.9", - "advisory_max_version": "0.9" - } - } - }, - { - "_comment_": "Incompatible due to compatibility override", - "version": "4.0", - "update_link": "http://localhost:%PORT%/addons/test_update9_4.xpi", - "applications": { - "gecko": { - "strict_min_version": "0.9", - "advisory_max_version": "0.9" - } - } - }, - { - "_comment_": "Addon for future version of app", - "version": "4.0", - "update_link": "http://localhost:%PORT%/addons/test_update9_5.xpi", - "applications": { - "gecko": { - "strict_min_version": "5", - "advisory_max_version": "6" - } - } - } - ] - }, - - "addon10@tests.mozilla.org": { - "updates": [ - { - "version": "1.0", - "update_link": "http://localhost:%PORT%/addons/test_update10.xpi", - "applications": { - "gecko": { - "strict_min_version": "0.1", - "advisory_max_version": "0.4" - } - } - } - ] - }, - - "addon11@tests.mozilla.org": { - "updates": [ - { - "version": "2.0", - "update_link": "http://localhost:%PORT%/addons/test_update11.xpi", - "applications": { - "gecko": { - "strict_min_version": "0.1", - "strict_max_version": "0.2" - } - } - } - ] - }, - - "addon12@tests.mozilla.org": { - "updates": [ - { - "version": "2.0", - "update_link": "http://localhost:%PORT%/addons/test_update12.xpi", - "applications": { - "gecko": { - "strict_min_version": "1", - "advisory_max_version": "1" - } - } - } - ] - } - } -} diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.json b/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.json deleted file mode 100644 index 811e50158ea8..000000000000 --- a/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.json +++ /dev/null @@ -1,327 +0,0 @@ -{ - "addons": { - "updatecheck1@tests.mozilla.org": { - "updates": [ - { - "version": "1.0", - "update_link": "https://localhost:4444/addons/test1.xpi", - "applications": { - "gecko": { - "strict_min_version": "1", - "strict_max_version": "1" - } - } - }, - { - "_comment_": "This update is incompatible and so should not be considered a valid update", - "version": "2.0", - "update_link": "https://localhost:4444/addons/test2.xpi", - "applications": { - "gecko": { - "strict_min_version": "2", - "strict_max_version": "2" - } - } - }, - { - "version": "3.0", - "update_link": "https://localhost:4444/addons/test3.xpi", - "applications": { - "gecko": { - "strict_min_version": "1", - "strict_max_version": "1" - } - } - }, - { - "version": "2.0", - "update_link": "https://localhost:4444/addons/test2.xpi", - "applications": { - "gecko": { - "strict_min_version": "1", - "strict_max_version": "2" - } - } - }, - { - "_comment_": "This update is incompatible and so should not be considered a valid update", - "version": "4.0", - "update_link": "https://localhost:4444/addons/test4.xpi", - "applications": { - "gecko": { - "strict_min_version": "2", - "strict_max_version": "2" - } - } - } - ] - }, - - "test_bug378216_5@tests.mozilla.org": { - "_comment_": "An update which expects a signature. It will fail since signatures are ", - "_comment_": "supported in this format.", - "_comment_": "The updateLink will also be ignored since it is not secure and there ", - "_comment_": "is no updateHash.", - - "updates": [ - { - "version": "2.0", - "update_link": "http://localhost:4444/broken.xpi", - "applications": { - "gecko": { - "strict_min_version": "1", - "strict_max_version": "1" - } - } - } - ] - }, - - "test_bug378216_5@tests.mozilla.org": { - "updates": [ - { - "version": "2.0", - "update_link": "http://localhost:4444/broken.xpi", - "applications": { - "gecko": { - "strict_min_version": "1", - "strict_max_version": "1" - } - } - } - ] - }, - - "test_bug378216_7@tests.mozilla.org": { - "_comment_": "An update which expects a signature. It will fail since signatures are ", - "_comment_": "supported in this format.", - "_comment_": "The updateLink will also be ignored since it is not secure ", - "_comment_": "and there is no updateHash.", - - "updates": [ - { - "version": "2.0", - "update_link": "http://localhost:4444/broken.xpi", - "applications": { - "gecko": { - "strict_min_version": "1", - "strict_max_version": "2" - } - } - } - ] - }, - - "test_bug378216_8@tests.mozilla.org": { - "_comment_": "The updateLink will be ignored since it is not secure and ", - "_comment_": "there is no updateHash.", - - "updates": [ - { - "version": "2.0", - "update_link": "http://localhost:4444/broken.xpi", - "applications": { - "gecko": { - "strict_min_version": "1", - "strict_max_version": "1" - } - } - } - ] - }, - - "test_bug378216_9@tests.mozilla.org": { - "_comment_": "The updateLink will used since there is an updateHash to verify it.", - - "updates": [ - { - "version": "2.0", - "update_link": "http://localhost:4444/broken.xpi", - "update_hash": "sha256:78fc1d2887eda35b4ad2e3a0b60120ca271ce6e6", - "applications": { - "gecko": { - "strict_min_version": "1", - "strict_max_version": "1" - } - } - } - ] - }, - - "test_bug378216_10@tests.mozilla.org": { - "_comment_": "The updateLink will used since it is a secure URL.", - - "updates": [ - { - "version": "2.0", - "update_link": "https://localhost:4444/broken.xpi", - "applications": { - "gecko": { - "strict_min_version": "1", - "strict_max_version": "1" - } - } - } - ] - }, - - "test_bug378216_11@tests.mozilla.org": { - "_comment_": "The updateLink will used since it is a secure URL.", - - "updates": [ - { - "version": "2.0", - "update_link": "https://localhost:4444/broken.xpi", - "applications": { - "gecko": { - "strict_min_version": "1", - "strict_max_version": "1" - } - } - } - ] - }, - - "test_bug378216_12@tests.mozilla.org": { - "_comment_": "The updateLink will not be used since the updateHash ", - "_comment_": "verifying it is not strong enough.", - - "updates": [ - { - "version": "2.0", - "update_link": "http://localhost:4444/broken.xpi", - "update_hash": "sha1:78fc1d2887eda35b4ad2e3a0b60120ca271ce6e6", - "applications": { - "gecko": { - "strict_min_version": "1", - "strict_max_version": "1" - } - } - } - ] - }, - - "test_bug378216_13@tests.mozilla.org": { - "_comment_": "An update with a weak hash. The updateLink will used since it is ", - "_comment_": "a secure URL.", - - "updates": [ - { - "version": "2.0", - "update_link": "https://localhost:4444/broken.xpi", - "update_hash": "sha1:78fc1d2887eda35b4ad2e3a0b60120ca271ce6e6", - "applications": { - "gecko": { - "strict_min_version": "1", - "strict_max_version": "1" - } - } - } - ] - }, - - "_comment_": "There should be no information present for test_bug378216_14", - - "test_bug378216_15@tests.mozilla.org": { - "_comment_": "Invalid update JSON", - - "updates": "foo" - }, - - "ignore-compat@tests.mozilla.org": { - "_comment_": "Various updates available - one is not compatible, but compatibility checking is disabled", - - "updates": [ - { - "version": "1.0", - "update_link": "https://localhost:4444/addons/test1.xpi", - "applications": { - "gecko": { - "strict_min_version": "0.1", - "advisory_max_version": "0.2" - } - } - }, - { - "version": "2.0", - "update_link": "https://localhost:4444/addons/test2.xpi", - "applications": { - "gecko": { - "strict_min_version": "0.5", - "advisory_max_version": "0.6" - } - } - }, - { - "_comment_": "Update for future app versions - should never be compatible", - "version": "3.0", - "update_link": "https://localhost:4444/addons/test3.xpi", - "applications": { - "gecko": { - "strict_min_version": "2", - "advisory_max_version": "3" - } - } - } - ] - }, - - "compat-override@tests.mozilla.org": { - "_comment_": "Various updates available - one is not compatible, but compatibility checking is disabled", - - "updates": [ - { - "_comment_": "Has compatibility override, but it doesn't match this app version", - "version": "1.0", - "update_link": "https://localhost:4444/addons/test1.xpi", - "applications": { - "gecko": { - "strict_min_version": "0.1", - "advisory_max_version": "0.2" - } - } - }, - { - "_comment_": "Has compatibility override, so is incompaible", - "version": "2.0", - "update_link": "https://localhost:4444/addons/test2.xpi", - "applications": { - "gecko": { - "strict_min_version": "0.5", - "advisory_max_version": "0.6" - } - } - }, - { - "_comment_": "Update for future app versions - should never be compatible", - "version": "3.0", - "update_link": "https://localhost:4444/addons/test3.xpi", - "applications": { - "gecko": { - "strict_min_version": "2", - "advisory_max_version": "3" - } - } - } - ] - }, - - "compat-strict-optin@tests.mozilla.org": { - "_comment_": "Opt-in to strict compatibility checking", - - "updates": [ - { - "version": "1.0", - "update_link": "https://localhost:4444/addons/test1.xpi", - "_comment_": "strictCompatibility: true", - "applications": { - "gecko": { - "strict_min_version": "0.1", - "strict_max_version": "0.2" - } - } - } - ] - } - } -} diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.rdf b/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.rdf index c5d97ada0d53..93c82886a65b 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.rdf +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.rdf @@ -236,7 +236,7 @@ A90eF5zy - diff --git a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js index 27bca41f0f26..a5e6ba3d1a37 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js +++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js @@ -1542,7 +1542,7 @@ if ("nsIWindowsRegKey" in AM_Ci) { * This is a mock nsIWindowsRegistry implementation. It only implements the * methods that the extension manager requires. */ - var MockWindowsRegKey = function MockWindowsRegKey() { + function MockWindowsRegKey() { } MockWindowsRegKey.prototype = { @@ -1723,30 +1723,6 @@ do_register_cleanup(function addon_cleanup() { } catch (e) {} }); -/** - * Creates a new HttpServer for testing, and begins listening on the - * specified port. Automatically shuts down the server when the test - * unit ends. - * - * @param port - * The port to listen on. If omitted, listen on a random - * port. The latter is the preferred behavior. - * - * @return HttpServer - */ -function createHttpServer(port = -1) { - let server = new HttpServer(); - server.start(port); - - do_register_cleanup(() => { - return new Promise(resolve => { - server.stop(resolve); - }); - }); - - return server; -} - /** * Handler function that responds with the interpolated * static file associated to the URL specified by request.path. @@ -1920,89 +1896,19 @@ function promiseAddonByID(aId) { */ function promiseFindAddonUpdates(addon, reason = AddonManager.UPDATE_WHEN_PERIODIC_UPDATE) { return new Promise((resolve, reject) => { - let result = {}; addon.findUpdates({ - onNoCompatibilityUpdateAvailable: function(addon2) { - if ("compatibilityUpdate" in result) { - do_throw("Saw multiple compatibility update events"); - } - equal(addon, addon2); - addon.compatibilityUpdate = false; + install: null, + + onUpdateAvailable: function(addon, install) { + this.install = install; }, - onCompatibilityUpdateAvailable: function(addon2) { - if ("compatibilityUpdate" in result) { - do_throw("Saw multiple compatibility update events"); - } - equal(addon, addon2); - addon.compatibilityUpdate = true; - }, - - onNoUpdateAvailable: function(addon2) { - if ("updateAvailable" in result) { - do_throw("Saw multiple update available events"); - } - equal(addon, addon2); - result.updateAvailable = false; - }, - - onUpdateAvailable: function(addon2, install) { - if ("updateAvailable" in result) { - do_throw("Saw multiple update available events"); - } - equal(addon, addon2); - result.updateAvailable = install; - }, - - onUpdateFinished: function(addon2, error) { - equal(addon, addon2); - if (error == AddonManager.UPDATE_STATUS_NO_ERROR) { - resolve(result); - } else { - result.error = error; - reject(result); - } + onUpdateFinished: function(addon, error) { + if (error == AddonManager.UPDATE_STATUS_NO_ERROR) + resolve(this.install); + else + reject(error); } }, reason); }); } - -/** - * Monitors console output for the duration of a task, and returns a promise - * which resolves to a tuple containing a list of all console messages - * generated during the task's execution, and the result of the task itself. - * - * @param {function} aTask - * The task to run while monitoring console output. May be - * either a generator function, per Task.jsm, or an ordinary - * function which returns promose. - * @return {Promise<[Array, *]>} - */ -var promiseConsoleOutput = Task.async(function*(aTask) { - const DONE = "=== xpcshell test console listener done ==="; - - let listener, messages = []; - let awaitListener = new Promise(resolve => { - listener = msg => { - if (msg == DONE) { - resolve(); - } else { - msg instanceof Components.interfaces.nsIScriptError; - messages.push(msg); - } - } - }); - - Services.console.registerListener(listener); - try { - let result = yield aTask(); - - Services.console.logStringMessage(DONE); - yield awaitListener; - - return { messages, result }; - } - finally { - Services.console.unregisterListener(listener); - } -}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js b/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js index 2ba21a8a213d..b2c0f59013e2 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js @@ -505,8 +505,9 @@ add_task(function* setup() { yield promiseInstallAllFiles(ADDON_FILES); yield promiseRestartManager(); - gServer = createHttpServer(PORT); + gServer = new HttpServer(); gServer.registerDirectory("/data/", do_get_file("data")); + gServer.start(PORT); }); // Tests AddonRepository.cacheEnabled @@ -703,3 +704,7 @@ add_task(function* run_test_17() { let aAddons = yield promiseAddonsByIDs(ADDON_IDS); check_results(aAddons, WITH_EXTENSION_CACHE); }); + +add_task(function* end_test() { + yield new Promise((resolve, reject) => gServer.stop(resolve)); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js b/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js index 7113f7cfd543..989c3e4ceead 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js @@ -41,7 +41,8 @@ Cu.import("resource://testing-common/MockRegistrar.jsm"); Services.prefs.setBoolPref("extensions.checkUpdateSecurity", false) Cu.import("resource://testing-common/httpd.js"); -var testserver = createHttpServer(); +var testserver = new HttpServer(); +testserver.start(-1); gPort = testserver.identity.primaryPort; // register static files with server and interpolate port numbers in them @@ -1304,3 +1305,9 @@ add_task(function* run_local_install_test() { check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); }); + +add_task(function* shutdown_httpserver() { + yield new Promise((resolve, reject) => { + testserver.stop(resolve); + }); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug542391.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug542391.js index 67b50d16190e..7c274d90aefd 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug542391.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug542391.js @@ -296,9 +296,10 @@ add_task(function* init() { }, profileDir); // Create and configure the HTTP server. - testserver = createHttpServer(4444); + testserver = new HttpServer(); testserver.registerDirectory("/data/", do_get_file("data")); testserver.registerDirectory("/addons/", do_get_file("addons")); + testserver.start(4444); startupManager(); @@ -463,3 +464,13 @@ add_task(function* run_test_6() { "override1x2-1x3@tests.mozilla.org"]); check_state_v1_2(addons); }); + +add_task(function* cleanup() { + return new Promise((resolve, reject) => { + testserver.stop(resolve); + }); +}); + +function run_test() { + run_next_test(); +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug570173.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug570173.js index c859c474c711..70de3b426d26 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug570173.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug570173.js @@ -16,47 +16,65 @@ function run_test() { createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); // Create and configure the HTTP server. - testserver = createHttpServer(); + testserver = new HttpServer(); testserver.registerDirectory("/data/", do_get_file("data")); testserver.registerDirectory("/addons/", do_get_file("addons")); + testserver.start(-1); gPort = testserver.identity.primaryPort; - run_next_test(); + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/test_missing.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + + startupManager(); + + do_test_pending(); + run_test_1(); +} + +function end_test() { + testserver.stop(do_test_finished); } // Verify that an update check returns the correct errors. -add_task(function* () { - for (let manifestType of ["rdf", "json"]) { - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: `http://localhost:${gPort}/data/test_missing.${manifestType}`, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - bootstrap: "true", - }, profileDir); +function run_test_1() { + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + do_check_neq(a1, null); + do_check_eq(a1.version, "1.0"); - yield promiseRestartManager(); + let sawCompat = false; + let sawUpdate = false; + a1.findUpdates({ + onNoCompatibilityUpdateAvailable: function(addon) { + sawCompat = true; + }, - let addon = yield promiseAddonByID("addon1@tests.mozilla.org"); + onCompatibilityUpdateAvailable: function(addon) { + do_throw("Should not have seen a compatibility update"); + }, - ok(addon); - ok(addon.updateURL.endsWith(manifestType)); - equal(addon.version, "1.0"); + onNoUpdateAvailable: function(addon) { + sawUpdate = true; + }, - // We're expecting an error, so resolve when the promise is rejected. - let update = yield promiseFindAddonUpdates(addon, AddonManager.UPDATE_WHEN_USER_REQUESTED) - .catch(Promise.resolve); + onUpdateAvailable: function(addon, install) { + do_throw("Should not have seen an update"); + }, - ok(!update.compatibilityUpdate, "not expecting a compatibility update"); - ok(!update.updateAvailable, "not expecting a compatibility update"); - - equal(update.error, AddonManager.UPDATE_STATUS_DOWNLOAD_ERROR); - - addon.uninstall(); - } -}); + onUpdateFinished: function(addon, error) { + do_check_true(sawCompat); + do_check_true(sawUpdate); + do_check_eq(error, AddonManager.UPDATE_STATUS_DOWNLOAD_ERROR); + end_test(); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_json_updatecheck.js b/toolkit/mozapps/extensions/test/xpcshell/test_json_updatecheck.js deleted file mode 100644 index 2e8cc6a972c1..000000000000 --- a/toolkit/mozapps/extensions/test/xpcshell/test_json_updatecheck.js +++ /dev/null @@ -1,373 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ -"use strict"; - -// This verifies that AddonUpdateChecker works correctly for JSON -// update manifests, particularly for behavior which does not -// cleanly overlap with RDF manifests. - -const TOOLKIT_ID = "toolkit@mozilla.org"; -const TOOLKIT_MINVERSION = "42.0a1"; - -createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "42.0a2", "42.0a2"); - -Components.utils.import("resource://gre/modules/addons/AddonUpdateChecker.jsm"); -Components.utils.import("resource://testing-common/httpd.js"); - -let testserver = createHttpServer(); -gPort = testserver.identity.primaryPort; - -let gUpdateManifests = {}; - -function mapManifest(aPath, aManifestData) { - gUpdateManifests[aPath] = aManifestData; - testserver.registerPathHandler(aPath, serveManifest); -} - -function serveManifest(request, response) { - let manifest = gUpdateManifests[request.path]; - - response.setHeader("Content-Type", manifest.contentType, false); - response.write(manifest.data); -} - -const extensionsDir = gProfD.clone(); -extensionsDir.append("extensions"); - - -function checkUpdates(aData) { - // Registers JSON update manifest for it with the testing server, - // checks for updates, and yields the list of updates on - // success. - - let extension = aData.manifestExtension || "json"; - - let path = `/updates/${aData.id}.${extension}`; - let updateUrl = `http://localhost:${gPort}${path}` - - let addonData = {}; - if ("updates" in aData) - addonData.updates = aData.updates; - - let manifestJSON = { - "addons": { - [aData.id]: addonData - } - }; - - mapManifest(path.replace(/\?.*/, ""), - { data: JSON.stringify(manifestJSON), - contentType: aData.contentType || "application/json" }); - - - return new Promise((resolve, reject) => { - AddonUpdateChecker.checkForUpdates(aData.id, aData.updateKey, updateUrl, { - onUpdateCheckComplete: resolve, - - onUpdateCheckError: function(status) { - reject(new Error("Update check failed with status " + status)); - } - }); - }); -} - - -add_task(function* test_default_values() { - // Checks that the appropriate defaults are used for omitted values. - - startupManager(); - - let updates = yield checkUpdates({ - id: "updatecheck-defaults@tests.mozilla.org", - version: "0.1", - updates: [{ - version: "0.2" - }] - }); - - equal(updates.length, 1); - let update = updates[0]; - - equal(update.targetApplications.length, 1); - let targetApp = update.targetApplications[0]; - - equal(targetApp.id, TOOLKIT_ID); - equal(targetApp.minVersion, TOOLKIT_MINVERSION); - equal(targetApp.maxVersion, "*"); - - equal(update.version, "0.2"); - equal(update.multiprocessCompatible, true, "multiprocess_compatible flag"); - equal(update.strictCompatibility, false, "inferred strictConpatibility flag"); - equal(update.updateURL, null, "updateURL"); - equal(update.updateHash, null, "updateHash"); - equal(update.updateInfoURL, null, "updateInfoURL"); - - // If there's no applications property, we default to using one - // containing "gecko". If there is an applications property, but - // it doesn't contain "gecko", the update is skipped. - updates = yield checkUpdates({ - id: "updatecheck-defaults@tests.mozilla.org", - version: "0.1", - updates: [{ - version: "0.2", - applications: { "foo": {} } - }] - }); - - equal(updates.length, 0); - - // Updates property is also optional. No updates, but also no error. - updates = yield checkUpdates({ - id: "updatecheck-defaults@tests.mozilla.org", - version: "0.1", - }); - - equal(updates.length, 0); -}); - - -add_task(function* test_explicit_values() { - // Checks that the appropriate explicit values are used when - // provided. - - let updates = yield checkUpdates({ - id: "updatecheck-explicit@tests.mozilla.org", - version: "0.1", - updates: [{ - version: "0.2", - update_link: "https://example.com/foo.xpi", - update_hash: "sha256:0", - update_info_url: "https://example.com/update_info.html", - multiprocess_compatible: false, - applications: { - gecko: { - strict_min_version: "42.0a2.xpcshell", - strict_max_version: "43.xpcshell" - } - } - }] - }); - - equal(updates.length, 1); - let update = updates[0]; - - equal(update.targetApplications.length, 1); - let targetApp = update.targetApplications[0]; - - equal(targetApp.id, TOOLKIT_ID); - equal(targetApp.minVersion, "42.0a2.xpcshell"); - equal(targetApp.maxVersion, "43.xpcshell"); - - equal(update.version, "0.2"); - equal(update.multiprocessCompatible, false, "multiprocess_compatible flag"); - equal(update.strictCompatibility, true, "inferred strictCompatibility flag"); - equal(update.updateURL, "https://example.com/foo.xpi", "updateURL"); - equal(update.updateHash, "sha256:0", "updateHash"); - equal(update.updateInfoURL, "https://example.com/update_info.html", "updateInfoURL"); -}); - - -add_task(function* test_secure_hashes() { - // Checks that only secure hash functions are accepted for - // non-secure update URLs. - - let hashFunctions = ["sha512", - "sha256", - "sha1", - "md5", - "md4", - "xxx"]; - - let updateItems = hashFunctions.map((hash, idx) => ({ - version: `0.${idx}`, - update_link: `http://localhost:${gPort}/updates/${idx}-${hash}.xpi`, - update_hash: `${hash}:08ac852190ecd81f40a514ea9299fe9143d9ab5e296b97e73fb2a314de49648a`, - })); - - let { messages, result: updates } = yield promiseConsoleOutput(() => { - return checkUpdates({ - id: "updatecheck-hashes@tests.mozilla.org", - version: "0.1", - updates: updateItems - }); - }); - - equal(updates.length, hashFunctions.length); - - updates = updates.filter(update => update.updateHash || update.updateURL); - equal(updates.length, 2, "expected number of update hashes were accepted"); - - ok(updates[0].updateHash.startsWith("sha512:"), "sha512 hash is present"); - ok(updates[0].updateURL); - - ok(updates[1].updateHash.startsWith("sha256:"), "sha256 hash is present"); - ok(updates[1].updateURL); - - messages = messages.filter(msg => /Update link.*not secure.*strong enough hash \(needs to be sha256 or sha512\)/.test(msg.message)); - equal(messages.length, hashFunctions.length - 2, "insecure hashes generated the expected warning"); -}); - - -add_task(function* test_strict_compat() { - // Checks that strict compatibility is enabled for strict max - // versions other than "*", but not for advisory max versions. - // Also, ensure that strict max versions take precedence over - // advisory versions. - - let { messages, result: updates } = yield promiseConsoleOutput(() => { - return checkUpdates({ - id: "updatecheck-strict@tests.mozilla.org", - version: "0.1", - updates: [ - { version: "0.2", - applications: { gecko: { strict_max_version: "*" } } }, - { version: "0.3", - applications: { gecko: { strict_max_version: "43" } } }, - { version: "0.4", - applications: { gecko: { advisory_max_version: "43" } } }, - { version: "0.5", - applications: { gecko: { advisory_max_version: "43", - strict_max_version: "44" } } }, - ] - }); - }); - - equal(updates.length, 4, "all update items accepted"); - - equal(updates[0].targetApplications[0].maxVersion, "*"); - equal(updates[0].strictCompatibility, false); - - equal(updates[1].targetApplications[0].maxVersion, "43"); - equal(updates[1].strictCompatibility, true); - - equal(updates[2].targetApplications[0].maxVersion, "43"); - equal(updates[2].strictCompatibility, false); - - equal(updates[3].targetApplications[0].maxVersion, "44"); - equal(updates[3].strictCompatibility, true); - - messages = messages.filter(msg => /Ignoring 'advisory_max_version'.*'strict_max_version' also present/.test(msg.message)); - equal(messages.length, 1, "mix of advisory_max_version and strict_max_version generated the expected warning"); -}); - - -add_task(function* test_update_url_security() { - // Checks that update links to privileged URLs are not accepted. - - let { messages, result: updates } = yield promiseConsoleOutput(() => { - return checkUpdates({ - id: "updatecheck-security@tests.mozilla.org", - version: "0.1", - updates: [ - { version: "0.2", - update_link: "chrome://browser/content/browser.xul", - update_hash: "sha256:08ac852190ecd81f40a514ea9299fe9143d9ab5e296b97e73fb2a314de49648a" }, - { version: "0.3", - update_link: "http://example.com/update.xpi", - update_hash: "sha256:18ac852190ecd81f40a514ea9299fe9143d9ab5e296b97e73fb2a314de49648a" }, - ] - }); - }); - - equal(updates.length, 2, "both updates were processed"); - equal(updates[0].updateURL, null, "privileged update URL was removed"); - equal(updates[1].updateURL, "http://example.com/update.xpi", "safe update URL was accepted"); - - messages = messages.filter(msg => /http:\/\/localhost.*\/updates\/.*may not load or link to chrome:/.test(msg.message)); - equal(messages.length, 1, "privileged upate URL generated the expected console message"); -}); - - -add_task(function* test_no_update_key() { - // Checks that updates fail when an update key has been specified. - - let { messages } = yield promiseConsoleOutput(function* () { - yield Assert.rejects( - checkUpdates({ - id: "updatecheck-updatekey@tests.mozilla.org", - version: "0.1", - updateKey: "ayzzx=", - updates: [ - { version: "0.2" }, - { version: "0.3" }, - ] - }), - null, "updated expected to fail"); - }); - - messages = messages.filter(msg => /Update keys are not supported for JSON update manifests/.test(msg.message)); - equal(messages.length, 1, "got expected update-key-unsupported error"); -}); - - -add_task(function* test_type_detection() { - // Checks that JSON update manifests are detected correctly - // regardless of extension or MIME type. - - let tests = [ - { contentType: "application/json", - extension: "json", - valid: true }, - { contentType: "application/json", - extension: "php", - valid: true }, - { contentType: "text/plain", - extension: "json", - valid: true }, - { contentType: "application/octet-stream", - extension: "json", - valid: true }, - { contentType: "text/plain", - extension: "json?foo=bar", - valid: true }, - { contentType: "text/plain", - extension: "php", - valid: true }, - { contentType: "text/plain", - extension: "rdf", - valid: true }, - { contentType: "application/json", - extension: "rdf", - valid: true }, - { contentType: "text/xml", - extension: "json", - valid: true }, - { contentType: "application/rdf+xml", - extension: "json", - valid: true }, - ]; - - for (let [i, test] of tests.entries()) { - let { messages } = yield promiseConsoleOutput(function *() { - let id = `updatecheck-typedetection-${i}@tests.mozilla.org`; - let updates; - try { - updates = yield checkUpdates({ - id: id, - version: "0.1", - contentType: test.contentType, - manifestExtension: test.extension, - updates: [{ version: "0.2" }] - }); - } catch (e) { - ok(!test.valid, "update manifest correctly detected as RDF"); - return; - } - - ok(test.valid, "update manifest correctly detected as JSON"); - equal(updates.length, 1, "correct number of updates"); - equal(updates[0].id, id, "update is for correct extension"); - }); - - if (test.valid) { - // Make sure we don't get any XML parsing errors from the - // XMLHttpRequest machinery. - ok(!messages.some(msg => /not well-formed/.test(msg.message)), - "expect XMLHttpRequest not to attempt XML parsing"); - } - - messages = messages.filter(msg => /Update manifest was not valid XML/.test(msg.message)); - equal(messages.length, !test.valid, "expected number of XML parsing errors"); - } -}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_metadata_update.js b/toolkit/mozapps/extensions/test/xpcshell/test_metadata_update.js index 962e0c8cc633..78d03e88cd2a 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_metadata_update.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_metadata_update.js @@ -69,9 +69,10 @@ add_task(function* checkFirstMetadata() { Services.prefs.setBoolPref(PREF_EM_SHOW_MISMATCH_UI, true); // Create and configure the HTTP server. - testserver = createHttpServer(); + testserver = new HttpServer(); testserver.registerDirectory("/data/", do_get_file("data")); testserver.registerDirectory("/addons/", do_get_file("addons")); + testserver.start(-1); gPort = testserver.identity.primaryPort; const BASE_URL = "http://localhost:" + gPort; const GETADDONS_RESULTS = BASE_URL + "/data/test_AddonRepository_cache.xml"; @@ -158,3 +159,15 @@ add_task(function* upgrade_young_pref_lastupdate() { yield promiseRestartManager("2"); do_check_false(WindowWatcher.expected); }); + + + +add_task(function* cleanup() { + return new Promise((resolve, reject) => { + testserver.stop(resolve); + }); +}); + +function run_test() { + run_next_test(); +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_signed_install.js b/toolkit/mozapps/extensions/test/xpcshell/test_signed_install.js index 392a9b7a2476..8c7f491d1452 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_signed_install.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_signed_install.js @@ -16,7 +16,8 @@ const WORKING = "signed_bootstrap_1.xpi"; const ID = "test@tests.mozilla.org"; Components.utils.import("resource://testing-common/httpd.js"); -var gServer = createHttpServer(4444); +var gServer = new HttpServer(); +gServer.start(4444); // Creates an add-on with a broken signature by changing an existing file function createBrokenAddonModify(file) { @@ -136,8 +137,7 @@ function* test_update_broken(file, expectedError) { serveUpdateRDF(file.leafName); let addon = yield promiseAddonByID(ID); - let update = yield promiseFindAddonUpdates(addon); - let install = update.updateAvailable; + let install = yield promiseFindAddonUpdates(addon); yield promiseCompleteAllInstalls([install]); do_check_eq(install.state, AddonManager.STATE_DOWNLOAD_FAILED); @@ -158,8 +158,7 @@ function* test_update_working(file, expectedSignedState) { serveUpdateRDF(file.leafName); let addon = yield promiseAddonByID(ID); - let update = yield promiseFindAddonUpdates(addon); - let install = update.updateAvailable; + let install = yield promiseFindAddonUpdates(addon); yield promiseCompleteAllInstalls([install]); do_check_eq(install.state, AddonManager.STATE_INSTALLED); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_update.js b/toolkit/mozapps/extensions/test/xpcshell/test_update.js index 07e8c37128c6..f410382c2d2e 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_update.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_update.js @@ -24,10 +24,10 @@ const PARAMS = "?%REQ_VERSION%/%ITEM_ID%/%ITEM_VERSION%/%ITEM_MAXAPPVERSION%/" + var gInstallDate; Components.utils.import("resource://testing-common/httpd.js"); -var testserver = createHttpServer(); +var testserver = new HttpServer(); +testserver.start(-1); gPort = testserver.identity.primaryPort; mapFile("/data/test_update.rdf", testserver); -mapFile("/data/test_update.json", testserver); mapFile("/data/test_update.xml", testserver); testserver.registerDirectory("/addons/", do_get_file("addons")); @@ -37,1160 +37,386 @@ profileDir.append("extensions"); var originalSyncGUID; function run_test() { - createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1"); - + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); Services.prefs.setBoolPref(PREF_MATCH_OS_LOCALE, false); Services.prefs.setCharPref(PREF_SELECTED_LOCALE, "fr-FR"); - run_next_test(); + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon2@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "0", + maxVersion: "0" + }], + name: "Test Addon 2", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon3@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "5", + maxVersion: "5" + }], + name: "Test Addon 3", + }, profileDir); + + startupManager(); + + do_test_pending(); + run_test_1(); } -let testParams = [ - { updateFile: "test_update.rdf", - appId: "xpcshell@tests.mozilla.org" }, - { updateFile: "test_update.json", - appId: "toolkit@mozilla.org" }, -]; +function end_test() { + testserver.stop(do_test_finished); +} -for (let test of testParams) { - let { updateFile, appId } = test; +// Verify that an update is available and can be installed. +function run_test_1() { + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + do_check_neq(a1, null); + do_check_eq(a1.version, "1.0"); + do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DEFAULT); + do_check_eq(a1.releaseNotesURI, null); + do_check_true(a1.foreignInstall); + do_check_neq(a1.syncGUID, null); - add_test(function run_test() { - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/" + updateFile, - targetApplications: [{ - id: appId, - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); + originalSyncGUID = a1.syncGUID; + a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT; - writeInstallRDFForExtension({ - id: "addon2@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/" + updateFile, - targetApplications: [{ - id: appId, - minVersion: "0", - maxVersion: "0" - }], - name: "Test Addon 2", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon3@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/" + updateFile, - targetApplications: [{ - id: appId, - minVersion: "5", - maxVersion: "5" - }], - name: "Test Addon 3", - }, profileDir); - - startupManager(); - - run_next_test(); - }); - - // Verify that an update is available and can be installed. - let check_test_1; - add_test(function run_test_1() { - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { - do_check_neq(a1, null); - do_check_eq(a1.version, "1.0"); - do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DEFAULT); - do_check_eq(a1.releaseNotesURI, null); - do_check_true(a1.foreignInstall); - do_check_neq(a1.syncGUID, null); - - originalSyncGUID = a1.syncGUID; - a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT; - - prepare_test({ - "addon1@tests.mozilla.org": [ - ["onPropertyChanged", ["applyBackgroundUpdates"]] - ] - }); - a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; - check_test_completed(); - - a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; - - prepare_test({}, [ - "onNewInstall", - ]); - - a1.findUpdates({ - onNoCompatibilityUpdateAvailable: function(addon) { - ok(false, "Should not have seen onNoCompatibilityUpdateAvailable notification"); - }, - - onUpdateAvailable: function(addon, install) { - ensure_test_completed(); - - AddonManager.getAllInstalls(function(aInstalls) { - do_check_eq(aInstalls.length, 1); - do_check_eq(aInstalls[0], install); - - do_check_eq(addon, a1); - do_check_eq(install.name, addon.name); - do_check_eq(install.version, "2.0"); - do_check_eq(install.state, AddonManager.STATE_AVAILABLE); - do_check_eq(install.existingAddon, addon); - do_check_eq(install.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); - - // Verify that another update check returns the same AddonInstall - a1.findUpdates({ - onNoCompatibilityUpdateAvailable: function(addon) { - ok(false, "Should not have seen onNoCompatibilityUpdateAvailable notification"); - }, - - onUpdateAvailable: function(newAddon, newInstall) { - AddonManager.getAllInstalls(function(aInstalls) { - do_check_eq(aInstalls.length, 1); - do_check_eq(aInstalls[0], install); - do_check_eq(newAddon, addon); - do_check_eq(newInstall, install); - - prepare_test({}, [ - "onDownloadStarted", - "onDownloadEnded", - ], check_test_1); - install.install(); - }); - }, - - onNoUpdateAvailable: function(addon) { - ok(false, "Should not have seen onNoUpdateAvailable notification"); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); - }); - }, - - onNoUpdateAvailable: function(addon) { - ok(false, "Should not have seen onNoUpdateAvailable notification"); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + prepare_test({ + "addon1@tests.mozilla.org": [ + ["onPropertyChanged", ["applyBackgroundUpdates"]] + ] }); - }); + a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + check_test_completed(); - let run_test_2; - check_test_1 = (install) => { - ensure_test_completed(); - do_check_eq(install.state, AddonManager.STATE_DOWNLOADED); - run_test_2(install); - return false; - }; + a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; - // Continue installing the update. - let check_test_2; - run_test_2 = (install) => { - // Verify that another update check returns no new update - install.existingAddon.findUpdates({ + prepare_test({}, [ + "onNewInstall", + ]); + + a1.findUpdates({ onNoCompatibilityUpdateAvailable: function(addon) { - ok(false, "Should not have seen onNoCompatibilityUpdateAvailable notification"); + do_throw("Should not have seen onNoCompatibilityUpdateAvailable notification"); }, onUpdateAvailable: function(addon, install) { - ok(false, "Should find no available update when one is already downloading"); - }, + ensure_test_completed(); - onNoUpdateAvailable: function(addon) { AddonManager.getAllInstalls(function(aInstalls) { do_check_eq(aInstalls.length, 1); do_check_eq(aInstalls[0], install); - prepare_test({ - "addon1@tests.mozilla.org": [ - "onInstalling" - ] - }, [ - "onInstallStarted", - "onInstallEnded", - ], check_test_2); - install.install(); + do_check_eq(addon, a1); + do_check_eq(install.name, addon.name); + do_check_eq(install.version, "2.0"); + do_check_eq(install.state, AddonManager.STATE_AVAILABLE); + do_check_eq(install.existingAddon, addon); + do_check_eq(install.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); + + // Verify that another update check returns the same AddonInstall + a1.findUpdates({ + onNoCompatibilityUpdateAvailable: function(addon) { + do_throw("Should not have seen onNoCompatibilityUpdateAvailable notification"); + }, + + onUpdateAvailable: function(newAddon, newInstall) { + AddonManager.getAllInstalls(function(aInstalls) { + do_check_eq(aInstalls.length, 1); + do_check_eq(aInstalls[0], install); + do_check_eq(newAddon, addon); + do_check_eq(newInstall, install); + + prepare_test({}, [ + "onDownloadStarted", + "onDownloadEnded", + ], check_test_1); + install.install(); + }); + }, + + onNoUpdateAvailable: function(addon) { + do_throw("Should not have seen onNoUpdateAvailable notification"); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); }); + }, + + onNoUpdateAvailable: function(addon) { + do_throw("Should not have seen onNoUpdateAvailable notification"); } }, AddonManager.UPDATE_WHEN_USER_REQUESTED); - }; + }); +} - check_test_2 = () => { - ensure_test_completed(); +function check_test_1(install) { + ensure_test_completed(); + do_check_eq(install.state, AddonManager.STATE_DOWNLOADED); + run_test_2(install); + return false; +} - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(olda1) { - do_check_neq(olda1, null); - do_check_eq(olda1.version, "1.0"); - do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); +// Continue installing the update. +function run_test_2(install) { + // Verify that another update check returns no new update + install.existingAddon.findUpdates({ + onNoCompatibilityUpdateAvailable: function(addon) { + do_throw("Should not have seen onNoCompatibilityUpdateAvailable notification"); + }, - shutdownManager(); + onUpdateAvailable: function(addon, install) { + do_throw("Should find no available update when one is already downloading"); + }, - startupManager(); + onNoUpdateAvailable: function(addon) { + AddonManager.getAllInstalls(function(aInstalls) { + do_check_eq(aInstalls.length, 1); + do_check_eq(aInstalls[0], install); - do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); - - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { - do_check_neq(a1, null); - do_check_eq(a1.version, "2.0"); - do_check_true(isExtensionInAddonsList(profileDir, a1.id)); - do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DISABLE); - do_check_eq(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); - do_check_true(a1.foreignInstall); - do_check_neq(a1.syncGUID, null); - do_check_eq(originalSyncGUID, a1.syncGUID); - - a1.uninstall(); - run_next_test(); + prepare_test({ + "addon1@tests.mozilla.org": [ + "onInstalling" + ] + }, [ + "onInstallStarted", + "onInstallEnded", + ], check_test_2); + install.install(); }); - })); - }; + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); +} +function check_test_2() { + ensure_test_completed(); - // Check that an update check finds compatibility updates and applies them - let check_test_3; - add_test(function run_test_3() { - restartManager(); - - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { - do_check_neq(a2, null); - do_check_true(a2.isActive); - do_check_true(a2.isCompatible); - do_check_false(a2.appDisabled); - do_check_true(a2.isCompatibleWith("0", "0")); - - a2.findUpdates({ - onCompatibilityUpdateAvailable: function(addon) { - do_check_true(a2.isCompatible); - do_check_false(a2.appDisabled); - do_check_true(a2.isActive); - }, - - onUpdateAvailable: function(addon, install) { - ok(false, "Should not have seen an available update"); - }, - - onNoUpdateAvailable: function(addon) { - do_check_eq(addon, a2); - do_execute_soon(check_test_3); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); - }); - }); - - check_test_3 = () => { - restartManager(); - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { - do_check_neq(a2, null); - do_check_true(a2.isActive); - do_check_true(a2.isCompatible); - do_check_false(a2.appDisabled); - a2.uninstall(); - - run_next_test(); - }); - } - - // Checks that we see no compatibility information when there is none. - add_test(function run_test_4() { - AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { - do_check_neq(a3, null); - do_check_false(a3.isActive); - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); - do_check_true(a3.isCompatibleWith("5", "5")); - do_check_false(a3.isCompatibleWith("2", "2")); - - a3.findUpdates({ - sawUpdate: false, - onCompatibilityUpdateAvailable: function(addon) { - ok(false, "Should not have seen compatibility information"); - }, - - onNoCompatibilityUpdateAvailable: function(addon) { - this.sawUpdate = true; - }, - - onUpdateAvailable: function(addon, install) { - ok(false, "Should not have seen an available update"); - }, - - onNoUpdateAvailable: function(addon) { - do_check_true(this.sawUpdate); - run_next_test(); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); - }); - }); - - // Checks that compatibility info for future apps are detected but don't make - // the item compatibile. - let check_test_5; - add_test(function run_test_5() { - AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { - do_check_neq(a3, null); - do_check_false(a3.isActive); - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); - do_check_true(a3.isCompatibleWith("5", "5")); - do_check_false(a3.isCompatibleWith("2", "2")); - - a3.findUpdates({ - sawUpdate: false, - onCompatibilityUpdateAvailable: function(addon) { - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); - do_check_false(a3.isActive); - this.sawUpdate = true; - }, - - onNoCompatibilityUpdateAvailable: function(addon) { - ok(false, "Should have seen some compatibility information"); - }, - - onUpdateAvailable: function(addon, install) { - ok(false, "Should not have seen an available update"); - }, - - onNoUpdateAvailable: function(addon) { - do_check_true(this.sawUpdate); - do_execute_soon(check_test_5); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED, "3.0", "3.0"); - }); - }); - - check_test_5 = () => { - restartManager(); - AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { - do_check_neq(a3, null); - do_check_false(a3.isActive); - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); - - a3.uninstall(); - run_next_test(); - }); - } - - // Test that background update checks work - let continue_test_6; - add_test(function run_test_6() { - restartManager(); - - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/" + updateFile, - targetApplications: [{ - id: appId, - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - restartManager(); - - prepare_test({}, [ - "onNewInstall", - "onDownloadStarted", - "onDownloadEnded" - ], continue_test_6); - - AddonManagerInternal.backgroundUpdateCheck(); - }); - - let check_test_6; - continue_test_6 = (install) => { - do_check_neq(install.existingAddon, null); - do_check_eq(install.existingAddon.id, "addon1@tests.mozilla.org"); - - prepare_test({ - "addon1@tests.mozilla.org": [ - "onInstalling" - ] - }, [ - "onInstallStarted", - "onInstallEnded", - ], callback_soon(check_test_6)); - } - - check_test_6 = (install) => { - do_check_eq(install.existingAddon.pendingUpgrade.install, install); - - restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { - do_check_neq(a1, null); - do_check_eq(a1.version, "2.0"); - do_check_eq(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); - a1.uninstall(); - run_next_test(); - }); - } - - // Verify the parameter escaping in update urls. - add_test(function run_test_8() { - restartManager(); - - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "5.0", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: appId, - minVersion: "1", - maxVersion: "2" - }], - name: "Test Addon 1", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon2@tests.mozilla.org", - version: "67.0.5b1", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "toolkit@mozilla.org", - minVersion: "0", - maxVersion: "3" - }], - name: "Test Addon 2", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon3@tests.mozilla.org", - version: "1.3+", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: appId, - minVersion: "0", - maxVersion: "0" - }, { - id: "toolkit@mozilla.org", - minVersion: "0", - maxVersion: "3" - }], - name: "Test Addon 3", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon4@tests.mozilla.org", - version: "0.5ab6", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: appId, - minVersion: "1", - maxVersion: "5" - }], - name: "Test Addon 4", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon5@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: appId, - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 5", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon6@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: appId, - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 6", - }, profileDir); - - restartManager(); - - AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(a2) { - a2.userDisabled = true; - restartManager(); - - testserver.registerPathHandler("/data/param_test.rdf", function(request, response) { - do_check_neq(request.queryString, ""); - let [req_version, item_id, item_version, - item_maxappversion, item_status, - app_id, app_version, current_app_version, - app_os, app_abi, app_locale, update_type] = - request.queryString.split("/").map(a => decodeURIComponent(a)); - - do_check_eq(req_version, "2"); - - switch(item_id) { - case "addon1@tests.mozilla.org": - do_check_eq(item_version, "5.0"); - do_check_eq(item_maxappversion, "2"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "97"); - break; - case "addon2@tests.mozilla.org": - do_check_eq(item_version, "67.0.5b1"); - do_check_eq(item_maxappversion, "3"); - do_check_eq(item_status, "userDisabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "49"); - break; - case "addon3@tests.mozilla.org": - do_check_eq(item_version, "1.3+"); - do_check_eq(item_maxappversion, "0"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "112"); - break; - case "addon4@tests.mozilla.org": - do_check_eq(item_version, "0.5ab6"); - do_check_eq(item_maxappversion, "5"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "2"); - do_check_eq(update_type, "98"); - break; - case "addon5@tests.mozilla.org": - do_check_eq(item_version, "1.0"); - do_check_eq(item_maxappversion, "1"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "35"); - break; - case "addon6@tests.mozilla.org": - do_check_eq(item_version, "1.0"); - do_check_eq(item_maxappversion, "1"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "99"); - break; - default: - ok(false, "Update request for unexpected add-on " + item_id); - } - - do_check_eq(app_id, "xpcshell@tests.mozilla.org"); - do_check_eq(current_app_version, "1"); - do_check_eq(app_os, "XPCShell"); - do_check_eq(app_abi, "noarch-spidermonkey"); - do_check_eq(app_locale, "fr-FR"); - - request.setStatusLine(null, 500, "Server Error"); - }); - - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon2@tests.mozilla.org", - "addon3@tests.mozilla.org", - "addon4@tests.mozilla.org", - "addon5@tests.mozilla.org", - "addon6@tests.mozilla.org"], - function([a1, a2, a3, a4, a5, a6]) { - let count = 6; - - function next_test() { - a1.uninstall(); - a2.uninstall(); - a3.uninstall(); - a4.uninstall(); - a5.uninstall(); - a6.uninstall(); - - restartManager(); - run_next_test(); - } - - let compatListener = { - onUpdateFinished: function(addon, error) { - if (--count == 0) - do_execute_soon(next_test); - } - }; - - let updateListener = { - onUpdateAvailable: function(addon, update) { - // Dummy so the update checker knows we care about new versions - }, - - onUpdateFinished: function(addon, error) { - if (--count == 0) - do_execute_soon(next_test); - } - }; - - a1.findUpdates(updateListener, AddonManager.UPDATE_WHEN_USER_REQUESTED); - a2.findUpdates(compatListener, AddonManager.UPDATE_WHEN_ADDON_INSTALLED); - a3.findUpdates(updateListener, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); - a4.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "2"); - a5.findUpdates(compatListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); - a6.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); - }); - })); - }); - - // Tests that if an install.rdf claims compatibility then the add-on will be - // seen as compatible regardless of what the update.rdf says. - add_test(function run_test_9() { - writeInstallRDFForExtension({ - id: "addon4@tests.mozilla.org", - version: "5.0", - updateURL: "http://localhost:" + gPort + "/data/" + updateFile, - targetApplications: [{ - id: appId, - minVersion: "0", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - restartManager(); - - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - do_check_true(a4.isActive, "addon4 is active"); - do_check_true(a4.isCompatible, "addon4 is compatible"); - - run_next_test(); - }); - }); - - // Tests that a normal update check won't decrease a targetApplication's - // maxVersion. - add_test(function run_test_10() { - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - a4.findUpdates({ - onUpdateFinished: function(addon) { - do_check_true(addon.isCompatible, "addon4 is compatible"); - - run_next_test(); - } - }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); - }); - }); - - // Tests that an update check for a new application will decrease a - // targetApplication's maxVersion. - add_test(function run_test_11() { - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - a4.findUpdates({ - onUpdateFinished: function(addon) { - do_check_true(addon.isCompatible, "addon4 is not compatible"); - - run_next_test(); - } - }, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); - }); - }); - - // Check that the decreased maxVersion applied and disables the add-on - add_test(function run_test_12() { - restartManager(); - - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - do_check_true(a4.isActive); - do_check_true(a4.isCompatible); - - a4.uninstall(); - run_next_test(); - }); - }); - - // Tests that a compatibility update is passed to the listener when there is - // compatibility info for the current version of the app but not for the - // version of the app that the caller requested an update check for, when - // strict compatibility checking is disabled. - let check_test_13; - add_test(function run_test_13() { - restartManager(); - - // Not initially compatible but the update check will make it compatible - writeInstallRDFForExtension({ - id: "addon7@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/" + updateFile, - targetApplications: [{ - id: appId, - minVersion: "0", - maxVersion: "0" - }], - name: "Test Addon 7", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { - do_check_neq(a7, null); - do_check_true(a7.isActive); - do_check_true(a7.isCompatible); - do_check_false(a7.appDisabled); - do_check_true(a7.isCompatibleWith("0", "0")); - - a7.findUpdates({ - sawUpdate: false, - onNoCompatibilityUpdateAvailable: function(addon) { - ok(false, "Should have seen compatibility information"); - }, - - onUpdateAvailable: function(addon, install) { - ok(false, "Should not have seen an available update"); - }, - - onUpdateFinished: function(addon) { - do_check_true(addon.isCompatible); - do_execute_soon(check_test_13); - } - }, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "3.0", "3.0"); - }); - }); - - check_test_13 = () => { - restartManager(); - AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { - do_check_neq(a7, null); - do_check_true(a7.isActive); - do_check_true(a7.isCompatible); - do_check_false(a7.appDisabled); - - a7.uninstall(); - run_next_test(); - }); - } - - // Test that background update checks doesn't update an add-on that isn't - // allowed to update automatically. - let check_test_14; - add_test(function run_test_14() { - restartManager(); - - // Have an add-on there that will be updated so we see some events from it - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/" + updateFile, - targetApplications: [{ - id: appId, - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon8@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/" + updateFile, - targetApplications: [{ - id: appId, - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 8", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { - a8.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; - - // The background update check will find updates for both add-ons but only - // proceed to install one of them. - AddonManager.addInstallListener({ - onNewInstall: function(aInstall) { - let id = aInstall.existingAddon.id; - ok((id == "addon1@tests.mozilla.org" || id == "addon8@tests.mozilla.org"), - "Saw unexpected onNewInstall for " + id); - }, - - onDownloadStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadFailed: function(aInstall) { - ok(false, "Should not have seen onDownloadFailed event"); - }, - - onDownloadCancelled: function(aInstall) { - ok(false, "Should not have seen onDownloadCancelled event"); - }, - - onInstallStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onInstallEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - do_check_eq(aInstall.existingAddon.pendingUpgrade.install, aInstall); - - do_execute_soon(check_test_14); - }, - - onInstallFailed: function(aInstall) { - ok(false, "Should not have seen onInstallFailed event"); - }, - - onInstallCancelled: function(aInstall) { - ok(false, "Should not have seen onInstallCancelled event"); - }, - }); - - AddonManagerInternal.backgroundUpdateCheck(); - }); - }); - - check_test_14 = () => { - restartManager(); - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon8@tests.mozilla.org"], function([a1, a8]) { - do_check_neq(a1, null); - do_check_eq(a1.version, "2.0"); - a1.uninstall(); - - do_check_neq(a8, null); - do_check_eq(a8.version, "1.0"); - a8.uninstall(); - - run_next_test(); - }); - } - - // Test that background update checks doesn't update an add-on that is - // pending uninstall - let check_test_15; - add_test(function run_test_15() { - restartManager(); - - // Have an add-on there that will be updated so we see some events from it - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/" + updateFile, - targetApplications: [{ - id: appId, - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon8@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/" + updateFile, - targetApplications: [{ - id: appId, - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 8", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { - a8.uninstall(); - do_check_false(hasFlag(a8.permissions, AddonManager.PERM_CAN_UPGRADE)); - - // The background update check will find updates for both add-ons but only - // proceed to install one of them. - AddonManager.addInstallListener({ - onNewInstall: function(aInstall) { - let id = aInstall.existingAddon.id; - ok((id == "addon1@tests.mozilla.org" || id == "addon8@tests.mozilla.org"), - "Saw unexpected onNewInstall for " + id); - }, - - onDownloadStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadFailed: function(aInstall) { - ok(false, "Should not have seen onDownloadFailed event"); - }, - - onDownloadCancelled: function(aInstall) { - ok(false, "Should not have seen onDownloadCancelled event"); - }, - - onInstallStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onInstallEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - do_execute_soon(check_test_15); - }, - - onInstallFailed: function(aInstall) { - ok(false, "Should not have seen onInstallFailed event"); - }, - - onInstallCancelled: function(aInstall) { - ok(false, "Should not have seen onInstallCancelled event"); - }, - }); - - AddonManagerInternal.backgroundUpdateCheck(); - }); - }); - - check_test_15 = () => { - restartManager(); - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon8@tests.mozilla.org"], function([a1, a8]) { - do_check_neq(a1, null); - do_check_eq(a1.version, "2.0"); - a1.uninstall(); - - do_check_eq(a8, null); - - run_next_test(); - }); - } - - add_test(function run_test_16() { - restartManager(); - - restartManager(); - - let url = "http://localhost:" + gPort + "/addons/test_install2_1.xpi"; - AddonManager.getInstallForURL(url, function(aInstall) { - aInstall.addListener({ - onInstallEnded: function() { - do_execute_soon(function install_2_1_ended() { - restartManager(); - - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a1) { - do_check_neq(a1.syncGUID, null); - let oldGUID = a1.syncGUID; - - let url = "http://localhost:" + gPort + "/addons/test_install2_2.xpi"; - AddonManager.getInstallForURL(url, function(aInstall) { - aInstall.addListener({ - onInstallEnded: function() { - do_execute_soon(function install_2_2_ended() { - restartManager(); - - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { - do_check_neq(a2.syncGUID, null); - do_check_eq(oldGUID, a2.syncGUID); - - a2.uninstall(); - run_next_test(); - }); - }); - } - }); - aInstall.install(); - }, "application/x-xpinstall"); - }); - }); - } - }); - aInstall.install(); - }, "application/x-xpinstall"); - }); - - // Test that the update check correctly observes the - // extensions.strictCompatibility pref and compatibility overrides. - add_test(function run_test_17() { - restartManager(); - - writeInstallRDFForExtension({ - id: "addon9@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/" + updateFile, - targetApplications: [{ - id: appId, - minVersion: "0.1", - maxVersion: "0.2" - }], - name: "Test Addon 9", - }, profileDir); - restartManager(); - - AddonManager.addInstallListener({ - onNewInstall: function(aInstall) { - equal(aInstall.existingAddon.id, "addon9@tests.mozilla.org", - "Saw unexpected onNewInstall for " + aInstall.existingAddon.id); - do_check_eq(aInstall.version, "3.0"); - }, - onDownloadFailed: function(aInstall) { - AddonManager.getAddonByID("addon9@tests.mozilla.org", function(a9) { - a9.uninstall(); - run_next_test(); - }); - } - }); - - Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, - "http://localhost:" + gPort + "/data/test_update.xml"); - Services.prefs.setCharPref(PREF_GETADDONS_BYIDS_PERFORMANCE, - "http://localhost:" + gPort + "/data/test_update.xml"); - Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); - - AddonManagerInternal.backgroundUpdateCheck(); - }); - - // Tests that compatibility updates are applied to addons when the updated - // compatibility data wouldn't match with strict compatibility enabled. - add_test(function run_test_18() { - restartManager(); - writeInstallRDFForExtension({ - id: "addon10@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/" + updateFile, - targetApplications: [{ - id: appId, - minVersion: "0.1", - maxVersion: "0.2" - }], - name: "Test Addon 10", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon10@tests.mozilla.org", function(a10) { - do_check_neq(a10, null); - - a10.findUpdates({ - onNoCompatibilityUpdateAvailable: function() { - ok(false, "Should have seen compatibility information"); - }, - - onUpdateAvailable: function() { - ok(false, "Should not have seen an available update"); - }, - - onUpdateFinished: function() { - a10.uninstall(); - run_next_test(); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); - }); - }); - - // Test that the update check correctly observes when an addon opts-in to - // strict compatibility checking. - add_test(function run_test_19() { - restartManager(); - writeInstallRDFForExtension({ - id: "addon11@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/" + updateFile, - targetApplications: [{ - id: appId, - minVersion: "0.1", - maxVersion: "0.2" - }], - name: "Test Addon 11", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon11@tests.mozilla.org", function(a11) { - do_check_neq(a11, null); - - a11.findUpdates({ - onCompatibilityUpdateAvailable: function() { - ok(false, "Should have not have seen compatibility information"); - }, - - onUpdateAvailable: function() { - ok(false, "Should not have seen an available update"); - }, - - onUpdateFinished: function() { - a11.uninstall(); - run_next_test(); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); - }); - }); - - // Test that the update succeeds when the update.rdf URN contains a type prefix - // different from the add-on type - let continue_test_20; - add_test(function run_test_20() { - restartManager(); - writeInstallRDFForExtension({ - id: "addon12@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/" + updateFile, - targetApplications: [{ - id: appId, - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 12", - }, profileDir); - restartManager(); - - prepare_test({}, [ - "onNewInstall", - "onDownloadStarted", - "onDownloadEnded" - ], continue_test_20); - - AddonManagerPrivate.backgroundUpdateCheck(); - }); - - let check_test_20; - continue_test_20 = (install) => { - do_check_neq(install.existingAddon, null); - do_check_eq(install.existingAddon.id, "addon12@tests.mozilla.org"); - - prepare_test({ - "addon12@tests.mozilla.org": [ - "onInstalling" - ] - }, [ - "onInstallStarted", - "onInstallEnded", - ], callback_soon(check_test_20)); - } - - check_test_20 = (install) => { - do_check_eq(install.existingAddon.pendingUpgrade.install, install); - - restartManager(); - AddonManager.getAddonByID("addon12@tests.mozilla.org", function(a12) { - do_check_neq(a12, null); - do_check_eq(a12.version, "2.0"); - do_check_eq(a12.type, "extension"); - a12.uninstall(); - - do_execute_soon(() => { - restartManager(); - run_next_test() - }); - }); - } - - add_task(function cleanup() { - let addons = yield new Promise(resolve => { - AddonManager.getAddonsByTypes(["extension"], resolve); - }); - - for (let addon of addons) - addon.uninstall(); - - yield promiseRestartManager(); + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(olda1) { + do_check_neq(olda1, null); + do_check_eq(olda1.version, "1.0"); + do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); shutdownManager(); - yield new Promise(do_execute_soon); + startupManager(); + + do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); + + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + do_check_neq(a1, null); + do_check_eq(a1.version, "2.0"); + do_check_true(isExtensionInAddonsList(profileDir, a1.id)); + do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DISABLE); + do_check_eq(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); + do_check_true(a1.foreignInstall); + do_check_neq(a1.syncGUID, null); + do_check_eq(originalSyncGUID, a1.syncGUID); + + a1.uninstall(); + do_execute_soon(run_test_3); + }); + })); +} + + +// Check that an update check finds compatibility updates and applies them +function run_test_3() { + restartManager(); + + AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { + do_check_neq(a2, null); + do_check_true(a2.isActive); + do_check_true(a2.isCompatible); + do_check_false(a2.appDisabled); + do_check_true(a2.isCompatibleWith("0")); + + a2.findUpdates({ + onCompatibilityUpdateAvailable: function(addon) { + do_check_true(a2.isCompatible); + do_check_false(a2.appDisabled); + do_check_true(a2.isActive); + }, + + onUpdateAvailable: function(addon, install) { + do_throw("Should not have seen an available update"); + }, + + onNoUpdateAvailable: function(addon) { + do_check_eq(addon, a2); + do_execute_soon(check_test_3); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); +} + +function check_test_3() { + restartManager(); + AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { + do_check_neq(a2, null); + do_check_true(a2.isActive); + do_check_true(a2.isCompatible); + do_check_false(a2.appDisabled); + a2.uninstall(); + + run_test_4(); + }); +} + +// Checks that we see no compatibility information when there is none. +function run_test_4() { + AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { + do_check_neq(a3, null); + do_check_false(a3.isActive); + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + do_check_true(a3.isCompatibleWith("5")); + do_check_false(a3.isCompatibleWith("2")); + + a3.findUpdates({ + sawUpdate: false, + onCompatibilityUpdateAvailable: function(addon) { + do_throw("Should not have seen compatibility information"); + }, + + onNoCompatibilityUpdateAvailable: function(addon) { + this.sawUpdate = true; + }, + + onUpdateAvailable: function(addon, install) { + do_throw("Should not have seen an available update"); + }, + + onNoUpdateAvailable: function(addon) { + do_check_true(this.sawUpdate); + run_test_5(); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); +} + +// Checks that compatibility info for future apps are detected but don't make +// the item compatibile. +function run_test_5() { + AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { + do_check_neq(a3, null); + do_check_false(a3.isActive); + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + do_check_true(a3.isCompatibleWith("5")); + do_check_false(a3.isCompatibleWith("2")); + + a3.findUpdates({ + sawUpdate: false, + onCompatibilityUpdateAvailable: function(addon) { + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + do_check_false(a3.isActive); + this.sawUpdate = true; + }, + + onNoCompatibilityUpdateAvailable: function(addon) { + do_throw("Should have seen some compatibility information"); + }, + + onUpdateAvailable: function(addon, install) { + do_throw("Should not have seen an available update"); + }, + + onNoUpdateAvailable: function(addon) { + do_check_true(this.sawUpdate); + do_execute_soon(check_test_5); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED, "3.0"); + }); +} + +function check_test_5() { + restartManager(); + AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { + do_check_neq(a3, null); + do_check_false(a3.isActive); + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + + a3.uninstall(); + do_execute_soon(run_test_6); + }); +} + +// Test that background update checks work +function run_test_6() { + restartManager(); + + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + restartManager(); + + prepare_test({}, [ + "onNewInstall", + "onDownloadStarted", + "onDownloadEnded" + ], continue_test_6); + + AddonManagerInternal.backgroundUpdateCheck(); +} + +function continue_test_6(install) { + do_check_neq(install.existingAddon, null); + do_check_eq(install.existingAddon.id, "addon1@tests.mozilla.org"); + + prepare_test({ + "addon1@tests.mozilla.org": [ + "onInstalling" + ] + }, [ + "onInstallStarted", + "onInstallEnded", + ], callback_soon(check_test_6)); +} + +function check_test_6(install) { + do_check_eq(install.existingAddon.pendingUpgrade.install, install); + + restartManager(); + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + do_check_neq(a1, null); + do_check_eq(a1.version, "2.0"); + do_check_eq(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); + a1.uninstall(); + do_execute_soon(run_test_7); }); } // Test that background update checks work for lightweight themes -add_test(function run_test_7() { - startupManager(); +function run_test_7() { + restartManager(); LightweightThemeManager.currentTheme = { id: "1", @@ -1258,7 +484,7 @@ add_test(function run_test_7() { AddonManagerInternal.backgroundUpdateCheck(); }); -}); +} function check_test_7() { AddonManager.getAddonByID("1@personas.mozilla.org", function(p1) { @@ -1275,13 +501,13 @@ function check_test_7() { gInstallDate = p1.installDate.getTime(); - run_next_test(); + run_test_7_cache(); }); } // Test that background update checks for lightweight themes do not use the cache // The update body from test 7 shouldn't be used since the cache should be bypassed. -add_test(function () { +function run_test_7_cache() { // XXX The lightweight theme manager strips non-https updateURLs so hack it // back in. let themes = JSON.parse(Services.prefs.getCharPref("lightweightThemes.usedThemes")); @@ -1324,7 +550,7 @@ add_test(function () { AddonManagerInternal.backgroundUpdateCheck(); }); -}); +} function check_test_7_cache() { AddonManager.getAddonByID("1@personas.mozilla.org", function(p1) { @@ -1345,6 +571,740 @@ function check_test_7_cache() { do_check_eq(p1.installDate.getTime(), gInstallDate); do_check_true(p1.installDate.getTime() < p1.updateDate.getTime()); - run_next_test(); + do_execute_soon(run_test_8); + }); +} + +// Verify the parameter escaping in update urls. +function run_test_8() { + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "5.0", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "2" + }], + name: "Test Addon 1", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon2@tests.mozilla.org", + version: "67.0.5b1", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: "toolkit@mozilla.org", + minVersion: "0", + maxVersion: "3" + }], + name: "Test Addon 2", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon3@tests.mozilla.org", + version: "1.3+", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "0", + maxVersion: "0" + }, { + id: "toolkit@mozilla.org", + minVersion: "0", + maxVersion: "3" + }], + name: "Test Addon 3", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon4@tests.mozilla.org", + version: "0.5ab6", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "5" + }], + name: "Test Addon 4", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon5@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 5", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon6@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 6", + }, profileDir); + + restartManager(); + + AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(a2) { + a2.userDisabled = true; + restartManager(); + + testserver.registerPathHandler("/data/param_test.rdf", function(request, response) { + do_check_neq(request.queryString, ""); + let [req_version, item_id, item_version, + item_maxappversion, item_status, + app_id, app_version, current_app_version, + app_os, app_abi, app_locale, update_type] = + request.queryString.split("/").map(a => decodeURIComponent(a)); + + do_check_eq(req_version, "2"); + + switch(item_id) { + case "addon1@tests.mozilla.org": + do_check_eq(item_version, "5.0"); + do_check_eq(item_maxappversion, "2"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "97"); + break; + case "addon2@tests.mozilla.org": + do_check_eq(item_version, "67.0.5b1"); + do_check_eq(item_maxappversion, "3"); + do_check_eq(item_status, "userDisabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "49"); + break; + case "addon3@tests.mozilla.org": + do_check_eq(item_version, "1.3+"); + do_check_eq(item_maxappversion, "0"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "112"); + break; + case "addon4@tests.mozilla.org": + do_check_eq(item_version, "0.5ab6"); + do_check_eq(item_maxappversion, "5"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "2"); + do_check_eq(update_type, "98"); + break; + case "addon5@tests.mozilla.org": + do_check_eq(item_version, "1.0"); + do_check_eq(item_maxappversion, "1"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "35"); + break; + case "addon6@tests.mozilla.org": + do_check_eq(item_version, "1.0"); + do_check_eq(item_maxappversion, "1"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "99"); + break; + default: + do_throw("Update request for unexpected add-on " + item_id); + } + + do_check_eq(app_id, "xpcshell@tests.mozilla.org"); + do_check_eq(current_app_version, "1"); + do_check_eq(app_os, "XPCShell"); + do_check_eq(app_abi, "noarch-spidermonkey"); + do_check_eq(app_locale, "fr-FR"); + + request.setStatusLine(null, 500, "Server Error"); + }); + + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon2@tests.mozilla.org", + "addon3@tests.mozilla.org", + "addon4@tests.mozilla.org", + "addon5@tests.mozilla.org", + "addon6@tests.mozilla.org"], + function([a1, a2, a3, a4, a5, a6]) { + let count = 6; + + function run_next_test() { + a1.uninstall(); + a2.uninstall(); + a3.uninstall(); + a4.uninstall(); + a5.uninstall(); + a6.uninstall(); + + restartManager(); + run_test_9(); + } + + let compatListener = { + onUpdateFinished: function(addon, error) { + if (--count == 0) + do_execute_soon(run_next_test); + } + }; + + let updateListener = { + onUpdateAvailable: function(addon, update) { + // Dummy so the update checker knows we care about new versions + }, + + onUpdateFinished: function(addon, error) { + if (--count == 0) + do_execute_soon(run_next_test); + } + }; + + a1.findUpdates(updateListener, AddonManager.UPDATE_WHEN_USER_REQUESTED); + a2.findUpdates(compatListener, AddonManager.UPDATE_WHEN_ADDON_INSTALLED); + a3.findUpdates(updateListener, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); + a4.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "2"); + a5.findUpdates(compatListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); + a6.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); + }); + })); +} + +// Tests that if an install.rdf claims compatibility then the add-on will be +// seen as compatible regardless of what the update.rdf says. +function run_test_9() { + writeInstallRDFForExtension({ + id: "addon4@tests.mozilla.org", + version: "5.0", + updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "0", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + + restartManager(); + + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + do_check_true(a4.isActive); + do_check_true(a4.isCompatible); + + run_test_10(); + }); +} + +// Tests that a normal update check won't decrease a targetApplication's +// maxVersion. +function run_test_10() { + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + a4.findUpdates({ + onUpdateFinished: function(addon) { + do_check_true(addon.isCompatible); + + run_test_11(); + } + }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); + }); +} + +// Tests that an update check for a new application will decrease a +// targetApplication's maxVersion. +function run_test_11() { + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + a4.findUpdates({ + onUpdateFinished: function(addon) { + do_check_true(addon.isCompatible); + + do_execute_soon(run_test_12); + } + }, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); + }); +} + +// Check that the decreased maxVersion applied and disables the add-on +function run_test_12() { + restartManager(); + + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + do_check_true(a4.isActive); + do_check_true(a4.isCompatible); + + a4.uninstall(); + do_execute_soon(run_test_13); + }); +} + +// Tests that a compatibility update is passed to the listener when there is +// compatibility info for the current version of the app but not for the +// version of the app that the caller requested an update check for, when +// strict compatibility checking is disabled. +function run_test_13() { + restartManager(); + + // Not initially compatible but the update check will make it compatible + writeInstallRDFForExtension({ + id: "addon7@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "0", + maxVersion: "0" + }], + name: "Test Addon 7", + }, profileDir); + restartManager(); + + AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { + do_check_neq(a7, null); + do_check_true(a7.isActive); + do_check_true(a7.isCompatible); + do_check_false(a7.appDisabled); + do_check_true(a7.isCompatibleWith("0")); + + a7.findUpdates({ + sawUpdate: false, + onNoCompatibilityUpdateAvailable: function(addon) { + do_throw("Should have seen compatibility information"); + }, + + onUpdateAvailable: function(addon, install) { + do_throw("Should not have seen an available update"); + }, + + onUpdateFinished: function(addon) { + do_check_true(addon.isCompatible); + do_execute_soon(check_test_13); + } + }, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "3.0"); + }); +} + +function check_test_13() { + restartManager(); + AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { + do_check_neq(a7, null); + do_check_true(a7.isActive); + do_check_true(a7.isCompatible); + do_check_false(a7.appDisabled); + + a7.uninstall(); + do_execute_soon(run_test_14); + }); +} + +// Test that background update checks doesn't update an add-on that isn't +// allowed to update automatically. +function run_test_14() { + restartManager(); + + // Have an add-on there that will be updated so we see some events from it + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon8@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 8", + }, profileDir); + restartManager(); + + AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { + a8.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + + // The background update check will find updates for both add-ons but only + // proceed to install one of them. + AddonManager.addInstallListener({ + onNewInstall: function(aInstall) { + if (aInstall.existingAddon.id != "addon1@tests.mozilla.org" && + aInstall.existingAddon.id != "addon8@tests.mozilla.org") + do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); + }, + + onDownloadStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadFailed: function(aInstall) { + do_throw("Should not have seen onDownloadFailed event"); + }, + + onDownloadCancelled: function(aInstall) { + do_throw("Should not have seen onDownloadCancelled event"); + }, + + onInstallStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onInstallEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + do_check_eq(aInstall.existingAddon.pendingUpgrade.install, aInstall); + + do_execute_soon(check_test_14); + }, + + onInstallFailed: function(aInstall) { + do_throw("Should not have seen onInstallFailed event"); + }, + + onInstallCancelled: function(aInstall) { + do_throw("Should not have seen onInstallCancelled event"); + }, + }); + + AddonManagerInternal.backgroundUpdateCheck(); + }); +} + +function check_test_14() { + restartManager(); + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon8@tests.mozilla.org"], function([a1, a8]) { + do_check_neq(a1, null); + do_check_eq(a1.version, "2.0"); + a1.uninstall(); + + do_check_neq(a8, null); + do_check_eq(a8.version, "1.0"); + a8.uninstall(); + + do_execute_soon(run_test_15); + }); +} + +// Test that background update checks doesn't update an add-on that is +// pending uninstall +function run_test_15() { + restartManager(); + + // Have an add-on there that will be updated so we see some events from it + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon8@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 8", + }, profileDir); + restartManager(); + + AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { + a8.uninstall(); + do_check_false(hasFlag(a8.permissions, AddonManager.PERM_CAN_UPGRADE)); + + // The background update check will find updates for both add-ons but only + // proceed to install one of them. + AddonManager.addInstallListener({ + onNewInstall: function(aInstall) { + if (aInstall.existingAddon.id != "addon1@tests.mozilla.org" && + aInstall.existingAddon.id != "addon8@tests.mozilla.org") + do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); + }, + + onDownloadStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadFailed: function(aInstall) { + do_throw("Should not have seen onDownloadFailed event"); + }, + + onDownloadCancelled: function(aInstall) { + do_throw("Should not have seen onDownloadCancelled event"); + }, + + onInstallStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onInstallEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + do_execute_soon(check_test_15); + }, + + onInstallFailed: function(aInstall) { + do_throw("Should not have seen onInstallFailed event"); + }, + + onInstallCancelled: function(aInstall) { + do_throw("Should not have seen onInstallCancelled event"); + }, + }); + + AddonManagerInternal.backgroundUpdateCheck(); + }); +} + +function check_test_15() { + restartManager(); + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon8@tests.mozilla.org"], function([a1, a8]) { + do_check_neq(a1, null); + do_check_eq(a1.version, "2.0"); + a1.uninstall(); + + do_check_eq(a8, null); + + do_execute_soon(run_test_16); + }); +} + +function run_test_16() { + restartManager(); + + restartManager(); + + let url = "http://localhost:" + gPort + "/addons/test_install2_1.xpi"; + AddonManager.getInstallForURL(url, function(aInstall) { + aInstall.addListener({ + onInstallEnded: function() { + do_execute_soon(function install_2_1_ended() { + restartManager(); + + AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a1) { + do_check_neq(a1.syncGUID, null); + let oldGUID = a1.syncGUID; + + let url = "http://localhost:" + gPort + "/addons/test_install2_2.xpi"; + AddonManager.getInstallForURL(url, function(aInstall) { + aInstall.addListener({ + onInstallEnded: function() { + do_execute_soon(function install_2_2_ended() { + restartManager(); + + AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { + do_check_neq(a2.syncGUID, null); + do_check_eq(oldGUID, a2.syncGUID); + + a2.uninstall(); + do_execute_soon(run_test_17); + }); + }); + } + }); + aInstall.install(); + }, "application/x-xpinstall"); + }); + }); + } + }); + aInstall.install(); + }, "application/x-xpinstall"); +} + +// Test that the update check correctly observes the +// extensions.strictCompatibility pref and compatibility overrides. +function run_test_17() { + restartManager(); + + writeInstallRDFForExtension({ + id: "addon9@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 9", + }, profileDir); + restartManager(); + + AddonManager.addInstallListener({ + onNewInstall: function(aInstall) { + if (aInstall.existingAddon.id != "addon9@tests.mozilla.org") + do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); + do_check_eq(aInstall.version, "3.0"); + }, + onDownloadFailed: function(aInstall) { + AddonManager.getAddonByID("addon9@tests.mozilla.org", function(a9) { + a9.uninstall(); + do_execute_soon(run_test_18); + }); + } + }); + + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, + "http://localhost:" + gPort + "/data/test_update.xml"); + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS_PERFORMANCE, + "http://localhost:" + gPort + "/data/test_update.xml"); + Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); + + AddonManagerInternal.backgroundUpdateCheck(); +} + +// Tests that compatibility updates are applied to addons when the updated +// compatibility data wouldn't match with strict compatibility enabled. +function run_test_18() { + restartManager(); + writeInstallRDFForExtension({ + id: "addon10@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 10", + }, profileDir); + restartManager(); + + AddonManager.getAddonByID("addon10@tests.mozilla.org", function(a10) { + do_check_neq(a10, null); + + a10.findUpdates({ + onNoCompatibilityUpdateAvailable: function() { + do_throw("Should have seen compatibility information"); + }, + + onUpdateAvailable: function() { + do_throw("Should not have seen an available update"); + }, + + onUpdateFinished: function() { + a10.uninstall(); + do_execute_soon(run_test_19); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); +} + +// Test that the update check correctly observes when an addon opts-in to +// strict compatibility checking. +function run_test_19() { + restartManager(); + writeInstallRDFForExtension({ + id: "addon11@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 11", + }, profileDir); + restartManager(); + + AddonManager.getAddonByID("addon11@tests.mozilla.org", function(a11) { + do_check_neq(a11, null); + + a11.findUpdates({ + onCompatibilityUpdateAvailable: function() { + do_throw("Should have not have seen compatibility information"); + }, + + onUpdateAvailable: function() { + do_throw("Should not have seen an available update"); + }, + + onUpdateFinished: function() { + a11.uninstall(); + do_execute_soon(run_test_20); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); +} + +// Test that the update succeeds when the update.rdf URN contains a type prefix +// different from the add-on type +function run_test_20() { + restartManager(); + writeInstallRDFForExtension({ + id: "addon12@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 12", + }, profileDir); + restartManager(); + + prepare_test({}, [ + "onNewInstall", + "onDownloadStarted", + "onDownloadEnded" + ], continue_test_20); + + AddonManagerPrivate.backgroundUpdateCheck(); +} + +function continue_test_20(install) { + do_check_neq(install.existingAddon, null); + do_check_eq(install.existingAddon.id, "addon12@tests.mozilla.org"); + + prepare_test({ + "addon12@tests.mozilla.org": [ + "onInstalling" + ] + }, [ + "onInstallStarted", + "onInstallEnded", + ], callback_soon(check_test_20)); +} + +function check_test_20(install) { + do_check_eq(install.existingAddon.pendingUpgrade.install, install); + + restartManager(); + AddonManager.getAddonByID("addon12@tests.mozilla.org", function(a12) { + do_check_neq(a12, null); + do_check_eq(a12.version, "2.0"); + do_check_eq(a12.type, "extension"); + a12.uninstall(); + + do_execute_soon(() => { + restartManager(); + end_test(); + }); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_update_ignorecompat.js b/toolkit/mozapps/extensions/test/xpcshell/test_update_ignorecompat.js index 16fa3a4c18b3..672594088fb3 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_update_ignorecompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_update_ignorecompat.js @@ -9,13 +9,12 @@ const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled"; // The test extension uses an insecure update url. Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false); -Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, false); Components.utils.import("resource://testing-common/httpd.js"); -var testserver = createHttpServer(); +var testserver = new HttpServer(); +testserver.start(-1); gPort = testserver.identity.primaryPort; mapFile("/data/test_update.rdf", testserver); -mapFile("/data/test_update.json", testserver); mapFile("/data/test_update.xml", testserver); testserver.registerDirectory("/addons/", do_get_file("addons")); @@ -23,86 +22,77 @@ const profileDir = gProfD.clone(); profileDir.append("extensions"); -createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1"); +function run_test() { + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); -let testParams = [ - { updateFile: "test_update.rdf", - appId: "xpcshell@tests.mozilla.org" }, - { updateFile: "test_update.json", - appId: "toolkit@mozilla.org" }, -]; + run_test_1(); +} -for (let test of testParams) { - let { updateFile, appId } = test; +// Test that the update check correctly observes the +// extensions.strictCompatibility pref and compatibility overrides. +function run_test_1() { + writeInstallRDFForExtension({ + id: "addon9@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 9", + }, profileDir); + restartManager(); - // Test that the update check correctly observes the - // extensions.strictCompatibility pref and compatibility overrides. - add_test(function () { - writeInstallRDFForExtension({ - id: "addon9@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/" + updateFile, - targetApplications: [{ - id: appId, - minVersion: "0.1", - maxVersion: "0.2" - }], - name: "Test Addon 9", - }, profileDir); - - restartManager(); - - AddonManager.addInstallListener({ - onNewInstall: function(aInstall) { - if (aInstall.existingAddon.id != "addon9@tests.mozilla.org") - do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); - do_check_eq(aInstall.version, "4.0"); - }, - onDownloadFailed: function(aInstall) { - run_next_test(); - } - }); - - Services.prefs.setCharPref(PREF_GETADDONS_BYIDS_PERFORMANCE, - "http://localhost:" + gPort + "/data/" + updateFile); - Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); - - AddonManagerInternal.backgroundUpdateCheck(); + AddonManager.addInstallListener({ + onNewInstall: function(aInstall) { + if (aInstall.existingAddon.id != "addon9@tests.mozilla.org") + do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); + do_check_eq(aInstall.version, "4.0"); + }, + onDownloadFailed: function(aInstall) { + do_execute_soon(run_test_2); + } }); - // Test that the update check correctly observes when an addon opts-in to - // strict compatibility checking. - add_test(function () { - writeInstallRDFForExtension({ - id: "addon11@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/" + updateFile, - targetApplications: [{ - id: appId, - minVersion: "0.1", - maxVersion: "0.2" - }], - name: "Test Addon 11", - }, profileDir); + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS_PERFORMANCE, + "http://localhost:" + gPort + "/data/test_update.xml"); + Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); - restartManager(); + AddonManagerInternal.backgroundUpdateCheck(); +} - AddonManager.getAddonByID("addon11@tests.mozilla.org", function(a11) { - do_check_neq(a11, null); +// Test that the update check correctly observes when an addon opts-in to +// strict compatibility checking. +function run_test_2() { + writeInstallRDFForExtension({ + id: "addon11@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 11", + }, profileDir); + restartManager(); - a11.findUpdates({ - onCompatibilityUpdateAvailable: function() { - do_throw("Should not have seen compatibility information"); - }, + AddonManager.getAddonByID("addon11@tests.mozilla.org", function(a11) { + do_check_neq(a11, null); - onUpdateAvailable: function() { - do_throw("Should not have seen an available update"); - }, + a11.findUpdates({ + onCompatibilityUpdateAvailable: function() { + do_throw("Should have not have seen compatibility information"); + }, - onUpdateFinished: function() { - run_next_test(); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); - }); + onNoUpdateAvailable: function() { + do_throw("Should have seen an available update"); + }, + + onUpdateFinished: function() { + end_test(); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js b/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js index 3fc16a0f7e15..3c56f9adb84a 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js @@ -9,8 +9,7 @@ const PREF_SELECTED_LOCALE = "general.useragent.locale"; const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled"; // The test extension uses an insecure update url. -Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false); -Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, true); +Services.prefs.setBoolPref("extensions.checkUpdateSecurity", false); // This test requires lightweight themes update to be enabled even if the app // doesn't support lightweight themes. Services.prefs.setBoolPref("lightweightThemes.update.enabled", true); @@ -24,10 +23,10 @@ const PARAMS = "?%REQ_VERSION%/%ITEM_ID%/%ITEM_VERSION%/%ITEM_MAXAPPVERSION%/" + var gInstallDate; Components.utils.import("resource://testing-common/httpd.js"); -var testserver = createHttpServer(); +var testserver = new HttpServer(); +testserver.start(-1); gPort = testserver.identity.primaryPort; mapFile("/data/test_update.rdf", testserver); -mapFile("/data/test_update.json", testserver); mapFile("/data/test_update.xml", testserver); testserver.registerDirectory("/addons/", do_get_file("addons")); @@ -35,1013 +34,383 @@ const profileDir = gProfD.clone(); profileDir.append("extensions"); function run_test() { - createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1"); - + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); Services.prefs.setBoolPref(PREF_MATCH_OS_LOCALE, false); Services.prefs.setCharPref(PREF_SELECTED_LOCALE, "fr-FR"); + Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, true); - run_next_test(); + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon2@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "0", + maxVersion: "0" + }], + name: "Test Addon 2", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon3@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "5", + maxVersion: "5" + }], + name: "Test Addon 3", + }, profileDir); + + startupManager(); + + do_test_pending(); + run_test_1(); } -let testParams = [ - { updateFile: "test_update.rdf", - appId: "xpcshell@tests.mozilla.org" }, - { updateFile: "test_update.json", - appId: "toolkit@mozilla.org" }, -]; +function end_test() { + Services.prefs.clearUserPref(PREF_EM_STRICT_COMPATIBILITY); -for (let test of testParams) { - let { updateFile, appId } = test; + testserver.stop(do_test_finished); +} - add_test(function run_test() { - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/" + updateFile, - targetApplications: [{ - id: appId, - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); +// Verify that an update is available and can be installed. +function run_test_1() { + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + do_check_neq(a1, null); + do_check_eq(a1.version, "1.0"); + do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DEFAULT); + do_check_eq(a1.releaseNotesURI, null); - writeInstallRDFForExtension({ - id: "addon2@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/" + updateFile, - targetApplications: [{ - id: appId, - minVersion: "0", - maxVersion: "0" - }], - name: "Test Addon 2", - }, profileDir); + a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT; - writeInstallRDFForExtension({ - id: "addon3@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/" + updateFile, - targetApplications: [{ - id: appId, - minVersion: "5", - maxVersion: "5" - }], - name: "Test Addon 3", - }, profileDir); - - startupManager(); - - run_next_test(); - }); - - // Verify that an update is available and can be installed. - let check_test_1; - add_test(function run_test_1() { - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { - do_check_neq(a1, null); - do_check_eq(a1.version, "1.0"); - do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DEFAULT); - do_check_eq(a1.releaseNotesURI, null); - - a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT; - - prepare_test({ - "addon1@tests.mozilla.org": [ - ["onPropertyChanged", ["applyBackgroundUpdates"]] - ] - }); - a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; - check_test_completed(); - - a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; - - prepare_test({}, [ - "onNewInstall", - ]); - - a1.findUpdates({ - onNoCompatibilityUpdateAvailable: function(addon) { - ok(false, "Should not have seen onNoCompatibilityUpdateAvailable notification"); - }, - - onUpdateAvailable: function(addon, install) { - ensure_test_completed(); - - AddonManager.getAllInstalls(function(aInstalls) { - do_check_eq(aInstalls.length, 1); - do_check_eq(aInstalls[0], install); - - do_check_eq(addon, a1); - do_check_eq(install.name, addon.name); - do_check_eq(install.version, "2.0"); - do_check_eq(install.state, AddonManager.STATE_AVAILABLE); - do_check_eq(install.existingAddon, addon); - do_check_eq(install.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); - - // Verify that another update check returns the same AddonInstall - a1.findUpdates({ - onNoCompatibilityUpdateAvailable: function(addon) { - ok(false, "Should not have seen onNoCompatibilityUpdateAvailable notification"); - }, - - onUpdateAvailable: function(newAddon, newInstall) { - AddonManager.getAllInstalls(function(aInstalls) { - do_check_eq(aInstalls.length, 1); - do_check_eq(aInstalls[0], install); - do_check_eq(newAddon, addon); - do_check_eq(newInstall, install); - - prepare_test({}, [ - "onDownloadStarted", - "onDownloadEnded", - ], check_test_1); - install.install(); - }); - }, - - onNoUpdateAvailable: function(addon) { - ok(false, "Should not have seen onNoUpdateAvailable notification"); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); - }); - }, - - onNoUpdateAvailable: function(addon) { - ok(false, "Should not have seen onNoUpdateAvailable notification"); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + prepare_test({ + "addon1@tests.mozilla.org": [ + ["onPropertyChanged", ["applyBackgroundUpdates"]] + ] }); - }); + a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + check_test_completed(); - let run_test_2; - check_test_1 = (install) => { - ensure_test_completed(); - do_check_eq(install.state, AddonManager.STATE_DOWNLOADED); - run_test_2(install); - return false; - }; + a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; - // Continue installing the update. - let check_test_2; - run_test_2 = (install) => { - // Verify that another update check returns no new update - install.existingAddon.findUpdates({ + prepare_test({}, [ + "onNewInstall", + ]); + + a1.findUpdates({ onNoCompatibilityUpdateAvailable: function(addon) { - ok(false, "Should not have seen onNoCompatibilityUpdateAvailable notification"); + do_throw("Should not have seen onNoCompatibilityUpdateAvailable notification"); }, onUpdateAvailable: function(addon, install) { - ok(false, "Should find no available update when one is already downloading"); - }, + ensure_test_completed(); - onNoUpdateAvailable: function(addon) { AddonManager.getAllInstalls(function(aInstalls) { do_check_eq(aInstalls.length, 1); do_check_eq(aInstalls[0], install); - prepare_test({ - "addon1@tests.mozilla.org": [ - "onInstalling" - ] - }, [ - "onInstallStarted", - "onInstallEnded", - ], check_test_2); - install.install(); + do_check_eq(addon, a1); + do_check_eq(install.name, addon.name); + do_check_eq(install.version, "2.0"); + do_check_eq(install.state, AddonManager.STATE_AVAILABLE); + do_check_eq(install.existingAddon, addon); + do_check_eq(install.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); + + // Verify that another update check returns the same AddonInstall + a1.findUpdates({ + onNoCompatibilityUpdateAvailable: function(addon) { + do_throw("Should not have seen onNoCompatibilityUpdateAvailable notification"); + }, + + onUpdateAvailable: function(newAddon, newInstall) { + AddonManager.getAllInstalls(function(aInstalls) { + do_check_eq(aInstalls.length, 1); + do_check_eq(aInstalls[0], install); + do_check_eq(newAddon, addon); + do_check_eq(newInstall, install); + + prepare_test({}, [ + "onDownloadStarted", + "onDownloadEnded", + ], check_test_1); + install.install(); + }); + }, + + onNoUpdateAvailable: function(addon) { + do_throw("Should not have seen onNoUpdateAvailable notification"); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); }); + }, + + onNoUpdateAvailable: function(addon) { + do_throw("Should not have seen onNoUpdateAvailable notification"); } }, AddonManager.UPDATE_WHEN_USER_REQUESTED); - }; + }); +} - check_test_2 = () => { - ensure_test_completed(); +function check_test_1(install) { + ensure_test_completed(); + do_check_eq(install.state, AddonManager.STATE_DOWNLOADED); + run_test_2(install); + return false; +} - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(olda1) { - do_check_neq(olda1, null); - do_check_eq(olda1.version, "1.0"); - do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); +// Continue installing the update. +function run_test_2(install) { + // Verify that another update check returns no new update + install.existingAddon.findUpdates({ + onNoCompatibilityUpdateAvailable: function(addon) { + do_throw("Should not have seen onNoCompatibilityUpdateAvailable notification"); + }, - shutdownManager(); + onUpdateAvailable: function(addon, install) { + do_throw("Should find no available update when one is already downloading"); + }, - startupManager(); + onNoUpdateAvailable: function(addon) { + AddonManager.getAllInstalls(function(aInstalls) { + do_check_eq(aInstalls.length, 1); + do_check_eq(aInstalls[0], install); - do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); - - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { - do_check_neq(a1, null); - do_check_eq(a1.version, "2.0"); - do_check_true(isExtensionInAddonsList(profileDir, a1.id)); - do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DISABLE); - do_check_eq(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); - - a1.uninstall(); - run_next_test(); + prepare_test({ + "addon1@tests.mozilla.org": [ + "onInstalling" + ] + }, [ + "onInstallStarted", + "onInstallEnded", + ], check_test_2); + install.install(); }); - })); - }; + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); +} +function check_test_2() { + ensure_test_completed(); - // Check that an update check finds compatibility updates and applies them - let check_test_3; - add_test(function run_test_3() { - restartManager(); - - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { - do_check_neq(a2, null); - do_check_false(a2.isActive); - do_check_false(a2.isCompatible); - do_check_true(a2.appDisabled); - do_check_true(a2.isCompatibleWith("0", "0")); - - a2.findUpdates({ - onCompatibilityUpdateAvailable: function(addon) { - do_check_true(a2.isCompatible); - do_check_false(a2.appDisabled); - do_check_false(a2.isActive); - }, - - onUpdateAvailable: function(addon, install) { - ok(false, "Should not have seen an available update"); - }, - - onNoUpdateAvailable: function(addon) { - do_check_eq(addon, a2); - do_execute_soon(check_test_3); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); - }); - }); - - check_test_3 = () => { - restartManager(); - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { - do_check_neq(a2, null); - do_check_true(a2.isActive); - do_check_true(a2.isCompatible); - do_check_false(a2.appDisabled); - a2.uninstall(); - - run_next_test(); - }); - } - - // Checks that we see no compatibility information when there is none. - add_test(function run_test_4() { - AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { - do_check_neq(a3, null); - do_check_false(a3.isActive); - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); - do_check_true(a3.isCompatibleWith("5", "5")); - do_check_false(a3.isCompatibleWith("2", "2")); - - a3.findUpdates({ - sawUpdate: false, - onCompatibilityUpdateAvailable: function(addon) { - ok(false, "Should not have seen compatibility information"); - }, - - onNoCompatibilityUpdateAvailable: function(addon) { - this.sawUpdate = true; - }, - - onUpdateAvailable: function(addon, install) { - ok(false, "Should not have seen an available update"); - }, - - onNoUpdateAvailable: function(addon) { - do_check_true(this.sawUpdate); - run_next_test(); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); - }); - }); - - // Checks that compatibility info for future apps are detected but don't make - // the item compatibile. - let check_test_5; - add_test(function run_test_5() { - AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { - do_check_neq(a3, null); - do_check_false(a3.isActive); - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); - do_check_true(a3.isCompatibleWith("5", "5")); - do_check_false(a3.isCompatibleWith("2", "2")); - - a3.findUpdates({ - sawUpdate: false, - onCompatibilityUpdateAvailable: function(addon) { - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); - do_check_false(a3.isActive); - this.sawUpdate = true; - }, - - onNoCompatibilityUpdateAvailable: function(addon) { - ok(false, "Should have seen some compatibility information"); - }, - - onUpdateAvailable: function(addon, install) { - ok(false, "Should not have seen an available update"); - }, - - onNoUpdateAvailable: function(addon) { - do_check_true(this.sawUpdate); - do_execute_soon(check_test_5); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED, "3.0", "3.0"); - }); - }); - - check_test_5 = () => { - restartManager(); - AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { - do_check_neq(a3, null); - do_check_false(a3.isActive); - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); - - a3.uninstall(); - run_next_test(); - }); - } - - // Test that background update checks work - let continue_test_6; - add_test(function run_test_6() { - restartManager(); - - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/" + updateFile, - targetApplications: [{ - id: appId, - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - restartManager(); - - prepare_test({}, [ - "onNewInstall", - "onDownloadStarted", - "onDownloadEnded" - ], continue_test_6); - - AddonManagerInternal.backgroundUpdateCheck(); - }); - - let check_test_6; - continue_test_6 = (install) => { - do_check_neq(install.existingAddon, null); - do_check_eq(install.existingAddon.id, "addon1@tests.mozilla.org"); - - prepare_test({ - "addon1@tests.mozilla.org": [ - "onInstalling" - ] - }, [ - "onInstallStarted", - "onInstallEnded", - ], callback_soon(check_test_6)); - } - - check_test_6 = (install) => { - do_check_eq(install.existingAddon.pendingUpgrade.install, install); - - restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { - do_check_neq(a1, null); - do_check_eq(a1.version, "2.0"); - do_check_eq(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); - a1.uninstall(); - run_next_test(); - }); - } - - // Verify the parameter escaping in update urls. - add_test(function run_test_8() { - restartManager(); - - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "5.0", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: appId, - minVersion: "1", - maxVersion: "2" - }], - name: "Test Addon 1", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon2@tests.mozilla.org", - version: "67.0.5b1", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "toolkit@mozilla.org", - minVersion: "0", - maxVersion: "3" - }], - name: "Test Addon 2", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon3@tests.mozilla.org", - version: "1.3+", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: appId, - minVersion: "0", - maxVersion: "0" - }, { - id: "toolkit@mozilla.org", - minVersion: "0", - maxVersion: "3" - }], - name: "Test Addon 3", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon4@tests.mozilla.org", - version: "0.5ab6", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: appId, - minVersion: "1", - maxVersion: "5" - }], - name: "Test Addon 4", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon5@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: appId, - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 5", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon6@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: appId, - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 6", - }, profileDir); - - restartManager(); - - AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(a2) { - a2.userDisabled = true; - restartManager(); - - testserver.registerPathHandler("/data/param_test.rdf", function(request, response) { - do_check_neq(request.queryString, ""); - let [req_version, item_id, item_version, - item_maxappversion, item_status, - app_id, app_version, current_app_version, - app_os, app_abi, app_locale, update_type] = - request.queryString.split("/").map(a => decodeURIComponent(a)); - - do_check_eq(req_version, "2"); - - switch(item_id) { - case "addon1@tests.mozilla.org": - do_check_eq(item_version, "5.0"); - do_check_eq(item_maxappversion, "2"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "97"); - break; - case "addon2@tests.mozilla.org": - do_check_eq(item_version, "67.0.5b1"); - do_check_eq(item_maxappversion, "3"); - do_check_eq(item_status, "userDisabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "49"); - break; - case "addon3@tests.mozilla.org": - do_check_eq(item_version, "1.3+"); - do_check_eq(item_maxappversion, "0"); - do_check_eq(item_status, "userEnabled,incompatible"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "112"); - break; - case "addon4@tests.mozilla.org": - do_check_eq(item_version, "0.5ab6"); - do_check_eq(item_maxappversion, "5"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "2"); - do_check_eq(update_type, "98"); - break; - case "addon5@tests.mozilla.org": - do_check_eq(item_version, "1.0"); - do_check_eq(item_maxappversion, "1"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "35"); - break; - case "addon6@tests.mozilla.org": - do_check_eq(item_version, "1.0"); - do_check_eq(item_maxappversion, "1"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "99"); - break; - default: - ok(false, "Update request for unexpected add-on " + item_id); - } - - do_check_eq(app_id, "xpcshell@tests.mozilla.org"); - do_check_eq(current_app_version, "1"); - do_check_eq(app_os, "XPCShell"); - do_check_eq(app_abi, "noarch-spidermonkey"); - do_check_eq(app_locale, "fr-FR"); - - request.setStatusLine(null, 500, "Server Error"); - }); - - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon2@tests.mozilla.org", - "addon3@tests.mozilla.org", - "addon4@tests.mozilla.org", - "addon5@tests.mozilla.org", - "addon6@tests.mozilla.org"], - function([a1, a2, a3, a4, a5, a6]) { - let count = 6; - - function next_test() { - a1.uninstall(); - a2.uninstall(); - a3.uninstall(); - a4.uninstall(); - a5.uninstall(); - a6.uninstall(); - - restartManager(); - run_next_test(); - } - - let compatListener = { - onUpdateFinished: function(addon, error) { - if (--count == 0) - do_execute_soon(next_test); - } - }; - - let updateListener = { - onUpdateAvailable: function(addon, update) { - // Dummy so the update checker knows we care about new versions - }, - - onUpdateFinished: function(addon, error) { - if (--count == 0) - do_execute_soon(next_test); - } - }; - - a1.findUpdates(updateListener, AddonManager.UPDATE_WHEN_USER_REQUESTED); - a2.findUpdates(compatListener, AddonManager.UPDATE_WHEN_ADDON_INSTALLED); - a3.findUpdates(updateListener, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); - a4.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "2"); - a5.findUpdates(compatListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); - a6.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); - }); - })); - }); - - // Tests that if an install.rdf claims compatibility then the add-on will be - // seen as compatible regardless of what the update.rdf says. - add_test(function run_test_9() { - writeInstallRDFForExtension({ - id: "addon4@tests.mozilla.org", - version: "5.0", - updateURL: "http://localhost:" + gPort + "/data/" + updateFile, - targetApplications: [{ - id: appId, - minVersion: "0", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - restartManager(); - - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - do_check_true(a4.isActive, "addon4 is active"); - do_check_true(a4.isCompatible, "addon4 is compatible"); - - run_next_test(); - }); - }); - - // Tests that a normal update check won't decrease a targetApplication's - // maxVersion. - add_test(function run_test_10() { - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - a4.findUpdates({ - onUpdateFinished: function(addon) { - do_check_true(addon.isCompatible, "addon4 is compatible"); - - run_next_test(); - } - }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); - }); - }); - - // Tests that an update check for a new application will decrease a - // targetApplication's maxVersion. - add_test(function run_test_11() { - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - a4.findUpdates({ - onUpdateFinished: function(addon) { - do_check_false(addon.isCompatible, "addon4 is compatible"); - - run_next_test(); - } - }, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); - }); - }); - - // Check that the decreased maxVersion applied and disables the add-on - add_test(function run_test_12() { - restartManager(); - - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - do_check_false(a4.isActive, "addon4 is active"); - do_check_false(a4.isCompatible, "addon4 is compatible"); - - a4.uninstall(); - run_next_test(); - }); - }); - - // Tests that no compatibility update is passed to the listener when there is - // compatibility info for the current version of the app but not for the - // version of the app that the caller requested an update check for. - let check_test_13; - add_test(function run_test_13() { - restartManager(); - - // Not initially compatible but the update check will make it compatible - writeInstallRDFForExtension({ - id: "addon7@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/" + updateFile, - targetApplications: [{ - id: appId, - minVersion: "0", - maxVersion: "0" - }], - name: "Test Addon 7", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { - do_check_neq(a7, null); - do_check_false(a7.isActive); - do_check_false(a7.isCompatible); - do_check_true(a7.appDisabled); - do_check_true(a7.isCompatibleWith("0", "0")); - - a7.findUpdates({ - sawUpdate: false, - onCompatibilityUpdateAvailable: function(addon) { - ok(false, "Should not have seen compatibility information"); - }, - - onUpdateAvailable: function(addon, install) { - ok(false, "Should not have seen an available update"); - }, - - onUpdateFinished: function(addon) { - do_check_true(addon.isCompatible); - do_execute_soon(check_test_13); - } - }, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "3.0", "3.0"); - }); - }); - - check_test_13 = () => { - restartManager(); - AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { - do_check_neq(a7, null); - do_check_true(a7.isActive); - do_check_true(a7.isCompatible); - do_check_false(a7.appDisabled); - - a7.uninstall(); - run_next_test(); - }); - } - - // Test that background update checks doesn't update an add-on that isn't - // allowed to update automatically. - let check_test_14; - add_test(function run_test_14() { - restartManager(); - - // Have an add-on there that will be updated so we see some events from it - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/" + updateFile, - targetApplications: [{ - id: appId, - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon8@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/" + updateFile, - targetApplications: [{ - id: appId, - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 8", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { - a8.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; - - // The background update check will find updates for both add-ons but only - // proceed to install one of them. - AddonManager.addInstallListener({ - onNewInstall: function(aInstall) { - let id = aInstall.existingAddon.id; - ok((id == "addon1@tests.mozilla.org" || id == "addon8@tests.mozilla.org"), - "Saw unexpected onNewInstall for " + id); - }, - - onDownloadStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadFailed: function(aInstall) { - ok(false, "Should not have seen onDownloadFailed event"); - }, - - onDownloadCancelled: function(aInstall) { - ok(false, "Should not have seen onDownloadCancelled event"); - }, - - onInstallStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onInstallEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - do_check_eq(aInstall.existingAddon.pendingUpgrade.install, aInstall); - - do_execute_soon(check_test_14); - }, - - onInstallFailed: function(aInstall) { - ok(false, "Should not have seen onInstallFailed event"); - }, - - onInstallCancelled: function(aInstall) { - ok(false, "Should not have seen onInstallCancelled event"); - }, - }); - - AddonManagerInternal.backgroundUpdateCheck(); - }); - }); - - check_test_14 = () => { - restartManager(); - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon8@tests.mozilla.org"], function([a1, a8]) { - do_check_neq(a1, null); - do_check_eq(a1.version, "2.0"); - a1.uninstall(); - - do_check_neq(a8, null); - do_check_eq(a8.version, "1.0"); - a8.uninstall(); - - run_next_test(); - }); - } - - // Test that background update checks doesn't update an add-on that is - // pending uninstall - let check_test_15; - add_test(function run_test_15() { - restartManager(); - - // Have an add-on there that will be updated so we see some events from it - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/" + updateFile, - targetApplications: [{ - id: appId, - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon8@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/" + updateFile, - targetApplications: [{ - id: appId, - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 8", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { - a8.uninstall(); - do_check_false(hasFlag(a8.permissions, AddonManager.PERM_CAN_UPGRADE)); - - // The background update check will find updates for both add-ons but only - // proceed to install one of them. - AddonManager.addInstallListener({ - onNewInstall: function(aInstall) { - let id = aInstall.existingAddon.id; - ok((id == "addon1@tests.mozilla.org" || id == "addon8@tests.mozilla.org"), - "Saw unexpected onNewInstall for " + id); - }, - - onDownloadStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadFailed: function(aInstall) { - ok(false, "Should not have seen onDownloadFailed event"); - }, - - onDownloadCancelled: function(aInstall) { - ok(false, "Should not have seen onDownloadCancelled event"); - }, - - onInstallStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onInstallEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - do_execute_soon(check_test_15); - }, - - onInstallFailed: function(aInstall) { - ok(false, "Should not have seen onInstallFailed event"); - }, - - onInstallCancelled: function(aInstall) { - ok(false, "Should not have seen onInstallCancelled event"); - }, - }); - - AddonManagerInternal.backgroundUpdateCheck(); - }); - }); - - check_test_15 = () => { - restartManager(); - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon8@tests.mozilla.org"], function([a1, a8]) { - do_check_neq(a1, null); - do_check_eq(a1.version, "2.0"); - a1.uninstall(); - - do_check_eq(a8, null); - - run_next_test(); - }); - } - - // Test that the update check correctly observes the - // extensions.strictCompatibility pref and compatibility overrides. - add_test(function run_test_17() { - restartManager(); - - writeInstallRDFForExtension({ - id: "addon9@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/" + updateFile, - targetApplications: [{ - id: appId, - minVersion: "0.1", - maxVersion: "0.2" - }], - name: "Test Addon 9", - }, profileDir); - restartManager(); - - AddonManager.addInstallListener({ - onNewInstall: function(aInstall) { - equal(aInstall.existingAddon.id, "addon9@tests.mozilla.org", - "Saw unexpected onNewInstall for " + aInstall.existingAddon.id); - do_check_eq(aInstall.version, "2.0"); - }, - onDownloadFailed: function(aInstall) { - AddonManager.getAddonByID("addon9@tests.mozilla.org", function(a9) { - a9.uninstall(); - run_next_test(); - }); - } - }); - - Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, - "http://localhost:" + gPort + "/data/test_update.xml"); - Services.prefs.setCharPref(PREF_GETADDONS_BYIDS_PERFORMANCE, - "http://localhost:" + gPort + "/data/test_update.xml"); - Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); - - AddonManagerInternal.backgroundUpdateCheck(); - }); - - // Test that the update check correctly observes when an addon opts-in to - // strict compatibility checking. - add_test(function run_test_19() { - restartManager(); - writeInstallRDFForExtension({ - id: "addon11@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/" + updateFile, - targetApplications: [{ - id: appId, - minVersion: "0.1", - maxVersion: "0.2" - }], - name: "Test Addon 11", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon11@tests.mozilla.org", function(a11) { - do_check_neq(a11, null); - - a11.findUpdates({ - onCompatibilityUpdateAvailable: function() { - ok(false, "Should have not have seen compatibility information"); - }, - - onUpdateAvailable: function() { - ok(false, "Should not have seen an available update"); - }, - - onUpdateFinished: function() { - run_next_test(); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); - }); - }); - - add_task(function cleanup() { - let addons = yield new Promise(resolve => { - AddonManager.getAddonsByTypes(["extension"], resolve); - }); - - for (let addon of addons) - addon.uninstall(); - - yield promiseRestartManager(); + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(olda1) { + do_check_neq(olda1, null); + do_check_eq(olda1.version, "1.0"); + do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); shutdownManager(); - yield new Promise(do_execute_soon); + startupManager(); + + do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); + + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + do_check_neq(a1, null); + do_check_eq(a1.version, "2.0"); + do_check_true(isExtensionInAddonsList(profileDir, a1.id)); + do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DISABLE); + do_check_eq(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); + + a1.uninstall(); + do_execute_soon(run_test_3); + }); + })); +} + + +// Check that an update check finds compatibility updates and applies them +function run_test_3() { + restartManager(); + + AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { + do_check_neq(a2, null); + do_check_false(a2.isActive); + do_check_false(a2.isCompatible); + do_check_true(a2.appDisabled); + do_check_true(a2.isCompatibleWith("0")); + + a2.findUpdates({ + onCompatibilityUpdateAvailable: function(addon) { + do_check_true(a2.isCompatible); + do_check_false(a2.appDisabled); + do_check_false(a2.isActive); + }, + + onUpdateAvailable: function(addon, install) { + do_throw("Should not have seen an available update"); + }, + + onNoUpdateAvailable: function(addon) { + do_check_eq(addon, a2); + do_execute_soon(check_test_3); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); +} + +function check_test_3() { + restartManager(); + AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { + do_check_neq(a2, null); + do_check_true(a2.isActive); + do_check_true(a2.isCompatible); + do_check_false(a2.appDisabled); + a2.uninstall(); + + run_test_4(); + }); +} + +// Checks that we see no compatibility information when there is none. +function run_test_4() { + AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { + do_check_neq(a3, null); + do_check_false(a3.isActive); + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + do_check_true(a3.isCompatibleWith("5")); + do_check_false(a3.isCompatibleWith("2")); + + a3.findUpdates({ + sawUpdate: false, + onCompatibilityUpdateAvailable: function(addon) { + do_throw("Should not have seen compatibility information"); + }, + + onNoCompatibilityUpdateAvailable: function(addon) { + this.sawUpdate = true; + }, + + onUpdateAvailable: function(addon, install) { + do_throw("Should not have seen an available update"); + }, + + onNoUpdateAvailable: function(addon) { + do_check_true(this.sawUpdate); + run_test_5(); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); +} + +// Checks that compatibility info for future apps are detected but don't make +// the item compatibile. +function run_test_5() { + AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { + do_check_neq(a3, null); + do_check_false(a3.isActive); + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + do_check_true(a3.isCompatibleWith("5")); + do_check_false(a3.isCompatibleWith("2")); + + a3.findUpdates({ + sawUpdate: false, + onCompatibilityUpdateAvailable: function(addon) { + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + do_check_false(a3.isActive); + this.sawUpdate = true; + }, + + onNoCompatibilityUpdateAvailable: function(addon) { + do_throw("Should have seen some compatibility information"); + }, + + onUpdateAvailable: function(addon, install) { + do_throw("Should not have seen an available update"); + }, + + onNoUpdateAvailable: function(addon) { + do_check_true(this.sawUpdate); + do_execute_soon(check_test_5); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED, "3.0"); + }); +} + +function check_test_5() { + restartManager(); + AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { + do_check_neq(a3, null); + do_check_false(a3.isActive); + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + + a3.uninstall(); + do_execute_soon(run_test_6); + }); +} + +// Test that background update checks work +function run_test_6() { + restartManager(); + + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + restartManager(); + + prepare_test({}, [ + "onNewInstall", + "onDownloadStarted", + "onDownloadEnded" + ], continue_test_6); + + AddonManagerInternal.backgroundUpdateCheck(); +} + +function continue_test_6(install) { + do_check_neq(install.existingAddon, null); + do_check_eq(install.existingAddon.id, "addon1@tests.mozilla.org"); + + prepare_test({ + "addon1@tests.mozilla.org": [ + "onInstalling" + ] + }, [ + "onInstallStarted", + "onInstallEnded", + ], callback_soon(check_test_6)); +} + +function check_test_6(install) { + do_check_eq(install.existingAddon.pendingUpgrade.install, install); + + restartManager(); + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + do_check_neq(a1, null); + do_check_eq(a1.version, "2.0"); + do_check_eq(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); + a1.uninstall(); + do_execute_soon(run_test_7); }); } // Test that background update checks work for lightweight themes -add_test(function run_test_7() { - startupManager(); +function run_test_7() { + restartManager(); LightweightThemeManager.currentTheme = { id: "1", @@ -1105,7 +474,7 @@ add_test(function run_test_7() { AddonManagerInternal.backgroundUpdateCheck(); }); -}); +} function check_test_7() { AddonManager.getAddonByID("1@personas.mozilla.org", function(p1) { @@ -1122,6 +491,595 @@ function check_test_7() { gInstallDate = p1.installDate.getTime(); - run_next_test(); + do_execute_soon(run_test_8); + }); +} + +// Verify the parameter escaping in update urls. +function run_test_8() { + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "5.0", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "2" + }], + name: "Test Addon 1", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon2@tests.mozilla.org", + version: "67.0.5b1", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: "toolkit@mozilla.org", + minVersion: "0", + maxVersion: "3" + }], + name: "Test Addon 2", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon3@tests.mozilla.org", + version: "1.3+", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "0", + maxVersion: "0" + }, { + id: "toolkit@mozilla.org", + minVersion: "0", + maxVersion: "3" + }], + name: "Test Addon 3", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon4@tests.mozilla.org", + version: "0.5ab6", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "5" + }], + name: "Test Addon 4", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon5@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 5", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon6@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 6", + }, profileDir); + + restartManager(); + + AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(a2) { + a2.userDisabled = true; + restartManager(); + + testserver.registerPathHandler("/data/param_test.rdf", function(request, response) { + do_check_neq(request.queryString, ""); + let [req_version, item_id, item_version, + item_maxappversion, item_status, + app_id, app_version, current_app_version, + app_os, app_abi, app_locale, update_type] = + request.queryString.split("/").map(a => decodeURIComponent(a)); + + do_check_eq(req_version, "2"); + + switch(item_id) { + case "addon1@tests.mozilla.org": + do_check_eq(item_version, "5.0"); + do_check_eq(item_maxappversion, "2"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "97"); + break; + case "addon2@tests.mozilla.org": + do_check_eq(item_version, "67.0.5b1"); + do_check_eq(item_maxappversion, "3"); + do_check_eq(item_status, "userDisabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "49"); + break; + case "addon3@tests.mozilla.org": + do_check_eq(item_version, "1.3+"); + do_check_eq(item_maxappversion, "0"); + do_check_eq(item_status, "userEnabled,incompatible"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "112"); + break; + case "addon4@tests.mozilla.org": + do_check_eq(item_version, "0.5ab6"); + do_check_eq(item_maxappversion, "5"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "2"); + do_check_eq(update_type, "98"); + break; + case "addon5@tests.mozilla.org": + do_check_eq(item_version, "1.0"); + do_check_eq(item_maxappversion, "1"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "35"); + break; + case "addon6@tests.mozilla.org": + do_check_eq(item_version, "1.0"); + do_check_eq(item_maxappversion, "1"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "99"); + break; + default: + do_throw("Update request for unexpected add-on " + item_id); + } + + do_check_eq(app_id, "xpcshell@tests.mozilla.org"); + do_check_eq(current_app_version, "1"); + do_check_eq(app_os, "XPCShell"); + do_check_eq(app_abi, "noarch-spidermonkey"); + do_check_eq(app_locale, "fr-FR"); + + request.setStatusLine(null, 500, "Server Error"); + }); + + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon2@tests.mozilla.org", + "addon3@tests.mozilla.org", + "addon4@tests.mozilla.org", + "addon5@tests.mozilla.org", + "addon6@tests.mozilla.org"], + function([a1, a2, a3, a4, a5, a6]) { + let count = 6; + + function run_next_test() { + a1.uninstall(); + a2.uninstall(); + a3.uninstall(); + a4.uninstall(); + a5.uninstall(); + a6.uninstall(); + + restartManager(); + run_test_9(); + } + + let compatListener = { + onUpdateFinished: function(addon, error) { + if (--count == 0) + do_execute_soon(run_next_test); + } + }; + + let updateListener = { + onUpdateAvailable: function(addon, update) { + // Dummy so the update checker knows we care about new versions + }, + + onUpdateFinished: function(addon, error) { + if (--count == 0) + do_execute_soon(run_next_test); + } + }; + + a1.findUpdates(updateListener, AddonManager.UPDATE_WHEN_USER_REQUESTED); + a2.findUpdates(compatListener, AddonManager.UPDATE_WHEN_ADDON_INSTALLED); + a3.findUpdates(updateListener, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); + a4.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "2"); + a5.findUpdates(compatListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); + a6.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); + }); + })); +} + +// Tests that if an install.rdf claims compatibility then the add-on will be +// seen as compatible regardless of what the update.rdf says. +function run_test_9() { + writeInstallRDFForExtension({ + id: "addon4@tests.mozilla.org", + version: "5.0", + updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "0", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + + restartManager(); + + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + do_check_true(a4.isActive); + do_check_true(a4.isCompatible); + + run_test_10(); + }); +} + +// Tests that a normal update check won't decrease a targetApplication's +// maxVersion. +function run_test_10() { + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + a4.findUpdates({ + onUpdateFinished: function(addon) { + do_check_true(addon.isCompatible); + + run_test_11(); + } + }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); + }); +} + +// Tests that an update check for a new application will decrease a +// targetApplication's maxVersion. +function run_test_11() { + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + a4.findUpdates({ + onUpdateFinished: function(addon) { + do_check_false(addon.isCompatible); + + do_execute_soon(run_test_12); + } + }, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); + }); +} + +// Check that the decreased maxVersion applied and disables the add-on +function run_test_12() { + restartManager(); + + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + do_check_false(a4.isActive); + do_check_false(a4.isCompatible); + + a4.uninstall(); + do_execute_soon(run_test_13); + }); +} + +// Tests that no compatibility update is passed to the listener when there is +// compatibility info for the current version of the app but not for the +// version of the app that the caller requested an update check for. +function run_test_13() { + restartManager(); + + // Not initially compatible but the update check will make it compatible + writeInstallRDFForExtension({ + id: "addon7@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "0", + maxVersion: "0" + }], + name: "Test Addon 7", + }, profileDir); + restartManager(); + + AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { + do_check_neq(a7, null); + do_check_false(a7.isActive); + do_check_false(a7.isCompatible); + do_check_true(a7.appDisabled); + do_check_true(a7.isCompatibleWith("0")); + + a7.findUpdates({ + sawUpdate: false, + onCompatibilityUpdateAvailable: function(addon) { + do_throw("Should have not have seen compatibility information"); + }, + + onUpdateAvailable: function(addon, install) { + do_throw("Should not have seen an available update"); + }, + + onUpdateFinished: function(addon) { + do_check_true(addon.isCompatible); + do_execute_soon(check_test_13); + } + }, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "3.0"); + }); +} + +function check_test_13() { + restartManager(); + AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { + do_check_neq(a7, null); + do_check_true(a7.isActive); + do_check_true(a7.isCompatible); + do_check_false(a7.appDisabled); + + a7.uninstall(); + do_execute_soon(run_test_14); + }); +} + +// Test that background update checks doesn't update an add-on that isn't +// allowed to update automatically. +function run_test_14() { + restartManager(); + + // Have an add-on there that will be updated so we see some events from it + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon8@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 8", + }, profileDir); + restartManager(); + + AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { + a8.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + + // The background update check will find updates for both add-ons but only + // proceed to install one of them. + AddonManager.addInstallListener({ + onNewInstall: function(aInstall) { + if (aInstall.existingAddon.id != "addon1@tests.mozilla.org" && + aInstall.existingAddon.id != "addon8@tests.mozilla.org") + do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); + }, + + onDownloadStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadFailed: function(aInstall) { + do_throw("Should not have seen onDownloadFailed event"); + }, + + onDownloadCancelled: function(aInstall) { + do_throw("Should not have seen onDownloadCancelled event"); + }, + + onInstallStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onInstallEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + do_check_eq(aInstall.existingAddon.pendingUpgrade.install, aInstall); + do_execute_soon(check_test_14); + }, + + onInstallFailed: function(aInstall) { + do_throw("Should not have seen onInstallFailed event"); + }, + + onInstallCancelled: function(aInstall) { + do_throw("Should not have seen onInstallCancelled event"); + }, + }); + + AddonManagerInternal.backgroundUpdateCheck(); + }); +} + +function check_test_14() { + restartManager(); + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon8@tests.mozilla.org"], function([a1, a8]) { + do_check_neq(a1, null); + do_check_eq(a1.version, "2.0"); + a1.uninstall(); + + do_check_neq(a8, null); + do_check_eq(a8.version, "1.0"); + a8.uninstall(); + + do_execute_soon(run_test_15); + }); +} + +// Test that background update checks doesn't update an add-on that is +// pending uninstall +function run_test_15() { + restartManager(); + + // Have an add-on there that will be updated so we see some events from it + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon8@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 8", + }, profileDir); + restartManager(); + + AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { + a8.uninstall(); + do_check_false(hasFlag(a8.permissions, AddonManager.PERM_CAN_UPGRADE)); + + // The background update check will find updates for both add-ons but only + // proceed to install one of them. + AddonManager.addInstallListener({ + onNewInstall: function(aInstall) { + if (aInstall.existingAddon.id != "addon1@tests.mozilla.org" && + aInstall.existingAddon.id != "addon8@tests.mozilla.org") + do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); + }, + + onDownloadStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadFailed: function(aInstall) { + do_throw("Should not have seen onDownloadFailed event"); + }, + + onDownloadCancelled: function(aInstall) { + do_throw("Should not have seen onDownloadCancelled event"); + }, + + onInstallStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onInstallEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + do_execute_soon(check_test_15); + }, + + onInstallFailed: function(aInstall) { + do_throw("Should not have seen onInstallFailed event"); + }, + + onInstallCancelled: function(aInstall) { + do_throw("Should not have seen onInstallCancelled event"); + }, + }); + + AddonManagerInternal.backgroundUpdateCheck(); + }); +} + +function check_test_15() { + restartManager(); + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon8@tests.mozilla.org"], function([a1, a8]) { + do_check_neq(a1, null); + do_check_eq(a1.version, "2.0"); + a1.uninstall(); + + do_check_eq(a8, null); + + do_execute_soon(run_test_16); + }); +} + +// Test that the update check correctly observes the +// extensions.strictCompatibility pref and compatibility overrides. +function run_test_16() { + restartManager(); + + writeInstallRDFForExtension({ + id: "addon9@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 9", + }, profileDir); + restartManager(); + + AddonManager.addInstallListener({ + onNewInstall: function(aInstall) { + if (aInstall.existingAddon.id != "addon9@tests.mozilla.org") + do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); + do_check_eq(aInstall.version, "2.0"); + }, + onDownloadFailed: function(aInstall) { + do_execute_soon(run_test_17); + } + }); + + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS_PERFORMANCE, + "http://localhost:" + gPort + "/data/test_update.xml"); + Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); + + AddonManagerInternal.backgroundUpdateCheck(); +} + +// Test that the update check correctly observes when an addon opts-in to +// strict compatibility checking. +function run_test_17() { + + writeInstallRDFForExtension({ + id: "addon11@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 11", + }, profileDir); + restartManager(); + + AddonManager.getAddonByID("addon11@tests.mozilla.org", function(a11) { + do_check_neq(a11, null); + + a11.findUpdates({ + onCompatibilityUpdateAvailable: function() { + do_throw("Should have not have seen compatibility information"); + }, + + onUpdateAvailable: function() { + do_throw("Should not have seen an available update"); + }, + + onUpdateFinished: function() { + do_execute_soon(end_test); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_updatecheck.js b/toolkit/mozapps/extensions/test/xpcshell/test_updatecheck.js index 9a2f6a01dbf4..9d251933aab5 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_updatecheck.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_updatecheck.js @@ -7,46 +7,51 @@ Components.utils.import("resource://gre/modules/addons/AddonUpdateChecker.jsm"); Components.utils.import("resource://testing-common/httpd.js"); - -var testserver = createHttpServer(4444); -testserver.registerDirectory("/data/", do_get_file("data")); - -function checkUpdates(aId, aUpdateKey, aUpdateFile) { - return new Promise((resolve, reject) => { - AddonUpdateChecker.checkForUpdates(aId, aUpdateKey, `http://localhost:4444/data/${aUpdateFile}`, { - onUpdateCheckComplete: resolve, - - onUpdateCheckError: function(status) { - let error = new Error("Update check failed with status " + status); - error.status = status; - reject(error); - } - }); - }); -} +var testserver; function run_test() { - createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1"); + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); - run_next_test(); + // Create and configure the HTTP server. + testserver = new HttpServer(); + testserver.registerDirectory("/data/", do_get_file("data")); + testserver.start(4444); + + do_test_pending(); + run_test_1(); +} + +function end_test() { + testserver.stop(do_test_finished); } // Test that a basic update check returns the expected available updates -add_task(function* () { - for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { - let updates = yield checkUpdates("updatecheck1@tests.mozilla.org", null, file); +function run_test_1() { + AddonUpdateChecker.checkForUpdates("updatecheck1@tests.mozilla.org", null, + "http://localhost:4444/data/test_updatecheck.rdf", { + onUpdateCheckComplete: function(updates) { + check_test_1(updates); + }, - equal(updates.length, 5); - let update = AddonUpdateChecker.getNewestCompatibleUpdate(updates); - notEqual(update, null); - equal(update.version, "3.0"); - update = AddonUpdateChecker.getCompatibilityUpdate(updates, "2"); - notEqual(update, null); - equal(update.version, "2.0"); - equal(update.targetApplications[0].minVersion, "1"); - equal(update.targetApplications[0].maxVersion, "2"); - } -}); + onUpdateCheckError: function(status) { + do_throw("Update check failed with status " + status); + } + }); +} + +function check_test_1(updates) { + do_check_eq(updates.length, 5); + let update = AddonUpdateChecker.getNewestCompatibleUpdate(updates); + do_check_neq(update, null); + do_check_eq(update.version, 3); + update = AddonUpdateChecker.getCompatibilityUpdate(updates, "2"); + do_check_neq(update, null); + do_check_eq(update.version, 2); + do_check_eq(update.targetApplications[0].minVersion, 1); + do_check_eq(update.targetApplications[0].maxVersion, 2); + + run_test_2(); +} /* * Tests that the security checks are applied correctly @@ -68,169 +73,240 @@ var updateKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDK426erD/H3XtsjvaB5+PJqbh "NyeP6i4LuUYjTURnn7Yw/IgzyIJ2oKsYa32RuxAyteqAWqPT/J63wBixIeCxmysf" + "awB/zH4KaPiY3vnrzQIDAQAB"; -add_task(function* () { - for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { - try { - yield checkUpdates("test_bug378216_5@tests.mozilla.org", - updateKey, file); - throw "Expected the update check to fail"; - } catch (e) {} - } -}); +function run_test_2() { + AddonUpdateChecker.checkForUpdates("test_bug378216_5@tests.mozilla.org", + updateKey, + "http://localhost:4444/data/test_updatecheck.rdf", { + onUpdateCheckComplete: function(updates) { + do_throw("Expected the update check to fail"); + }, -add_task(function* () { - for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { - try { - yield checkUpdates("test_bug378216_7@tests.mozilla.org", - updateKey, file); - - throw "Expected the update check to fail"; - } catch (e) {} - } -}); - -add_task(function* () { - // Make sure that the JSON manifest is rejected when an update key is - // required, but perform the remaining tests which aren't expected to fail - // because of the update key, without requiring one for the JSON variant. - - try { - let updates = yield checkUpdates("test_bug378216_8@tests.mozilla.org", - updateKey, "test_updatecheck.json"); - - throw "Expected the update check to fail"; - } catch(e) {} - - for (let [file, key] of [["test_updatecheck.rdf", updateKey], - ["test_updatecheck.json", null]]) { - let updates = yield checkUpdates("test_bug378216_8@tests.mozilla.org", - key, file); - equal(updates.length, 1); - ok(!("updateURL" in updates[0])); - } -}); - -add_task(function* () { - for (let [file, key] of [["test_updatecheck.rdf", updateKey], - ["test_updatecheck.json", null]]) { - let updates = yield checkUpdates("test_bug378216_9@tests.mozilla.org", - key, file); - equal(updates.length, 1); - equal(updates[0].version, "2.0"); - ok("updateURL" in updates[0]); - } -}); - -add_task(function* () { - for (let [file, key] of [["test_updatecheck.rdf", updateKey], - ["test_updatecheck.json", null]]) { - let updates = yield checkUpdates("test_bug378216_10@tests.mozilla.org", - key, file); - equal(updates.length, 1); - equal(updates[0].version, "2.0"); - ok("updateURL" in updates[0]); - } -}); - -add_task(function* () { - for (let [file, key] of [["test_updatecheck.rdf", updateKey], - ["test_updatecheck.json", null]]) { - let updates = yield checkUpdates("test_bug378216_11@tests.mozilla.org", - key, file); - equal(updates.length, 1); - equal(updates[0].version, "2.0"); - ok("updateURL" in updates[0]); - } -}); - -add_task(function* () { - for (let [file, key] of [["test_updatecheck.rdf", updateKey], - ["test_updatecheck.json", null]]) { - let updates = yield checkUpdates("test_bug378216_12@tests.mozilla.org", - key, file); - equal(updates.length, 1); - do_check_false("updateURL" in updates[0]); - } -}); - -add_task(function* () { - for (let [file, key] of [["test_updatecheck.rdf", updateKey], - ["test_updatecheck.json", null]]) { - let updates = yield checkUpdates("test_bug378216_13@tests.mozilla.org", - key, file); - equal(updates.length, 1); - equal(updates[0].version, "2.0"); - ok("updateURL" in updates[0]); - } -}); - -add_task(function* () { - for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { - let updates = yield checkUpdates("test_bug378216_14@tests.mozilla.org", - null, file); - equal(updates.length, 0); - } -}); - -add_task(function* () { - for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { - try { - yield checkUpdates("test_bug378216_15@tests.mozilla.org", - null, file); - - throw "Update check should have failed"; - } catch (e) { - equal(e.status, AddonUpdateChecker.ERROR_PARSE_ERROR); + onUpdateCheckError: function(status) { + run_test_3(); } - } -}); + }); +} -add_task(function* () { - for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { - let updates = yield checkUpdates("ignore-compat@tests.mozilla.org", - null, file); - equal(updates.length, 3); - let update = AddonUpdateChecker.getNewestCompatibleUpdate( - updates, null, null, true); - notEqual(update, null); - equal(update.version, 2); - } -}); +function run_test_3() { + AddonUpdateChecker.checkForUpdates("test_bug378216_7@tests.mozilla.org", + updateKey, + "http://localhost:4444/data/test_updatecheck.rdf", { + onUpdateCheckComplete: function(updates) { + do_throw("Expected the update check to fail"); + }, -add_task(function* () { - for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { - let updates = yield checkUpdates("compat-override@tests.mozilla.org", - null, file); - equal(updates.length, 3); - let overrides = [{ - type: "incompatible", - minVersion: 1, - maxVersion: 2, - appID: "xpcshell@tests.mozilla.org", - appMinVersion: 0.1, - appMaxVersion: 0.2 - }, { - type: "incompatible", - minVersion: 2, - maxVersion: 2, - appID: "xpcshell@tests.mozilla.org", - appMinVersion: 1, - appMaxVersion: 2 - }]; - let update = AddonUpdateChecker.getNewestCompatibleUpdate( - updates, null, null, true, false, overrides); - notEqual(update, null); - equal(update.version, 1); - } -}); + onUpdateCheckError: function(status) { + run_test_4(); + } + }); +} -add_task(function* () { - for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { - let updates = yield checkUpdates("compat-strict-optin@tests.mozilla.org", - null, file); - equal(updates.length, 1); - let update = AddonUpdateChecker.getNewestCompatibleUpdate( - updates, null, null, true, false); - equal(update, null); - } -}); +function run_test_4() { + AddonUpdateChecker.checkForUpdates("test_bug378216_8@tests.mozilla.org", + updateKey, + "http://localhost:4444/data/test_updatecheck.rdf", { + onUpdateCheckComplete: function(updates) { + do_check_eq(updates.length, 1); + do_check_false("updateURL" in updates[0]); + run_test_5(); + }, + + onUpdateCheckError: function(status) { + do_throw("Update check failed with status " + status); + } + }); +} + +function run_test_5() { + AddonUpdateChecker.checkForUpdates("test_bug378216_9@tests.mozilla.org", + updateKey, + "http://localhost:4444/data/test_updatecheck.rdf", { + onUpdateCheckComplete: function(updates) { + do_check_eq(updates.length, 1); + do_check_eq(updates[0].version, "2.0"); + do_check_true("updateURL" in updates[0]); + run_test_6(); + }, + + onUpdateCheckError: function(status) { + do_throw("Update check failed with status " + status); + } + }); +} + +function run_test_6() { + AddonUpdateChecker.checkForUpdates("test_bug378216_10@tests.mozilla.org", + updateKey, + "http://localhost:4444/data/test_updatecheck.rdf", { + onUpdateCheckComplete: function(updates) { + do_check_eq(updates.length, 1); + do_check_eq(updates[0].version, "2.0"); + do_check_true("updateURL" in updates[0]); + run_test_7(); + }, + + onUpdateCheckError: function(status) { + do_throw("Update check failed with status " + status); + } + }); +} + +function run_test_7() { + AddonUpdateChecker.checkForUpdates("test_bug378216_11@tests.mozilla.org", + updateKey, + "http://localhost:4444/data/test_updatecheck.rdf", { + onUpdateCheckComplete: function(updates) { + do_check_eq(updates.length, 1); + do_check_eq(updates[0].version, "2.0"); + do_check_true("updateURL" in updates[0]); + run_test_8(); + }, + + onUpdateCheckError: function(status) { + do_throw("Update check failed with status " + status); + } + }); +} + +function run_test_8() { + AddonUpdateChecker.checkForUpdates("test_bug378216_12@tests.mozilla.org", + updateKey, + "http://localhost:4444/data/test_updatecheck.rdf", { + onUpdateCheckComplete: function(updates) { + do_check_eq(updates.length, 1); + do_check_false("updateURL" in updates[0]); + run_test_9(); + }, + + onUpdateCheckError: function(status) { + do_throw("Update check failed with status " + status); + } + }); +} + +function run_test_9() { + AddonUpdateChecker.checkForUpdates("test_bug378216_13@tests.mozilla.org", + updateKey, + "http://localhost:4444/data/test_updatecheck.rdf", { + onUpdateCheckComplete: function(updates) { + do_check_eq(updates.length, 1); + do_check_eq(updates[0].version, "2.0"); + do_check_true("updateURL" in updates[0]); + run_test_10(); + }, + + onUpdateCheckError: function(status) { + do_throw("Update check failed with status " + status); + } + }); +} + +function run_test_10() { + AddonUpdateChecker.checkForUpdates("test_bug378216_14@tests.mozilla.org", + null, + "http://localhost:4444/data/test_updatecheck.rdf", { + onUpdateCheckComplete: function(updates) { + do_check_eq(updates.length, 0); + run_test_11(); + }, + + onUpdateCheckError: function(status) { + do_throw("Update check failed with status " + status); + } + }); +} + +function run_test_11() { + AddonUpdateChecker.checkForUpdates("test_bug378216_15@tests.mozilla.org", + null, + "http://localhost:4444/data/test_updatecheck.rdf", { + onUpdateCheckComplete: function(updates) { + do_throw("Update check should have failed"); + }, + + onUpdateCheckError: function(status) { + do_check_eq(status, AddonUpdateChecker.ERROR_PARSE_ERROR); + run_test_12(); + } + }); +} + +function run_test_12() { + AddonUpdateChecker.checkForUpdates("ignore-compat@tests.mozilla.org", + null, + "http://localhost:4444/data/test_updatecheck.rdf", { + onUpdateCheckComplete: function(updates) { + do_check_eq(updates.length, 3); + let update = AddonUpdateChecker.getNewestCompatibleUpdate(updates, + null, + null, + true); + do_check_neq(update, null); + do_check_eq(update.version, 2); + run_test_13(); + }, + + onUpdateCheckError: function(status) { + do_throw("Update check failed with status " + status); + } + }); +} + +function run_test_13() { + AddonUpdateChecker.checkForUpdates("compat-override@tests.mozilla.org", + null, + "http://localhost:4444/data/test_updatecheck.rdf", { + onUpdateCheckComplete: function(updates) { + do_check_eq(updates.length, 3); + let overrides = [{ + type: "incompatible", + minVersion: 1, + maxVersion: 2, + appID: "xpcshell@tests.mozilla.org", + appMinVersion: 0.1, + appMaxVersion: 0.2 + }, { + type: "incompatible", + minVersion: 2, + maxVersion: 2, + appID: "xpcshell@tests.mozilla.org", + appMinVersion: 1, + appMaxVersion: 2 + }]; + let update = AddonUpdateChecker.getNewestCompatibleUpdate(updates, + null, + null, + true, + false, + overrides); + do_check_neq(update, null); + do_check_eq(update.version, 1); + run_test_14(); + }, + + onUpdateCheckError: function(status) { + do_throw("Update check failed with status " + status); + } + }); +} + +function run_test_14() { + AddonUpdateChecker.checkForUpdates("compat-strict-optin@tests.mozilla.org", + null, + "http://localhost:4444/data/test_updatecheck.rdf", { + onUpdateCheckComplete: function(updates) { + do_check_eq(updates.length, 1); + let update = AddonUpdateChecker.getNewestCompatibleUpdate(updates, + null, + null, + true, + false); + do_check_eq(update, null); + end_test(); + }, + + onUpdateCheckError: function(status) { + do_throw("Update check failed with status " + status); + } + }); +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini index fdd2dd9ac356..f8b9972cf289 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini +++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini @@ -279,7 +279,6 @@ skip-if = os == "android" # Bug 676992: test consistently hangs on Android skip-if = os == "android" run-sequentially = Uses hardcoded ports in xpi files. -[test_json_updatecheck.js] [test_updateid.js] # Bug 676992: test consistently hangs on Android skip-if = os == "android" From 95adec5b5ef9879b96d125349681bb39e76ded43 Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Thu, 29 Oct 2015 16:56:30 +1300 Subject: [PATCH 088/113] Bug 1209994. Fix a couple of issues that make the percentage-height-calculation.html test unreliable. r=bz --HG-- extra : commitid : DGDN3jYJWJ0 extra : rebase_source : e4a93114144139f9c1e19cc1e10eb1a227f4214c --- .../percentage-height-calculation.html.ini | 13 ------------- .../quirks-mode/percentage-height-calculation.html | 11 +++++++++-- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/testing/web-platform/meta/quirks-mode/percentage-height-calculation.html.ini b/testing/web-platform/meta/quirks-mode/percentage-height-calculation.html.ini index faed2f86aba5..2d0b5b2a6d91 100644 --- a/testing/web-platform/meta/quirks-mode/percentage-height-calculation.html.ini +++ b/testing/web-platform/meta/quirks-mode/percentage-height-calculation.html.ini @@ -14,16 +14,3 @@ [The percentage height calculation quirk,
] expected: FAIL - - [The percentage height calculation quirk, ] - expected: - if (os == "mac") and (version == "OS X 10.8") and (processor == "x86_64") and (bits == 64): FAIL - if (os == "mac") and (version == "OS X 10.10.2") and (processor == "x86") and (bits == 32): FAIL - if not debug and (os == "mac") and (version == "OS X 10.6.8") and (processor == "x86_64") and (bits == 64): FAIL - if debug and (os == "mac") and (version == "OS X 10.6.8") and (processor == "x86_64") and (bits == 64): FAIL - if debug and (os == "mac") and (version == "OS X 10.10.2") and (processor == "x86_64") and (bits == 64): FAIL - if not debug and not e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86") and (bits == 32): FAIL - if not debug and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86") and (bits == 32): FAIL - if debug and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): FAIL - if debug and not e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): FAIL - diff --git a/testing/web-platform/tests/quirks-mode/percentage-height-calculation.html b/testing/web-platform/tests/quirks-mode/percentage-height-calculation.html index 03aae3f3e217..a19cb0b812de 100644 --- a/testing/web-platform/tests/quirks-mode/percentage-height-calculation.html +++ b/testing/web-platform/tests/quirks-mode/percentage-height-calculation.html @@ -11,11 +11,16 @@ + + diff --git a/layout/reftests/bugs/1209994-2-ref.html b/layout/reftests/bugs/1209994-2-ref.html new file mode 100644 index 000000000000..8858616b69a0 --- /dev/null +++ b/layout/reftests/bugs/1209994-2-ref.html @@ -0,0 +1,17 @@ + + +There should be a visible button below: +
+ +
+ diff --git a/layout/reftests/bugs/1209994-2.html b/layout/reftests/bugs/1209994-2.html new file mode 100644 index 000000000000..485b0fe5854b --- /dev/null +++ b/layout/reftests/bugs/1209994-2.html @@ -0,0 +1,21 @@ + + +There should be a visible button below: +
+ +
+ + diff --git a/layout/reftests/bugs/1209994-3-ref.html b/layout/reftests/bugs/1209994-3-ref.html new file mode 100644 index 000000000000..bd219e03ae55 --- /dev/null +++ b/layout/reftests/bugs/1209994-3-ref.html @@ -0,0 +1,17 @@ + + +There should be a visible table below: +
+ This is a table
+
+ diff --git a/layout/reftests/bugs/1209994-3.html b/layout/reftests/bugs/1209994-3.html new file mode 100644 index 000000000000..94df02fabb1b --- /dev/null +++ b/layout/reftests/bugs/1209994-3.html @@ -0,0 +1,21 @@ + + +There should be a visible table below: +
+ This is a table
+
+ + diff --git a/layout/reftests/bugs/1209994-4-ref.html b/layout/reftests/bugs/1209994-4-ref.html new file mode 100644 index 000000000000..2f15740fc164 --- /dev/null +++ b/layout/reftests/bugs/1209994-4-ref.html @@ -0,0 +1,17 @@ + + +There should be a visible select below: +
+ +
+ diff --git a/layout/reftests/bugs/1209994-4.html b/layout/reftests/bugs/1209994-4.html new file mode 100644 index 000000000000..95e2d062faca --- /dev/null +++ b/layout/reftests/bugs/1209994-4.html @@ -0,0 +1,21 @@ + + +There should be a visible select below: +
+ +
+ + diff --git a/layout/reftests/bugs/reftest.list b/layout/reftests/bugs/reftest.list index 5297adbbaf60..ec093885652b 100644 --- a/layout/reftests/bugs/reftest.list +++ b/layout/reftests/bugs/reftest.list @@ -1937,3 +1937,7 @@ fuzzy(1,74) fuzzy-if(gtkWidget,6,79) == 1174332-1.html 1174332-1-ref.html == 1202512-2.html 1202512-2-ref.html != 1207326-1.html about:blank == 1209603-1.html 1209603-1-ref.html +== 1209994-1.html 1209994-1-ref.html +== 1209994-2.html 1209994-2-ref.html +== 1209994-3.html 1209994-3-ref.html +== 1209994-4.html 1209994-4-ref.html From 8c47ae7506f95b5af8474154dd6a84d15e560c0e Mon Sep 17 00:00:00 2001 From: Matt Woodrow Date: Tue, 3 Nov 2015 16:49:22 +1300 Subject: [PATCH 090/113] Bug 1216288 - Disable warning when we don't build an active layer for RenderFrameParent within an opacity:0 subtree. r=roc --- layout/base/FrameLayerBuilder.h | 4 ++++ layout/base/nsDisplayList.cpp | 4 +++- layout/ipc/RenderFrameParent.cpp | 4 +++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/layout/base/FrameLayerBuilder.h b/layout/base/FrameLayerBuilder.h index 9f8e395c894b..2a1da5cc2daf 100644 --- a/layout/base/FrameLayerBuilder.h +++ b/layout/base/FrameLayerBuilder.h @@ -61,6 +61,7 @@ struct ContainerLayerParameters { , mInActiveTransformedSubtree(false) , mDisableSubpixelAntialiasingInDescendants(false) , mInLowPrecisionDisplayPort(false) + , mForEventsOnly(false) {} ContainerLayerParameters(float aXScale, float aYScale) : mXScale(aXScale) @@ -71,6 +72,7 @@ struct ContainerLayerParameters { , mInActiveTransformedSubtree(false) , mDisableSubpixelAntialiasingInDescendants(false) , mInLowPrecisionDisplayPort(false) + , mForEventsOnly(false) {} ContainerLayerParameters(float aXScale, float aYScale, const nsIntPoint& aOffset, @@ -84,6 +86,7 @@ struct ContainerLayerParameters { , mInActiveTransformedSubtree(aParent.mInActiveTransformedSubtree) , mDisableSubpixelAntialiasingInDescendants(aParent.mDisableSubpixelAntialiasingInDescendants) , mInLowPrecisionDisplayPort(aParent.mInLowPrecisionDisplayPort) + , mForEventsOnly(aParent.mForEventsOnly) {} float mXScale, mYScale; @@ -112,6 +115,7 @@ struct ContainerLayerParameters { bool mInActiveTransformedSubtree; bool mDisableSubpixelAntialiasingInDescendants; bool mInLowPrecisionDisplayPort; + bool mForEventsOnly; /** * When this is false, PaintedLayer coordinates are drawn to with an integer * translation and the scale in mXScale/mYScale. diff --git a/layout/base/nsDisplayList.cpp b/layout/base/nsDisplayList.cpp index 9250d8fba219..4113a7d45eee 100644 --- a/layout/base/nsDisplayList.cpp +++ b/layout/base/nsDisplayList.cpp @@ -3937,9 +3937,11 @@ already_AddRefed nsDisplayOpacity::BuildLayer(nsDisplayListBuilder* aBuilder, LayerManager* aManager, const ContainerLayerParameters& aContainerParameters) { + ContainerLayerParameters params = aContainerParameters; + params.mForEventsOnly = mForEventsOnly; RefPtr container = aManager->GetLayerBuilder()-> BuildContainerLayerFor(aBuilder, aManager, mFrame, this, &mList, - aContainerParameters, nullptr, + params, nullptr, FrameLayerBuilder::CONTAINER_ALLOW_PULL_BACKGROUND_COLOR); if (!container) return nullptr; diff --git a/layout/ipc/RenderFrameParent.cpp b/layout/ipc/RenderFrameParent.cpp index 7f38a395e3db..4f1b07faa1e8 100644 --- a/layout/ipc/RenderFrameParent.cpp +++ b/layout/ipc/RenderFrameParent.cpp @@ -388,7 +388,9 @@ RenderFrameParent::BuildLayer(nsDisplayListBuilder* aBuilder, // draw a manager's subtree. The latter is bad bad bad, but the the // MOZ_ASSERT() above will flag it. Returning nullptr here will just // cause the shadow subtree not to be rendered. - NS_WARNING("Remote iframe not rendered"); + if (!aContainerParameters.mForEventsOnly) { + NS_WARNING("Remote iframe not rendered"); + } return nullptr; } From 631340121d26ff214a85ba4162433abf002785e7 Mon Sep 17 00:00:00 2001 From: Matt Woodrow Date: Thu, 22 Oct 2015 12:35:52 +1300 Subject: [PATCH 091/113] Bug 1203199 - Blacklist DXVA on some older intel drivers for causing crashes. r=jrmuizel --- widget/windows/GfxInfo.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/widget/windows/GfxInfo.cpp b/widget/windows/GfxInfo.cpp index 7c520d094a0f..cfe472f3d2ba 100644 --- a/widget/windows/GfxInfo.cpp +++ b/widget/windows/GfxInfo.cpp @@ -1101,6 +1101,12 @@ GfxInfo::GetGfxDriverInfo() nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN_OR_EQUAL, V(8,15,10,2869)); + /* Bug 1203199/1092166: DXVA startup crashes on some intel drivers. */ + APPEND_TO_DRIVER_BLOCKLIST2(DRIVER_OS_ALL, + (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel), GfxDriverInfo::allDevices, + nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_LESS_THAN_OR_EQUAL, V(9,17,10,2849)); + APPEND_TO_DRIVER_BLOCKLIST2(DRIVER_OS_ALL, (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(Nvidia8800GTS), nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, From 739b7da869c6498e4c4612be982fea42d1443e95 Mon Sep 17 00:00:00 2001 From: Matt Woodrow Date: Wed, 4 Nov 2015 15:12:24 +1300 Subject: [PATCH 092/113] Bug 1217225 - Differentiate between images optimized for skia and skia-gl in the CanvasImageCache. r=mstange --- dom/canvas/CanvasImageCache.cpp | 69 ++++++++++++++++++------- dom/canvas/CanvasImageCache.h | 9 ++-- dom/canvas/CanvasRenderingContext2D.cpp | 7 +-- 3 files changed, 59 insertions(+), 26 deletions(-) diff --git a/dom/canvas/CanvasImageCache.cpp b/dom/canvas/CanvasImageCache.cpp index 5925d7a7c228..260db515a569 100644 --- a/dom/canvas/CanvasImageCache.cpp +++ b/dom/canvas/CanvasImageCache.cpp @@ -21,10 +21,16 @@ using namespace dom; using namespace gfx; struct ImageCacheKey { - ImageCacheKey(Element* aImage, HTMLCanvasElement* aCanvas) - : mImage(aImage), mCanvas(aCanvas) {} + ImageCacheKey(Element* aImage, + HTMLCanvasElement* aCanvas, + bool aIsAccelerated) + : mImage(aImage) + , mCanvas(aCanvas) + , mIsAccelerated(aIsAccelerated) + {} Element* mImage; HTMLCanvasElement* mCanvas; + bool mIsAccelerated; }; struct ImageCacheEntryData { @@ -32,6 +38,7 @@ struct ImageCacheEntryData { : mImage(aOther.mImage) , mILC(aOther.mILC) , mCanvas(aOther.mCanvas) + , mIsAccelerated(aOther.mIsAccelerated) , mRequest(aOther.mRequest) , mSourceSurface(aOther.mSourceSurface) , mSize(aOther.mSize) @@ -40,6 +47,7 @@ struct ImageCacheEntryData { : mImage(aKey.mImage) , mILC(nullptr) , mCanvas(aKey.mCanvas) + , mIsAccelerated(aKey.mIsAccelerated) {} nsExpirationState* GetExpirationState() { return &mState; } @@ -50,6 +58,7 @@ struct ImageCacheEntryData { RefPtr mImage; nsIImageLoadingContent* mILC; RefPtr mCanvas; + bool mIsAccelerated; // Value nsCOMPtr mRequest; RefPtr mSourceSurface; @@ -70,46 +79,61 @@ public: bool KeyEquals(KeyTypePointer key) const { - return mData->mImage == key->mImage && mData->mCanvas == key->mCanvas; + return mData->mImage == key->mImage && + mData->mCanvas == key->mCanvas && + mData->mIsAccelerated == key->mIsAccelerated; } static KeyTypePointer KeyToPointer(KeyType& key) { return &key; } static PLDHashNumber HashKey(KeyTypePointer key) { - return HashGeneric(key->mImage, key->mCanvas); + return HashGeneric(key->mImage, key->mCanvas, key->mIsAccelerated); } enum { ALLOW_MEMMOVE = true }; nsAutoPtr mData; }; +struct SimpleImageCacheKey { + SimpleImageCacheKey(const imgIRequest* aImage, + bool aIsAccelerated) + : mImage(aImage) + , mIsAccelerated(aIsAccelerated) + {} + const imgIRequest* mImage; + bool mIsAccelerated; +}; + class SimpleImageCacheEntry : public PLDHashEntryHdr { public: - typedef imgIRequest& KeyType; - typedef const imgIRequest* KeyTypePointer; + typedef SimpleImageCacheKey KeyType; + typedef const SimpleImageCacheKey* KeyTypePointer; explicit SimpleImageCacheEntry(KeyTypePointer aKey) - : mRequest(const_cast(aKey)) + : mRequest(const_cast(aKey->mImage)) + , mIsAccelerated(aKey->mIsAccelerated) {} SimpleImageCacheEntry(const SimpleImageCacheEntry &toCopy) : mRequest(toCopy.mRequest) + , mIsAccelerated(toCopy.mIsAccelerated) , mSourceSurface(toCopy.mSourceSurface) {} ~SimpleImageCacheEntry() {} bool KeyEquals(KeyTypePointer key) const { - return key == mRequest; + return key->mImage == mRequest && key->mIsAccelerated == mIsAccelerated; } - static KeyTypePointer KeyToPointer(KeyType key) { return &key; } + static KeyTypePointer KeyToPointer(KeyType& key) { return &key; } static PLDHashNumber HashKey(KeyTypePointer key) { - return NS_PTR_TO_UINT32(key) >> 2; + return HashGeneric(key->mImage, key->mIsAccelerated); } enum { ALLOW_MEMMOVE = true }; nsCOMPtr mRequest; + bool mIsAccelerated; RefPtr mSourceSurface; }; @@ -130,8 +154,8 @@ public: mTotal -= aObject->SizeInBytes(); RemoveObject(aObject); // Deleting the entry will delete aObject since the entry owns aObject - mSimpleCache.RemoveEntry(*aObject->mRequest); - mCache.RemoveEntry(ImageCacheKey(aObject->mImage, aObject->mCanvas)); + mSimpleCache.RemoveEntry(SimpleImageCacheKey(aObject->mRequest, aObject->mIsAccelerated)); + mCache.RemoveEntry(ImageCacheKey(aObject->mImage, aObject->mCanvas, aObject->mIsAccelerated)); } nsTHashtable mCache; @@ -237,20 +261,22 @@ CanvasImageCache::NotifyDrawImage(Element* aImage, HTMLCanvasElement* aCanvas, imgIRequest* aRequest, SourceSurface* aSource, - const IntSize& aSize) + const IntSize& aSize, + bool aIsAccelerated) { if (!gImageCache) { gImageCache = new ImageCache(); nsContentUtils::RegisterShutdownObserver(new CanvasImageCacheShutdownObserver()); } - ImageCacheEntry* entry = gImageCache->mCache.PutEntry(ImageCacheKey(aImage, aCanvas)); + ImageCacheEntry* entry = + gImageCache->mCache.PutEntry(ImageCacheKey(aImage, aCanvas, aIsAccelerated)); if (entry) { if (entry->mData->mSourceSurface) { // We are overwriting an existing entry. gImageCache->mTotal -= entry->mData->SizeInBytes(); gImageCache->RemoveObject(entry->mData); - gImageCache->mSimpleCache.RemoveEntry(*entry->mData->mRequest); + gImageCache->mSimpleCache.RemoveEntry(SimpleImageCacheKey(entry->mData->mRequest, entry->mData->mIsAccelerated)); } gImageCache->AddObject(entry->mData); @@ -267,7 +293,7 @@ CanvasImageCache::NotifyDrawImage(Element* aImage, if (entry->mData->mRequest) { SimpleImageCacheEntry* simpleentry = - gImageCache->mSimpleCache.PutEntry(*entry->mData->mRequest); + gImageCache->mSimpleCache.PutEntry(SimpleImageCacheKey(entry->mData->mRequest, aIsAccelerated)); simpleentry->mSourceSurface = aSource; } } @@ -283,12 +309,14 @@ CanvasImageCache::NotifyDrawImage(Element* aImage, SourceSurface* CanvasImageCache::Lookup(Element* aImage, HTMLCanvasElement* aCanvas, - gfx::IntSize* aSize) + gfx::IntSize* aSize, + bool aIsAccelerated) { if (!gImageCache) return nullptr; - ImageCacheEntry* entry = gImageCache->mCache.GetEntry(ImageCacheKey(aImage, aCanvas)); + ImageCacheEntry* entry = + gImageCache->mCache.GetEntry(ImageCacheKey(aImage, aCanvas, aIsAccelerated)); if (!entry || !entry->mData->mILC) return nullptr; @@ -304,7 +332,8 @@ CanvasImageCache::Lookup(Element* aImage, } SourceSurface* -CanvasImageCache::SimpleLookup(Element* aImage) +CanvasImageCache::SimpleLookup(Element* aImage, + bool aIsAccelerated) { if (!gImageCache) return nullptr; @@ -319,7 +348,7 @@ CanvasImageCache::SimpleLookup(Element* aImage) if (!request) return nullptr; - SimpleImageCacheEntry* entry = gImageCache->mSimpleCache.GetEntry(*request); + SimpleImageCacheEntry* entry = gImageCache->mSimpleCache.GetEntry(SimpleImageCacheKey(request, aIsAccelerated)); if (!entry) return nullptr; diff --git a/dom/canvas/CanvasImageCache.h b/dom/canvas/CanvasImageCache.h index 836368c6c3b3..ca38818801d5 100644 --- a/dom/canvas/CanvasImageCache.h +++ b/dom/canvas/CanvasImageCache.h @@ -33,7 +33,8 @@ public: dom::HTMLCanvasElement* aCanvas, imgIRequest* aRequest, SourceSurface* aSource, - const gfx::IntSize& aSize); + const gfx::IntSize& aSize, + bool aIsAccelerated); /** * Check whether aImage has recently been drawn into aCanvas. If we return @@ -43,14 +44,16 @@ public: */ static SourceSurface* Lookup(dom::Element* aImage, dom::HTMLCanvasElement* aCanvas, - gfx::IntSize* aSize); + gfx::IntSize* aSize, + bool aIsAccelerated); /** * This is the same as Lookup, except it works on any image recently drawn * into any canvas. Security checks need to be done again if using the * results from this. */ - static SourceSurface* SimpleLookup(dom::Element* aImage); + static SourceSurface* SimpleLookup(dom::Element* aImage, + bool aIsAccelerated); }; } // namespace mozilla diff --git a/dom/canvas/CanvasRenderingContext2D.cpp b/dom/canvas/CanvasRenderingContext2D.cpp index 5c366bd1e8ed..05860bea8a4d 100644 --- a/dom/canvas/CanvasRenderingContext2D.cpp +++ b/dom/canvas/CanvasRenderingContext2D.cpp @@ -4312,7 +4312,8 @@ CanvasRenderingContext2D::CachedSurfaceFromElement(Element* aElement) return res; } - res.mSourceSurface = CanvasImageCache::SimpleLookup(aElement); + res.mSourceSurface = + CanvasImageCache::SimpleLookup(aElement, mIsSkiaGL); if (!res.mSourceSurface) { return res; } @@ -4418,7 +4419,7 @@ CanvasRenderingContext2D::DrawImage(const CanvasImageSource& image, } srcSurf = - CanvasImageCache::Lookup(element, mCanvasElement, &imgSize); + CanvasImageCache::Lookup(element, mCanvasElement, &imgSize, mIsSkiaGL); } nsLayoutUtils::DirectDrawInfo drawInfo; @@ -4566,7 +4567,7 @@ CanvasRenderingContext2D::DrawImage(const CanvasImageSource& image, if (res.mSourceSurface) { if (res.mImageRequest) { CanvasImageCache::NotifyDrawImage(element, mCanvasElement, res.mImageRequest, - res.mSourceSurface, imgSize); + res.mSourceSurface, imgSize, mIsSkiaGL); } srcSurf = res.mSourceSurface; From d2d8de9a5dbd4aa5361012d08760fbc7c05d29c5 Mon Sep 17 00:00:00 2001 From: Mike Hommey Date: Sat, 31 Oct 2015 07:18:24 +0900 Subject: [PATCH 093/113] Bug 1219147 - Use addEntriesToListFile in mozbuild.jar.JarMaker.updateManifest. r=mshal Also make addEntriesToListFile emit files with CR instead of CRLF on Windows. --- python/mozbuild/mozbuild/action/buildlist.py | 2 +- python/mozbuild/mozbuild/jar.py | 30 ++------------------ 2 files changed, 4 insertions(+), 28 deletions(-) diff --git a/python/mozbuild/mozbuild/action/buildlist.py b/python/mozbuild/mozbuild/action/buildlist.py index 4c25e09449a7..9d601d69a90f 100644 --- a/python/mozbuild/mozbuild/action/buildlist.py +++ b/python/mozbuild/mozbuild/action/buildlist.py @@ -33,7 +33,7 @@ def addEntriesToListFile(listFile, entries): for e in entries: if e not in existing: existing.add(e) - with open(listFile, 'w') as f: + with open(listFile, 'wb') as f: f.write("\n".join(sorted(existing))+"\n") finally: lock = None diff --git a/python/mozbuild/mozbuild/jar.py b/python/mozbuild/mozbuild/jar.py index 01dd50600a7a..484266211ff3 100644 --- a/python/mozbuild/mozbuild/jar.py +++ b/python/mozbuild/mozbuild/jar.py @@ -20,11 +20,6 @@ from MozZipFile import ZipFile from cStringIO import StringIO from collections import defaultdict -from mozbuild.util import ( - ensureParentDir, - lock_file, -) - from mozbuild.preprocessor import Preprocessor from mozbuild.action.buildlist import addEntriesToListFile from mozpack.files import FileFinder @@ -320,28 +315,9 @@ class JarMaker(object): '''updateManifest replaces the % in the chrome registration entries with the given chrome base path, and updates the given manifest file. ''' - - ensureParentDir(manifestPath) - lock = lock_file(manifestPath + '.lck') - try: - myregister = dict.fromkeys(map(lambda s: s.replace('%', - chromebasepath), register)) - manifestExists = os.path.isfile(manifestPath) - mode = manifestExists and 'r+b' or 'wb' - mf = open(manifestPath, mode) - if manifestExists: - # import previous content into hash, ignoring empty ones and comments - imf = re.compile('(#.*)?$') - for l in re.split('[\r\n]+', mf.read()): - if imf.match(l): - continue - myregister[l] = None - mf.seek(0) - for k in sorted(myregister.iterkeys()): - mf.write(k + os.linesep) - mf.close() - finally: - lock = None + myregister = dict.fromkeys(map(lambda s: s.replace('%', + chromebasepath), register)) + addEntriesToListFile(manifestPath, myregister.iterkeys()) def makeJar(self, infile, jardir): '''makeJar is the main entry point to JarMaker. From 9b411d21fd9083891b2f0a951408f007886c3b03 Mon Sep 17 00:00:00 2001 From: Mike Hommey Date: Wed, 28 Oct 2015 16:20:30 +0900 Subject: [PATCH 094/113] Bug 1219147 - Move resource chrome.manifest line for webapprt to jar.mn. r=mshal This allows FasterMake to produce this line properly. --- webapprt/Makefile.in | 8 ++------ webapprt/jar.mn | 4 ++++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/webapprt/Makefile.in b/webapprt/Makefile.in index ac3998cd7290..7c9d774ce80e 100644 --- a/webapprt/Makefile.in +++ b/webapprt/Makefile.in @@ -2,14 +2,10 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this file, # You can obtain one at http://mozilla.org/MPL/2.0/. -# Include config.mk explicitly so we can override FINAL_TARGET. -include $(topsrcdir)/config/config.mk - +# Include rules.mk explicitly so we can use FINAL_TARGET. Also, the dependencies +# for webapprt.ini need to be set after PP_TARGETS are expanded in rules.mk. include $(topsrcdir)/config/rules.mk -libs:: $(call mkdir_deps,$(FINAL_TARGET)) - $(call py_action,buildlist,$(FINAL_TARGET)/chrome.manifest 'resource webapprt ./') - MOZ_APP_BUILDID := $(shell cat $(DEPTH)/config/buildid) DEFINES += -DMOZ_APP_BUILDID=$(MOZ_APP_BUILDID) diff --git a/webapprt/jar.mn b/webapprt/jar.mn index fdf7fef06801..4bd129783c7f 100644 --- a/webapprt/jar.mn +++ b/webapprt/jar.mn @@ -15,3 +15,7 @@ webapprt.jar: content/downloads/downloads.js (content/downloads/downloads.js) content/downloads/downloads.css (content/downloads/downloads.css) content/downloads/download.xml (content/downloads/download.xml) + +# Trick to put the resource line in dist/bin/webapprt/chrome.manifest +[.] chrome.jar: +% resource webapprt ./ From f8b450ee345f148ea1731544eb0ebec1bbba96e4 Mon Sep 17 00:00:00 2001 From: Mike Hommey Date: Tue, 3 Nov 2015 11:25:59 +0900 Subject: [PATCH 095/113] Bug 1186748 - Make ASAN builds happy with Gtk+3. r=mccr8,r=bholley - Add a suppression for a leak LSAN detects in system libpixman. - Skip an intermittently failing test. --- build/sanitizers/lsan_suppressions.txt | 1 + dom/browser-element/mochitest/mochitest-oop.ini | 2 ++ 2 files changed, 3 insertions(+) diff --git a/build/sanitizers/lsan_suppressions.txt b/build/sanitizers/lsan_suppressions.txt index 44a4261f3b1c..7417ad29a370 100644 --- a/build/sanitizers/lsan_suppressions.txt +++ b/build/sanitizers/lsan_suppressions.txt @@ -109,6 +109,7 @@ leak:libdricore.so leak:libGL.so leak:libglib-2.0.so leak:libp11-kit.so +leak:libpixman-1.so leak:libpulse.so leak:libpulsecommon-1.1.so leak:libresolv.so diff --git a/dom/browser-element/mochitest/mochitest-oop.ini b/dom/browser-element/mochitest/mochitest-oop.ini index 82f72246e242..17b4c8051999 100644 --- a/dom/browser-element/mochitest/mochitest-oop.ini +++ b/dom/browser-element/mochitest/mochitest-oop.ini @@ -102,6 +102,8 @@ skip-if = (toolkit == 'gonk' && !debug) [test_browserElement_oop_VisibilityChange.html] [test_browserElement_oop_XFrameOptions.html] [test_browserElement_oop_XFrameOptionsAllowFrom.html] +# bug 1189592 +skip-if = asan [test_browserElement_oop_XFrameOptionsDeny.html] [test_browserElement_oop_XFrameOptionsSameOrigin.html] # Disabled until bug 930449 makes it stop timing out From b36da4bf897ba387cac950f1b2aa8a13104fb28e Mon Sep 17 00:00:00 2001 From: Mike Hommey Date: Fri, 31 Jul 2015 16:39:38 +0900 Subject: [PATCH 096/113] Bug 1186748 - Switch ASan builds to Gtk+3. r=mshal --- browser/config/tooltool-manifests/linux64/asan.manifest | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/browser/config/tooltool-manifests/linux64/asan.manifest b/browser/config/tooltool-manifests/linux64/asan.manifest index 820177316a8a..6c174581bfe4 100644 --- a/browser/config/tooltool-manifests/linux64/asan.manifest +++ b/browser/config/tooltool-manifests/linux64/asan.manifest @@ -8,5 +8,13 @@ "algorithm": "sha512", "filename": "clang.tar.bz2", "unpack": true +}, +{ +"size": 12057960, +"digest": "6105d6432943141cffb40020dc5ba3a793650bdeb3af9bd5e56d3796c5f03df9962a73e521646cd71fbfb5e266c1e74716ad722fb6055589dfb7d35175bca89e", +"algorithm": "sha512", +"filename": "gtk3.tar.xz", +"setup": "setup.sh", +"unpack": true } ] From eeb6e0e1c2e52d9e23c73964103120f6e52e01a8 Mon Sep 17 00:00:00 2001 From: Mike Hommey Date: Tue, 27 Oct 2015 09:49:48 +0900 Subject: [PATCH 097/113] Bug 1186748 - Now that all builds are pulling the Gtk+3 tooltool package, remove the Gtk+2 fallback in mozconfig.gtk. r=mshal --- build/unix/mozconfig.gtk | 53 ++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/build/unix/mozconfig.gtk b/build/unix/mozconfig.gtk index f1451c434627..0414ba859a0c 100644 --- a/build/unix/mozconfig.gtk +++ b/build/unix/mozconfig.gtk @@ -1,30 +1,29 @@ +# To do try builds with Gtk+2, uncomment the following line, and remove +# everything after that. +#ac_add_options --enable-default-toolkit=cairo-gtk2 + TOOLTOOL_DIR=${TOOLTOOL_DIR:-$topsrcdir} -# $TOOLTOOL_DIR/gtk3 comes from tooltool, when the tooltool manifest contains it. -if [ -d "$TOOLTOOL_DIR/gtk3" ]; then - if [ -z "$PKG_CONFIG_LIBDIR" ]; then - echo PKG_CONFIG_LIBDIR must be set >&2 - exit 1 - fi - export PKG_CONFIG_SYSROOT_DIR="$TOOLTOOL_DIR/gtk3" - export PKG_CONFIG_PATH="$TOOLTOOL_DIR/gtk3/usr/local/lib/pkgconfig" - PKG_CONFIG="$TOOLTOOL_DIR/gtk3/usr/local/bin/pkg-config" - export PATH="$TOOLTOOL_DIR/gtk3/usr/local/bin:${PATH}" - # Ensure cairo, gdk-pixbuf, etc. are not taken from the system installed packages. - LDFLAGS="-L$TOOLTOOL_DIR/gtk3/usr/local/lib ${LDFLAGS}" - ac_add_options --enable-default-toolkit=cairo-gtk3 - - # Set things up to use Gtk+3 from the tooltool package - mk_add_options "export FONTCONFIG_PATH=$TOOLTOOL_DIR/gtk3/usr/local/etc/fonts" - mk_add_options "export PANGO_SYSCONFDIR=$TOOLTOOL_DIR/gtk3/usr/local/etc" - mk_add_options "export PANGO_LIBDIR=$TOOLTOOL_DIR/gtk3/usr/local/lib" - mk_add_options "export GDK_PIXBUF_MODULE_FILE=$TOOLTOOL_DIR/gtk3/usr/local/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache" - mk_add_options "export GDK_PIXBUF_MODULEDIR=$TOOLTOOL_DIR/gtk3/usr/local/lib/gdk-pixbuf-2.0/2.10.0/loaders" - mk_add_options "export LD_LIBRARY_PATH=$TOOLTOOL_DIR/gtk3/usr/local/lib" - - # Until a tooltool with bug 1188571 landed is available everywhere - $TOOLTOOL_DIR/gtk3/setup.sh -else - PKG_CONFIG=pkg-config - ac_add_options --enable-default-toolkit=cairo-gtk2 +# $TOOLTOOL_DIR/gtk3 comes from tooltool, and must be included in the tooltool manifest. +if [ -z "$PKG_CONFIG_LIBDIR" ]; then + echo PKG_CONFIG_LIBDIR must be set >&2 + exit 1 fi +export PKG_CONFIG_SYSROOT_DIR="$TOOLTOOL_DIR/gtk3" +export PKG_CONFIG_PATH="$TOOLTOOL_DIR/gtk3/usr/local/lib/pkgconfig" +PKG_CONFIG="$TOOLTOOL_DIR/gtk3/usr/local/bin/pkg-config" +export PATH="$TOOLTOOL_DIR/gtk3/usr/local/bin:${PATH}" +# Ensure cairo, gdk-pixbuf, etc. are not taken from the system installed packages. +LDFLAGS="-L$TOOLTOOL_DIR/gtk3/usr/local/lib ${LDFLAGS}" +ac_add_options --enable-default-toolkit=cairo-gtk3 + +# Set things up to use Gtk+3 from the tooltool package +mk_add_options "export FONTCONFIG_PATH=$TOOLTOOL_DIR/gtk3/usr/local/etc/fonts" +mk_add_options "export PANGO_SYSCONFDIR=$TOOLTOOL_DIR/gtk3/usr/local/etc" +mk_add_options "export PANGO_LIBDIR=$TOOLTOOL_DIR/gtk3/usr/local/lib" +mk_add_options "export GDK_PIXBUF_MODULE_FILE=$TOOLTOOL_DIR/gtk3/usr/local/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache" +mk_add_options "export GDK_PIXBUF_MODULEDIR=$TOOLTOOL_DIR/gtk3/usr/local/lib/gdk-pixbuf-2.0/2.10.0/loaders" +mk_add_options "export LD_LIBRARY_PATH=$TOOLTOOL_DIR/gtk3/usr/local/lib" + +# Until a tooltool with bug 1188571 landed is available everywhere +$TOOLTOOL_DIR/gtk3/setup.sh From bb88489403a87ae09f564aae13e70a7df0ec55a1 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Sun, 1 Nov 2015 18:54:48 -0800 Subject: [PATCH 098/113] Bug 1187138 (part 1) - Replace nsBaseHashtable::Enumerate() calls in toolkit/ with iterators. r=froydnj. --- toolkit/components/telemetry/Telemetry.cpp | 48 +++++++--------------- 1 file changed, 14 insertions(+), 34 deletions(-) diff --git a/toolkit/components/telemetry/Telemetry.cpp b/toolkit/components/telemetry/Telemetry.cpp index 502059bc3602..9d1689a334ae 100644 --- a/toolkit/components/telemetry/Telemetry.cpp +++ b/toolkit/components/telemetry/Telemetry.cpp @@ -811,12 +811,6 @@ private: typedef nsClassHashtable KeyedHistogramMapType; KeyedHistogramMapType mKeyedHistograms; - - struct KeyedHistogramReflectArgs { - JSContext* jsContext; - JS::Handle object; - }; - static PLDHashOperator KeyedHistogramsReflector(const nsACString&, nsAutoPtr&, void* args); }; TelemetryImpl* TelemetryImpl::sTelemetry = nullptr; @@ -2467,29 +2461,6 @@ TelemetryImpl::GetAddonHistogramSnapshots(JSContext *cx, JS::MutableHandle& entry, void* arg) -{ - KeyedHistogramReflectArgs* args = static_cast(arg); - JSContext *cx = args->jsContext; - JS::RootedObject snapshot(cx, JS_NewPlainObject(cx)); - if (!snapshot) { - return PL_DHASH_STOP; - } - - if (!NS_SUCCEEDED(entry->GetJSSnapshot(cx, snapshot, false, false))) { - return PL_DHASH_STOP; - } - - if (!JS_DefineProperty(cx, args->object, PromiseFlatCString(key).get(), - snapshot, JSPROP_ENUMERATE)) { - return PL_DHASH_STOP; - } - - return PL_DHASH_NEXT; -} - NS_IMETHODIMP TelemetryImpl::GetKeyedHistogramSnapshots(JSContext *cx, JS::MutableHandle ret) { @@ -2498,11 +2469,20 @@ TelemetryImpl::GetKeyedHistogramSnapshots(JSContext *cx, JS::MutableHandle(&reflectArgs)); - if (num != mKeyedHistograms.Count()) { - return NS_ERROR_FAILURE; + for (auto iter = mKeyedHistograms.Iter(); !iter.Done(); iter.Next()) { + JS::RootedObject snapshot(cx, JS_NewPlainObject(cx)); + if (!snapshot) { + return NS_ERROR_FAILURE; + } + + if (!NS_SUCCEEDED(iter.Data()->GetJSSnapshot(cx, snapshot, false, false))) { + return NS_ERROR_FAILURE; + } + + if (!JS_DefineProperty(cx, obj, PromiseFlatCString(iter.Key()).get(), + snapshot, JSPROP_ENUMERATE)) { + return NS_ERROR_FAILURE; + } } ret.setObject(*obj); From 7b1bc3018df21f09f879b00646bba5ca5ff83c59 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Sun, 1 Nov 2015 20:12:36 -0800 Subject: [PATCH 099/113] Bug 1186812 (part 1) - Replace nsBaseHashtable::EnumerateRead() calls in dom/{ipc,plugins}/. r=jimm. --- dom/ipc/ProcessPriorityManager.cpp | 30 ++++++------------------------ 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/dom/ipc/ProcessPriorityManager.cpp b/dom/ipc/ProcessPriorityManager.cpp index 43a98345ea9f..1b1a94105d3a 100644 --- a/dom/ipc/ProcessPriorityManager.cpp +++ b/dom/ipc/ProcessPriorityManager.cpp @@ -599,37 +599,19 @@ ProcessPriorityManagerImpl::ObserveContentParentDestroyed(nsISupports* aSubject) } } -static PLDHashOperator -FreezeParticularProcessPriorityManagers( - const uint64_t& aKey, - RefPtr aValue, - void* aUserData) -{ - aValue->Freeze(); - return PL_DHASH_NEXT; -} - -static PLDHashOperator -UnfreezeParticularProcessPriorityManagers( - const uint64_t& aKey, - RefPtr aValue, - void* aUserData) -{ - aValue->Unfreeze(); - return PL_DHASH_NEXT; -} - void ProcessPriorityManagerImpl::ObserveScreenStateChanged(const char16_t* aData) { if (NS_LITERAL_STRING("on").Equals(aData)) { sFrozen = false; - mParticularManagers.EnumerateRead( - &UnfreezeParticularProcessPriorityManagers, nullptr); + for (auto iter = mParticularManagers.Iter(); !iter.Done(); iter.Next()) { + iter.UserData()->Unfreeze(); + } } else { sFrozen = true; - mParticularManagers.EnumerateRead( - &FreezeParticularProcessPriorityManagers, nullptr); + for (auto iter = mParticularManagers.Iter(); !iter.Done(); iter.Next()) { + iter.UserData()->Freeze(); + } } } From 6895f45d14c852986b5e2520cf84ac9fbc5103b5 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Sun, 1 Nov 2015 20:18:41 -0800 Subject: [PATCH 100/113] Bug 1186812 (part 2) - Replace nsBaseHashtable::EnumerateRead() calls in dom/{ipc,plugins}/. r=jimm. --- dom/ipc/ProcessHangMonitor.cpp | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/dom/ipc/ProcessHangMonitor.cpp b/dom/ipc/ProcessHangMonitor.cpp index 1ed9d09adf8c..231ba691933a 100644 --- a/dom/ipc/ProcessHangMonitor.cpp +++ b/dom/ipc/ProcessHangMonitor.cpp @@ -459,25 +459,22 @@ HangMonitorParent::HangMonitorParent(ProcessHangMonitor* aMonitor) mReportHangs = mozilla::Preferences::GetBool("dom.ipc.reportProcessHangs", false); } -static PLDHashOperator -DeleteMinidump(const uint32_t& aPluginId, nsString aCrashId, void* aUserData) -{ -#ifdef MOZ_CRASHREPORTER - if (!aCrashId.IsEmpty()) { - CrashReporter::DeleteMinidumpFilesForID(aCrashId); - } -#endif - return PL_DHASH_NEXT; -} - HangMonitorParent::~HangMonitorParent() { // For some reason IPDL doesn't automatically delete the channel for a // bridged protocol (bug 1090570). So we have to do it ourselves. XRE_GetIOMessageLoop()->PostTask(FROM_HERE, new DeleteTask(GetTransport())); +#ifdef MOZ_CRASHREPORTER MutexAutoLock lock(mBrowserCrashDumpHashLock); - mBrowserCrashDumpIds.EnumerateRead(DeleteMinidump, nullptr); + + for (auto iter = mBrowserCrashDumpIds.Iter(); !iter.Done(); iter.Next()) { + nsString crashId = iter.UserData(); + if (!crashId.IsEmpty()) { + CrashReporter::DeleteMinidumpFilesForID(crashId); + } + } +#endif } void From 2b8162e7ec8bb5044f01f1361482341fd411aec0 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Sun, 1 Nov 2015 20:18:51 -0800 Subject: [PATCH 101/113] Bug 1186812 (part 3) - Replace nsBaseHashtable::EnumerateRead() calls in dom/{ipc,plugins}/. r=jimm. --- dom/plugins/ipc/PluginInstanceParent.cpp | 32 +++--------------------- 1 file changed, 4 insertions(+), 28 deletions(-) diff --git a/dom/plugins/ipc/PluginInstanceParent.cpp b/dom/plugins/ipc/PluginInstanceParent.cpp index 577e6695ed83..e16f5d1365c5 100644 --- a/dom/plugins/ipc/PluginInstanceParent.cpp +++ b/dom/plugins/ipc/PluginInstanceParent.cpp @@ -1444,31 +1444,6 @@ PluginInstanceParent::AllocPPluginScriptableObjectParent() return new PluginScriptableObjectParent(Proxy); } -#ifdef DEBUG -namespace { - -struct ActorSearchData -{ - PluginScriptableObjectParent* actor; - bool found; -}; - -PLDHashOperator -ActorSearch(NPObject* aKey, - PluginScriptableObjectParent* aData, - void* aUserData) -{ - ActorSearchData* asd = reinterpret_cast(aUserData); - if (asd->actor == aData) { - asd->found = true; - return PL_DHASH_STOP; - } - return PL_DHASH_NEXT; -} - -} // namespace -#endif // DEBUG - bool PluginInstanceParent::DeallocPPluginScriptableObjectParent( PPluginScriptableObjectParent* aObject) @@ -1484,9 +1459,10 @@ PluginInstanceParent::DeallocPPluginScriptableObjectParent( } #ifdef DEBUG else { - ActorSearchData asd = { actor, false }; - mScriptableObjects.EnumerateRead(ActorSearch, &asd); - NS_ASSERTION(!asd.found, "Actor in the hash with a null NPObject!"); + for (auto iter = mScriptableObjects.Iter(); !iter.Done(); iter.Next()) { + NS_ASSERTION(actor != iter.UserData(), + "Actor in the hash with a null NPObject!"); + } } #endif From 399eff85fcb440f84d28db533ede93b00b5c7c42 Mon Sep 17 00:00:00 2001 From: JW Wang Date: Tue, 3 Nov 2015 20:26:57 +0800 Subject: [PATCH 102/113] Bug 1220646 - don't access mOwner which is invalid after shutdown. r=kinetik. --- dom/media/MediaDecoder.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dom/media/MediaDecoder.cpp b/dom/media/MediaDecoder.cpp index a156346e7a55..063ad7657622 100644 --- a/dom/media/MediaDecoder.cpp +++ b/dom/media/MediaDecoder.cpp @@ -745,6 +745,9 @@ MediaDecoder::NotifyDataEnded(nsresult aStatus) { RefPtr self = this; nsCOMPtr r = NS_NewRunnableFunction([=] () { + if (self->mShuttingDown) { + return; + } self->NotifyDownloadEnded(aStatus); if (NS_SUCCEEDED(aStatus)) { HTMLMediaElement* element = self->mOwner->GetMediaElement(); From 20693def2e4480dfee1ee11e5d42e85ff0bbf0cc Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Sun, 1 Nov 2015 18:41:40 -0800 Subject: [PATCH 103/113] Bug 1186794 (part 1) - Replace nsBaseHashtable::EnumerateRead() calls in embedding/ with iterators. r=bz. Also make mGroupNames const to avoid a cast. --HG-- extra : rebase_source : 09b2341b3a18265cfb8fddd70b008a8daa5dc272 --- .../commandhandler/nsCommandGroup.cpp | 25 ++++++------------- .../commandhandler/nsCommandManager.cpp | 24 ++++++------------ 2 files changed, 14 insertions(+), 35 deletions(-) diff --git a/embedding/components/commandhandler/nsCommandGroup.cpp b/embedding/components/commandhandler/nsCommandGroup.cpp index 69a4757986b6..8619e207ad47 100644 --- a/embedding/components/commandhandler/nsCommandGroup.cpp +++ b/embedding/components/commandhandler/nsCommandGroup.cpp @@ -27,14 +27,12 @@ public: protected: virtual ~nsGroupsEnumerator(); - static PLDHashOperator HashEnum(const nsACString& aKey, - nsTArray* aData, void* aClosure); nsresult Initialize(); protected: nsControllerCommandGroup::GroupsHashtable& mHashTable; int32_t mIndex; - char** mGroupNames; // array of pointers to char16_t* in the hash table + const char** mGroupNames; // array of pointers to char16_t* in the hash table bool mInitted; }; @@ -92,7 +90,7 @@ nsGroupsEnumerator::GetNext(nsISupports** aResult) return NS_ERROR_FAILURE; } - char* thisGroupName = mGroupNames[mIndex]; + const char* thisGroupName = mGroupNames[mIndex]; nsCOMPtr supportsString = do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID, &rv); @@ -104,18 +102,6 @@ nsGroupsEnumerator::GetNext(nsISupports** aResult) return CallQueryInterface(supportsString, aResult); } -/* static */ -/* return false to stop */ -PLDHashOperator -nsGroupsEnumerator::HashEnum(const nsACString& aKey, nsTArray* aData, - void* aClosure) -{ - nsGroupsEnumerator* groupsEnum = static_cast(aClosure); - groupsEnum->mGroupNames[groupsEnum->mIndex] = (char*)aKey.Data(); - groupsEnum->mIndex++; - return PL_DHASH_NEXT; -} - nsresult nsGroupsEnumerator::Initialize() { @@ -123,13 +109,16 @@ nsGroupsEnumerator::Initialize() return NS_OK; } - mGroupNames = new char*[mHashTable.Count()]; + mGroupNames = new const char*[mHashTable.Count()]; if (!mGroupNames) { return NS_ERROR_OUT_OF_MEMORY; } mIndex = 0; - mHashTable.EnumerateRead(HashEnum, this); + for (auto iter = mHashTable.Iter(); !iter.Done(); iter.Next()) { + mGroupNames[mIndex] = iter.Key().Data(); + mIndex++; + } mIndex = -1; mInitted = true; diff --git a/embedding/components/commandhandler/nsCommandManager.cpp b/embedding/components/commandhandler/nsCommandManager.cpp index 6e0274d7f40c..53f6e7e408a9 100644 --- a/embedding/components/commandhandler/nsCommandManager.cpp +++ b/embedding/components/commandhandler/nsCommandManager.cpp @@ -34,29 +34,19 @@ nsCommandManager::~nsCommandManager() { } -static PLDHashOperator -TraverseCommandObservers(const char* aKey, - nsCommandManager::ObserverList* aObservers, - void* aClosure) -{ - nsCycleCollectionTraversalCallback* cb = - static_cast(aClosure); - - int32_t i, numItems = aObservers->Length(); - for (i = 0; i < numItems; ++i) { - cb->NoteXPCOMChild(aObservers->ElementAt(i)); - } - - return PL_DHASH_NEXT; -} - NS_IMPL_CYCLE_COLLECTION_CLASS(nsCommandManager) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsCommandManager) tmp->mObserversTable.Clear(); NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsCommandManager) - tmp->mObserversTable.EnumerateRead(TraverseCommandObservers, &cb); + for (auto iter = tmp->mObserversTable.Iter(); !iter.Done(); iter.Next()) { + nsCommandManager::ObserverList* observers = iter.UserData(); + int32_t numItems = observers->Length(); + for (int32_t i = 0; i < numItems; ++i) { + cb.NoteXPCOMChild(observers->ElementAt(i)); + } + } NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTING_ADDREF(nsCommandManager) From 3fe818ed43941bcb4dbaf9a2697eaf2b842620fc Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Sun, 1 Nov 2015 18:41:57 -0800 Subject: [PATCH 104/113] Bug 1186794 (part 2) - Replace nsBaseHashtable::EnumerateRead() calls in embedding/ with iterators. r=bz. --HG-- extra : rebase_source : a8a42a93324936c089ce9b1e9167dcef6fe14b2c --- .../nsControllerCommandTable.cpp | 17 ++++------------- .../webbrowserpersist/nsWebBrowserPersist.cpp | 18 ++++++------------ .../webbrowserpersist/nsWebBrowserPersist.h | 2 -- 3 files changed, 10 insertions(+), 27 deletions(-) diff --git a/embedding/components/commandhandler/nsControllerCommandTable.cpp b/embedding/components/commandhandler/nsControllerCommandTable.cpp index 721188b3ae95..91075bba461d 100644 --- a/embedding/components/commandhandler/nsControllerCommandTable.cpp +++ b/embedding/components/commandhandler/nsControllerCommandTable.cpp @@ -178,18 +178,6 @@ nsControllerCommandTable::GetCommandState(const char* aCommandName, aCommandRefCon); } -static PLDHashOperator -AddCommand(const nsACString& aKey, nsIControllerCommand* aData, void* aArg) -{ - // aArg is a pointer to a array of strings. It gets incremented after - // allocating each one so that it points to the next location for AddCommand - // to assign a string to. - char*** commands = static_cast(aArg); - (**commands) = ToNewCString(aKey); - (*commands)++; - return PL_DHASH_NEXT; -} - NS_IMETHODIMP nsControllerCommandTable::GetSupportedCommands(uint32_t* aCount, char*** aCommands) @@ -199,7 +187,10 @@ nsControllerCommandTable::GetSupportedCommands(uint32_t* aCount, *aCount = mCommandsTable.Count(); *aCommands = commands; - mCommandsTable.EnumerateRead(AddCommand, &commands); + for (auto iter = mCommandsTable.Iter(); !iter.Done(); iter.Next()) { + *commands = ToNewCString(iter.Key()); + commands++; + } return NS_OK; } diff --git a/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp b/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp index e2776264822d..ec551d10f984 100644 --- a/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp +++ b/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp @@ -593,7 +593,12 @@ nsWebBrowserPersist::SerializeNextFile() // number of times this method is called. If it becomes a // bottleneck, the count of not-yet-persisted URIs could be // maintained separately. - mURIMap.EnumerateRead(EnumCountURIsToPersist, &urisToPersist); + for (auto iter = mURIMap.Iter(); !iter.Done(); iter.Next()) { + URIData *data = iter.UserData(); + if (data->mNeedsPersisting && !data->mSaved) { + urisToPersist++; + } + } } if (urisToPersist > 0) { @@ -2436,17 +2441,6 @@ nsWebBrowserPersist::EnumCalcUploadProgress(nsISupports *aKey, UploadData *aData return PL_DHASH_NEXT; } -PLDHashOperator -nsWebBrowserPersist::EnumCountURIsToPersist(const nsACString &aKey, URIData *aData, void* aClosure) -{ - uint32_t *count = static_cast(aClosure); - if (aData->mNeedsPersisting && !aData->mSaved) - { - (*count)++; - } - return PL_DHASH_NEXT; -} - PLDHashOperator nsWebBrowserPersist::EnumPersistURIs(const nsACString &aKey, URIData *aData, void* aClosure) { diff --git a/embedding/components/webbrowserpersist/nsWebBrowserPersist.h b/embedding/components/webbrowserpersist/nsWebBrowserPersist.h index cb01ce076551..d882b64b9e69 100644 --- a/embedding/components/webbrowserpersist/nsWebBrowserPersist.h +++ b/embedding/components/webbrowserpersist/nsWebBrowserPersist.h @@ -149,8 +149,6 @@ private: nsISupports *aKey, UploadData *aData, void* aClosure); static PLDHashOperator EnumFixRedirect( nsISupports *aKey, OutputData *aData, void* aClosure); - static PLDHashOperator EnumCountURIsToPersist( - const nsACString &aKey, URIData *aData, void* aClosure); static PLDHashOperator EnumCopyURIsToFlatMap( const nsACString &aKey, URIData *aData, void* aClosure); From cee3459f003750b746983b264ce359bc71d3fc79 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Sun, 1 Nov 2015 18:42:08 -0800 Subject: [PATCH 105/113] Bug 1186794 (part 3) - Replace nsBaseHashtable::EnumerateRead() calls in embedding/ with iterators. r=bz. --HG-- extra : rebase_source : 0a7f9b99376c0ab6c89c0ffb4dc69f9c9c5c7f6c --- .../webbrowserpersist/nsWebBrowserPersist.cpp | 141 ++++++++---------- .../webbrowserpersist/nsWebBrowserPersist.h | 4 - 2 files changed, 60 insertions(+), 85 deletions(-) diff --git a/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp b/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp index ec551d10f984..2f561a01b5a3 100644 --- a/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp +++ b/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp @@ -103,12 +103,6 @@ struct nsWebBrowserPersist::URIData nsresult GetLocalURI(nsIURI *targetBaseURI, nsCString& aSpecOut); }; -struct nsWebBrowserPersist::URIFixupData -{ - RefPtr mFlatMap; - nsCOMPtr mTargetBaseURI; -}; - // Information about the output stream struct nsWebBrowserPersist::OutputData { @@ -604,7 +598,58 @@ nsWebBrowserPersist::SerializeNextFile() if (urisToPersist > 0) { // Persist each file in the uri map. The document(s) // will be saved after the last one of these is saved. - mURIMap.EnumerateRead(EnumPersistURIs, this); + for (auto iter = mURIMap.Iter(); !iter.Done(); iter.Next()) { + URIData *data = iter.UserData(); + + if (!data->mNeedsPersisting || data->mSaved) { + continue; + } + + nsresult rv; + + // Create a URI from the key. + nsCOMPtr uri; + rv = NS_NewURI(getter_AddRefs(uri), iter.Key(), + data->mCharset.get()); + if (NS_WARN_IF(NS_FAILED(rv))) { + break; + } + + // Make a URI to save the data to. + nsCOMPtr fileAsURI; + rv = data->mDataPath->Clone(getter_AddRefs(fileAsURI)); + if (NS_WARN_IF(NS_FAILED(rv))) { + break; + } + rv = AppendPathToURI(fileAsURI, data->mFilename); + if (NS_WARN_IF(NS_FAILED(rv))) { + break; + } + + // The Referrer Policy doesn't matter here since the referrer is + // nullptr. + rv = SaveURIInternal(uri, nullptr, nullptr, + mozilla::net::RP_Default, nullptr, nullptr, + fileAsURI, true, mIsPrivate); + // If SaveURIInternal fails, then it will have called EndDownload, + // which means that |data| is no longer valid memory. We MUST bail. + if (NS_WARN_IF(NS_FAILED(rv))) { + break; + } + + if (rv == NS_OK) { + // Store the actual object because once it's persisted this + // will be fixed up with the right file extension. + data->mFile = fileAsURI; + data->mSaved = true; + } else { + data->mNeedsFixup = false; + } + + if (mSerializingOutput) { + break; + } + } } // If there are downloads happening, wait until they're done; the @@ -657,17 +702,18 @@ nsWebBrowserPersist::SerializeNextFile() return; } } - + // mFlatURIMap must be rebuilt each time through SerializeNextFile, as // mTargetBaseURI is used to create the relative URLs and will be different // with each serialized document. RefPtr flatMap = new FlatURIMap(targetBaseSpec); - - URIFixupData fixupData; - fixupData.mFlatMap = flatMap; - fixupData.mTargetBaseURI = mTargetBaseURI; - - mURIMap.EnumerateRead(EnumCopyURIsToFlatMap, &fixupData); + for (auto iter = mURIMap.Iter(); !iter.Done(); iter.Next()) { + nsAutoCString mapTo; + nsresult rv = iter.UserData()->GetLocalURI(mTargetBaseURI, mapTo); + if (NS_SUCCEEDED(rv) || !mapTo.IsVoid()) { + flatMap->Add(iter.Key(), mapTo); + } + } mFlatURIMap = flatMap.forget(); nsCOMPtr localFile; @@ -2441,58 +2487,6 @@ nsWebBrowserPersist::EnumCalcUploadProgress(nsISupports *aKey, UploadData *aData return PL_DHASH_NEXT; } -PLDHashOperator -nsWebBrowserPersist::EnumPersistURIs(const nsACString &aKey, URIData *aData, void* aClosure) -{ - if (!aData->mNeedsPersisting || aData->mSaved) - { - return PL_DHASH_NEXT; - } - - nsWebBrowserPersist *pthis = static_cast(aClosure); - nsresult rv; - - // Create a URI from the key - nsAutoCString key = nsAutoCString(aKey); - nsCOMPtr uri; - rv = NS_NewURI(getter_AddRefs(uri), - nsDependentCString(key.get(), key.Length()), - aData->mCharset.get()); - NS_ENSURE_SUCCESS(rv, PL_DHASH_STOP); - - // Make a URI to save the data to - nsCOMPtr fileAsURI; - rv = aData->mDataPath->Clone(getter_AddRefs(fileAsURI)); - NS_ENSURE_SUCCESS(rv, PL_DHASH_STOP); - rv = pthis->AppendPathToURI(fileAsURI, aData->mFilename); - NS_ENSURE_SUCCESS(rv, PL_DHASH_STOP); - - // The Referrer Policy doesn't matter here since the referrer is nullptr. - rv = pthis->SaveURIInternal(uri, nullptr, nullptr, mozilla::net::RP_Default, - nullptr, nullptr, fileAsURI, true, pthis->mIsPrivate); - // if SaveURIInternal fails, then it will have called EndDownload, - // which means that |aData| is no longer valid memory. we MUST bail. - NS_ENSURE_SUCCESS(rv, PL_DHASH_STOP); - - if (rv == NS_OK) - { - // Store the actual object because once it's persisted this - // will be fixed up with the right file extension. - - aData->mFile = fileAsURI; - aData->mSaved = true; - } - else - { - aData->mNeedsFixup = false; - } - - if (pthis->mSerializingOutput) - return PL_DHASH_STOP; - - return PL_DHASH_NEXT; -} - PLDHashOperator nsWebBrowserPersist::EnumCleanupOutputMap(nsISupports *aKey, OutputData *aData, void* aClosure) { @@ -2515,21 +2509,6 @@ nsWebBrowserPersist::EnumCleanupUploadList(nsISupports *aKey, UploadData *aData, return PL_DHASH_NEXT; } -/* static */ PLDHashOperator -nsWebBrowserPersist::EnumCopyURIsToFlatMap(const nsACString &aKey, - URIData *aData, - void* aClosure) -{ - URIFixupData *fixupData = static_cast(aClosure); - FlatURIMap* theMap = fixupData->mFlatMap; - nsAutoCString mapTo; - nsresult rv = aData->GetLocalURI(fixupData->mTargetBaseURI, mapTo); - if (NS_SUCCEEDED(rv) || !mapTo.IsVoid()) { - theMap->Add(aKey, mapTo); - } - return PL_DHASH_NEXT; -} - nsresult nsWebBrowserPersist::StoreURI( const char *aURI, bool aNeedsPersisting, URIData **aData) diff --git a/embedding/components/webbrowserpersist/nsWebBrowserPersist.h b/embedding/components/webbrowserpersist/nsWebBrowserPersist.h index d882b64b9e69..e2169ee8bb9d 100644 --- a/embedding/components/webbrowserpersist/nsWebBrowserPersist.h +++ b/embedding/components/webbrowserpersist/nsWebBrowserPersist.h @@ -137,8 +137,6 @@ private: void SetApplyConversionIfNeeded(nsIChannel *aChannel); // Hash table enumerators - static PLDHashOperator EnumPersistURIs( - const nsACString &aKey, URIData *aData, void* aClosure); static PLDHashOperator EnumCleanupOutputMap( nsISupports *aKey, OutputData *aData, void* aClosure); static PLDHashOperator EnumCleanupUploadList( @@ -149,8 +147,6 @@ private: nsISupports *aKey, UploadData *aData, void* aClosure); static PLDHashOperator EnumFixRedirect( nsISupports *aKey, OutputData *aData, void* aClosure); - static PLDHashOperator EnumCopyURIsToFlatMap( - const nsACString &aKey, URIData *aData, void* aClosure); nsCOMPtr mCurrentDataPath; bool mCurrentDataPathIsRelative; From 8977a4ab4d8d322dd52641d8fd0315ea0f10704f Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Sun, 1 Nov 2015 18:42:28 -0800 Subject: [PATCH 106/113] Bug 1186794 (part 4) - Replace nsBaseHashtable::EnumerateRead() calls in embedding/ with iterators. r=bz. --HG-- extra : rebase_source : 673814624ed8523ef2e33d9d3eab8dc935638666 --- .../webbrowserpersist/nsWebBrowserPersist.cpp | 46 ++++++++----------- .../webbrowserpersist/nsWebBrowserPersist.h | 4 -- 2 files changed, 18 insertions(+), 32 deletions(-) diff --git a/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp b/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp index 2f561a01b5a3..dd5ce564fe67 100644 --- a/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp +++ b/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp @@ -1806,23 +1806,35 @@ nsWebBrowserPersist::FinishSaveDocumentInternal(nsIURI* aFile, void nsWebBrowserPersist::Cleanup() { mURIMap.Clear(); - mOutputMap.EnumerateRead(EnumCleanupOutputMap, this); + for (auto iter = mOutputMap.Iter(); !iter.Done(); iter.Next()) { + nsCOMPtr channel = do_QueryInterface(iter.Key()); + if (channel) { + channel->Cancel(NS_BINDING_ABORTED); + } + } mOutputMap.Clear(); - mUploadList.EnumerateRead(EnumCleanupUploadList, this); + + for (auto iter = mUploadList.Iter(); !iter.Done(); iter.Next()) { + nsCOMPtr channel = do_QueryInterface(iter.Key()); + if (channel) { + channel->Cancel(NS_BINDING_ABORTED); + } + } mUploadList.Clear(); + uint32_t i; - for (i = 0; i < mDocList.Length(); i++) - { + for (i = 0; i < mDocList.Length(); i++) { DocData *docData = mDocList.ElementAt(i); delete docData; } mDocList.Clear(); - for (i = 0; i < mCleanupList.Length(); i++) - { + + for (i = 0; i < mCleanupList.Length(); i++) { CleanupData *cleanupData = mCleanupList.ElementAt(i); delete cleanupData; } mCleanupList.Clear(); + mFilenameList.Clear(); } @@ -2487,28 +2499,6 @@ nsWebBrowserPersist::EnumCalcUploadProgress(nsISupports *aKey, UploadData *aData return PL_DHASH_NEXT; } -PLDHashOperator -nsWebBrowserPersist::EnumCleanupOutputMap(nsISupports *aKey, OutputData *aData, void* aClosure) -{ - nsCOMPtr channel = do_QueryInterface(aKey); - if (channel) - { - channel->Cancel(NS_BINDING_ABORTED); - } - return PL_DHASH_NEXT; -} - -PLDHashOperator -nsWebBrowserPersist::EnumCleanupUploadList(nsISupports *aKey, UploadData *aData, void* aClosure) -{ - nsCOMPtr channel = do_QueryInterface(aKey); - if (channel) - { - channel->Cancel(NS_BINDING_ABORTED); - } - return PL_DHASH_NEXT; -} - nsresult nsWebBrowserPersist::StoreURI( const char *aURI, bool aNeedsPersisting, URIData **aData) diff --git a/embedding/components/webbrowserpersist/nsWebBrowserPersist.h b/embedding/components/webbrowserpersist/nsWebBrowserPersist.h index e2169ee8bb9d..ae1acb757ec1 100644 --- a/embedding/components/webbrowserpersist/nsWebBrowserPersist.h +++ b/embedding/components/webbrowserpersist/nsWebBrowserPersist.h @@ -137,10 +137,6 @@ private: void SetApplyConversionIfNeeded(nsIChannel *aChannel); // Hash table enumerators - static PLDHashOperator EnumCleanupOutputMap( - nsISupports *aKey, OutputData *aData, void* aClosure); - static PLDHashOperator EnumCleanupUploadList( - nsISupports *aKey, UploadData *aData, void* aClosure); static PLDHashOperator EnumCalcProgress( nsISupports *aKey, OutputData *aData, void* aClosure); static PLDHashOperator EnumCalcUploadProgress( From 7823098274c02b4e9301cd20526351c0c4368ad1 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Sun, 1 Nov 2015 18:42:41 -0800 Subject: [PATCH 107/113] Bug 1186794 (part 5) - Replace nsBaseHashtable::EnumerateRead() calls in embedding/ with iterators. r=bz. --HG-- extra : rebase_source : 42a816294ecb1f94bbb6d077c3d69fbab0700610 --- .../webbrowserpersist/nsWebBrowserPersist.cpp | 73 ++++++------------- .../webbrowserpersist/nsWebBrowserPersist.h | 2 - 2 files changed, 24 insertions(+), 51 deletions(-) diff --git a/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp b/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp index dd5ce564fe67..d962ba7ba39a 100644 --- a/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp +++ b/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp @@ -2382,69 +2382,44 @@ nsWebBrowserPersist::EndDownload(nsresult aResult) mEventSink = nullptr; } -struct MOZ_STACK_CLASS FixRedirectData -{ - nsCOMPtr mNewChannel; - nsCOMPtr mOriginalURI; - nsCOMPtr mMatchingKey; -}; - nsresult nsWebBrowserPersist::FixRedirectedChannelEntry(nsIChannel *aNewChannel) { NS_ENSURE_ARG_POINTER(aNewChannel); + + // Iterate through existing open channels looking for one with a URI + // matching the one specified. nsCOMPtr originalURI; + aNewChannel->GetOriginalURI(getter_AddRefs(originalURI)); + for (auto iter = mOutputMap.Iter(); !iter.Done(); iter.Next()) { + nsISupports* key = iter.Key(); + nsCOMPtr thisChannel = do_QueryInterface(key); + nsCOMPtr thisURI; - // Enumerate through existing open channels looking for one with - // a URI matching the one specified. + thisChannel->GetOriginalURI(getter_AddRefs(thisURI)); - FixRedirectData data; - data.mNewChannel = aNewChannel; - data.mNewChannel->GetOriginalURI(getter_AddRefs(data.mOriginalURI)); - mOutputMap.EnumerateRead(EnumFixRedirect, &data); + // Compare this channel's URI to the one passed in. + bool matchingURI = false; + thisURI->Equals(originalURI, &matchingURI); + if (matchingURI) { + // If a match is found, remove the data entry with the old channel + // key and re-add it with the new channel key. + nsAutoPtr outputData; + mOutputMap.RemoveAndForget(key, outputData); + NS_ENSURE_TRUE(outputData, NS_ERROR_FAILURE); - // If a match is found, remove the data entry with the old channel key - // and re-add it with the new channel key. + // Store data again with new channel unless told to ignore redirects + if (!(mPersistFlags & PERSIST_FLAGS_IGNORE_REDIRECTED_DATA)) { + nsCOMPtr keyPtr = do_QueryInterface(aNewChannel); + mOutputMap.Put(keyPtr, outputData.forget()); + } - if (data.mMatchingKey) - { - nsAutoPtr outputData; - mOutputMap.RemoveAndForget(data.mMatchingKey, outputData); - NS_ENSURE_TRUE(outputData, NS_ERROR_FAILURE); - - // Store data again with new channel unless told to ignore redirects - if (!(mPersistFlags & PERSIST_FLAGS_IGNORE_REDIRECTED_DATA)) - { - nsCOMPtr keyPtr = do_QueryInterface(aNewChannel); - mOutputMap.Put(keyPtr, outputData.forget()); + break; } } - return NS_OK; } -PLDHashOperator -nsWebBrowserPersist::EnumFixRedirect(nsISupports *aKey, OutputData *aData, void* aClosure) -{ - FixRedirectData *data = static_cast(aClosure); - - nsCOMPtr thisChannel = do_QueryInterface(aKey); - nsCOMPtr thisURI; - - thisChannel->GetOriginalURI(getter_AddRefs(thisURI)); - - // Compare this channel's URI to the one passed in. - bool matchingURI = false; - thisURI->Equals(data->mOriginalURI, &matchingURI); - if (matchingURI) - { - data->mMatchingKey = aKey; - return PL_DHASH_STOP; - } - - return PL_DHASH_NEXT; -} - void nsWebBrowserPersist::CalcTotalProgress() { diff --git a/embedding/components/webbrowserpersist/nsWebBrowserPersist.h b/embedding/components/webbrowserpersist/nsWebBrowserPersist.h index ae1acb757ec1..bb332e6d9dfd 100644 --- a/embedding/components/webbrowserpersist/nsWebBrowserPersist.h +++ b/embedding/components/webbrowserpersist/nsWebBrowserPersist.h @@ -141,8 +141,6 @@ private: nsISupports *aKey, OutputData *aData, void* aClosure); static PLDHashOperator EnumCalcUploadProgress( nsISupports *aKey, UploadData *aData, void* aClosure); - static PLDHashOperator EnumFixRedirect( - nsISupports *aKey, OutputData *aData, void* aClosure); nsCOMPtr mCurrentDataPath; bool mCurrentDataPathIsRelative; From e30a48ab835d6435b427c38de1cadf30b9f52065 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Sun, 1 Nov 2015 18:42:54 -0800 Subject: [PATCH 108/113] Bug 1186794 (part 6) - Replace nsBaseHashtable::EnumerateRead() calls in embedding/ with iterators. r=bz. --HG-- extra : rebase_source : e83b173903e4f7fbf3680d84e957dcc69c50bd06 --- .../webbrowserpersist/nsWebBrowserPersist.cpp | 51 +++++++------------ .../webbrowserpersist/nsWebBrowserPersist.h | 6 --- 2 files changed, 18 insertions(+), 39 deletions(-) diff --git a/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp b/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp index d962ba7ba39a..c39d7571ad68 100644 --- a/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp +++ b/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp @@ -2426,16 +2426,28 @@ nsWebBrowserPersist::CalcTotalProgress() mTotalCurrentProgress = 0; mTotalMaxProgress = 0; - if (mOutputMap.Count() > 0) - { + if (mOutputMap.Count() > 0) { // Total up the progress of each output stream - mOutputMap.EnumerateRead(EnumCalcProgress, this); + for (auto iter = mOutputMap.Iter(); !iter.Done(); iter.Next()) { + // Only count toward total progress if destination file is local. + OutputData* data = iter.UserData(); + nsCOMPtr fileURL = do_QueryInterface(data->mFile); + if (fileURL) { + mTotalCurrentProgress += data->mSelfProgress; + mTotalMaxProgress += data->mSelfProgressMax; + } + } } - if (mUploadList.Count() > 0) - { + if (mUploadList.Count() > 0) { // Total up the progress of each upload - mUploadList.EnumerateRead(EnumCalcUploadProgress, this); + for (auto iter = mUploadList.Iter(); !iter.Done(); iter.Next()) { + UploadData* data = iter.UserData(); + if (data) { + mTotalCurrentProgress += data->mSelfProgress; + mTotalMaxProgress += data->mSelfProgressMax; + } + } } // XXX this code seems pretty bogus and pointless @@ -2447,33 +2459,6 @@ nsWebBrowserPersist::CalcTotalProgress() } } -PLDHashOperator -nsWebBrowserPersist::EnumCalcProgress(nsISupports *aKey, OutputData *aData, void* aClosure) -{ - nsWebBrowserPersist *pthis = static_cast(aClosure); - - // only count toward total progress if destination file is local - nsCOMPtr fileURL = do_QueryInterface(aData->mFile); - if (fileURL) - { - pthis->mTotalCurrentProgress += aData->mSelfProgress; - pthis->mTotalMaxProgress += aData->mSelfProgressMax; - } - return PL_DHASH_NEXT; -} - -PLDHashOperator -nsWebBrowserPersist::EnumCalcUploadProgress(nsISupports *aKey, UploadData *aData, void* aClosure) -{ - if (aData && aClosure) - { - nsWebBrowserPersist *pthis = static_cast(aClosure); - pthis->mTotalCurrentProgress += aData->mSelfProgress; - pthis->mTotalMaxProgress += aData->mSelfProgressMax; - } - return PL_DHASH_NEXT; -} - nsresult nsWebBrowserPersist::StoreURI( const char *aURI, bool aNeedsPersisting, URIData **aData) diff --git a/embedding/components/webbrowserpersist/nsWebBrowserPersist.h b/embedding/components/webbrowserpersist/nsWebBrowserPersist.h index bb332e6d9dfd..0b8687d79882 100644 --- a/embedding/components/webbrowserpersist/nsWebBrowserPersist.h +++ b/embedding/components/webbrowserpersist/nsWebBrowserPersist.h @@ -136,12 +136,6 @@ private: void SetApplyConversionIfNeeded(nsIChannel *aChannel); - // Hash table enumerators - static PLDHashOperator EnumCalcProgress( - nsISupports *aKey, OutputData *aData, void* aClosure); - static PLDHashOperator EnumCalcUploadProgress( - nsISupports *aKey, UploadData *aData, void* aClosure); - nsCOMPtr mCurrentDataPath; bool mCurrentDataPathIsRelative; nsCString mCurrentRelativePathToData; From c663cc072719c52e6f04a37f9132de9976287689 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Tue, 3 Nov 2015 14:49:46 -0800 Subject: [PATCH 109/113] Bug 1214058: Part 1 - Add a simplified JSON-based add-on update protocol. r=Mossop --HG-- extra : commitid : HUFAitZpdVA extra : rebase_source : b2eb9ede0582b804e1e583570e165e27f42c8fa7 extra : source : a4d5d63a03ef3938d95f629a6a9ea31d3e88627d --- modules/libpref/init/all.js | 1 + toolkit/mozapps/extensions/AddonManager.jsm | 16 + .../internal/AddonUpdateChecker.jsm | 248 ++++++++-- .../extensions/internal/XPIProvider.jsm | 2 +- .../test/xpcshell/data/test_updatecheck.json | 327 +++++++++++++ .../test/xpcshell/data/test_updatecheck.rdf | 2 +- .../extensions/test/xpcshell/head_addons.js | 66 ++- .../test/xpcshell/test_json_updatecheck.js | 373 ++++++++++++++ .../test/xpcshell/test_updatecheck.js | 462 ++++++++---------- .../test/xpcshell/xpcshell-shared.ini | 1 + 10 files changed, 1190 insertions(+), 308 deletions(-) create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.json create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_json_updatecheck.js diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 65b9b7ebbd2a..001a6d9570c5 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -4402,6 +4402,7 @@ pref("xpinstall.whitelist.required", true); pref("xpinstall.signatures.required", false); pref("extensions.alwaysUnpack", false); pref("extensions.minCompatiblePlatformVersion", "2.0"); +pref("extensions.webExtensionsMinPlatformVersion", "42.0a1"); pref("network.buffer.cache.count", 24); pref("network.buffer.cache.size", 32768); diff --git a/toolkit/mozapps/extensions/AddonManager.jsm b/toolkit/mozapps/extensions/AddonManager.jsm index 0864cfbec3de..184bf3b1f3cf 100644 --- a/toolkit/mozapps/extensions/AddonManager.jsm +++ b/toolkit/mozapps/extensions/AddonManager.jsm @@ -42,6 +42,8 @@ const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS"; const PREF_SELECTED_LOCALE = "general.useragent.locale"; const UNKNOWN_XPCOM_ABI = "unknownABI"; +const PREF_MIN_WEBEXT_PLATFORM_VERSION = "extensions.webExtensionsMinPlatformVersion"; + const UPDATE_REQUEST_VERSION = 2; const CATEGORY_UPDATE_PARAMS = "extension-update-params"; @@ -663,6 +665,7 @@ var gCheckUpdateSecurity = gCheckUpdateSecurityDefault; var gUpdateEnabled = true; var gAutoUpdateDefault = true; var gHotfixID = null; +var gWebExtensionsMinPlatformVersion = null; var gShutdownBarrier = null; var gRepoShutdownState = ""; var gShutdownInProgress = false; @@ -947,6 +950,11 @@ var AddonManagerInternal = { } catch (e) {} Services.prefs.addObserver(PREF_EM_HOTFIX_ID, this, false); + try { + gWebExtensionsMinPlatformVersion = Services.prefs.getCharPref(PREF_MIN_WEBEXT_PLATFORM_VERSION); + } catch (e) {} + Services.prefs.addObserver(PREF_MIN_WEBEXT_PLATFORM_VERSION, this, false); + let defaultProvidersEnabled = true; try { defaultProvidersEnabled = Services.prefs.getBoolPref(PREF_DEFAULT_PROVIDERS_ENABLED); @@ -1377,6 +1385,10 @@ var AddonManagerInternal = { } break; } + case PREF_MIN_WEBEXT_PLATFORM_VERSION: { + gWebExtensionsMinPlatformVersion = Services.prefs.getCharPref(PREF_MIN_WEBEXT_PLATFORM_VERSION); + break; + } } }, @@ -2894,6 +2906,10 @@ this.AddonManagerPrivate = { safeCall(listener.onUpdateFinished.bind(listener), addon); } }, + + get webExtensionsMinPlatformVersion() { + return gWebExtensionsMinPlatformVersion; + }, }; /** diff --git a/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm b/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm index f6becf98fb43..a567eb678de0 100644 --- a/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm +++ b/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm @@ -31,6 +31,8 @@ Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate", + "resource://gre/modules/AddonManager.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository", "resource://gre/modules/addons/AddonRepository.jsm"); @@ -217,6 +219,48 @@ RDFSerializer.prototype = { } } +/** + * Sanitizes the update URL in an update item, as returned by + * parseRDFManifest and parseJSONManifest. Ensures that: + * + * - The URL is secure, or secured by a strong enough hash. + * - The security principal of the update manifest has permission to + * load the URL. + * + * @param aUpdate + * The update item to sanitize. + * @param aRequest + * The XMLHttpRequest used to load the manifest. + * @param aHashPattern + * The regular expression used to validate the update hash. + * @param aHashString + * The human-readable string specifying which hash functions + * are accepted. + */ +function sanitizeUpdateURL(aUpdate, aRequest, aHashPattern, aHashString) { + if (aUpdate.updateURL) { + let scriptSecurity = Services.scriptSecurityManager; + let principal = scriptSecurity.getChannelURIPrincipal(aRequest.channel); + try { + // This logs an error on failure, so no need to log it a second time + scriptSecurity.checkLoadURIStrWithPrincipal(principal, aUpdate.updateURL, + scriptSecurity.DISALLOW_SCRIPT); + } catch (e) { + delete aUpdate.updateURL; + return; + } + + if (AddonManager.checkUpdateSecurity && + !aUpdate.updateURL.startsWith("https:") && + !aHashPattern.test(aUpdate.updateHash)) { + logger.warn(`Update link ${aUpdate.updateURL} is not secure and is not verified ` + + `by a strong enough hash (needs to be ${aHashString}).`); + delete aUpdate.updateURL; + delete aUpdate.updateHash; + } + } +} + /** * Parses an RDF style update manifest into an array of update objects. * @@ -226,10 +270,17 @@ RDFSerializer.prototype = { * An optional update key for the add-on * @param aRequest * The XMLHttpRequest that has retrieved the update manifest + * @param aManifestData + * The pre-parsed manifest, as a bare XML DOM document * @return an array of update objects * @throws if the update manifest is invalid in any way */ -function parseRDFManifest(aId, aUpdateKey, aRequest) { +function parseRDFManifest(aId, aUpdateKey, aRequest, aManifestData) { + if (aManifestData.documentElement.namespaceURI != PREFIX_NS_RDF) { + throw Components.Exception("Update manifest had an unrecognised namespace: " + xml.documentElement.namespaceURI); + return; + } + function EM_R(aProp) { return gRDF.GetResource(PREFIX_NS_EM + aProp); } @@ -366,20 +417,136 @@ function parseRDFManifest(aId, aUpdateKey, aRequest) { targetApplications: [appEntry] }; - if (result.updateURL && AddonManager.checkUpdateSecurity && - result.updateURL.substring(0, 6) != "https:" && - (!result.updateHash || result.updateHash.substring(0, 3) != "sha")) { - logger.warn("updateLink " + result.updateURL + " is not secure and is not verified" + - " by a strong enough hash (needs to be sha1 or stronger)."); - delete result.updateURL; - delete result.updateHash; - } + // The JSON update protocol requires an SHA-2 hash. RDF still + // supports SHA-1, for compatibility reasons. + sanitizeUpdateURL(result, aRequest, /^sha/, "sha1 or stronger"); + results.push(result); } } return results; } +/** + * Parses an JSON update manifest into an array of update objects. + * + * @param aId + * The ID of the add-on being checked for updates + * @param aUpdateKey + * An optional update key for the add-on + * @param aRequest + * The XMLHttpRequest that has retrieved the update manifest + * @param aManifestData + * The pre-parsed manifest, as a JSON object tree + * @return an array of update objects + * @throws if the update manifest is invalid in any way + */ +function parseJSONManifest(aId, aUpdateKey, aRequest, aManifestData) { + if (aUpdateKey) + throw Components.Exception("Update keys are not supported for JSON update manifests"); + + let TYPE_CHECK = { + "array": val => Array.isArray(val), + "object": val => val && typeof val == "object" && !Array.isArray(val), + }; + + function getProperty(aObj, aProperty, aType, aDefault = undefined) { + if (!(aProperty in aObj)) + return aDefault; + + let value = aObj[aProperty]; + + let matchesType = aType in TYPE_CHECK ? TYPE_CHECK[aType](value) : typeof value == aType; + if (!matchesType) + throw Components.Exception(`Update manifest property '${aProperty}' has incorrect type (expected ${aType})`); + + return value; + } + + function getRequiredProperty(aObj, aProperty, aType) { + let value = getProperty(aObj, aProperty, aType); + if (value === undefined) + throw Components.Exception(`Update manifest is missing a required ${aProperty} property.`); + return value; + } + + let manifest = aManifestData; + + if (!TYPE_CHECK["object"](manifest)) + throw Components.Exception("Root element of update manifest must be a JSON object literal"); + + // The set of add-ons this manifest has updates for + let addons = getRequiredProperty(manifest, "addons", "object"); + + // The entry for this particular add-on + let addon = getProperty(addons, aId, "object"); + + // A missing entry doesn't count as a failure, just as no avialable update + // information + if (!addon) { + logger.warn("Update manifest did not contain an entry for " + aId); + return []; + } + + // The list of available updates + let updates = getProperty(addon, "updates", "array", []); + + let results = []; + + for (let update of updates) { + let version = getRequiredProperty(update, "version", "string"); + + logger.debug(`Found an update entry for ${aId} version ${version}`); + + let applications = getProperty(update, "applications", "object", + { gecko: {} }); + + // "gecko" is currently the only supported application entry. If + // it's missing, skip this update. + if (!("gecko" in applications)) + continue; + + let app = getProperty(applications, "gecko", "object"); + + let appEntry = { + id: TOOLKIT_ID, + minVersion: getProperty(app, "strict_min_version", "string", + AddonManagerPrivate.webExtensionsMinPlatformVersion), + maxVersion: "*", + }; + + let result = { + id: aId, + version: version, + multiprocessCompatible: getProperty(update, "multiprocess_compatible", "boolean", true), + updateURL: getProperty(update, "update_link", "string"), + updateHash: getProperty(update, "update_hash", "string"), + updateInfoURL: getProperty(update, "update_info_url", "string"), + strictCompatibility: false, + targetApplications: [appEntry], + }; + + if ("strict_max_version" in app) { + if ("advisory_max_version" in app) { + logger.warn("Ignoring 'advisory_max_version' update manifest property for " + + aId + " property since 'strict_max_version' also present"); + } + + appEntry.maxVersion = getProperty(app, "strict_max_version", "string"); + result.strictCompatibility = appEntry.maxVersion != "*"; + } else if ("advisory_max_version" in app) { + appEntry.maxVersion = getProperty(app, "advisory_max_version", "string"); + } + + // The JSON update protocol requires an SHA-2 hash. RDF still + // supports SHA-1, for compatibility reasons. + sanitizeUpdateURL(result, aRequest, /^sha(256|512):/, "sha256 or sha512"); + + results.push(result); + } + return results; +} + /** * Starts downloading an update manifest and then passes it to an appropriate * parser to convert to an array of update objects @@ -415,7 +582,7 @@ function UpdateParser(aId, aUpdateKey, aUrl, aObserver) { this.request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE; // Prevent the request from writing to cache. this.request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING; - this.request.overrideMimeType("text/xml"); + this.request.overrideMimeType("text/plain"); this.request.setRequestHeader("Moz-XPI-Update", "1", true); this.request.timeout = TIMEOUT; var self = this; @@ -474,41 +641,50 @@ UpdateParser.prototype = { return; } - let xml = request.responseXML; - if (!xml || xml.documentElement.namespaceURI == XMLURI_PARSE_ERROR) { - logger.warn("Update manifest was not valid XML"); + // Detect the manifest type by first attempting to parse it as + // JSON, and falling back to parsing it as XML if that fails. + let parser; + try { + try { + let json = JSON.parse(request.responseText); + + parser = () => parseJSONManifest(this.id, this.updateKey, request, json); + } catch (e if e instanceof SyntaxError) { + let domParser = Cc["@mozilla.org/xmlextras/domparser;1"].createInstance(Ci.nsIDOMParser); + let xml = domParser.parseFromString(request.responseText, "text/xml"); + + if (xml.documentElement.namespaceURI == XMLURI_PARSE_ERROR) + throw new Error("Update manifest was not valid XML or JSON"); + + parser = () => parseRDFManifest(this.id, this.updateKey, request, xml); + } + } catch (e) { + logger.warn("onUpdateCheckComplete failed to determine manifest type"); + this.notifyError(AddonUpdateChecker.ERROR_UNKNOWN_FORMAT); + return; + } + + let results; + try { + results = parser(); + } + catch (e) { + logger.warn("onUpdateCheckComplete failed to parse update manifest", e); this.notifyError(AddonUpdateChecker.ERROR_PARSE_ERROR); return; } - // We currently only know about RDF update manifests - if (xml.documentElement.namespaceURI == PREFIX_NS_RDF) { - let results = null; - + if ("onUpdateCheckComplete" in this.observer) { try { - results = parseRDFManifest(this.id, this.updateKey, request); + this.observer.onUpdateCheckComplete(results); } catch (e) { - logger.warn("onUpdateCheckComplete failed to parse RDF manifest", e); - this.notifyError(AddonUpdateChecker.ERROR_PARSE_ERROR); - return; + logger.warn("onUpdateCheckComplete notification failed", e); } - if ("onUpdateCheckComplete" in this.observer) { - try { - this.observer.onUpdateCheckComplete(results); - } - catch (e) { - logger.warn("onUpdateCheckComplete notification failed", e); - } - } - else { - logger.warn("onUpdateCheckComplete may not properly cancel", new Error("stack marker")); - } - return; } - - logger.warn("Update manifest had an unrecognised namespace: " + xml.documentElement.namespaceURI); - this.notifyError(AddonUpdateChecker.ERROR_UNKNOWN_FORMAT); + else { + logger.warn("onUpdateCheckComplete may not properly cancel", new Error("stack marker")); + } }, /** diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index 0d5ed412f3e8..6a8a8a9d0647 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -906,7 +906,7 @@ function loadManifestFromWebManifest(aStream) { addon.targetApplications = [{ id: TOOLKIT_ID, - minVersion: "42a1", + minVersion: AddonManagerPrivate.webExtensionsMinPlatformVersion, maxVersion: "*", }]; diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.json b/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.json new file mode 100644 index 000000000000..811e50158ea8 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.json @@ -0,0 +1,327 @@ +{ + "addons": { + "updatecheck1@tests.mozilla.org": { + "updates": [ + { + "version": "1.0", + "update_link": "https://localhost:4444/addons/test1.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + }, + { + "_comment_": "This update is incompatible and so should not be considered a valid update", + "version": "2.0", + "update_link": "https://localhost:4444/addons/test2.xpi", + "applications": { + "gecko": { + "strict_min_version": "2", + "strict_max_version": "2" + } + } + }, + { + "version": "3.0", + "update_link": "https://localhost:4444/addons/test3.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + }, + { + "version": "2.0", + "update_link": "https://localhost:4444/addons/test2.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "2" + } + } + }, + { + "_comment_": "This update is incompatible and so should not be considered a valid update", + "version": "4.0", + "update_link": "https://localhost:4444/addons/test4.xpi", + "applications": { + "gecko": { + "strict_min_version": "2", + "strict_max_version": "2" + } + } + } + ] + }, + + "test_bug378216_5@tests.mozilla.org": { + "_comment_": "An update which expects a signature. It will fail since signatures are ", + "_comment_": "supported in this format.", + "_comment_": "The updateLink will also be ignored since it is not secure and there ", + "_comment_": "is no updateHash.", + + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:4444/broken.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + } + ] + }, + + "test_bug378216_5@tests.mozilla.org": { + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:4444/broken.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + } + ] + }, + + "test_bug378216_7@tests.mozilla.org": { + "_comment_": "An update which expects a signature. It will fail since signatures are ", + "_comment_": "supported in this format.", + "_comment_": "The updateLink will also be ignored since it is not secure ", + "_comment_": "and there is no updateHash.", + + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:4444/broken.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "2" + } + } + } + ] + }, + + "test_bug378216_8@tests.mozilla.org": { + "_comment_": "The updateLink will be ignored since it is not secure and ", + "_comment_": "there is no updateHash.", + + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:4444/broken.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + } + ] + }, + + "test_bug378216_9@tests.mozilla.org": { + "_comment_": "The updateLink will used since there is an updateHash to verify it.", + + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:4444/broken.xpi", + "update_hash": "sha256:78fc1d2887eda35b4ad2e3a0b60120ca271ce6e6", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + } + ] + }, + + "test_bug378216_10@tests.mozilla.org": { + "_comment_": "The updateLink will used since it is a secure URL.", + + "updates": [ + { + "version": "2.0", + "update_link": "https://localhost:4444/broken.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + } + ] + }, + + "test_bug378216_11@tests.mozilla.org": { + "_comment_": "The updateLink will used since it is a secure URL.", + + "updates": [ + { + "version": "2.0", + "update_link": "https://localhost:4444/broken.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + } + ] + }, + + "test_bug378216_12@tests.mozilla.org": { + "_comment_": "The updateLink will not be used since the updateHash ", + "_comment_": "verifying it is not strong enough.", + + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:4444/broken.xpi", + "update_hash": "sha1:78fc1d2887eda35b4ad2e3a0b60120ca271ce6e6", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + } + ] + }, + + "test_bug378216_13@tests.mozilla.org": { + "_comment_": "An update with a weak hash. The updateLink will used since it is ", + "_comment_": "a secure URL.", + + "updates": [ + { + "version": "2.0", + "update_link": "https://localhost:4444/broken.xpi", + "update_hash": "sha1:78fc1d2887eda35b4ad2e3a0b60120ca271ce6e6", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + } + ] + }, + + "_comment_": "There should be no information present for test_bug378216_14", + + "test_bug378216_15@tests.mozilla.org": { + "_comment_": "Invalid update JSON", + + "updates": "foo" + }, + + "ignore-compat@tests.mozilla.org": { + "_comment_": "Various updates available - one is not compatible, but compatibility checking is disabled", + + "updates": [ + { + "version": "1.0", + "update_link": "https://localhost:4444/addons/test1.xpi", + "applications": { + "gecko": { + "strict_min_version": "0.1", + "advisory_max_version": "0.2" + } + } + }, + { + "version": "2.0", + "update_link": "https://localhost:4444/addons/test2.xpi", + "applications": { + "gecko": { + "strict_min_version": "0.5", + "advisory_max_version": "0.6" + } + } + }, + { + "_comment_": "Update for future app versions - should never be compatible", + "version": "3.0", + "update_link": "https://localhost:4444/addons/test3.xpi", + "applications": { + "gecko": { + "strict_min_version": "2", + "advisory_max_version": "3" + } + } + } + ] + }, + + "compat-override@tests.mozilla.org": { + "_comment_": "Various updates available - one is not compatible, but compatibility checking is disabled", + + "updates": [ + { + "_comment_": "Has compatibility override, but it doesn't match this app version", + "version": "1.0", + "update_link": "https://localhost:4444/addons/test1.xpi", + "applications": { + "gecko": { + "strict_min_version": "0.1", + "advisory_max_version": "0.2" + } + } + }, + { + "_comment_": "Has compatibility override, so is incompaible", + "version": "2.0", + "update_link": "https://localhost:4444/addons/test2.xpi", + "applications": { + "gecko": { + "strict_min_version": "0.5", + "advisory_max_version": "0.6" + } + } + }, + { + "_comment_": "Update for future app versions - should never be compatible", + "version": "3.0", + "update_link": "https://localhost:4444/addons/test3.xpi", + "applications": { + "gecko": { + "strict_min_version": "2", + "advisory_max_version": "3" + } + } + } + ] + }, + + "compat-strict-optin@tests.mozilla.org": { + "_comment_": "Opt-in to strict compatibility checking", + + "updates": [ + { + "version": "1.0", + "update_link": "https://localhost:4444/addons/test1.xpi", + "_comment_": "strictCompatibility: true", + "applications": { + "gecko": { + "strict_min_version": "0.1", + "strict_max_version": "0.2" + } + } + } + ] + } + } +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.rdf b/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.rdf index 93c82886a65b..c5d97ada0d53 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.rdf +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.rdf @@ -236,7 +236,7 @@ A90eF5zy - diff --git a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js index a5e6ba3d1a37..d8f3b7959095 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js +++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js @@ -1542,7 +1542,7 @@ if ("nsIWindowsRegKey" in AM_Ci) { * This is a mock nsIWindowsRegistry implementation. It only implements the * methods that the extension manager requires. */ - function MockWindowsRegKey() { + var MockWindowsRegKey = function MockWindowsRegKey() { } MockWindowsRegKey.prototype = { @@ -1723,6 +1723,30 @@ do_register_cleanup(function addon_cleanup() { } catch (e) {} }); +/** + * Creates a new HttpServer for testing, and begins listening on the + * specified port. Automatically shuts down the server when the test + * unit ends. + * + * @param port + * The port to listen on. If omitted, listen on a random + * port. The latter is the preferred behavior. + * + * @return HttpServer + */ +function createHttpServer(port = -1) { + let server = new HttpServer(); + server.start(port); + + do_register_cleanup(() => { + return new Promise(resolve => { + server.stop(resolve); + }); + }); + + return server; +} + /** * Handler function that responds with the interpolated * static file associated to the URL specified by request.path. @@ -1912,3 +1936,43 @@ function promiseFindAddonUpdates(addon, reason = AddonManager.UPDATE_WHEN_PERIOD }, reason); }); } + +/** + * Monitors console output for the duration of a task, and returns a promise + * which resolves to a tuple containing a list of all console messages + * generated during the task's execution, and the result of the task itself. + * + * @param {function} aTask + * The task to run while monitoring console output. May be + * either a generator function, per Task.jsm, or an ordinary + * function which returns promose. + * @return {Promise<[Array, *]>} + */ +var promiseConsoleOutput = Task.async(function*(aTask) { + const DONE = "=== xpcshell test console listener done ==="; + + let listener, messages = []; + let awaitListener = new Promise(resolve => { + listener = msg => { + if (msg == DONE) { + resolve(); + } else { + msg instanceof Components.interfaces.nsIScriptError; + messages.push(msg); + } + } + }); + + Services.console.registerListener(listener); + try { + let result = yield aTask(); + + Services.console.logStringMessage(DONE); + yield awaitListener; + + return { messages, result }; + } + finally { + Services.console.unregisterListener(listener); + } +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_json_updatecheck.js b/toolkit/mozapps/extensions/test/xpcshell/test_json_updatecheck.js new file mode 100644 index 000000000000..2e8cc6a972c1 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_json_updatecheck.js @@ -0,0 +1,373 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +"use strict"; + +// This verifies that AddonUpdateChecker works correctly for JSON +// update manifests, particularly for behavior which does not +// cleanly overlap with RDF manifests. + +const TOOLKIT_ID = "toolkit@mozilla.org"; +const TOOLKIT_MINVERSION = "42.0a1"; + +createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "42.0a2", "42.0a2"); + +Components.utils.import("resource://gre/modules/addons/AddonUpdateChecker.jsm"); +Components.utils.import("resource://testing-common/httpd.js"); + +let testserver = createHttpServer(); +gPort = testserver.identity.primaryPort; + +let gUpdateManifests = {}; + +function mapManifest(aPath, aManifestData) { + gUpdateManifests[aPath] = aManifestData; + testserver.registerPathHandler(aPath, serveManifest); +} + +function serveManifest(request, response) { + let manifest = gUpdateManifests[request.path]; + + response.setHeader("Content-Type", manifest.contentType, false); + response.write(manifest.data); +} + +const extensionsDir = gProfD.clone(); +extensionsDir.append("extensions"); + + +function checkUpdates(aData) { + // Registers JSON update manifest for it with the testing server, + // checks for updates, and yields the list of updates on + // success. + + let extension = aData.manifestExtension || "json"; + + let path = `/updates/${aData.id}.${extension}`; + let updateUrl = `http://localhost:${gPort}${path}` + + let addonData = {}; + if ("updates" in aData) + addonData.updates = aData.updates; + + let manifestJSON = { + "addons": { + [aData.id]: addonData + } + }; + + mapManifest(path.replace(/\?.*/, ""), + { data: JSON.stringify(manifestJSON), + contentType: aData.contentType || "application/json" }); + + + return new Promise((resolve, reject) => { + AddonUpdateChecker.checkForUpdates(aData.id, aData.updateKey, updateUrl, { + onUpdateCheckComplete: resolve, + + onUpdateCheckError: function(status) { + reject(new Error("Update check failed with status " + status)); + } + }); + }); +} + + +add_task(function* test_default_values() { + // Checks that the appropriate defaults are used for omitted values. + + startupManager(); + + let updates = yield checkUpdates({ + id: "updatecheck-defaults@tests.mozilla.org", + version: "0.1", + updates: [{ + version: "0.2" + }] + }); + + equal(updates.length, 1); + let update = updates[0]; + + equal(update.targetApplications.length, 1); + let targetApp = update.targetApplications[0]; + + equal(targetApp.id, TOOLKIT_ID); + equal(targetApp.minVersion, TOOLKIT_MINVERSION); + equal(targetApp.maxVersion, "*"); + + equal(update.version, "0.2"); + equal(update.multiprocessCompatible, true, "multiprocess_compatible flag"); + equal(update.strictCompatibility, false, "inferred strictConpatibility flag"); + equal(update.updateURL, null, "updateURL"); + equal(update.updateHash, null, "updateHash"); + equal(update.updateInfoURL, null, "updateInfoURL"); + + // If there's no applications property, we default to using one + // containing "gecko". If there is an applications property, but + // it doesn't contain "gecko", the update is skipped. + updates = yield checkUpdates({ + id: "updatecheck-defaults@tests.mozilla.org", + version: "0.1", + updates: [{ + version: "0.2", + applications: { "foo": {} } + }] + }); + + equal(updates.length, 0); + + // Updates property is also optional. No updates, but also no error. + updates = yield checkUpdates({ + id: "updatecheck-defaults@tests.mozilla.org", + version: "0.1", + }); + + equal(updates.length, 0); +}); + + +add_task(function* test_explicit_values() { + // Checks that the appropriate explicit values are used when + // provided. + + let updates = yield checkUpdates({ + id: "updatecheck-explicit@tests.mozilla.org", + version: "0.1", + updates: [{ + version: "0.2", + update_link: "https://example.com/foo.xpi", + update_hash: "sha256:0", + update_info_url: "https://example.com/update_info.html", + multiprocess_compatible: false, + applications: { + gecko: { + strict_min_version: "42.0a2.xpcshell", + strict_max_version: "43.xpcshell" + } + } + }] + }); + + equal(updates.length, 1); + let update = updates[0]; + + equal(update.targetApplications.length, 1); + let targetApp = update.targetApplications[0]; + + equal(targetApp.id, TOOLKIT_ID); + equal(targetApp.minVersion, "42.0a2.xpcshell"); + equal(targetApp.maxVersion, "43.xpcshell"); + + equal(update.version, "0.2"); + equal(update.multiprocessCompatible, false, "multiprocess_compatible flag"); + equal(update.strictCompatibility, true, "inferred strictCompatibility flag"); + equal(update.updateURL, "https://example.com/foo.xpi", "updateURL"); + equal(update.updateHash, "sha256:0", "updateHash"); + equal(update.updateInfoURL, "https://example.com/update_info.html", "updateInfoURL"); +}); + + +add_task(function* test_secure_hashes() { + // Checks that only secure hash functions are accepted for + // non-secure update URLs. + + let hashFunctions = ["sha512", + "sha256", + "sha1", + "md5", + "md4", + "xxx"]; + + let updateItems = hashFunctions.map((hash, idx) => ({ + version: `0.${idx}`, + update_link: `http://localhost:${gPort}/updates/${idx}-${hash}.xpi`, + update_hash: `${hash}:08ac852190ecd81f40a514ea9299fe9143d9ab5e296b97e73fb2a314de49648a`, + })); + + let { messages, result: updates } = yield promiseConsoleOutput(() => { + return checkUpdates({ + id: "updatecheck-hashes@tests.mozilla.org", + version: "0.1", + updates: updateItems + }); + }); + + equal(updates.length, hashFunctions.length); + + updates = updates.filter(update => update.updateHash || update.updateURL); + equal(updates.length, 2, "expected number of update hashes were accepted"); + + ok(updates[0].updateHash.startsWith("sha512:"), "sha512 hash is present"); + ok(updates[0].updateURL); + + ok(updates[1].updateHash.startsWith("sha256:"), "sha256 hash is present"); + ok(updates[1].updateURL); + + messages = messages.filter(msg => /Update link.*not secure.*strong enough hash \(needs to be sha256 or sha512\)/.test(msg.message)); + equal(messages.length, hashFunctions.length - 2, "insecure hashes generated the expected warning"); +}); + + +add_task(function* test_strict_compat() { + // Checks that strict compatibility is enabled for strict max + // versions other than "*", but not for advisory max versions. + // Also, ensure that strict max versions take precedence over + // advisory versions. + + let { messages, result: updates } = yield promiseConsoleOutput(() => { + return checkUpdates({ + id: "updatecheck-strict@tests.mozilla.org", + version: "0.1", + updates: [ + { version: "0.2", + applications: { gecko: { strict_max_version: "*" } } }, + { version: "0.3", + applications: { gecko: { strict_max_version: "43" } } }, + { version: "0.4", + applications: { gecko: { advisory_max_version: "43" } } }, + { version: "0.5", + applications: { gecko: { advisory_max_version: "43", + strict_max_version: "44" } } }, + ] + }); + }); + + equal(updates.length, 4, "all update items accepted"); + + equal(updates[0].targetApplications[0].maxVersion, "*"); + equal(updates[0].strictCompatibility, false); + + equal(updates[1].targetApplications[0].maxVersion, "43"); + equal(updates[1].strictCompatibility, true); + + equal(updates[2].targetApplications[0].maxVersion, "43"); + equal(updates[2].strictCompatibility, false); + + equal(updates[3].targetApplications[0].maxVersion, "44"); + equal(updates[3].strictCompatibility, true); + + messages = messages.filter(msg => /Ignoring 'advisory_max_version'.*'strict_max_version' also present/.test(msg.message)); + equal(messages.length, 1, "mix of advisory_max_version and strict_max_version generated the expected warning"); +}); + + +add_task(function* test_update_url_security() { + // Checks that update links to privileged URLs are not accepted. + + let { messages, result: updates } = yield promiseConsoleOutput(() => { + return checkUpdates({ + id: "updatecheck-security@tests.mozilla.org", + version: "0.1", + updates: [ + { version: "0.2", + update_link: "chrome://browser/content/browser.xul", + update_hash: "sha256:08ac852190ecd81f40a514ea9299fe9143d9ab5e296b97e73fb2a314de49648a" }, + { version: "0.3", + update_link: "http://example.com/update.xpi", + update_hash: "sha256:18ac852190ecd81f40a514ea9299fe9143d9ab5e296b97e73fb2a314de49648a" }, + ] + }); + }); + + equal(updates.length, 2, "both updates were processed"); + equal(updates[0].updateURL, null, "privileged update URL was removed"); + equal(updates[1].updateURL, "http://example.com/update.xpi", "safe update URL was accepted"); + + messages = messages.filter(msg => /http:\/\/localhost.*\/updates\/.*may not load or link to chrome:/.test(msg.message)); + equal(messages.length, 1, "privileged upate URL generated the expected console message"); +}); + + +add_task(function* test_no_update_key() { + // Checks that updates fail when an update key has been specified. + + let { messages } = yield promiseConsoleOutput(function* () { + yield Assert.rejects( + checkUpdates({ + id: "updatecheck-updatekey@tests.mozilla.org", + version: "0.1", + updateKey: "ayzzx=", + updates: [ + { version: "0.2" }, + { version: "0.3" }, + ] + }), + null, "updated expected to fail"); + }); + + messages = messages.filter(msg => /Update keys are not supported for JSON update manifests/.test(msg.message)); + equal(messages.length, 1, "got expected update-key-unsupported error"); +}); + + +add_task(function* test_type_detection() { + // Checks that JSON update manifests are detected correctly + // regardless of extension or MIME type. + + let tests = [ + { contentType: "application/json", + extension: "json", + valid: true }, + { contentType: "application/json", + extension: "php", + valid: true }, + { contentType: "text/plain", + extension: "json", + valid: true }, + { contentType: "application/octet-stream", + extension: "json", + valid: true }, + { contentType: "text/plain", + extension: "json?foo=bar", + valid: true }, + { contentType: "text/plain", + extension: "php", + valid: true }, + { contentType: "text/plain", + extension: "rdf", + valid: true }, + { contentType: "application/json", + extension: "rdf", + valid: true }, + { contentType: "text/xml", + extension: "json", + valid: true }, + { contentType: "application/rdf+xml", + extension: "json", + valid: true }, + ]; + + for (let [i, test] of tests.entries()) { + let { messages } = yield promiseConsoleOutput(function *() { + let id = `updatecheck-typedetection-${i}@tests.mozilla.org`; + let updates; + try { + updates = yield checkUpdates({ + id: id, + version: "0.1", + contentType: test.contentType, + manifestExtension: test.extension, + updates: [{ version: "0.2" }] + }); + } catch (e) { + ok(!test.valid, "update manifest correctly detected as RDF"); + return; + } + + ok(test.valid, "update manifest correctly detected as JSON"); + equal(updates.length, 1, "correct number of updates"); + equal(updates[0].id, id, "update is for correct extension"); + }); + + if (test.valid) { + // Make sure we don't get any XML parsing errors from the + // XMLHttpRequest machinery. + ok(!messages.some(msg => /not well-formed/.test(msg.message)), + "expect XMLHttpRequest not to attempt XML parsing"); + } + + messages = messages.filter(msg => /Update manifest was not valid XML/.test(msg.message)); + equal(messages.length, !test.valid, "expected number of XML parsing errors"); + } +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_updatecheck.js b/toolkit/mozapps/extensions/test/xpcshell/test_updatecheck.js index 9d251933aab5..9a2f6a01dbf4 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_updatecheck.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_updatecheck.js @@ -7,52 +7,47 @@ Components.utils.import("resource://gre/modules/addons/AddonUpdateChecker.jsm"); Components.utils.import("resource://testing-common/httpd.js"); -var testserver; -function run_test() { - createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); +var testserver = createHttpServer(4444); +testserver.registerDirectory("/data/", do_get_file("data")); - // Create and configure the HTTP server. - testserver = new HttpServer(); - testserver.registerDirectory("/data/", do_get_file("data")); - testserver.start(4444); +function checkUpdates(aId, aUpdateKey, aUpdateFile) { + return new Promise((resolve, reject) => { + AddonUpdateChecker.checkForUpdates(aId, aUpdateKey, `http://localhost:4444/data/${aUpdateFile}`, { + onUpdateCheckComplete: resolve, - do_test_pending(); - run_test_1(); -} - -function end_test() { - testserver.stop(do_test_finished); -} - -// Test that a basic update check returns the expected available updates -function run_test_1() { - AddonUpdateChecker.checkForUpdates("updatecheck1@tests.mozilla.org", null, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - check_test_1(updates); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } + onUpdateCheckError: function(status) { + let error = new Error("Update check failed with status " + status); + error.status = status; + reject(error); + } + }); }); } -function check_test_1(updates) { - do_check_eq(updates.length, 5); - let update = AddonUpdateChecker.getNewestCompatibleUpdate(updates); - do_check_neq(update, null); - do_check_eq(update.version, 3); - update = AddonUpdateChecker.getCompatibilityUpdate(updates, "2"); - do_check_neq(update, null); - do_check_eq(update.version, 2); - do_check_eq(update.targetApplications[0].minVersion, 1); - do_check_eq(update.targetApplications[0].maxVersion, 2); +function run_test() { + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1"); - run_test_2(); + run_next_test(); } +// Test that a basic update check returns the expected available updates +add_task(function* () { + for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { + let updates = yield checkUpdates("updatecheck1@tests.mozilla.org", null, file); + + equal(updates.length, 5); + let update = AddonUpdateChecker.getNewestCompatibleUpdate(updates); + notEqual(update, null); + equal(update.version, "3.0"); + update = AddonUpdateChecker.getCompatibilityUpdate(updates, "2"); + notEqual(update, null); + equal(update.version, "2.0"); + equal(update.targetApplications[0].minVersion, "1"); + equal(update.targetApplications[0].maxVersion, "2"); + } +}); + /* * Tests that the security checks are applied correctly * @@ -73,240 +68,169 @@ var updateKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDK426erD/H3XtsjvaB5+PJqbh "NyeP6i4LuUYjTURnn7Yw/IgzyIJ2oKsYa32RuxAyteqAWqPT/J63wBixIeCxmysf" + "awB/zH4KaPiY3vnrzQIDAQAB"; -function run_test_2() { - AddonUpdateChecker.checkForUpdates("test_bug378216_5@tests.mozilla.org", - updateKey, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_throw("Expected the update check to fail"); - }, +add_task(function* () { + for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { + try { + yield checkUpdates("test_bug378216_5@tests.mozilla.org", + updateKey, file); + throw "Expected the update check to fail"; + } catch (e) {} + } +}); - onUpdateCheckError: function(status) { - run_test_3(); +add_task(function* () { + for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { + try { + yield checkUpdates("test_bug378216_7@tests.mozilla.org", + updateKey, file); + + throw "Expected the update check to fail"; + } catch (e) {} + } +}); + +add_task(function* () { + // Make sure that the JSON manifest is rejected when an update key is + // required, but perform the remaining tests which aren't expected to fail + // because of the update key, without requiring one for the JSON variant. + + try { + let updates = yield checkUpdates("test_bug378216_8@tests.mozilla.org", + updateKey, "test_updatecheck.json"); + + throw "Expected the update check to fail"; + } catch(e) {} + + for (let [file, key] of [["test_updatecheck.rdf", updateKey], + ["test_updatecheck.json", null]]) { + let updates = yield checkUpdates("test_bug378216_8@tests.mozilla.org", + key, file); + equal(updates.length, 1); + ok(!("updateURL" in updates[0])); + } +}); + +add_task(function* () { + for (let [file, key] of [["test_updatecheck.rdf", updateKey], + ["test_updatecheck.json", null]]) { + let updates = yield checkUpdates("test_bug378216_9@tests.mozilla.org", + key, file); + equal(updates.length, 1); + equal(updates[0].version, "2.0"); + ok("updateURL" in updates[0]); + } +}); + +add_task(function* () { + for (let [file, key] of [["test_updatecheck.rdf", updateKey], + ["test_updatecheck.json", null]]) { + let updates = yield checkUpdates("test_bug378216_10@tests.mozilla.org", + key, file); + equal(updates.length, 1); + equal(updates[0].version, "2.0"); + ok("updateURL" in updates[0]); + } +}); + +add_task(function* () { + for (let [file, key] of [["test_updatecheck.rdf", updateKey], + ["test_updatecheck.json", null]]) { + let updates = yield checkUpdates("test_bug378216_11@tests.mozilla.org", + key, file); + equal(updates.length, 1); + equal(updates[0].version, "2.0"); + ok("updateURL" in updates[0]); + } +}); + +add_task(function* () { + for (let [file, key] of [["test_updatecheck.rdf", updateKey], + ["test_updatecheck.json", null]]) { + let updates = yield checkUpdates("test_bug378216_12@tests.mozilla.org", + key, file); + equal(updates.length, 1); + do_check_false("updateURL" in updates[0]); + } +}); + +add_task(function* () { + for (let [file, key] of [["test_updatecheck.rdf", updateKey], + ["test_updatecheck.json", null]]) { + let updates = yield checkUpdates("test_bug378216_13@tests.mozilla.org", + key, file); + equal(updates.length, 1); + equal(updates[0].version, "2.0"); + ok("updateURL" in updates[0]); + } +}); + +add_task(function* () { + for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { + let updates = yield checkUpdates("test_bug378216_14@tests.mozilla.org", + null, file); + equal(updates.length, 0); + } +}); + +add_task(function* () { + for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { + try { + yield checkUpdates("test_bug378216_15@tests.mozilla.org", + null, file); + + throw "Update check should have failed"; + } catch (e) { + equal(e.status, AddonUpdateChecker.ERROR_PARSE_ERROR); } - }); -} + } +}); -function run_test_3() { - AddonUpdateChecker.checkForUpdates("test_bug378216_7@tests.mozilla.org", - updateKey, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_throw("Expected the update check to fail"); - }, +add_task(function* () { + for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { + let updates = yield checkUpdates("ignore-compat@tests.mozilla.org", + null, file); + equal(updates.length, 3); + let update = AddonUpdateChecker.getNewestCompatibleUpdate( + updates, null, null, true); + notEqual(update, null); + equal(update.version, 2); + } +}); - onUpdateCheckError: function(status) { - run_test_4(); - } - }); -} +add_task(function* () { + for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { + let updates = yield checkUpdates("compat-override@tests.mozilla.org", + null, file); + equal(updates.length, 3); + let overrides = [{ + type: "incompatible", + minVersion: 1, + maxVersion: 2, + appID: "xpcshell@tests.mozilla.org", + appMinVersion: 0.1, + appMaxVersion: 0.2 + }, { + type: "incompatible", + minVersion: 2, + maxVersion: 2, + appID: "xpcshell@tests.mozilla.org", + appMinVersion: 1, + appMaxVersion: 2 + }]; + let update = AddonUpdateChecker.getNewestCompatibleUpdate( + updates, null, null, true, false, overrides); + notEqual(update, null); + equal(update.version, 1); + } +}); -function run_test_4() { - AddonUpdateChecker.checkForUpdates("test_bug378216_8@tests.mozilla.org", - updateKey, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 1); - do_check_false("updateURL" in updates[0]); - run_test_5(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} - -function run_test_5() { - AddonUpdateChecker.checkForUpdates("test_bug378216_9@tests.mozilla.org", - updateKey, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 1); - do_check_eq(updates[0].version, "2.0"); - do_check_true("updateURL" in updates[0]); - run_test_6(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} - -function run_test_6() { - AddonUpdateChecker.checkForUpdates("test_bug378216_10@tests.mozilla.org", - updateKey, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 1); - do_check_eq(updates[0].version, "2.0"); - do_check_true("updateURL" in updates[0]); - run_test_7(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} - -function run_test_7() { - AddonUpdateChecker.checkForUpdates("test_bug378216_11@tests.mozilla.org", - updateKey, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 1); - do_check_eq(updates[0].version, "2.0"); - do_check_true("updateURL" in updates[0]); - run_test_8(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} - -function run_test_8() { - AddonUpdateChecker.checkForUpdates("test_bug378216_12@tests.mozilla.org", - updateKey, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 1); - do_check_false("updateURL" in updates[0]); - run_test_9(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} - -function run_test_9() { - AddonUpdateChecker.checkForUpdates("test_bug378216_13@tests.mozilla.org", - updateKey, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 1); - do_check_eq(updates[0].version, "2.0"); - do_check_true("updateURL" in updates[0]); - run_test_10(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} - -function run_test_10() { - AddonUpdateChecker.checkForUpdates("test_bug378216_14@tests.mozilla.org", - null, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 0); - run_test_11(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} - -function run_test_11() { - AddonUpdateChecker.checkForUpdates("test_bug378216_15@tests.mozilla.org", - null, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_throw("Update check should have failed"); - }, - - onUpdateCheckError: function(status) { - do_check_eq(status, AddonUpdateChecker.ERROR_PARSE_ERROR); - run_test_12(); - } - }); -} - -function run_test_12() { - AddonUpdateChecker.checkForUpdates("ignore-compat@tests.mozilla.org", - null, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 3); - let update = AddonUpdateChecker.getNewestCompatibleUpdate(updates, - null, - null, - true); - do_check_neq(update, null); - do_check_eq(update.version, 2); - run_test_13(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} - -function run_test_13() { - AddonUpdateChecker.checkForUpdates("compat-override@tests.mozilla.org", - null, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 3); - let overrides = [{ - type: "incompatible", - minVersion: 1, - maxVersion: 2, - appID: "xpcshell@tests.mozilla.org", - appMinVersion: 0.1, - appMaxVersion: 0.2 - }, { - type: "incompatible", - minVersion: 2, - maxVersion: 2, - appID: "xpcshell@tests.mozilla.org", - appMinVersion: 1, - appMaxVersion: 2 - }]; - let update = AddonUpdateChecker.getNewestCompatibleUpdate(updates, - null, - null, - true, - false, - overrides); - do_check_neq(update, null); - do_check_eq(update.version, 1); - run_test_14(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} - -function run_test_14() { - AddonUpdateChecker.checkForUpdates("compat-strict-optin@tests.mozilla.org", - null, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 1); - let update = AddonUpdateChecker.getNewestCompatibleUpdate(updates, - null, - null, - true, - false); - do_check_eq(update, null); - end_test(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} +add_task(function* () { + for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { + let updates = yield checkUpdates("compat-strict-optin@tests.mozilla.org", + null, file); + equal(updates.length, 1); + let update = AddonUpdateChecker.getNewestCompatibleUpdate( + updates, null, null, true, false); + equal(update, null); + } +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini index f8b9972cf289..fdd2dd9ac356 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini +++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini @@ -279,6 +279,7 @@ skip-if = os == "android" # Bug 676992: test consistently hangs on Android skip-if = os == "android" run-sequentially = Uses hardcoded ports in xpi files. +[test_json_updatecheck.js] [test_updateid.js] # Bug 676992: test consistently hangs on Android skip-if = os == "android" From 1f33d6ac2d9efeec1484aafa7e6508fae9e58f93 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Mon, 19 Oct 2015 09:18:42 -0700 Subject: [PATCH 110/113] Bug 1214058: Part 2 - Run add-on update tests against comparable JSON and RDF manifests. r=Mossop I tried to keep the changes to existing tests as minimal as possible. There were a few exceptions, though: * test_update_ignorecompat.js was completely broken. I couldn't figure out why it was suddenly failing after I changed it to use `add_test`, and it turned out that it had been failing all along, but in a way that the harness didn't pick up. * I changed most of the `do_throw` in update callbacks to `ok(false` because it took me about an hour to figure out where the test was failing when I hit one of them. * I made some changes to sync `test_update.js` and `test_update_ignorecompat.js` where one appeared to have been changed without updating the other. * I made `promiseFindAddonUpdates` a bit more generic, because I was planning to convert most of `test_update.js` to use it, rather than nested callbacks. I changed my mind a quarter of the way through, but decided to keep the changes, since they'll probably be useful elsewhere. --HG-- extra : commitid : 2jBJ2yUht46 extra : rebase_source : a123db431a26b29f3deb81e6b961bf556005c2a6 extra : source : 90e625ac70b2071f1c2430725892f7c266928521 --- .../test/xpcshell/data/test_update.json | 215 ++ .../extensions/test/xpcshell/head_addons.js | 48 +- .../xpcshell/test_AddonRepository_cache.js | 7 +- .../test/xpcshell/test_blocklistchange.js | 9 +- .../test/xpcshell/test_bug542391.js | 13 +- .../test/xpcshell/test_bug570173.js | 82 +- .../test/xpcshell/test_metadata_update.js | 15 +- .../test/xpcshell/test_signed_install.js | 9 +- .../extensions/test/xpcshell/test_update.js | 2132 +++++++++-------- .../test/xpcshell/test_update_ignorecompat.js | 144 +- .../test/xpcshell/test_update_strictcompat.js | 1862 +++++++------- 11 files changed, 2410 insertions(+), 2126 deletions(-) create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_update.json diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_update.json b/toolkit/mozapps/extensions/test/xpcshell/data/test_update.json new file mode 100644 index 000000000000..027a9b2333f8 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_update.json @@ -0,0 +1,215 @@ +{ + "addons": { + "addon1@tests.mozilla.org": { + "updates": [ + { + "version": "1.0", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_min_version": "1" + } + } + }, + { + "version": "1.0", + "applications": { + "gecko": { + "strict_min_version": "2", + "strict_min_version": "2" + } + } + }, + { + "version": "2.0", + "update_link": "http://localhost:%PORT%/addons/test_update.xpi", + "update_info_url": "http://example.com/updateInfo.xhtml", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_min_version": "1" + } + } + } + ] + }, + + "addon2@tests.mozilla.org": { + "updates": [ + { + "version": "1.0", + "applications": { + "gecko": { + "strict_min_version": "0", + "advisory_max_version": "1" + } + } + } + ] + }, + + "addon2@tests.mozilla.org": { + "updates": [ + { + "version": "1.0", + "applications": { + "gecko": { + "strict_min_version": "0", + "advisory_max_version": "1" + } + } + } + ] + }, + + "addon3@tests.mozilla.org": { + "updates": [ + { + "version": "1.0", + "applications": { + "gecko": { + "strict_min_version": "3", + "advisory_max_version": "3" + } + } + } + ] + }, + + "addon4@tests.mozilla.org": { + "updates": [ + { + "version": "5.0", + "applications": { + "gecko": { + "strict_min_version": "0", + "advisory_max_version": "0" + } + } + } + ] + }, + + "addon7@tests.mozilla.org": { + "updates": [ + { + "version": "1.0", + "applications": { + "gecko": { + "strict_min_version": "0", + "advisory_max_version": "1" + } + } + } + ] + }, + + "addon8@tests.mozilla.org": { + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:%PORT%/addons/test_update8.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "advisory_max_version": "1" + } + } + } + ] + }, + + "addon9@tests.mozilla.org": { + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:%PORT%/addons/test_update9_2.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "advisory_max_version": "1" + } + } + }, + { + "_comment_": "Incompatible when strict compatibility is enabled", + "version": "3.0", + "update_link": "http://localhost:%PORT%/addons/test_update9_3.xpi", + "applications": { + "gecko": { + "strict_min_version": "0.9", + "advisory_max_version": "0.9" + } + } + }, + { + "_comment_": "Incompatible due to compatibility override", + "version": "4.0", + "update_link": "http://localhost:%PORT%/addons/test_update9_4.xpi", + "applications": { + "gecko": { + "strict_min_version": "0.9", + "advisory_max_version": "0.9" + } + } + }, + { + "_comment_": "Addon for future version of app", + "version": "4.0", + "update_link": "http://localhost:%PORT%/addons/test_update9_5.xpi", + "applications": { + "gecko": { + "strict_min_version": "5", + "advisory_max_version": "6" + } + } + } + ] + }, + + "addon10@tests.mozilla.org": { + "updates": [ + { + "version": "1.0", + "update_link": "http://localhost:%PORT%/addons/test_update10.xpi", + "applications": { + "gecko": { + "strict_min_version": "0.1", + "advisory_max_version": "0.4" + } + } + } + ] + }, + + "addon11@tests.mozilla.org": { + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:%PORT%/addons/test_update11.xpi", + "applications": { + "gecko": { + "strict_min_version": "0.1", + "strict_max_version": "0.2" + } + } + } + ] + }, + + "addon12@tests.mozilla.org": { + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:%PORT%/addons/test_update12.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "advisory_max_version": "1" + } + } + } + ] + } + } +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js index d8f3b7959095..27bca41f0f26 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js +++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js @@ -1920,18 +1920,48 @@ function promiseAddonByID(aId) { */ function promiseFindAddonUpdates(addon, reason = AddonManager.UPDATE_WHEN_PERIODIC_UPDATE) { return new Promise((resolve, reject) => { + let result = {}; addon.findUpdates({ - install: null, - - onUpdateAvailable: function(addon, install) { - this.install = install; + onNoCompatibilityUpdateAvailable: function(addon2) { + if ("compatibilityUpdate" in result) { + do_throw("Saw multiple compatibility update events"); + } + equal(addon, addon2); + addon.compatibilityUpdate = false; }, - onUpdateFinished: function(addon, error) { - if (error == AddonManager.UPDATE_STATUS_NO_ERROR) - resolve(this.install); - else - reject(error); + onCompatibilityUpdateAvailable: function(addon2) { + if ("compatibilityUpdate" in result) { + do_throw("Saw multiple compatibility update events"); + } + equal(addon, addon2); + addon.compatibilityUpdate = true; + }, + + onNoUpdateAvailable: function(addon2) { + if ("updateAvailable" in result) { + do_throw("Saw multiple update available events"); + } + equal(addon, addon2); + result.updateAvailable = false; + }, + + onUpdateAvailable: function(addon2, install) { + if ("updateAvailable" in result) { + do_throw("Saw multiple update available events"); + } + equal(addon, addon2); + result.updateAvailable = install; + }, + + onUpdateFinished: function(addon2, error) { + equal(addon, addon2); + if (error == AddonManager.UPDATE_STATUS_NO_ERROR) { + resolve(result); + } else { + result.error = error; + reject(result); + } } }, reason); }); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js b/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js index b2c0f59013e2..2ba21a8a213d 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js @@ -505,9 +505,8 @@ add_task(function* setup() { yield promiseInstallAllFiles(ADDON_FILES); yield promiseRestartManager(); - gServer = new HttpServer(); + gServer = createHttpServer(PORT); gServer.registerDirectory("/data/", do_get_file("data")); - gServer.start(PORT); }); // Tests AddonRepository.cacheEnabled @@ -704,7 +703,3 @@ add_task(function* run_test_17() { let aAddons = yield promiseAddonsByIDs(ADDON_IDS); check_results(aAddons, WITH_EXTENSION_CACHE); }); - -add_task(function* end_test() { - yield new Promise((resolve, reject) => gServer.stop(resolve)); -}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js b/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js index 989c3e4ceead..7113f7cfd543 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js @@ -41,8 +41,7 @@ Cu.import("resource://testing-common/MockRegistrar.jsm"); Services.prefs.setBoolPref("extensions.checkUpdateSecurity", false) Cu.import("resource://testing-common/httpd.js"); -var testserver = new HttpServer(); -testserver.start(-1); +var testserver = createHttpServer(); gPort = testserver.identity.primaryPort; // register static files with server and interpolate port numbers in them @@ -1305,9 +1304,3 @@ add_task(function* run_local_install_test() { check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); }); - -add_task(function* shutdown_httpserver() { - yield new Promise((resolve, reject) => { - testserver.stop(resolve); - }); -}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug542391.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug542391.js index 7c274d90aefd..67b50d16190e 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug542391.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug542391.js @@ -296,10 +296,9 @@ add_task(function* init() { }, profileDir); // Create and configure the HTTP server. - testserver = new HttpServer(); + testserver = createHttpServer(4444); testserver.registerDirectory("/data/", do_get_file("data")); testserver.registerDirectory("/addons/", do_get_file("addons")); - testserver.start(4444); startupManager(); @@ -464,13 +463,3 @@ add_task(function* run_test_6() { "override1x2-1x3@tests.mozilla.org"]); check_state_v1_2(addons); }); - -add_task(function* cleanup() { - return new Promise((resolve, reject) => { - testserver.stop(resolve); - }); -}); - -function run_test() { - run_next_test(); -} diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug570173.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug570173.js index 70de3b426d26..c859c474c711 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug570173.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug570173.js @@ -16,65 +16,47 @@ function run_test() { createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); // Create and configure the HTTP server. - testserver = new HttpServer(); + testserver = createHttpServer(); testserver.registerDirectory("/data/", do_get_file("data")); testserver.registerDirectory("/addons/", do_get_file("addons")); - testserver.start(-1); gPort = testserver.identity.primaryPort; - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_missing.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - startupManager(); - - do_test_pending(); - run_test_1(); -} - -function end_test() { - testserver.stop(do_test_finished); + run_next_test(); } // Verify that an update check returns the correct errors. -function run_test_1() { - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { - do_check_neq(a1, null); - do_check_eq(a1.version, "1.0"); +add_task(function* () { + for (let manifestType of ["rdf", "json"]) { + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: `http://localhost:${gPort}/data/test_missing.${manifestType}`, + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + bootstrap: "true", + }, profileDir); - let sawCompat = false; - let sawUpdate = false; - a1.findUpdates({ - onNoCompatibilityUpdateAvailable: function(addon) { - sawCompat = true; - }, + yield promiseRestartManager(); - onCompatibilityUpdateAvailable: function(addon) { - do_throw("Should not have seen a compatibility update"); - }, + let addon = yield promiseAddonByID("addon1@tests.mozilla.org"); - onNoUpdateAvailable: function(addon) { - sawUpdate = true; - }, + ok(addon); + ok(addon.updateURL.endsWith(manifestType)); + equal(addon.version, "1.0"); - onUpdateAvailable: function(addon, install) { - do_throw("Should not have seen an update"); - }, + // We're expecting an error, so resolve when the promise is rejected. + let update = yield promiseFindAddonUpdates(addon, AddonManager.UPDATE_WHEN_USER_REQUESTED) + .catch(Promise.resolve); - onUpdateFinished: function(addon, error) { - do_check_true(sawCompat); - do_check_true(sawUpdate); - do_check_eq(error, AddonManager.UPDATE_STATUS_DOWNLOAD_ERROR); - end_test(); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); - }); -} + ok(!update.compatibilityUpdate, "not expecting a compatibility update"); + ok(!update.updateAvailable, "not expecting a compatibility update"); + + equal(update.error, AddonManager.UPDATE_STATUS_DOWNLOAD_ERROR); + + addon.uninstall(); + } +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_metadata_update.js b/toolkit/mozapps/extensions/test/xpcshell/test_metadata_update.js index 78d03e88cd2a..962e0c8cc633 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_metadata_update.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_metadata_update.js @@ -69,10 +69,9 @@ add_task(function* checkFirstMetadata() { Services.prefs.setBoolPref(PREF_EM_SHOW_MISMATCH_UI, true); // Create and configure the HTTP server. - testserver = new HttpServer(); + testserver = createHttpServer(); testserver.registerDirectory("/data/", do_get_file("data")); testserver.registerDirectory("/addons/", do_get_file("addons")); - testserver.start(-1); gPort = testserver.identity.primaryPort; const BASE_URL = "http://localhost:" + gPort; const GETADDONS_RESULTS = BASE_URL + "/data/test_AddonRepository_cache.xml"; @@ -159,15 +158,3 @@ add_task(function* upgrade_young_pref_lastupdate() { yield promiseRestartManager("2"); do_check_false(WindowWatcher.expected); }); - - - -add_task(function* cleanup() { - return new Promise((resolve, reject) => { - testserver.stop(resolve); - }); -}); - -function run_test() { - run_next_test(); -} diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_signed_install.js b/toolkit/mozapps/extensions/test/xpcshell/test_signed_install.js index 8c7f491d1452..392a9b7a2476 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_signed_install.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_signed_install.js @@ -16,8 +16,7 @@ const WORKING = "signed_bootstrap_1.xpi"; const ID = "test@tests.mozilla.org"; Components.utils.import("resource://testing-common/httpd.js"); -var gServer = new HttpServer(); -gServer.start(4444); +var gServer = createHttpServer(4444); // Creates an add-on with a broken signature by changing an existing file function createBrokenAddonModify(file) { @@ -137,7 +136,8 @@ function* test_update_broken(file, expectedError) { serveUpdateRDF(file.leafName); let addon = yield promiseAddonByID(ID); - let install = yield promiseFindAddonUpdates(addon); + let update = yield promiseFindAddonUpdates(addon); + let install = update.updateAvailable; yield promiseCompleteAllInstalls([install]); do_check_eq(install.state, AddonManager.STATE_DOWNLOAD_FAILED); @@ -158,7 +158,8 @@ function* test_update_working(file, expectedSignedState) { serveUpdateRDF(file.leafName); let addon = yield promiseAddonByID(ID); - let install = yield promiseFindAddonUpdates(addon); + let update = yield promiseFindAddonUpdates(addon); + let install = update.updateAvailable; yield promiseCompleteAllInstalls([install]); do_check_eq(install.state, AddonManager.STATE_INSTALLED); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_update.js b/toolkit/mozapps/extensions/test/xpcshell/test_update.js index f410382c2d2e..07e8c37128c6 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_update.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_update.js @@ -24,10 +24,10 @@ const PARAMS = "?%REQ_VERSION%/%ITEM_ID%/%ITEM_VERSION%/%ITEM_MAXAPPVERSION%/" + var gInstallDate; Components.utils.import("resource://testing-common/httpd.js"); -var testserver = new HttpServer(); -testserver.start(-1); +var testserver = createHttpServer(); gPort = testserver.identity.primaryPort; mapFile("/data/test_update.rdf", testserver); +mapFile("/data/test_update.json", testserver); mapFile("/data/test_update.xml", testserver); testserver.registerDirectory("/addons/", do_get_file("addons")); @@ -37,386 +37,1160 @@ profileDir.append("extensions"); var originalSyncGUID; function run_test() { - createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1"); + Services.prefs.setBoolPref(PREF_MATCH_OS_LOCALE, false); Services.prefs.setCharPref(PREF_SELECTED_LOCALE, "fr-FR"); - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon2@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0", - maxVersion: "0" - }], - name: "Test Addon 2", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon3@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "5", - maxVersion: "5" - }], - name: "Test Addon 3", - }, profileDir); - - startupManager(); - - do_test_pending(); - run_test_1(); + run_next_test(); } -function end_test() { - testserver.stop(do_test_finished); -} +let testParams = [ + { updateFile: "test_update.rdf", + appId: "xpcshell@tests.mozilla.org" }, + { updateFile: "test_update.json", + appId: "toolkit@mozilla.org" }, +]; -// Verify that an update is available and can be installed. -function run_test_1() { - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { - do_check_neq(a1, null); - do_check_eq(a1.version, "1.0"); - do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DEFAULT); - do_check_eq(a1.releaseNotesURI, null); - do_check_true(a1.foreignInstall); - do_check_neq(a1.syncGUID, null); +for (let test of testParams) { + let { updateFile, appId } = test; - originalSyncGUID = a1.syncGUID; - a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT; + add_test(function run_test() { + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); - prepare_test({ - "addon1@tests.mozilla.org": [ - ["onPropertyChanged", ["applyBackgroundUpdates"]] - ] + writeInstallRDFForExtension({ + id: "addon2@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0", + maxVersion: "0" + }], + name: "Test Addon 2", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon3@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "5", + maxVersion: "5" + }], + name: "Test Addon 3", + }, profileDir); + + startupManager(); + + run_next_test(); + }); + + // Verify that an update is available and can be installed. + let check_test_1; + add_test(function run_test_1() { + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + do_check_neq(a1, null); + do_check_eq(a1.version, "1.0"); + do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DEFAULT); + do_check_eq(a1.releaseNotesURI, null); + do_check_true(a1.foreignInstall); + do_check_neq(a1.syncGUID, null); + + originalSyncGUID = a1.syncGUID; + a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT; + + prepare_test({ + "addon1@tests.mozilla.org": [ + ["onPropertyChanged", ["applyBackgroundUpdates"]] + ] + }); + a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + check_test_completed(); + + a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + + prepare_test({}, [ + "onNewInstall", + ]); + + a1.findUpdates({ + onNoCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should not have seen onNoCompatibilityUpdateAvailable notification"); + }, + + onUpdateAvailable: function(addon, install) { + ensure_test_completed(); + + AddonManager.getAllInstalls(function(aInstalls) { + do_check_eq(aInstalls.length, 1); + do_check_eq(aInstalls[0], install); + + do_check_eq(addon, a1); + do_check_eq(install.name, addon.name); + do_check_eq(install.version, "2.0"); + do_check_eq(install.state, AddonManager.STATE_AVAILABLE); + do_check_eq(install.existingAddon, addon); + do_check_eq(install.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); + + // Verify that another update check returns the same AddonInstall + a1.findUpdates({ + onNoCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should not have seen onNoCompatibilityUpdateAvailable notification"); + }, + + onUpdateAvailable: function(newAddon, newInstall) { + AddonManager.getAllInstalls(function(aInstalls) { + do_check_eq(aInstalls.length, 1); + do_check_eq(aInstalls[0], install); + do_check_eq(newAddon, addon); + do_check_eq(newInstall, install); + + prepare_test({}, [ + "onDownloadStarted", + "onDownloadEnded", + ], check_test_1); + install.install(); + }); + }, + + onNoUpdateAvailable: function(addon) { + ok(false, "Should not have seen onNoUpdateAvailable notification"); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); + }, + + onNoUpdateAvailable: function(addon) { + ok(false, "Should not have seen onNoUpdateAvailable notification"); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); }); - a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; - check_test_completed(); + }); - a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + let run_test_2; + check_test_1 = (install) => { + ensure_test_completed(); + do_check_eq(install.state, AddonManager.STATE_DOWNLOADED); + run_test_2(install); + return false; + }; - prepare_test({}, [ - "onNewInstall", - ]); - - a1.findUpdates({ + // Continue installing the update. + let check_test_2; + run_test_2 = (install) => { + // Verify that another update check returns no new update + install.existingAddon.findUpdates({ onNoCompatibilityUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoCompatibilityUpdateAvailable notification"); + ok(false, "Should not have seen onNoCompatibilityUpdateAvailable notification"); }, onUpdateAvailable: function(addon, install) { - ensure_test_completed(); + ok(false, "Should find no available update when one is already downloading"); + }, + onNoUpdateAvailable: function(addon) { AddonManager.getAllInstalls(function(aInstalls) { do_check_eq(aInstalls.length, 1); do_check_eq(aInstalls[0], install); - do_check_eq(addon, a1); - do_check_eq(install.name, addon.name); - do_check_eq(install.version, "2.0"); - do_check_eq(install.state, AddonManager.STATE_AVAILABLE); - do_check_eq(install.existingAddon, addon); - do_check_eq(install.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); - - // Verify that another update check returns the same AddonInstall - a1.findUpdates({ - onNoCompatibilityUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoCompatibilityUpdateAvailable notification"); - }, - - onUpdateAvailable: function(newAddon, newInstall) { - AddonManager.getAllInstalls(function(aInstalls) { - do_check_eq(aInstalls.length, 1); - do_check_eq(aInstalls[0], install); - do_check_eq(newAddon, addon); - do_check_eq(newInstall, install); - - prepare_test({}, [ - "onDownloadStarted", - "onDownloadEnded", - ], check_test_1); - install.install(); - }); - }, - - onNoUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoUpdateAvailable notification"); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + prepare_test({ + "addon1@tests.mozilla.org": [ + "onInstalling" + ] + }, [ + "onInstallStarted", + "onInstallEnded", + ], check_test_2); + install.install(); }); - }, - - onNoUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoUpdateAvailable notification"); } }, AddonManager.UPDATE_WHEN_USER_REQUESTED); - }); -} + }; -function check_test_1(install) { - ensure_test_completed(); - do_check_eq(install.state, AddonManager.STATE_DOWNLOADED); - run_test_2(install); - return false; -} + check_test_2 = () => { + ensure_test_completed(); -// Continue installing the update. -function run_test_2(install) { - // Verify that another update check returns no new update - install.existingAddon.findUpdates({ - onNoCompatibilityUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoCompatibilityUpdateAvailable notification"); - }, + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(olda1) { + do_check_neq(olda1, null); + do_check_eq(olda1.version, "1.0"); + do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); - onUpdateAvailable: function(addon, install) { - do_throw("Should find no available update when one is already downloading"); - }, + shutdownManager(); - onNoUpdateAvailable: function(addon) { - AddonManager.getAllInstalls(function(aInstalls) { - do_check_eq(aInstalls.length, 1); - do_check_eq(aInstalls[0], install); + startupManager(); - prepare_test({ - "addon1@tests.mozilla.org": [ - "onInstalling" - ] - }, [ - "onInstallStarted", - "onInstallEnded", - ], check_test_2); - install.install(); + do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); + + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + do_check_neq(a1, null); + do_check_eq(a1.version, "2.0"); + do_check_true(isExtensionInAddonsList(profileDir, a1.id)); + do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DISABLE); + do_check_eq(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); + do_check_true(a1.foreignInstall); + do_check_neq(a1.syncGUID, null); + do_check_eq(originalSyncGUID, a1.syncGUID); + + a1.uninstall(); + run_next_test(); }); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); -} + })); + }; -function check_test_2() { - ensure_test_completed(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(olda1) { - do_check_neq(olda1, null); - do_check_eq(olda1.version, "1.0"); - do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); + // Check that an update check finds compatibility updates and applies them + let check_test_3; + add_test(function run_test_3() { + restartManager(); - shutdownManager(); + AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { + do_check_neq(a2, null); + do_check_true(a2.isActive); + do_check_true(a2.isCompatible); + do_check_false(a2.appDisabled); + do_check_true(a2.isCompatibleWith("0", "0")); - startupManager(); + a2.findUpdates({ + onCompatibilityUpdateAvailable: function(addon) { + do_check_true(a2.isCompatible); + do_check_false(a2.appDisabled); + do_check_true(a2.isActive); + }, - do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); + onUpdateAvailable: function(addon, install) { + ok(false, "Should not have seen an available update"); + }, + onNoUpdateAvailable: function(addon) { + do_check_eq(addon, a2); + do_execute_soon(check_test_3); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); + }); + + check_test_3 = () => { + restartManager(); + AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { + do_check_neq(a2, null); + do_check_true(a2.isActive); + do_check_true(a2.isCompatible); + do_check_false(a2.appDisabled); + a2.uninstall(); + + run_next_test(); + }); + } + + // Checks that we see no compatibility information when there is none. + add_test(function run_test_4() { + AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { + do_check_neq(a3, null); + do_check_false(a3.isActive); + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + do_check_true(a3.isCompatibleWith("5", "5")); + do_check_false(a3.isCompatibleWith("2", "2")); + + a3.findUpdates({ + sawUpdate: false, + onCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should not have seen compatibility information"); + }, + + onNoCompatibilityUpdateAvailable: function(addon) { + this.sawUpdate = true; + }, + + onUpdateAvailable: function(addon, install) { + ok(false, "Should not have seen an available update"); + }, + + onNoUpdateAvailable: function(addon) { + do_check_true(this.sawUpdate); + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); + }); + + // Checks that compatibility info for future apps are detected but don't make + // the item compatibile. + let check_test_5; + add_test(function run_test_5() { + AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { + do_check_neq(a3, null); + do_check_false(a3.isActive); + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + do_check_true(a3.isCompatibleWith("5", "5")); + do_check_false(a3.isCompatibleWith("2", "2")); + + a3.findUpdates({ + sawUpdate: false, + onCompatibilityUpdateAvailable: function(addon) { + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + do_check_false(a3.isActive); + this.sawUpdate = true; + }, + + onNoCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should have seen some compatibility information"); + }, + + onUpdateAvailable: function(addon, install) { + ok(false, "Should not have seen an available update"); + }, + + onNoUpdateAvailable: function(addon) { + do_check_true(this.sawUpdate); + do_execute_soon(check_test_5); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED, "3.0", "3.0"); + }); + }); + + check_test_5 = () => { + restartManager(); + AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { + do_check_neq(a3, null); + do_check_false(a3.isActive); + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + + a3.uninstall(); + run_next_test(); + }); + } + + // Test that background update checks work + let continue_test_6; + add_test(function run_test_6() { + restartManager(); + + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + restartManager(); + + prepare_test({}, [ + "onNewInstall", + "onDownloadStarted", + "onDownloadEnded" + ], continue_test_6); + + AddonManagerInternal.backgroundUpdateCheck(); + }); + + let check_test_6; + continue_test_6 = (install) => { + do_check_neq(install.existingAddon, null); + do_check_eq(install.existingAddon.id, "addon1@tests.mozilla.org"); + + prepare_test({ + "addon1@tests.mozilla.org": [ + "onInstalling" + ] + }, [ + "onInstallStarted", + "onInstallEnded", + ], callback_soon(check_test_6)); + } + + check_test_6 = (install) => { + do_check_eq(install.existingAddon.pendingUpgrade.install, install); + + restartManager(); AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { do_check_neq(a1, null); do_check_eq(a1.version, "2.0"); - do_check_true(isExtensionInAddonsList(profileDir, a1.id)); - do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DISABLE); do_check_eq(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); - do_check_true(a1.foreignInstall); - do_check_neq(a1.syncGUID, null); - do_check_eq(originalSyncGUID, a1.syncGUID); - a1.uninstall(); - do_execute_soon(run_test_3); + run_next_test(); }); - })); -} + } + // Verify the parameter escaping in update urls. + add_test(function run_test_8() { + restartManager(); -// Check that an update check finds compatibility updates and applies them -function run_test_3() { - restartManager(); + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "5.0", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "2" + }], + name: "Test Addon 1", + }, profileDir); - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { - do_check_neq(a2, null); - do_check_true(a2.isActive); - do_check_true(a2.isCompatible); - do_check_false(a2.appDisabled); - do_check_true(a2.isCompatibleWith("0")); + writeInstallRDFForExtension({ + id: "addon2@tests.mozilla.org", + version: "67.0.5b1", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: "toolkit@mozilla.org", + minVersion: "0", + maxVersion: "3" + }], + name: "Test Addon 2", + }, profileDir); - a2.findUpdates({ - onCompatibilityUpdateAvailable: function(addon) { - do_check_true(a2.isCompatible); - do_check_false(a2.appDisabled); - do_check_true(a2.isActive); + writeInstallRDFForExtension({ + id: "addon3@tests.mozilla.org", + version: "1.3+", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "0", + maxVersion: "0" + }, { + id: "toolkit@mozilla.org", + minVersion: "0", + maxVersion: "3" + }], + name: "Test Addon 3", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon4@tests.mozilla.org", + version: "0.5ab6", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "5" + }], + name: "Test Addon 4", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon5@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 5", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon6@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 6", + }, profileDir); + + restartManager(); + + AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(a2) { + a2.userDisabled = true; + restartManager(); + + testserver.registerPathHandler("/data/param_test.rdf", function(request, response) { + do_check_neq(request.queryString, ""); + let [req_version, item_id, item_version, + item_maxappversion, item_status, + app_id, app_version, current_app_version, + app_os, app_abi, app_locale, update_type] = + request.queryString.split("/").map(a => decodeURIComponent(a)); + + do_check_eq(req_version, "2"); + + switch(item_id) { + case "addon1@tests.mozilla.org": + do_check_eq(item_version, "5.0"); + do_check_eq(item_maxappversion, "2"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "97"); + break; + case "addon2@tests.mozilla.org": + do_check_eq(item_version, "67.0.5b1"); + do_check_eq(item_maxappversion, "3"); + do_check_eq(item_status, "userDisabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "49"); + break; + case "addon3@tests.mozilla.org": + do_check_eq(item_version, "1.3+"); + do_check_eq(item_maxappversion, "0"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "112"); + break; + case "addon4@tests.mozilla.org": + do_check_eq(item_version, "0.5ab6"); + do_check_eq(item_maxappversion, "5"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "2"); + do_check_eq(update_type, "98"); + break; + case "addon5@tests.mozilla.org": + do_check_eq(item_version, "1.0"); + do_check_eq(item_maxappversion, "1"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "35"); + break; + case "addon6@tests.mozilla.org": + do_check_eq(item_version, "1.0"); + do_check_eq(item_maxappversion, "1"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "99"); + break; + default: + ok(false, "Update request for unexpected add-on " + item_id); + } + + do_check_eq(app_id, "xpcshell@tests.mozilla.org"); + do_check_eq(current_app_version, "1"); + do_check_eq(app_os, "XPCShell"); + do_check_eq(app_abi, "noarch-spidermonkey"); + do_check_eq(app_locale, "fr-FR"); + + request.setStatusLine(null, 500, "Server Error"); + }); + + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon2@tests.mozilla.org", + "addon3@tests.mozilla.org", + "addon4@tests.mozilla.org", + "addon5@tests.mozilla.org", + "addon6@tests.mozilla.org"], + function([a1, a2, a3, a4, a5, a6]) { + let count = 6; + + function next_test() { + a1.uninstall(); + a2.uninstall(); + a3.uninstall(); + a4.uninstall(); + a5.uninstall(); + a6.uninstall(); + + restartManager(); + run_next_test(); + } + + let compatListener = { + onUpdateFinished: function(addon, error) { + if (--count == 0) + do_execute_soon(next_test); + } + }; + + let updateListener = { + onUpdateAvailable: function(addon, update) { + // Dummy so the update checker knows we care about new versions + }, + + onUpdateFinished: function(addon, error) { + if (--count == 0) + do_execute_soon(next_test); + } + }; + + a1.findUpdates(updateListener, AddonManager.UPDATE_WHEN_USER_REQUESTED); + a2.findUpdates(compatListener, AddonManager.UPDATE_WHEN_ADDON_INSTALLED); + a3.findUpdates(updateListener, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); + a4.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "2"); + a5.findUpdates(compatListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); + a6.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); + }); + })); + }); + + // Tests that if an install.rdf claims compatibility then the add-on will be + // seen as compatible regardless of what the update.rdf says. + add_test(function run_test_9() { + writeInstallRDFForExtension({ + id: "addon4@tests.mozilla.org", + version: "5.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + + restartManager(); + + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + do_check_true(a4.isActive, "addon4 is active"); + do_check_true(a4.isCompatible, "addon4 is compatible"); + + run_next_test(); + }); + }); + + // Tests that a normal update check won't decrease a targetApplication's + // maxVersion. + add_test(function run_test_10() { + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + a4.findUpdates({ + onUpdateFinished: function(addon) { + do_check_true(addon.isCompatible, "addon4 is compatible"); + + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); + }); + }); + + // Tests that an update check for a new application will decrease a + // targetApplication's maxVersion. + add_test(function run_test_11() { + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + a4.findUpdates({ + onUpdateFinished: function(addon) { + do_check_true(addon.isCompatible, "addon4 is not compatible"); + + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); + }); + }); + + // Check that the decreased maxVersion applied and disables the add-on + add_test(function run_test_12() { + restartManager(); + + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + do_check_true(a4.isActive); + do_check_true(a4.isCompatible); + + a4.uninstall(); + run_next_test(); + }); + }); + + // Tests that a compatibility update is passed to the listener when there is + // compatibility info for the current version of the app but not for the + // version of the app that the caller requested an update check for, when + // strict compatibility checking is disabled. + let check_test_13; + add_test(function run_test_13() { + restartManager(); + + // Not initially compatible but the update check will make it compatible + writeInstallRDFForExtension({ + id: "addon7@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0", + maxVersion: "0" + }], + name: "Test Addon 7", + }, profileDir); + restartManager(); + + AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { + do_check_neq(a7, null); + do_check_true(a7.isActive); + do_check_true(a7.isCompatible); + do_check_false(a7.appDisabled); + do_check_true(a7.isCompatibleWith("0", "0")); + + a7.findUpdates({ + sawUpdate: false, + onNoCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should have seen compatibility information"); + }, + + onUpdateAvailable: function(addon, install) { + ok(false, "Should not have seen an available update"); + }, + + onUpdateFinished: function(addon) { + do_check_true(addon.isCompatible); + do_execute_soon(check_test_13); + } + }, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "3.0", "3.0"); + }); + }); + + check_test_13 = () => { + restartManager(); + AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { + do_check_neq(a7, null); + do_check_true(a7.isActive); + do_check_true(a7.isCompatible); + do_check_false(a7.appDisabled); + + a7.uninstall(); + run_next_test(); + }); + } + + // Test that background update checks doesn't update an add-on that isn't + // allowed to update automatically. + let check_test_14; + add_test(function run_test_14() { + restartManager(); + + // Have an add-on there that will be updated so we see some events from it + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon8@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 8", + }, profileDir); + restartManager(); + + AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { + a8.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + + // The background update check will find updates for both add-ons but only + // proceed to install one of them. + AddonManager.addInstallListener({ + onNewInstall: function(aInstall) { + let id = aInstall.existingAddon.id; + ok((id == "addon1@tests.mozilla.org" || id == "addon8@tests.mozilla.org"), + "Saw unexpected onNewInstall for " + id); + }, + + onDownloadStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadFailed: function(aInstall) { + ok(false, "Should not have seen onDownloadFailed event"); + }, + + onDownloadCancelled: function(aInstall) { + ok(false, "Should not have seen onDownloadCancelled event"); + }, + + onInstallStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onInstallEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + do_check_eq(aInstall.existingAddon.pendingUpgrade.install, aInstall); + + do_execute_soon(check_test_14); + }, + + onInstallFailed: function(aInstall) { + ok(false, "Should not have seen onInstallFailed event"); + }, + + onInstallCancelled: function(aInstall) { + ok(false, "Should not have seen onInstallCancelled event"); + }, + }); + + AddonManagerInternal.backgroundUpdateCheck(); + }); + }); + + check_test_14 = () => { + restartManager(); + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon8@tests.mozilla.org"], function([a1, a8]) { + do_check_neq(a1, null); + do_check_eq(a1.version, "2.0"); + a1.uninstall(); + + do_check_neq(a8, null); + do_check_eq(a8.version, "1.0"); + a8.uninstall(); + + run_next_test(); + }); + } + + // Test that background update checks doesn't update an add-on that is + // pending uninstall + let check_test_15; + add_test(function run_test_15() { + restartManager(); + + // Have an add-on there that will be updated so we see some events from it + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon8@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 8", + }, profileDir); + restartManager(); + + AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { + a8.uninstall(); + do_check_false(hasFlag(a8.permissions, AddonManager.PERM_CAN_UPGRADE)); + + // The background update check will find updates for both add-ons but only + // proceed to install one of them. + AddonManager.addInstallListener({ + onNewInstall: function(aInstall) { + let id = aInstall.existingAddon.id; + ok((id == "addon1@tests.mozilla.org" || id == "addon8@tests.mozilla.org"), + "Saw unexpected onNewInstall for " + id); + }, + + onDownloadStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadFailed: function(aInstall) { + ok(false, "Should not have seen onDownloadFailed event"); + }, + + onDownloadCancelled: function(aInstall) { + ok(false, "Should not have seen onDownloadCancelled event"); + }, + + onInstallStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onInstallEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + do_execute_soon(check_test_15); + }, + + onInstallFailed: function(aInstall) { + ok(false, "Should not have seen onInstallFailed event"); + }, + + onInstallCancelled: function(aInstall) { + ok(false, "Should not have seen onInstallCancelled event"); + }, + }); + + AddonManagerInternal.backgroundUpdateCheck(); + }); + }); + + check_test_15 = () => { + restartManager(); + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon8@tests.mozilla.org"], function([a1, a8]) { + do_check_neq(a1, null); + do_check_eq(a1.version, "2.0"); + a1.uninstall(); + + do_check_eq(a8, null); + + run_next_test(); + }); + } + + add_test(function run_test_16() { + restartManager(); + + restartManager(); + + let url = "http://localhost:" + gPort + "/addons/test_install2_1.xpi"; + AddonManager.getInstallForURL(url, function(aInstall) { + aInstall.addListener({ + onInstallEnded: function() { + do_execute_soon(function install_2_1_ended() { + restartManager(); + + AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a1) { + do_check_neq(a1.syncGUID, null); + let oldGUID = a1.syncGUID; + + let url = "http://localhost:" + gPort + "/addons/test_install2_2.xpi"; + AddonManager.getInstallForURL(url, function(aInstall) { + aInstall.addListener({ + onInstallEnded: function() { + do_execute_soon(function install_2_2_ended() { + restartManager(); + + AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { + do_check_neq(a2.syncGUID, null); + do_check_eq(oldGUID, a2.syncGUID); + + a2.uninstall(); + run_next_test(); + }); + }); + } + }); + aInstall.install(); + }, "application/x-xpinstall"); + }); + }); + } + }); + aInstall.install(); + }, "application/x-xpinstall"); + }); + + // Test that the update check correctly observes the + // extensions.strictCompatibility pref and compatibility overrides. + add_test(function run_test_17() { + restartManager(); + + writeInstallRDFForExtension({ + id: "addon9@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 9", + }, profileDir); + restartManager(); + + AddonManager.addInstallListener({ + onNewInstall: function(aInstall) { + equal(aInstall.existingAddon.id, "addon9@tests.mozilla.org", + "Saw unexpected onNewInstall for " + aInstall.existingAddon.id); + do_check_eq(aInstall.version, "3.0"); }, - - onUpdateAvailable: function(addon, install) { - do_throw("Should not have seen an available update"); - }, - - onNoUpdateAvailable: function(addon) { - do_check_eq(addon, a2); - do_execute_soon(check_test_3); + onDownloadFailed: function(aInstall) { + AddonManager.getAddonByID("addon9@tests.mozilla.org", function(a9) { + a9.uninstall(); + run_next_test(); + }); } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); + + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, + "http://localhost:" + gPort + "/data/test_update.xml"); + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS_PERFORMANCE, + "http://localhost:" + gPort + "/data/test_update.xml"); + Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); + + AddonManagerInternal.backgroundUpdateCheck(); }); -} -function check_test_3() { - restartManager(); - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { - do_check_neq(a2, null); - do_check_true(a2.isActive); - do_check_true(a2.isCompatible); - do_check_false(a2.appDisabled); - a2.uninstall(); + // Tests that compatibility updates are applied to addons when the updated + // compatibility data wouldn't match with strict compatibility enabled. + add_test(function run_test_18() { + restartManager(); + writeInstallRDFForExtension({ + id: "addon10@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 10", + }, profileDir); + restartManager(); - run_test_4(); + AddonManager.getAddonByID("addon10@tests.mozilla.org", function(a10) { + do_check_neq(a10, null); + + a10.findUpdates({ + onNoCompatibilityUpdateAvailable: function() { + ok(false, "Should have seen compatibility information"); + }, + + onUpdateAvailable: function() { + ok(false, "Should not have seen an available update"); + }, + + onUpdateFinished: function() { + a10.uninstall(); + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); }); -} -// Checks that we see no compatibility information when there is none. -function run_test_4() { - AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { - do_check_neq(a3, null); - do_check_false(a3.isActive); - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); - do_check_true(a3.isCompatibleWith("5")); - do_check_false(a3.isCompatibleWith("2")); + // Test that the update check correctly observes when an addon opts-in to + // strict compatibility checking. + add_test(function run_test_19() { + restartManager(); + writeInstallRDFForExtension({ + id: "addon11@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 11", + }, profileDir); + restartManager(); - a3.findUpdates({ - sawUpdate: false, - onCompatibilityUpdateAvailable: function(addon) { - do_throw("Should not have seen compatibility information"); - }, + AddonManager.getAddonByID("addon11@tests.mozilla.org", function(a11) { + do_check_neq(a11, null); - onNoCompatibilityUpdateAvailable: function(addon) { - this.sawUpdate = true; - }, + a11.findUpdates({ + onCompatibilityUpdateAvailable: function() { + ok(false, "Should have not have seen compatibility information"); + }, - onUpdateAvailable: function(addon, install) { - do_throw("Should not have seen an available update"); - }, + onUpdateAvailable: function() { + ok(false, "Should not have seen an available update"); + }, - onNoUpdateAvailable: function(addon) { - do_check_true(this.sawUpdate); - run_test_5(); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + onUpdateFinished: function() { + a11.uninstall(); + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); }); -} -// Checks that compatibility info for future apps are detected but don't make -// the item compatibile. -function run_test_5() { - AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { - do_check_neq(a3, null); - do_check_false(a3.isActive); - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); - do_check_true(a3.isCompatibleWith("5")); - do_check_false(a3.isCompatibleWith("2")); + // Test that the update succeeds when the update.rdf URN contains a type prefix + // different from the add-on type + let continue_test_20; + add_test(function run_test_20() { + restartManager(); + writeInstallRDFForExtension({ + id: "addon12@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 12", + }, profileDir); + restartManager(); - a3.findUpdates({ - sawUpdate: false, - onCompatibilityUpdateAvailable: function(addon) { - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); - do_check_false(a3.isActive); - this.sawUpdate = true; - }, + prepare_test({}, [ + "onNewInstall", + "onDownloadStarted", + "onDownloadEnded" + ], continue_test_20); - onNoCompatibilityUpdateAvailable: function(addon) { - do_throw("Should have seen some compatibility information"); - }, - - onUpdateAvailable: function(addon, install) { - do_throw("Should not have seen an available update"); - }, - - onNoUpdateAvailable: function(addon) { - do_check_true(this.sawUpdate); - do_execute_soon(check_test_5); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED, "3.0"); + AddonManagerPrivate.backgroundUpdateCheck(); }); -} -function check_test_5() { - restartManager(); - AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { - do_check_neq(a3, null); - do_check_false(a3.isActive); - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); + let check_test_20; + continue_test_20 = (install) => { + do_check_neq(install.existingAddon, null); + do_check_eq(install.existingAddon.id, "addon12@tests.mozilla.org"); - a3.uninstall(); - do_execute_soon(run_test_6); - }); -} + prepare_test({ + "addon12@tests.mozilla.org": [ + "onInstalling" + ] + }, [ + "onInstallStarted", + "onInstallEnded", + ], callback_soon(check_test_20)); + } -// Test that background update checks work -function run_test_6() { - restartManager(); + check_test_20 = (install) => { + do_check_eq(install.existingAddon.pendingUpgrade.install, install); - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - restartManager(); + restartManager(); + AddonManager.getAddonByID("addon12@tests.mozilla.org", function(a12) { + do_check_neq(a12, null); + do_check_eq(a12.version, "2.0"); + do_check_eq(a12.type, "extension"); + a12.uninstall(); - prepare_test({}, [ - "onNewInstall", - "onDownloadStarted", - "onDownloadEnded" - ], continue_test_6); + do_execute_soon(() => { + restartManager(); + run_next_test() + }); + }); + } - AddonManagerInternal.backgroundUpdateCheck(); -} + add_task(function cleanup() { + let addons = yield new Promise(resolve => { + AddonManager.getAddonsByTypes(["extension"], resolve); + }); -function continue_test_6(install) { - do_check_neq(install.existingAddon, null); - do_check_eq(install.existingAddon.id, "addon1@tests.mozilla.org"); + for (let addon of addons) + addon.uninstall(); - prepare_test({ - "addon1@tests.mozilla.org": [ - "onInstalling" - ] - }, [ - "onInstallStarted", - "onInstallEnded", - ], callback_soon(check_test_6)); -} + yield promiseRestartManager(); -function check_test_6(install) { - do_check_eq(install.existingAddon.pendingUpgrade.install, install); + shutdownManager(); - restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { - do_check_neq(a1, null); - do_check_eq(a1.version, "2.0"); - do_check_eq(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); - a1.uninstall(); - do_execute_soon(run_test_7); + yield new Promise(do_execute_soon); }); } // Test that background update checks work for lightweight themes -function run_test_7() { - restartManager(); +add_test(function run_test_7() { + startupManager(); LightweightThemeManager.currentTheme = { id: "1", @@ -484,7 +1258,7 @@ function run_test_7() { AddonManagerInternal.backgroundUpdateCheck(); }); -} +}); function check_test_7() { AddonManager.getAddonByID("1@personas.mozilla.org", function(p1) { @@ -501,13 +1275,13 @@ function check_test_7() { gInstallDate = p1.installDate.getTime(); - run_test_7_cache(); + run_next_test(); }); } // Test that background update checks for lightweight themes do not use the cache // The update body from test 7 shouldn't be used since the cache should be bypassed. -function run_test_7_cache() { +add_test(function () { // XXX The lightweight theme manager strips non-https updateURLs so hack it // back in. let themes = JSON.parse(Services.prefs.getCharPref("lightweightThemes.usedThemes")); @@ -550,7 +1324,7 @@ function run_test_7_cache() { AddonManagerInternal.backgroundUpdateCheck(); }); -} +}); function check_test_7_cache() { AddonManager.getAddonByID("1@personas.mozilla.org", function(p1) { @@ -571,740 +1345,6 @@ function check_test_7_cache() { do_check_eq(p1.installDate.getTime(), gInstallDate); do_check_true(p1.installDate.getTime() < p1.updateDate.getTime()); - do_execute_soon(run_test_8); - }); -} - -// Verify the parameter escaping in update urls. -function run_test_8() { - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "5.0", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "2" - }], - name: "Test Addon 1", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon2@tests.mozilla.org", - version: "67.0.5b1", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "toolkit@mozilla.org", - minVersion: "0", - maxVersion: "3" - }], - name: "Test Addon 2", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon3@tests.mozilla.org", - version: "1.3+", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0", - maxVersion: "0" - }, { - id: "toolkit@mozilla.org", - minVersion: "0", - maxVersion: "3" - }], - name: "Test Addon 3", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon4@tests.mozilla.org", - version: "0.5ab6", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "5" - }], - name: "Test Addon 4", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon5@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 5", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon6@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 6", - }, profileDir); - - restartManager(); - - AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(a2) { - a2.userDisabled = true; - restartManager(); - - testserver.registerPathHandler("/data/param_test.rdf", function(request, response) { - do_check_neq(request.queryString, ""); - let [req_version, item_id, item_version, - item_maxappversion, item_status, - app_id, app_version, current_app_version, - app_os, app_abi, app_locale, update_type] = - request.queryString.split("/").map(a => decodeURIComponent(a)); - - do_check_eq(req_version, "2"); - - switch(item_id) { - case "addon1@tests.mozilla.org": - do_check_eq(item_version, "5.0"); - do_check_eq(item_maxappversion, "2"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "97"); - break; - case "addon2@tests.mozilla.org": - do_check_eq(item_version, "67.0.5b1"); - do_check_eq(item_maxappversion, "3"); - do_check_eq(item_status, "userDisabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "49"); - break; - case "addon3@tests.mozilla.org": - do_check_eq(item_version, "1.3+"); - do_check_eq(item_maxappversion, "0"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "112"); - break; - case "addon4@tests.mozilla.org": - do_check_eq(item_version, "0.5ab6"); - do_check_eq(item_maxappversion, "5"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "2"); - do_check_eq(update_type, "98"); - break; - case "addon5@tests.mozilla.org": - do_check_eq(item_version, "1.0"); - do_check_eq(item_maxappversion, "1"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "35"); - break; - case "addon6@tests.mozilla.org": - do_check_eq(item_version, "1.0"); - do_check_eq(item_maxappversion, "1"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "99"); - break; - default: - do_throw("Update request for unexpected add-on " + item_id); - } - - do_check_eq(app_id, "xpcshell@tests.mozilla.org"); - do_check_eq(current_app_version, "1"); - do_check_eq(app_os, "XPCShell"); - do_check_eq(app_abi, "noarch-spidermonkey"); - do_check_eq(app_locale, "fr-FR"); - - request.setStatusLine(null, 500, "Server Error"); - }); - - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon2@tests.mozilla.org", - "addon3@tests.mozilla.org", - "addon4@tests.mozilla.org", - "addon5@tests.mozilla.org", - "addon6@tests.mozilla.org"], - function([a1, a2, a3, a4, a5, a6]) { - let count = 6; - - function run_next_test() { - a1.uninstall(); - a2.uninstall(); - a3.uninstall(); - a4.uninstall(); - a5.uninstall(); - a6.uninstall(); - - restartManager(); - run_test_9(); - } - - let compatListener = { - onUpdateFinished: function(addon, error) { - if (--count == 0) - do_execute_soon(run_next_test); - } - }; - - let updateListener = { - onUpdateAvailable: function(addon, update) { - // Dummy so the update checker knows we care about new versions - }, - - onUpdateFinished: function(addon, error) { - if (--count == 0) - do_execute_soon(run_next_test); - } - }; - - a1.findUpdates(updateListener, AddonManager.UPDATE_WHEN_USER_REQUESTED); - a2.findUpdates(compatListener, AddonManager.UPDATE_WHEN_ADDON_INSTALLED); - a3.findUpdates(updateListener, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); - a4.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "2"); - a5.findUpdates(compatListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); - a6.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); - }); - })); -} - -// Tests that if an install.rdf claims compatibility then the add-on will be -// seen as compatible regardless of what the update.rdf says. -function run_test_9() { - writeInstallRDFForExtension({ - id: "addon4@tests.mozilla.org", - version: "5.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - restartManager(); - - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - do_check_true(a4.isActive); - do_check_true(a4.isCompatible); - - run_test_10(); - }); -} - -// Tests that a normal update check won't decrease a targetApplication's -// maxVersion. -function run_test_10() { - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - a4.findUpdates({ - onUpdateFinished: function(addon) { - do_check_true(addon.isCompatible); - - run_test_11(); - } - }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); - }); -} - -// Tests that an update check for a new application will decrease a -// targetApplication's maxVersion. -function run_test_11() { - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - a4.findUpdates({ - onUpdateFinished: function(addon) { - do_check_true(addon.isCompatible); - - do_execute_soon(run_test_12); - } - }, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); - }); -} - -// Check that the decreased maxVersion applied and disables the add-on -function run_test_12() { - restartManager(); - - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - do_check_true(a4.isActive); - do_check_true(a4.isCompatible); - - a4.uninstall(); - do_execute_soon(run_test_13); - }); -} - -// Tests that a compatibility update is passed to the listener when there is -// compatibility info for the current version of the app but not for the -// version of the app that the caller requested an update check for, when -// strict compatibility checking is disabled. -function run_test_13() { - restartManager(); - - // Not initially compatible but the update check will make it compatible - writeInstallRDFForExtension({ - id: "addon7@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0", - maxVersion: "0" - }], - name: "Test Addon 7", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { - do_check_neq(a7, null); - do_check_true(a7.isActive); - do_check_true(a7.isCompatible); - do_check_false(a7.appDisabled); - do_check_true(a7.isCompatibleWith("0")); - - a7.findUpdates({ - sawUpdate: false, - onNoCompatibilityUpdateAvailable: function(addon) { - do_throw("Should have seen compatibility information"); - }, - - onUpdateAvailable: function(addon, install) { - do_throw("Should not have seen an available update"); - }, - - onUpdateFinished: function(addon) { - do_check_true(addon.isCompatible); - do_execute_soon(check_test_13); - } - }, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "3.0"); - }); -} - -function check_test_13() { - restartManager(); - AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { - do_check_neq(a7, null); - do_check_true(a7.isActive); - do_check_true(a7.isCompatible); - do_check_false(a7.appDisabled); - - a7.uninstall(); - do_execute_soon(run_test_14); - }); -} - -// Test that background update checks doesn't update an add-on that isn't -// allowed to update automatically. -function run_test_14() { - restartManager(); - - // Have an add-on there that will be updated so we see some events from it - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon8@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 8", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { - a8.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; - - // The background update check will find updates for both add-ons but only - // proceed to install one of them. - AddonManager.addInstallListener({ - onNewInstall: function(aInstall) { - if (aInstall.existingAddon.id != "addon1@tests.mozilla.org" && - aInstall.existingAddon.id != "addon8@tests.mozilla.org") - do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); - }, - - onDownloadStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadFailed: function(aInstall) { - do_throw("Should not have seen onDownloadFailed event"); - }, - - onDownloadCancelled: function(aInstall) { - do_throw("Should not have seen onDownloadCancelled event"); - }, - - onInstallStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onInstallEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - do_check_eq(aInstall.existingAddon.pendingUpgrade.install, aInstall); - - do_execute_soon(check_test_14); - }, - - onInstallFailed: function(aInstall) { - do_throw("Should not have seen onInstallFailed event"); - }, - - onInstallCancelled: function(aInstall) { - do_throw("Should not have seen onInstallCancelled event"); - }, - }); - - AddonManagerInternal.backgroundUpdateCheck(); - }); -} - -function check_test_14() { - restartManager(); - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon8@tests.mozilla.org"], function([a1, a8]) { - do_check_neq(a1, null); - do_check_eq(a1.version, "2.0"); - a1.uninstall(); - - do_check_neq(a8, null); - do_check_eq(a8.version, "1.0"); - a8.uninstall(); - - do_execute_soon(run_test_15); - }); -} - -// Test that background update checks doesn't update an add-on that is -// pending uninstall -function run_test_15() { - restartManager(); - - // Have an add-on there that will be updated so we see some events from it - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon8@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 8", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { - a8.uninstall(); - do_check_false(hasFlag(a8.permissions, AddonManager.PERM_CAN_UPGRADE)); - - // The background update check will find updates for both add-ons but only - // proceed to install one of them. - AddonManager.addInstallListener({ - onNewInstall: function(aInstall) { - if (aInstall.existingAddon.id != "addon1@tests.mozilla.org" && - aInstall.existingAddon.id != "addon8@tests.mozilla.org") - do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); - }, - - onDownloadStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadFailed: function(aInstall) { - do_throw("Should not have seen onDownloadFailed event"); - }, - - onDownloadCancelled: function(aInstall) { - do_throw("Should not have seen onDownloadCancelled event"); - }, - - onInstallStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onInstallEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - do_execute_soon(check_test_15); - }, - - onInstallFailed: function(aInstall) { - do_throw("Should not have seen onInstallFailed event"); - }, - - onInstallCancelled: function(aInstall) { - do_throw("Should not have seen onInstallCancelled event"); - }, - }); - - AddonManagerInternal.backgroundUpdateCheck(); - }); -} - -function check_test_15() { - restartManager(); - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon8@tests.mozilla.org"], function([a1, a8]) { - do_check_neq(a1, null); - do_check_eq(a1.version, "2.0"); - a1.uninstall(); - - do_check_eq(a8, null); - - do_execute_soon(run_test_16); - }); -} - -function run_test_16() { - restartManager(); - - restartManager(); - - let url = "http://localhost:" + gPort + "/addons/test_install2_1.xpi"; - AddonManager.getInstallForURL(url, function(aInstall) { - aInstall.addListener({ - onInstallEnded: function() { - do_execute_soon(function install_2_1_ended() { - restartManager(); - - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a1) { - do_check_neq(a1.syncGUID, null); - let oldGUID = a1.syncGUID; - - let url = "http://localhost:" + gPort + "/addons/test_install2_2.xpi"; - AddonManager.getInstallForURL(url, function(aInstall) { - aInstall.addListener({ - onInstallEnded: function() { - do_execute_soon(function install_2_2_ended() { - restartManager(); - - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { - do_check_neq(a2.syncGUID, null); - do_check_eq(oldGUID, a2.syncGUID); - - a2.uninstall(); - do_execute_soon(run_test_17); - }); - }); - } - }); - aInstall.install(); - }, "application/x-xpinstall"); - }); - }); - } - }); - aInstall.install(); - }, "application/x-xpinstall"); -} - -// Test that the update check correctly observes the -// extensions.strictCompatibility pref and compatibility overrides. -function run_test_17() { - restartManager(); - - writeInstallRDFForExtension({ - id: "addon9@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0.1", - maxVersion: "0.2" - }], - name: "Test Addon 9", - }, profileDir); - restartManager(); - - AddonManager.addInstallListener({ - onNewInstall: function(aInstall) { - if (aInstall.existingAddon.id != "addon9@tests.mozilla.org") - do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); - do_check_eq(aInstall.version, "3.0"); - }, - onDownloadFailed: function(aInstall) { - AddonManager.getAddonByID("addon9@tests.mozilla.org", function(a9) { - a9.uninstall(); - do_execute_soon(run_test_18); - }); - } - }); - - Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, - "http://localhost:" + gPort + "/data/test_update.xml"); - Services.prefs.setCharPref(PREF_GETADDONS_BYIDS_PERFORMANCE, - "http://localhost:" + gPort + "/data/test_update.xml"); - Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); - - AddonManagerInternal.backgroundUpdateCheck(); -} - -// Tests that compatibility updates are applied to addons when the updated -// compatibility data wouldn't match with strict compatibility enabled. -function run_test_18() { - restartManager(); - writeInstallRDFForExtension({ - id: "addon10@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0.1", - maxVersion: "0.2" - }], - name: "Test Addon 10", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon10@tests.mozilla.org", function(a10) { - do_check_neq(a10, null); - - a10.findUpdates({ - onNoCompatibilityUpdateAvailable: function() { - do_throw("Should have seen compatibility information"); - }, - - onUpdateAvailable: function() { - do_throw("Should not have seen an available update"); - }, - - onUpdateFinished: function() { - a10.uninstall(); - do_execute_soon(run_test_19); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); - }); -} - -// Test that the update check correctly observes when an addon opts-in to -// strict compatibility checking. -function run_test_19() { - restartManager(); - writeInstallRDFForExtension({ - id: "addon11@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0.1", - maxVersion: "0.2" - }], - name: "Test Addon 11", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon11@tests.mozilla.org", function(a11) { - do_check_neq(a11, null); - - a11.findUpdates({ - onCompatibilityUpdateAvailable: function() { - do_throw("Should have not have seen compatibility information"); - }, - - onUpdateAvailable: function() { - do_throw("Should not have seen an available update"); - }, - - onUpdateFinished: function() { - a11.uninstall(); - do_execute_soon(run_test_20); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); - }); -} - -// Test that the update succeeds when the update.rdf URN contains a type prefix -// different from the add-on type -function run_test_20() { - restartManager(); - writeInstallRDFForExtension({ - id: "addon12@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 12", - }, profileDir); - restartManager(); - - prepare_test({}, [ - "onNewInstall", - "onDownloadStarted", - "onDownloadEnded" - ], continue_test_20); - - AddonManagerPrivate.backgroundUpdateCheck(); -} - -function continue_test_20(install) { - do_check_neq(install.existingAddon, null); - do_check_eq(install.existingAddon.id, "addon12@tests.mozilla.org"); - - prepare_test({ - "addon12@tests.mozilla.org": [ - "onInstalling" - ] - }, [ - "onInstallStarted", - "onInstallEnded", - ], callback_soon(check_test_20)); -} - -function check_test_20(install) { - do_check_eq(install.existingAddon.pendingUpgrade.install, install); - - restartManager(); - AddonManager.getAddonByID("addon12@tests.mozilla.org", function(a12) { - do_check_neq(a12, null); - do_check_eq(a12.version, "2.0"); - do_check_eq(a12.type, "extension"); - a12.uninstall(); - - do_execute_soon(() => { - restartManager(); - end_test(); - }); + run_next_test(); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_update_ignorecompat.js b/toolkit/mozapps/extensions/test/xpcshell/test_update_ignorecompat.js index 672594088fb3..16fa3a4c18b3 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_update_ignorecompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_update_ignorecompat.js @@ -9,12 +9,13 @@ const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled"; // The test extension uses an insecure update url. Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false); +Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, false); Components.utils.import("resource://testing-common/httpd.js"); -var testserver = new HttpServer(); -testserver.start(-1); +var testserver = createHttpServer(); gPort = testserver.identity.primaryPort; mapFile("/data/test_update.rdf", testserver); +mapFile("/data/test_update.json", testserver); mapFile("/data/test_update.xml", testserver); testserver.registerDirectory("/addons/", do_get_file("addons")); @@ -22,77 +23,86 @@ const profileDir = gProfD.clone(); profileDir.append("extensions"); -function run_test() { - createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); +createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1"); - run_test_1(); -} +let testParams = [ + { updateFile: "test_update.rdf", + appId: "xpcshell@tests.mozilla.org" }, + { updateFile: "test_update.json", + appId: "toolkit@mozilla.org" }, +]; -// Test that the update check correctly observes the -// extensions.strictCompatibility pref and compatibility overrides. -function run_test_1() { - writeInstallRDFForExtension({ - id: "addon9@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0.1", - maxVersion: "0.2" - }], - name: "Test Addon 9", - }, profileDir); - restartManager(); +for (let test of testParams) { + let { updateFile, appId } = test; - AddonManager.addInstallListener({ - onNewInstall: function(aInstall) { - if (aInstall.existingAddon.id != "addon9@tests.mozilla.org") - do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); - do_check_eq(aInstall.version, "4.0"); - }, - onDownloadFailed: function(aInstall) { - do_execute_soon(run_test_2); - } - }); + // Test that the update check correctly observes the + // extensions.strictCompatibility pref and compatibility overrides. + add_test(function () { + writeInstallRDFForExtension({ + id: "addon9@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 9", + }, profileDir); - Services.prefs.setCharPref(PREF_GETADDONS_BYIDS_PERFORMANCE, - "http://localhost:" + gPort + "/data/test_update.xml"); - Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); + restartManager(); - AddonManagerInternal.backgroundUpdateCheck(); -} - -// Test that the update check correctly observes when an addon opts-in to -// strict compatibility checking. -function run_test_2() { - writeInstallRDFForExtension({ - id: "addon11@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0.1", - maxVersion: "0.2" - }], - name: "Test Addon 11", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon11@tests.mozilla.org", function(a11) { - do_check_neq(a11, null); - - a11.findUpdates({ - onCompatibilityUpdateAvailable: function() { - do_throw("Should have not have seen compatibility information"); + AddonManager.addInstallListener({ + onNewInstall: function(aInstall) { + if (aInstall.existingAddon.id != "addon9@tests.mozilla.org") + do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); + do_check_eq(aInstall.version, "4.0"); }, - - onNoUpdateAvailable: function() { - do_throw("Should have seen an available update"); - }, - - onUpdateFinished: function() { - end_test(); + onDownloadFailed: function(aInstall) { + run_next_test(); } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); + + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS_PERFORMANCE, + "http://localhost:" + gPort + "/data/" + updateFile); + Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); + + AddonManagerInternal.backgroundUpdateCheck(); + }); + + // Test that the update check correctly observes when an addon opts-in to + // strict compatibility checking. + add_test(function () { + writeInstallRDFForExtension({ + id: "addon11@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 11", + }, profileDir); + + restartManager(); + + AddonManager.getAddonByID("addon11@tests.mozilla.org", function(a11) { + do_check_neq(a11, null); + + a11.findUpdates({ + onCompatibilityUpdateAvailable: function() { + do_throw("Should not have seen compatibility information"); + }, + + onUpdateAvailable: function() { + do_throw("Should not have seen an available update"); + }, + + onUpdateFinished: function() { + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js b/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js index 3c56f9adb84a..3fc16a0f7e15 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js @@ -9,7 +9,8 @@ const PREF_SELECTED_LOCALE = "general.useragent.locale"; const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled"; // The test extension uses an insecure update url. -Services.prefs.setBoolPref("extensions.checkUpdateSecurity", false); +Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false); +Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, true); // This test requires lightweight themes update to be enabled even if the app // doesn't support lightweight themes. Services.prefs.setBoolPref("lightweightThemes.update.enabled", true); @@ -23,10 +24,10 @@ const PARAMS = "?%REQ_VERSION%/%ITEM_ID%/%ITEM_VERSION%/%ITEM_MAXAPPVERSION%/" + var gInstallDate; Components.utils.import("resource://testing-common/httpd.js"); -var testserver = new HttpServer(); -testserver.start(-1); +var testserver = createHttpServer(); gPort = testserver.identity.primaryPort; mapFile("/data/test_update.rdf", testserver); +mapFile("/data/test_update.json", testserver); mapFile("/data/test_update.xml", testserver); testserver.registerDirectory("/addons/", do_get_file("addons")); @@ -34,383 +35,1013 @@ const profileDir = gProfD.clone(); profileDir.append("extensions"); function run_test() { - createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1"); + Services.prefs.setBoolPref(PREF_MATCH_OS_LOCALE, false); Services.prefs.setCharPref(PREF_SELECTED_LOCALE, "fr-FR"); - Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, true); - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon2@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0", - maxVersion: "0" - }], - name: "Test Addon 2", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon3@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "5", - maxVersion: "5" - }], - name: "Test Addon 3", - }, profileDir); - - startupManager(); - - do_test_pending(); - run_test_1(); + run_next_test(); } -function end_test() { - Services.prefs.clearUserPref(PREF_EM_STRICT_COMPATIBILITY); +let testParams = [ + { updateFile: "test_update.rdf", + appId: "xpcshell@tests.mozilla.org" }, + { updateFile: "test_update.json", + appId: "toolkit@mozilla.org" }, +]; - testserver.stop(do_test_finished); -} +for (let test of testParams) { + let { updateFile, appId } = test; -// Verify that an update is available and can be installed. -function run_test_1() { - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { - do_check_neq(a1, null); - do_check_eq(a1.version, "1.0"); - do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DEFAULT); - do_check_eq(a1.releaseNotesURI, null); + add_test(function run_test() { + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); - a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT; + writeInstallRDFForExtension({ + id: "addon2@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0", + maxVersion: "0" + }], + name: "Test Addon 2", + }, profileDir); - prepare_test({ - "addon1@tests.mozilla.org": [ - ["onPropertyChanged", ["applyBackgroundUpdates"]] - ] + writeInstallRDFForExtension({ + id: "addon3@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "5", + maxVersion: "5" + }], + name: "Test Addon 3", + }, profileDir); + + startupManager(); + + run_next_test(); + }); + + // Verify that an update is available and can be installed. + let check_test_1; + add_test(function run_test_1() { + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + do_check_neq(a1, null); + do_check_eq(a1.version, "1.0"); + do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DEFAULT); + do_check_eq(a1.releaseNotesURI, null); + + a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT; + + prepare_test({ + "addon1@tests.mozilla.org": [ + ["onPropertyChanged", ["applyBackgroundUpdates"]] + ] + }); + a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + check_test_completed(); + + a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + + prepare_test({}, [ + "onNewInstall", + ]); + + a1.findUpdates({ + onNoCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should not have seen onNoCompatibilityUpdateAvailable notification"); + }, + + onUpdateAvailable: function(addon, install) { + ensure_test_completed(); + + AddonManager.getAllInstalls(function(aInstalls) { + do_check_eq(aInstalls.length, 1); + do_check_eq(aInstalls[0], install); + + do_check_eq(addon, a1); + do_check_eq(install.name, addon.name); + do_check_eq(install.version, "2.0"); + do_check_eq(install.state, AddonManager.STATE_AVAILABLE); + do_check_eq(install.existingAddon, addon); + do_check_eq(install.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); + + // Verify that another update check returns the same AddonInstall + a1.findUpdates({ + onNoCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should not have seen onNoCompatibilityUpdateAvailable notification"); + }, + + onUpdateAvailable: function(newAddon, newInstall) { + AddonManager.getAllInstalls(function(aInstalls) { + do_check_eq(aInstalls.length, 1); + do_check_eq(aInstalls[0], install); + do_check_eq(newAddon, addon); + do_check_eq(newInstall, install); + + prepare_test({}, [ + "onDownloadStarted", + "onDownloadEnded", + ], check_test_1); + install.install(); + }); + }, + + onNoUpdateAvailable: function(addon) { + ok(false, "Should not have seen onNoUpdateAvailable notification"); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); + }, + + onNoUpdateAvailable: function(addon) { + ok(false, "Should not have seen onNoUpdateAvailable notification"); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); }); - a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; - check_test_completed(); + }); - a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + let run_test_2; + check_test_1 = (install) => { + ensure_test_completed(); + do_check_eq(install.state, AddonManager.STATE_DOWNLOADED); + run_test_2(install); + return false; + }; - prepare_test({}, [ - "onNewInstall", - ]); - - a1.findUpdates({ + // Continue installing the update. + let check_test_2; + run_test_2 = (install) => { + // Verify that another update check returns no new update + install.existingAddon.findUpdates({ onNoCompatibilityUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoCompatibilityUpdateAvailable notification"); + ok(false, "Should not have seen onNoCompatibilityUpdateAvailable notification"); }, onUpdateAvailable: function(addon, install) { - ensure_test_completed(); + ok(false, "Should find no available update when one is already downloading"); + }, + onNoUpdateAvailable: function(addon) { AddonManager.getAllInstalls(function(aInstalls) { do_check_eq(aInstalls.length, 1); do_check_eq(aInstalls[0], install); - do_check_eq(addon, a1); - do_check_eq(install.name, addon.name); - do_check_eq(install.version, "2.0"); - do_check_eq(install.state, AddonManager.STATE_AVAILABLE); - do_check_eq(install.existingAddon, addon); - do_check_eq(install.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); - - // Verify that another update check returns the same AddonInstall - a1.findUpdates({ - onNoCompatibilityUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoCompatibilityUpdateAvailable notification"); - }, - - onUpdateAvailable: function(newAddon, newInstall) { - AddonManager.getAllInstalls(function(aInstalls) { - do_check_eq(aInstalls.length, 1); - do_check_eq(aInstalls[0], install); - do_check_eq(newAddon, addon); - do_check_eq(newInstall, install); - - prepare_test({}, [ - "onDownloadStarted", - "onDownloadEnded", - ], check_test_1); - install.install(); - }); - }, - - onNoUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoUpdateAvailable notification"); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + prepare_test({ + "addon1@tests.mozilla.org": [ + "onInstalling" + ] + }, [ + "onInstallStarted", + "onInstallEnded", + ], check_test_2); + install.install(); }); - }, - - onNoUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoUpdateAvailable notification"); } }, AddonManager.UPDATE_WHEN_USER_REQUESTED); - }); -} + }; -function check_test_1(install) { - ensure_test_completed(); - do_check_eq(install.state, AddonManager.STATE_DOWNLOADED); - run_test_2(install); - return false; -} + check_test_2 = () => { + ensure_test_completed(); -// Continue installing the update. -function run_test_2(install) { - // Verify that another update check returns no new update - install.existingAddon.findUpdates({ - onNoCompatibilityUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoCompatibilityUpdateAvailable notification"); - }, + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(olda1) { + do_check_neq(olda1, null); + do_check_eq(olda1.version, "1.0"); + do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); - onUpdateAvailable: function(addon, install) { - do_throw("Should find no available update when one is already downloading"); - }, + shutdownManager(); - onNoUpdateAvailable: function(addon) { - AddonManager.getAllInstalls(function(aInstalls) { - do_check_eq(aInstalls.length, 1); - do_check_eq(aInstalls[0], install); + startupManager(); - prepare_test({ - "addon1@tests.mozilla.org": [ - "onInstalling" - ] - }, [ - "onInstallStarted", - "onInstallEnded", - ], check_test_2); - install.install(); + do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); + + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + do_check_neq(a1, null); + do_check_eq(a1.version, "2.0"); + do_check_true(isExtensionInAddonsList(profileDir, a1.id)); + do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DISABLE); + do_check_eq(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); + + a1.uninstall(); + run_next_test(); }); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); -} + })); + }; -function check_test_2() { - ensure_test_completed(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(olda1) { - do_check_neq(olda1, null); - do_check_eq(olda1.version, "1.0"); - do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); + // Check that an update check finds compatibility updates and applies them + let check_test_3; + add_test(function run_test_3() { + restartManager(); - shutdownManager(); + AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { + do_check_neq(a2, null); + do_check_false(a2.isActive); + do_check_false(a2.isCompatible); + do_check_true(a2.appDisabled); + do_check_true(a2.isCompatibleWith("0", "0")); - startupManager(); + a2.findUpdates({ + onCompatibilityUpdateAvailable: function(addon) { + do_check_true(a2.isCompatible); + do_check_false(a2.appDisabled); + do_check_false(a2.isActive); + }, - do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); + onUpdateAvailable: function(addon, install) { + ok(false, "Should not have seen an available update"); + }, + onNoUpdateAvailable: function(addon) { + do_check_eq(addon, a2); + do_execute_soon(check_test_3); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); + }); + + check_test_3 = () => { + restartManager(); + AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { + do_check_neq(a2, null); + do_check_true(a2.isActive); + do_check_true(a2.isCompatible); + do_check_false(a2.appDisabled); + a2.uninstall(); + + run_next_test(); + }); + } + + // Checks that we see no compatibility information when there is none. + add_test(function run_test_4() { + AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { + do_check_neq(a3, null); + do_check_false(a3.isActive); + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + do_check_true(a3.isCompatibleWith("5", "5")); + do_check_false(a3.isCompatibleWith("2", "2")); + + a3.findUpdates({ + sawUpdate: false, + onCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should not have seen compatibility information"); + }, + + onNoCompatibilityUpdateAvailable: function(addon) { + this.sawUpdate = true; + }, + + onUpdateAvailable: function(addon, install) { + ok(false, "Should not have seen an available update"); + }, + + onNoUpdateAvailable: function(addon) { + do_check_true(this.sawUpdate); + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); + }); + + // Checks that compatibility info for future apps are detected but don't make + // the item compatibile. + let check_test_5; + add_test(function run_test_5() { + AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { + do_check_neq(a3, null); + do_check_false(a3.isActive); + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + do_check_true(a3.isCompatibleWith("5", "5")); + do_check_false(a3.isCompatibleWith("2", "2")); + + a3.findUpdates({ + sawUpdate: false, + onCompatibilityUpdateAvailable: function(addon) { + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + do_check_false(a3.isActive); + this.sawUpdate = true; + }, + + onNoCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should have seen some compatibility information"); + }, + + onUpdateAvailable: function(addon, install) { + ok(false, "Should not have seen an available update"); + }, + + onNoUpdateAvailable: function(addon) { + do_check_true(this.sawUpdate); + do_execute_soon(check_test_5); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED, "3.0", "3.0"); + }); + }); + + check_test_5 = () => { + restartManager(); + AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { + do_check_neq(a3, null); + do_check_false(a3.isActive); + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + + a3.uninstall(); + run_next_test(); + }); + } + + // Test that background update checks work + let continue_test_6; + add_test(function run_test_6() { + restartManager(); + + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + restartManager(); + + prepare_test({}, [ + "onNewInstall", + "onDownloadStarted", + "onDownloadEnded" + ], continue_test_6); + + AddonManagerInternal.backgroundUpdateCheck(); + }); + + let check_test_6; + continue_test_6 = (install) => { + do_check_neq(install.existingAddon, null); + do_check_eq(install.existingAddon.id, "addon1@tests.mozilla.org"); + + prepare_test({ + "addon1@tests.mozilla.org": [ + "onInstalling" + ] + }, [ + "onInstallStarted", + "onInstallEnded", + ], callback_soon(check_test_6)); + } + + check_test_6 = (install) => { + do_check_eq(install.existingAddon.pendingUpgrade.install, install); + + restartManager(); AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { do_check_neq(a1, null); do_check_eq(a1.version, "2.0"); - do_check_true(isExtensionInAddonsList(profileDir, a1.id)); - do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DISABLE); do_check_eq(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); - a1.uninstall(); - do_execute_soon(run_test_3); + run_next_test(); }); - })); -} + } + // Verify the parameter escaping in update urls. + add_test(function run_test_8() { + restartManager(); -// Check that an update check finds compatibility updates and applies them -function run_test_3() { - restartManager(); + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "5.0", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "2" + }], + name: "Test Addon 1", + }, profileDir); - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { - do_check_neq(a2, null); - do_check_false(a2.isActive); - do_check_false(a2.isCompatible); - do_check_true(a2.appDisabled); - do_check_true(a2.isCompatibleWith("0")); + writeInstallRDFForExtension({ + id: "addon2@tests.mozilla.org", + version: "67.0.5b1", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: "toolkit@mozilla.org", + minVersion: "0", + maxVersion: "3" + }], + name: "Test Addon 2", + }, profileDir); - a2.findUpdates({ - onCompatibilityUpdateAvailable: function(addon) { - do_check_true(a2.isCompatible); - do_check_false(a2.appDisabled); - do_check_false(a2.isActive); + writeInstallRDFForExtension({ + id: "addon3@tests.mozilla.org", + version: "1.3+", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "0", + maxVersion: "0" + }, { + id: "toolkit@mozilla.org", + minVersion: "0", + maxVersion: "3" + }], + name: "Test Addon 3", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon4@tests.mozilla.org", + version: "0.5ab6", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "5" + }], + name: "Test Addon 4", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon5@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 5", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon6@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 6", + }, profileDir); + + restartManager(); + + AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(a2) { + a2.userDisabled = true; + restartManager(); + + testserver.registerPathHandler("/data/param_test.rdf", function(request, response) { + do_check_neq(request.queryString, ""); + let [req_version, item_id, item_version, + item_maxappversion, item_status, + app_id, app_version, current_app_version, + app_os, app_abi, app_locale, update_type] = + request.queryString.split("/").map(a => decodeURIComponent(a)); + + do_check_eq(req_version, "2"); + + switch(item_id) { + case "addon1@tests.mozilla.org": + do_check_eq(item_version, "5.0"); + do_check_eq(item_maxappversion, "2"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "97"); + break; + case "addon2@tests.mozilla.org": + do_check_eq(item_version, "67.0.5b1"); + do_check_eq(item_maxappversion, "3"); + do_check_eq(item_status, "userDisabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "49"); + break; + case "addon3@tests.mozilla.org": + do_check_eq(item_version, "1.3+"); + do_check_eq(item_maxappversion, "0"); + do_check_eq(item_status, "userEnabled,incompatible"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "112"); + break; + case "addon4@tests.mozilla.org": + do_check_eq(item_version, "0.5ab6"); + do_check_eq(item_maxappversion, "5"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "2"); + do_check_eq(update_type, "98"); + break; + case "addon5@tests.mozilla.org": + do_check_eq(item_version, "1.0"); + do_check_eq(item_maxappversion, "1"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "35"); + break; + case "addon6@tests.mozilla.org": + do_check_eq(item_version, "1.0"); + do_check_eq(item_maxappversion, "1"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "99"); + break; + default: + ok(false, "Update request for unexpected add-on " + item_id); + } + + do_check_eq(app_id, "xpcshell@tests.mozilla.org"); + do_check_eq(current_app_version, "1"); + do_check_eq(app_os, "XPCShell"); + do_check_eq(app_abi, "noarch-spidermonkey"); + do_check_eq(app_locale, "fr-FR"); + + request.setStatusLine(null, 500, "Server Error"); + }); + + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon2@tests.mozilla.org", + "addon3@tests.mozilla.org", + "addon4@tests.mozilla.org", + "addon5@tests.mozilla.org", + "addon6@tests.mozilla.org"], + function([a1, a2, a3, a4, a5, a6]) { + let count = 6; + + function next_test() { + a1.uninstall(); + a2.uninstall(); + a3.uninstall(); + a4.uninstall(); + a5.uninstall(); + a6.uninstall(); + + restartManager(); + run_next_test(); + } + + let compatListener = { + onUpdateFinished: function(addon, error) { + if (--count == 0) + do_execute_soon(next_test); + } + }; + + let updateListener = { + onUpdateAvailable: function(addon, update) { + // Dummy so the update checker knows we care about new versions + }, + + onUpdateFinished: function(addon, error) { + if (--count == 0) + do_execute_soon(next_test); + } + }; + + a1.findUpdates(updateListener, AddonManager.UPDATE_WHEN_USER_REQUESTED); + a2.findUpdates(compatListener, AddonManager.UPDATE_WHEN_ADDON_INSTALLED); + a3.findUpdates(updateListener, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); + a4.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "2"); + a5.findUpdates(compatListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); + a6.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); + }); + })); + }); + + // Tests that if an install.rdf claims compatibility then the add-on will be + // seen as compatible regardless of what the update.rdf says. + add_test(function run_test_9() { + writeInstallRDFForExtension({ + id: "addon4@tests.mozilla.org", + version: "5.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + + restartManager(); + + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + do_check_true(a4.isActive, "addon4 is active"); + do_check_true(a4.isCompatible, "addon4 is compatible"); + + run_next_test(); + }); + }); + + // Tests that a normal update check won't decrease a targetApplication's + // maxVersion. + add_test(function run_test_10() { + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + a4.findUpdates({ + onUpdateFinished: function(addon) { + do_check_true(addon.isCompatible, "addon4 is compatible"); + + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); + }); + }); + + // Tests that an update check for a new application will decrease a + // targetApplication's maxVersion. + add_test(function run_test_11() { + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + a4.findUpdates({ + onUpdateFinished: function(addon) { + do_check_false(addon.isCompatible, "addon4 is compatible"); + + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); + }); + }); + + // Check that the decreased maxVersion applied and disables the add-on + add_test(function run_test_12() { + restartManager(); + + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + do_check_false(a4.isActive, "addon4 is active"); + do_check_false(a4.isCompatible, "addon4 is compatible"); + + a4.uninstall(); + run_next_test(); + }); + }); + + // Tests that no compatibility update is passed to the listener when there is + // compatibility info for the current version of the app but not for the + // version of the app that the caller requested an update check for. + let check_test_13; + add_test(function run_test_13() { + restartManager(); + + // Not initially compatible but the update check will make it compatible + writeInstallRDFForExtension({ + id: "addon7@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0", + maxVersion: "0" + }], + name: "Test Addon 7", + }, profileDir); + restartManager(); + + AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { + do_check_neq(a7, null); + do_check_false(a7.isActive); + do_check_false(a7.isCompatible); + do_check_true(a7.appDisabled); + do_check_true(a7.isCompatibleWith("0", "0")); + + a7.findUpdates({ + sawUpdate: false, + onCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should not have seen compatibility information"); + }, + + onUpdateAvailable: function(addon, install) { + ok(false, "Should not have seen an available update"); + }, + + onUpdateFinished: function(addon) { + do_check_true(addon.isCompatible); + do_execute_soon(check_test_13); + } + }, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "3.0", "3.0"); + }); + }); + + check_test_13 = () => { + restartManager(); + AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { + do_check_neq(a7, null); + do_check_true(a7.isActive); + do_check_true(a7.isCompatible); + do_check_false(a7.appDisabled); + + a7.uninstall(); + run_next_test(); + }); + } + + // Test that background update checks doesn't update an add-on that isn't + // allowed to update automatically. + let check_test_14; + add_test(function run_test_14() { + restartManager(); + + // Have an add-on there that will be updated so we see some events from it + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon8@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 8", + }, profileDir); + restartManager(); + + AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { + a8.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + + // The background update check will find updates for both add-ons but only + // proceed to install one of them. + AddonManager.addInstallListener({ + onNewInstall: function(aInstall) { + let id = aInstall.existingAddon.id; + ok((id == "addon1@tests.mozilla.org" || id == "addon8@tests.mozilla.org"), + "Saw unexpected onNewInstall for " + id); + }, + + onDownloadStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadFailed: function(aInstall) { + ok(false, "Should not have seen onDownloadFailed event"); + }, + + onDownloadCancelled: function(aInstall) { + ok(false, "Should not have seen onDownloadCancelled event"); + }, + + onInstallStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onInstallEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + do_check_eq(aInstall.existingAddon.pendingUpgrade.install, aInstall); + + do_execute_soon(check_test_14); + }, + + onInstallFailed: function(aInstall) { + ok(false, "Should not have seen onInstallFailed event"); + }, + + onInstallCancelled: function(aInstall) { + ok(false, "Should not have seen onInstallCancelled event"); + }, + }); + + AddonManagerInternal.backgroundUpdateCheck(); + }); + }); + + check_test_14 = () => { + restartManager(); + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon8@tests.mozilla.org"], function([a1, a8]) { + do_check_neq(a1, null); + do_check_eq(a1.version, "2.0"); + a1.uninstall(); + + do_check_neq(a8, null); + do_check_eq(a8.version, "1.0"); + a8.uninstall(); + + run_next_test(); + }); + } + + // Test that background update checks doesn't update an add-on that is + // pending uninstall + let check_test_15; + add_test(function run_test_15() { + restartManager(); + + // Have an add-on there that will be updated so we see some events from it + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon8@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 8", + }, profileDir); + restartManager(); + + AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { + a8.uninstall(); + do_check_false(hasFlag(a8.permissions, AddonManager.PERM_CAN_UPGRADE)); + + // The background update check will find updates for both add-ons but only + // proceed to install one of them. + AddonManager.addInstallListener({ + onNewInstall: function(aInstall) { + let id = aInstall.existingAddon.id; + ok((id == "addon1@tests.mozilla.org" || id == "addon8@tests.mozilla.org"), + "Saw unexpected onNewInstall for " + id); + }, + + onDownloadStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadFailed: function(aInstall) { + ok(false, "Should not have seen onDownloadFailed event"); + }, + + onDownloadCancelled: function(aInstall) { + ok(false, "Should not have seen onDownloadCancelled event"); + }, + + onInstallStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onInstallEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + do_execute_soon(check_test_15); + }, + + onInstallFailed: function(aInstall) { + ok(false, "Should not have seen onInstallFailed event"); + }, + + onInstallCancelled: function(aInstall) { + ok(false, "Should not have seen onInstallCancelled event"); + }, + }); + + AddonManagerInternal.backgroundUpdateCheck(); + }); + }); + + check_test_15 = () => { + restartManager(); + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon8@tests.mozilla.org"], function([a1, a8]) { + do_check_neq(a1, null); + do_check_eq(a1.version, "2.0"); + a1.uninstall(); + + do_check_eq(a8, null); + + run_next_test(); + }); + } + + // Test that the update check correctly observes the + // extensions.strictCompatibility pref and compatibility overrides. + add_test(function run_test_17() { + restartManager(); + + writeInstallRDFForExtension({ + id: "addon9@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 9", + }, profileDir); + restartManager(); + + AddonManager.addInstallListener({ + onNewInstall: function(aInstall) { + equal(aInstall.existingAddon.id, "addon9@tests.mozilla.org", + "Saw unexpected onNewInstall for " + aInstall.existingAddon.id); + do_check_eq(aInstall.version, "2.0"); }, - - onUpdateAvailable: function(addon, install) { - do_throw("Should not have seen an available update"); - }, - - onNoUpdateAvailable: function(addon) { - do_check_eq(addon, a2); - do_execute_soon(check_test_3); + onDownloadFailed: function(aInstall) { + AddonManager.getAddonByID("addon9@tests.mozilla.org", function(a9) { + a9.uninstall(); + run_next_test(); + }); } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); + + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, + "http://localhost:" + gPort + "/data/test_update.xml"); + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS_PERFORMANCE, + "http://localhost:" + gPort + "/data/test_update.xml"); + Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); + + AddonManagerInternal.backgroundUpdateCheck(); }); -} -function check_test_3() { - restartManager(); - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { - do_check_neq(a2, null); - do_check_true(a2.isActive); - do_check_true(a2.isCompatible); - do_check_false(a2.appDisabled); - a2.uninstall(); + // Test that the update check correctly observes when an addon opts-in to + // strict compatibility checking. + add_test(function run_test_19() { + restartManager(); + writeInstallRDFForExtension({ + id: "addon11@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 11", + }, profileDir); + restartManager(); - run_test_4(); + AddonManager.getAddonByID("addon11@tests.mozilla.org", function(a11) { + do_check_neq(a11, null); + + a11.findUpdates({ + onCompatibilityUpdateAvailable: function() { + ok(false, "Should have not have seen compatibility information"); + }, + + onUpdateAvailable: function() { + ok(false, "Should not have seen an available update"); + }, + + onUpdateFinished: function() { + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); }); -} -// Checks that we see no compatibility information when there is none. -function run_test_4() { - AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { - do_check_neq(a3, null); - do_check_false(a3.isActive); - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); - do_check_true(a3.isCompatibleWith("5")); - do_check_false(a3.isCompatibleWith("2")); + add_task(function cleanup() { + let addons = yield new Promise(resolve => { + AddonManager.getAddonsByTypes(["extension"], resolve); + }); - a3.findUpdates({ - sawUpdate: false, - onCompatibilityUpdateAvailable: function(addon) { - do_throw("Should not have seen compatibility information"); - }, + for (let addon of addons) + addon.uninstall(); - onNoCompatibilityUpdateAvailable: function(addon) { - this.sawUpdate = true; - }, + yield promiseRestartManager(); - onUpdateAvailable: function(addon, install) { - do_throw("Should not have seen an available update"); - }, + shutdownManager(); - onNoUpdateAvailable: function(addon) { - do_check_true(this.sawUpdate); - run_test_5(); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); - }); -} - -// Checks that compatibility info for future apps are detected but don't make -// the item compatibile. -function run_test_5() { - AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { - do_check_neq(a3, null); - do_check_false(a3.isActive); - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); - do_check_true(a3.isCompatibleWith("5")); - do_check_false(a3.isCompatibleWith("2")); - - a3.findUpdates({ - sawUpdate: false, - onCompatibilityUpdateAvailable: function(addon) { - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); - do_check_false(a3.isActive); - this.sawUpdate = true; - }, - - onNoCompatibilityUpdateAvailable: function(addon) { - do_throw("Should have seen some compatibility information"); - }, - - onUpdateAvailable: function(addon, install) { - do_throw("Should not have seen an available update"); - }, - - onNoUpdateAvailable: function(addon) { - do_check_true(this.sawUpdate); - do_execute_soon(check_test_5); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED, "3.0"); - }); -} - -function check_test_5() { - restartManager(); - AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { - do_check_neq(a3, null); - do_check_false(a3.isActive); - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); - - a3.uninstall(); - do_execute_soon(run_test_6); - }); -} - -// Test that background update checks work -function run_test_6() { - restartManager(); - - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - restartManager(); - - prepare_test({}, [ - "onNewInstall", - "onDownloadStarted", - "onDownloadEnded" - ], continue_test_6); - - AddonManagerInternal.backgroundUpdateCheck(); -} - -function continue_test_6(install) { - do_check_neq(install.existingAddon, null); - do_check_eq(install.existingAddon.id, "addon1@tests.mozilla.org"); - - prepare_test({ - "addon1@tests.mozilla.org": [ - "onInstalling" - ] - }, [ - "onInstallStarted", - "onInstallEnded", - ], callback_soon(check_test_6)); -} - -function check_test_6(install) { - do_check_eq(install.existingAddon.pendingUpgrade.install, install); - - restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { - do_check_neq(a1, null); - do_check_eq(a1.version, "2.0"); - do_check_eq(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); - a1.uninstall(); - do_execute_soon(run_test_7); + yield new Promise(do_execute_soon); }); } // Test that background update checks work for lightweight themes -function run_test_7() { - restartManager(); +add_test(function run_test_7() { + startupManager(); LightweightThemeManager.currentTheme = { id: "1", @@ -474,7 +1105,7 @@ function run_test_7() { AddonManagerInternal.backgroundUpdateCheck(); }); -} +}); function check_test_7() { AddonManager.getAddonByID("1@personas.mozilla.org", function(p1) { @@ -491,595 +1122,6 @@ function check_test_7() { gInstallDate = p1.installDate.getTime(); - do_execute_soon(run_test_8); - }); -} - -// Verify the parameter escaping in update urls. -function run_test_8() { - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "5.0", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "2" - }], - name: "Test Addon 1", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon2@tests.mozilla.org", - version: "67.0.5b1", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "toolkit@mozilla.org", - minVersion: "0", - maxVersion: "3" - }], - name: "Test Addon 2", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon3@tests.mozilla.org", - version: "1.3+", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0", - maxVersion: "0" - }, { - id: "toolkit@mozilla.org", - minVersion: "0", - maxVersion: "3" - }], - name: "Test Addon 3", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon4@tests.mozilla.org", - version: "0.5ab6", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "5" - }], - name: "Test Addon 4", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon5@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 5", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon6@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 6", - }, profileDir); - - restartManager(); - - AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(a2) { - a2.userDisabled = true; - restartManager(); - - testserver.registerPathHandler("/data/param_test.rdf", function(request, response) { - do_check_neq(request.queryString, ""); - let [req_version, item_id, item_version, - item_maxappversion, item_status, - app_id, app_version, current_app_version, - app_os, app_abi, app_locale, update_type] = - request.queryString.split("/").map(a => decodeURIComponent(a)); - - do_check_eq(req_version, "2"); - - switch(item_id) { - case "addon1@tests.mozilla.org": - do_check_eq(item_version, "5.0"); - do_check_eq(item_maxappversion, "2"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "97"); - break; - case "addon2@tests.mozilla.org": - do_check_eq(item_version, "67.0.5b1"); - do_check_eq(item_maxappversion, "3"); - do_check_eq(item_status, "userDisabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "49"); - break; - case "addon3@tests.mozilla.org": - do_check_eq(item_version, "1.3+"); - do_check_eq(item_maxappversion, "0"); - do_check_eq(item_status, "userEnabled,incompatible"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "112"); - break; - case "addon4@tests.mozilla.org": - do_check_eq(item_version, "0.5ab6"); - do_check_eq(item_maxappversion, "5"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "2"); - do_check_eq(update_type, "98"); - break; - case "addon5@tests.mozilla.org": - do_check_eq(item_version, "1.0"); - do_check_eq(item_maxappversion, "1"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "35"); - break; - case "addon6@tests.mozilla.org": - do_check_eq(item_version, "1.0"); - do_check_eq(item_maxappversion, "1"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "99"); - break; - default: - do_throw("Update request for unexpected add-on " + item_id); - } - - do_check_eq(app_id, "xpcshell@tests.mozilla.org"); - do_check_eq(current_app_version, "1"); - do_check_eq(app_os, "XPCShell"); - do_check_eq(app_abi, "noarch-spidermonkey"); - do_check_eq(app_locale, "fr-FR"); - - request.setStatusLine(null, 500, "Server Error"); - }); - - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon2@tests.mozilla.org", - "addon3@tests.mozilla.org", - "addon4@tests.mozilla.org", - "addon5@tests.mozilla.org", - "addon6@tests.mozilla.org"], - function([a1, a2, a3, a4, a5, a6]) { - let count = 6; - - function run_next_test() { - a1.uninstall(); - a2.uninstall(); - a3.uninstall(); - a4.uninstall(); - a5.uninstall(); - a6.uninstall(); - - restartManager(); - run_test_9(); - } - - let compatListener = { - onUpdateFinished: function(addon, error) { - if (--count == 0) - do_execute_soon(run_next_test); - } - }; - - let updateListener = { - onUpdateAvailable: function(addon, update) { - // Dummy so the update checker knows we care about new versions - }, - - onUpdateFinished: function(addon, error) { - if (--count == 0) - do_execute_soon(run_next_test); - } - }; - - a1.findUpdates(updateListener, AddonManager.UPDATE_WHEN_USER_REQUESTED); - a2.findUpdates(compatListener, AddonManager.UPDATE_WHEN_ADDON_INSTALLED); - a3.findUpdates(updateListener, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); - a4.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "2"); - a5.findUpdates(compatListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); - a6.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); - }); - })); -} - -// Tests that if an install.rdf claims compatibility then the add-on will be -// seen as compatible regardless of what the update.rdf says. -function run_test_9() { - writeInstallRDFForExtension({ - id: "addon4@tests.mozilla.org", - version: "5.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - restartManager(); - - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - do_check_true(a4.isActive); - do_check_true(a4.isCompatible); - - run_test_10(); - }); -} - -// Tests that a normal update check won't decrease a targetApplication's -// maxVersion. -function run_test_10() { - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - a4.findUpdates({ - onUpdateFinished: function(addon) { - do_check_true(addon.isCompatible); - - run_test_11(); - } - }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); - }); -} - -// Tests that an update check for a new application will decrease a -// targetApplication's maxVersion. -function run_test_11() { - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - a4.findUpdates({ - onUpdateFinished: function(addon) { - do_check_false(addon.isCompatible); - - do_execute_soon(run_test_12); - } - }, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); - }); -} - -// Check that the decreased maxVersion applied and disables the add-on -function run_test_12() { - restartManager(); - - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - do_check_false(a4.isActive); - do_check_false(a4.isCompatible); - - a4.uninstall(); - do_execute_soon(run_test_13); - }); -} - -// Tests that no compatibility update is passed to the listener when there is -// compatibility info for the current version of the app but not for the -// version of the app that the caller requested an update check for. -function run_test_13() { - restartManager(); - - // Not initially compatible but the update check will make it compatible - writeInstallRDFForExtension({ - id: "addon7@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0", - maxVersion: "0" - }], - name: "Test Addon 7", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { - do_check_neq(a7, null); - do_check_false(a7.isActive); - do_check_false(a7.isCompatible); - do_check_true(a7.appDisabled); - do_check_true(a7.isCompatibleWith("0")); - - a7.findUpdates({ - sawUpdate: false, - onCompatibilityUpdateAvailable: function(addon) { - do_throw("Should have not have seen compatibility information"); - }, - - onUpdateAvailable: function(addon, install) { - do_throw("Should not have seen an available update"); - }, - - onUpdateFinished: function(addon) { - do_check_true(addon.isCompatible); - do_execute_soon(check_test_13); - } - }, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "3.0"); - }); -} - -function check_test_13() { - restartManager(); - AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { - do_check_neq(a7, null); - do_check_true(a7.isActive); - do_check_true(a7.isCompatible); - do_check_false(a7.appDisabled); - - a7.uninstall(); - do_execute_soon(run_test_14); - }); -} - -// Test that background update checks doesn't update an add-on that isn't -// allowed to update automatically. -function run_test_14() { - restartManager(); - - // Have an add-on there that will be updated so we see some events from it - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon8@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 8", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { - a8.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; - - // The background update check will find updates for both add-ons but only - // proceed to install one of them. - AddonManager.addInstallListener({ - onNewInstall: function(aInstall) { - if (aInstall.existingAddon.id != "addon1@tests.mozilla.org" && - aInstall.existingAddon.id != "addon8@tests.mozilla.org") - do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); - }, - - onDownloadStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadFailed: function(aInstall) { - do_throw("Should not have seen onDownloadFailed event"); - }, - - onDownloadCancelled: function(aInstall) { - do_throw("Should not have seen onDownloadCancelled event"); - }, - - onInstallStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onInstallEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - do_check_eq(aInstall.existingAddon.pendingUpgrade.install, aInstall); - do_execute_soon(check_test_14); - }, - - onInstallFailed: function(aInstall) { - do_throw("Should not have seen onInstallFailed event"); - }, - - onInstallCancelled: function(aInstall) { - do_throw("Should not have seen onInstallCancelled event"); - }, - }); - - AddonManagerInternal.backgroundUpdateCheck(); - }); -} - -function check_test_14() { - restartManager(); - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon8@tests.mozilla.org"], function([a1, a8]) { - do_check_neq(a1, null); - do_check_eq(a1.version, "2.0"); - a1.uninstall(); - - do_check_neq(a8, null); - do_check_eq(a8.version, "1.0"); - a8.uninstall(); - - do_execute_soon(run_test_15); - }); -} - -// Test that background update checks doesn't update an add-on that is -// pending uninstall -function run_test_15() { - restartManager(); - - // Have an add-on there that will be updated so we see some events from it - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon8@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 8", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { - a8.uninstall(); - do_check_false(hasFlag(a8.permissions, AddonManager.PERM_CAN_UPGRADE)); - - // The background update check will find updates for both add-ons but only - // proceed to install one of them. - AddonManager.addInstallListener({ - onNewInstall: function(aInstall) { - if (aInstall.existingAddon.id != "addon1@tests.mozilla.org" && - aInstall.existingAddon.id != "addon8@tests.mozilla.org") - do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); - }, - - onDownloadStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadFailed: function(aInstall) { - do_throw("Should not have seen onDownloadFailed event"); - }, - - onDownloadCancelled: function(aInstall) { - do_throw("Should not have seen onDownloadCancelled event"); - }, - - onInstallStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onInstallEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - do_execute_soon(check_test_15); - }, - - onInstallFailed: function(aInstall) { - do_throw("Should not have seen onInstallFailed event"); - }, - - onInstallCancelled: function(aInstall) { - do_throw("Should not have seen onInstallCancelled event"); - }, - }); - - AddonManagerInternal.backgroundUpdateCheck(); - }); -} - -function check_test_15() { - restartManager(); - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon8@tests.mozilla.org"], function([a1, a8]) { - do_check_neq(a1, null); - do_check_eq(a1.version, "2.0"); - a1.uninstall(); - - do_check_eq(a8, null); - - do_execute_soon(run_test_16); - }); -} - -// Test that the update check correctly observes the -// extensions.strictCompatibility pref and compatibility overrides. -function run_test_16() { - restartManager(); - - writeInstallRDFForExtension({ - id: "addon9@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0.1", - maxVersion: "0.2" - }], - name: "Test Addon 9", - }, profileDir); - restartManager(); - - AddonManager.addInstallListener({ - onNewInstall: function(aInstall) { - if (aInstall.existingAddon.id != "addon9@tests.mozilla.org") - do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); - do_check_eq(aInstall.version, "2.0"); - }, - onDownloadFailed: function(aInstall) { - do_execute_soon(run_test_17); - } - }); - - Services.prefs.setCharPref(PREF_GETADDONS_BYIDS_PERFORMANCE, - "http://localhost:" + gPort + "/data/test_update.xml"); - Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); - - AddonManagerInternal.backgroundUpdateCheck(); -} - -// Test that the update check correctly observes when an addon opts-in to -// strict compatibility checking. -function run_test_17() { - - writeInstallRDFForExtension({ - id: "addon11@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0.1", - maxVersion: "0.2" - }], - name: "Test Addon 11", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon11@tests.mozilla.org", function(a11) { - do_check_neq(a11, null); - - a11.findUpdates({ - onCompatibilityUpdateAvailable: function() { - do_throw("Should have not have seen compatibility information"); - }, - - onUpdateAvailable: function() { - do_throw("Should not have seen an available update"); - }, - - onUpdateFinished: function() { - do_execute_soon(end_test); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + run_next_test(); }); } From d9dba55428e23eafb323eded5d9b46b0e2ba3c26 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Tue, 3 Nov 2015 16:25:13 -0800 Subject: [PATCH 111/113] Bug 1214058: Part 3 - Touch CLOBBER. r=trivial --HG-- extra : commitid : E3PuqrHEg9E extra : rebase_source : fc600110385393ca680613dcceda69e856941366 extra : source : a7f2b158f8d168d617d209b2ab9c285c9b4fd9ec --- CLOBBER | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CLOBBER b/CLOBBER index 5630ff8575ec..da63a8fed89c 100644 --- a/CLOBBER +++ b/CLOBBER @@ -22,4 +22,4 @@ # changes to stick? As of bug 928195, this shouldn't be necessary! Please # don't change CLOBBER for WebIDL changes any more. -Bug 1220007 Clobber since .idl changes not getting picked up on emulator-L in b2g-inbound +Bug 1214058 New xpcshell test not getting picked up From dad4ebe210945b0e7790986ee7ecf5ab43d23725 Mon Sep 17 00:00:00 2001 From: Matt Woodrow Date: Wed, 4 Nov 2015 16:17:00 +1300 Subject: [PATCH 112/113] Bug 1203199 - Fix driver range with blacklist to avoid blacklisting other OSes. --HG-- extra : rebase_source : e2dc29a94aa88fd3d181aa2171cece9d61c103c5 --- widget/windows/GfxInfo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/widget/windows/GfxInfo.cpp b/widget/windows/GfxInfo.cpp index cfe472f3d2ba..f42307e3ae6d 100644 --- a/widget/windows/GfxInfo.cpp +++ b/widget/windows/GfxInfo.cpp @@ -1105,7 +1105,7 @@ GfxInfo::GetGfxDriverInfo() APPEND_TO_DRIVER_BLOCKLIST2(DRIVER_OS_ALL, (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel), GfxDriverInfo::allDevices, nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, - DRIVER_LESS_THAN_OR_EQUAL, V(9,17,10,2849)); + DRIVER_BETWEEN_INCLUSIVE, V(9,17,10,0), V(9,17,10,2849), "Intel driver > 9.17.10.2849"); APPEND_TO_DRIVER_BLOCKLIST2(DRIVER_OS_ALL, (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(Nvidia8800GTS), From 43e4c98e28385a2ed6dd08fab8f267fcb4d16c25 Mon Sep 17 00:00:00 2001 From: Matt Woodrow Date: Wed, 4 Nov 2015 16:51:22 +1300 Subject: [PATCH 113/113] Bug 1203199 - Bustage fix. CLOSED TREE --- widget/windows/GfxInfo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/widget/windows/GfxInfo.cpp b/widget/windows/GfxInfo.cpp index f42307e3ae6d..6769051dc2c3 100644 --- a/widget/windows/GfxInfo.cpp +++ b/widget/windows/GfxInfo.cpp @@ -1102,7 +1102,7 @@ GfxInfo::GetGfxDriverInfo() DRIVER_LESS_THAN_OR_EQUAL, V(8,15,10,2869)); /* Bug 1203199/1092166: DXVA startup crashes on some intel drivers. */ - APPEND_TO_DRIVER_BLOCKLIST2(DRIVER_OS_ALL, + APPEND_TO_DRIVER_BLOCKLIST_RANGE(DRIVER_OS_ALL, (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel), GfxDriverInfo::allDevices, nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_BETWEEN_INCLUSIVE, V(9,17,10,0), V(9,17,10,2849), "Intel driver > 9.17.10.2849");