diff --git a/accessible/generic/Accessible.cpp b/accessible/generic/Accessible.cpp index 4a8c693685..420489ad98 100644 --- a/accessible/generic/Accessible.cpp +++ b/accessible/generic/Accessible.cpp @@ -94,8 +94,7 @@ using namespace mozilla::a11y; //////////////////////////////////////////////////////////////////////////////// // Accessible: nsISupports and cycle collection -NS_IMPL_CYCLE_COLLECTION(Accessible, - mContent, mParent, mChildren) +NS_IMPL_CYCLE_COLLECTION(Accessible, mContent) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Accessible) if (aIID.Equals(NS_GET_IID(Accessible))) @@ -1979,7 +1978,7 @@ Accessible::BindToParent(Accessible* aParent, uint32_t aIndexInParent) #ifdef A11Y_LOG if (mParent) { logging::TreeInfo("BindToParent: stealing accessible", 0, - "old parent", mParent.get(), + "old parent", mParent, "new parent", aParent, "child", this, nullptr); } diff --git a/accessible/generic/Accessible.h b/accessible/generic/Accessible.h index 5c30db765e..db7c52b246 100644 --- a/accessible/generic/Accessible.h +++ b/accessible/generic/Accessible.h @@ -1094,8 +1094,8 @@ protected: nsCOMPtr mContent; DocAccessible* mDoc; - RefPtr mParent; - nsTArray > mChildren; + Accessible* mParent; + nsTArray mChildren; int32_t mIndexInParent; static const uint8_t kStateFlagsBits = 13; diff --git a/accessible/html/HTMLSelectAccessible.h b/accessible/html/HTMLSelectAccessible.h index 2cef32b486..0c781034fb 100644 --- a/accessible/html/HTMLSelectAccessible.h +++ b/accessible/html/HTMLSelectAccessible.h @@ -99,7 +99,7 @@ private: if (parent && parent->IsListControl()) { Accessible* combobox = parent->Parent(); - return combobox && combobox->IsCombobox() ? combobox : mParent.get(); + return combobox && combobox->IsCombobox() ? combobox : mParent; } return nullptr; diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 4860f54b33..d2ae8c9c3a 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -5728,7 +5728,7 @@ function handleLinkClick(event, href, linkNode) { linkNode) { let referrerAttrValue = Services.netUtils.parseAttributePolicyString(linkNode. getAttribute("referrerpolicy")); - if (referrerAttrValue != Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT) { + if (referrerAttrValue != Ci.nsIHttpChannel.REFERRER_POLICY_UNSET) { referrerPolicy = referrerAttrValue; } } diff --git a/browser/base/content/content.js b/browser/base/content/content.js index 57cc579387..e6ed5b5e85 100644 --- a/browser/base/content/content.js +++ b/browser/base/content/content.js @@ -153,7 +153,7 @@ var handleContentContextMenu = function (event) { if (Services.prefs.getBoolPref("network.http.enablePerElementReferrer")) { let referrerAttrValue = Services.netUtils.parseAttributePolicyString(event.target. getAttribute("referrerpolicy")); - if (referrerAttrValue !== Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT) { + if (referrerAttrValue !== Ci.nsIHttpChannel.REFERRER_POLICY_UNSET) { referrerPolicy = referrerAttrValue; } } @@ -452,7 +452,7 @@ var ClickEventHandler = { node) { let referrerAttrValue = Services.netUtils.parseAttributePolicyString(node. getAttribute("referrerpolicy")); - if (referrerAttrValue !== Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT) { + if (referrerAttrValue !== Ci.nsIHttpChannel.REFERRER_POLICY_UNSET) { referrerPolicy = referrerAttrValue; } } diff --git a/browser/base/content/test/general/healthreport_testRemoteCommands.html b/browser/base/content/test/general/healthreport_testRemoteCommands.html index 550c198df9..cc9e707670 100644 --- a/browser/base/content/test/general/healthreport_testRemoteCommands.html +++ b/browser/base/content/test/general/healthreport_testRemoteCommands.html @@ -1,267 +1,272 @@ - - - - - - - - - + + + + + + + + + diff --git a/dom/base/nsFrameLoader.cpp b/dom/base/nsFrameLoader.cpp index 25780d8f4c..aa321f35c4 100644 --- a/dom/base/nsFrameLoader.cpp +++ b/dom/base/nsFrameLoader.cpp @@ -905,8 +905,25 @@ nsFrameLoader::SwapWithOtherRemoteLoader(nsFrameLoader* aOther, return NS_ERROR_NOT_IMPLEMENTED; } - if (mRemoteBrowser->OriginAttributesRef() != - aOther->mRemoteBrowser->OriginAttributesRef()) { + // When we swap docShells, maybe we have to deal with a new page created just + // for this operation. In this case, the browser code should already have set + // the correct userContextId attribute value in the owning XULElement, but our + // docShell, that has been created way before) doesn't know that that + // happened. + // This is the reason why now we must retrieve the correct value from the + // usercontextid attribute before comparing our originAttributes with the + // other one. + DocShellOriginAttributes ourOriginAttributes = + mRemoteBrowser->OriginAttributesRef(); + rv = PopulateUserContextIdFromAttribute(ourOriginAttributes); + NS_ENSURE_SUCCESS(rv,rv); + + DocShellOriginAttributes otherOriginAttributes = + aOther->mRemoteBrowser->OriginAttributesRef(); + rv = aOther->PopulateUserContextIdFromAttribute(otherOriginAttributes); + NS_ENSURE_SUCCESS(rv,rv); + + if (ourOriginAttributes != otherOriginAttributes) { return NS_ERROR_NOT_IMPLEMENTED; } @@ -1286,8 +1303,25 @@ nsFrameLoader::SwapWithOtherLoader(nsFrameLoader* aOther, return NS_ERROR_NOT_IMPLEMENTED; } - if (ourDocshell->GetOriginAttributes() != - otherDocshell->GetOriginAttributes()) { + // When we swap docShells, maybe we have to deal with a new page created just + // for this operation. In this case, the browser code should already have set + // the correct userContextId attribute value in the owning XULElement, but our + // docShell, that has been created way before) doesn't know that that + // happened. + // This is the reason why now we must retrieve the correct value from the + // usercontextid attribute before comparing our originAttributes with the + // other one. + DocShellOriginAttributes ourOriginAttributes = + ourDocshell->GetOriginAttributes(); + rv = PopulateUserContextIdFromAttribute(ourOriginAttributes); + NS_ENSURE_SUCCESS(rv,rv); + + DocShellOriginAttributes otherOriginAttributes = + otherDocshell->GetOriginAttributes(); + rv = aOther->PopulateUserContextIdFromAttribute(otherOriginAttributes); + NS_ENSURE_SUCCESS(rv,rv); + + if (ourOriginAttributes != otherOriginAttributes) { return NS_ERROR_NOT_IMPLEMENTED; } @@ -2047,13 +2081,9 @@ nsFrameLoader::MaybeCreateDocShell() } // Grab the userContextId from owner if XUL - nsAutoString userContextIdStr; - if ((namespaceID == kNameSpaceID_XUL) && - mOwnerContent->GetAttr(kNameSpaceID_None, nsGkAtoms::usercontextid, userContextIdStr) && - !userContextIdStr.IsEmpty()) { - nsresult rv; - attrs.mUserContextId = userContextIdStr.ToInteger(&rv); - NS_ENSURE_SUCCESS(rv, rv); + nsresult rv = PopulateUserContextIdFromAttribute(attrs); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; } nsDocShell::Cast(mDocShell)->SetOriginAttributes(attrs); @@ -3270,3 +3300,24 @@ nsFrameLoader::GetNewTabContext(MutableTabContext* aTabContext, return NS_OK; } + +nsresult +nsFrameLoader::PopulateUserContextIdFromAttribute(DocShellOriginAttributes& aAttr) +{ + if (aAttr.mUserContextId == + nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID) { + // Grab the userContextId from owner if XUL + nsAutoString userContextIdStr; + int32_t namespaceID = mOwnerContent->GetNameSpaceID(); + if ((namespaceID == kNameSpaceID_XUL) && + mOwnerContent->GetAttr(kNameSpaceID_None, nsGkAtoms::usercontextid, + userContextIdStr) && + !userContextIdStr.IsEmpty()) { + nsresult rv; + aAttr.mUserContextId = userContextIdStr.ToInteger(&rv); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + return NS_OK; +} diff --git a/dom/base/nsFrameLoader.h b/dom/base/nsFrameLoader.h index 7fe427efd0..fcf99edd4d 100644 --- a/dom/base/nsFrameLoader.h +++ b/dom/base/nsFrameLoader.h @@ -39,6 +39,9 @@ class nsIDocShellTreeOwner; class mozIApplication; namespace mozilla { + +class DocShellOriginAttributes; + namespace dom { class ContentParent; class PBrowserParent; @@ -340,6 +343,9 @@ private: }; void MaybeUpdatePrimaryTabParent(TabParentChange aChange); + nsresult + PopulateUserContextIdFromAttribute(mozilla::DocShellOriginAttributes& aAttr); + nsCOMPtr mDocShell; nsCOMPtr mURIToLoad; mozilla::dom::Element* mOwnerContent; // WEAK diff --git a/dom/base/test/img_referrer_testserver.sjs b/dom/base/test/img_referrer_testserver.sjs index 25b6636bd5..db60f8a463 100644 --- a/dom/base/test/img_referrer_testserver.sjs +++ b/dom/base/test/img_referrer_testserver.sjs @@ -14,7 +14,7 @@ function createTestPage(aHead, aImgPolicy, aName) { '+ aHead + '\n\ - \n\ + \n\ - - - - - - Mozilla Bug 965421 - - This image is going to load - - - - - diff --git a/dom/canvas/CanvasRenderingContext2D.cpp b/dom/canvas/CanvasRenderingContext2D.cpp index 65429b2865..0ae300d7b5 100644 --- a/dom/canvas/CanvasRenderingContext2D.cpp +++ b/dom/canvas/CanvasRenderingContext2D.cpp @@ -872,6 +872,35 @@ private: CanvasRenderingContext2D *mContext; }; +class CanvasFilterChainObserver : public nsSVGFilterChainObserver +{ +public: + CanvasFilterChainObserver(nsTArray& aFilters, + Element* aCanvasElement, + CanvasRenderingContext2D* aContext) + : nsSVGFilterChainObserver(aFilters, aCanvasElement) + , mContext(aContext) + { + } + + virtual void DoUpdate() override + { + if (!mContext) { + MOZ_CRASH("This should never be called without a context"); + } + // Refresh the cached FilterDescription in mContext->CurrentState().filter. + // If this filter is not at the top of the state stack, we'll refresh the + // wrong filter, but that's ok, because we'll refresh the right filter + // when we pop the state stack in CanvasRenderingContext2D::Restore(). + mContext->UpdateFilter(); + } + + void DetachFromContext() { mContext = nullptr; } + +private: + CanvasRenderingContext2D *mContext; +}; + NS_IMPL_CYCLE_COLLECTING_ADDREF(CanvasRenderingContext2D) NS_IMPL_CYCLE_COLLECTING_RELEASE(CanvasRenderingContext2D) @@ -887,6 +916,12 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CanvasRenderingContext2D) ImplCycleCollectionUnlink(tmp->mStyleStack[i].patternStyles[Style::FILL]); ImplCycleCollectionUnlink(tmp->mStyleStack[i].gradientStyles[Style::STROKE]); ImplCycleCollectionUnlink(tmp->mStyleStack[i].gradientStyles[Style::FILL]); + CanvasFilterChainObserver *filterChainObserver = + static_cast(tmp->mStyleStack[i].filterChainObserver.get()); + if (filterChainObserver) { + filterChainObserver->DetachFromContext(); + } + ImplCycleCollectionUnlink(tmp->mStyleStack[i].filterChainObserver); } for (size_t x = 0 ; x < tmp->mHitRegionsOptions.Length(); x++) { RegionInfo& info = tmp->mHitRegionsOptions[x]; @@ -904,6 +939,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CanvasRenderingContext2D) ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].patternStyles[Style::FILL], "Fill CanvasPattern"); ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].gradientStyles[Style::STROKE], "Stroke CanvasGradient"); ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].gradientStyles[Style::FILL], "Fill CanvasGradient"); + ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].filterChainObserver, "Filter Chain Observer"); } for (size_t x = 0 ; x < tmp->mHitRegionsOptions.Length(); x++) { RegionInfo& info = tmp->mHitRegionsOptions[x]; @@ -1182,7 +1218,7 @@ CanvasRenderingContext2D::Redraw(const gfx::Rect& aR) void CanvasRenderingContext2D::DidRefresh() { - if (IsTargetValid() && SkiaGLTex()) { + if (IsTargetValid() && mIsSkiaGL) { SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue(); MOZ_ASSERT(glue); @@ -2425,30 +2461,6 @@ CanvasRenderingContext2D::ParseFilter(const nsAString& aString, return true; } -class CanvasFilterChainObserver : public nsSVGFilterChainObserver -{ -public: - CanvasFilterChainObserver(nsTArray& aFilters, - Element* aCanvasElement, - CanvasRenderingContext2D* aContext) - : nsSVGFilterChainObserver(aFilters, aCanvasElement) - , mContext(aContext) - { - } - - virtual void DoUpdate() override - { - // Refresh the cached FilterDescription in mContext->CurrentState().filter. - // If this filter is not at the top of the state stack, we'll refresh the - // wrong filter, but that's ok, because we'll refresh the right filter - // when we pop the state stack in CanvasRenderingContext2D::Restore(). - mContext->UpdateFilter(); - } - -private: - CanvasRenderingContext2D *mContext; -}; - void CanvasRenderingContext2D::SetFilter(const nsAString& aFilter, ErrorResult& aError) { @@ -5641,20 +5653,19 @@ CanvasRenderingContext2D::GetCanvasLayer(nsDisplayListBuilder* aBuilder, CanvasLayer::Data data; - GLuint skiaGLTex = SkiaGLTex(); - if (mIsSkiaGL && skiaGLTex) { - SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue(); - MOZ_ASSERT(glue); - - data.mGLContext = glue->GetGLContext(); - data.mFrontbufferGLTex = skiaGLTex; - PersistentBufferProvider *provider = GetBufferProvider(aManager); - data.mBufferProvider = provider; - } else { - PersistentBufferProvider *provider = GetBufferProvider(aManager); - data.mBufferProvider = provider; + if (mIsSkiaGL) { + GLuint skiaGLTex = SkiaGLTex(); + if (skiaGLTex) { + SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue(); + MOZ_ASSERT(glue); + data.mGLContext = glue->GetGLContext(); + data.mFrontbufferGLTex = skiaGLTex; + } } + PersistentBufferProvider *provider = GetBufferProvider(aManager); + data.mBufferProvider = provider; + if (userData && userData->IsForContext(this) && static_cast(aOldLayer)->IsDataValid(data)) { @@ -5695,20 +5706,20 @@ CanvasRenderingContext2D::GetCanvasLayer(nsDisplayListBuilder* aBuilder, canvasLayer->SetPreTransactionCallback( CanvasRenderingContext2DUserData::PreTransactionCallback, userData); - GLuint skiaGLTex = SkiaGLTex(); - if (mIsSkiaGL && skiaGLTex) { - SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue(); - MOZ_ASSERT(glue); - data.mGLContext = glue->GetGLContext(); - data.mFrontbufferGLTex = skiaGLTex; - PersistentBufferProvider *provider = GetBufferProvider(aManager); - data.mBufferProvider = provider; - } else { - PersistentBufferProvider *provider = GetBufferProvider(aManager); - data.mBufferProvider = provider; + if (mIsSkiaGL) { + GLuint skiaGLTex = SkiaGLTex(); + if (skiaGLTex) { + SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue(); + MOZ_ASSERT(glue); + data.mGLContext = glue->GetGLContext(); + data.mFrontbufferGLTex = skiaGLTex; + } } + PersistentBufferProvider *provider = GetBufferProvider(aManager); + data.mBufferProvider = provider; + canvasLayer->Initialize(data); uint32_t flags = mOpaque ? Layer::CONTENT_OPAQUE : 0; canvasLayer->SetContentFlags(flags); diff --git a/dom/canvas/compiledtest/TestWebGLElementArrayCache.cpp b/dom/canvas/compiledtest/TestWebGLElementArrayCache.cpp index 2d0c98b29c..21bc0838f4 100644 --- a/dom/canvas/compiledtest/TestWebGLElementArrayCache.cpp +++ b/dom/canvas/compiledtest/TestWebGLElementArrayCache.cpp @@ -8,8 +8,8 @@ #include "WebGLElementArrayCache.cpp" +#include #include -#include #include "nscore.h" #include "nsTArray.h" @@ -21,7 +21,7 @@ VerifyImplFunction(bool condition, const char* file, int line) if (condition) { gTestsPassed++; } else { - std::cerr << "Test failed at " << file << ":" << line << std::endl; + std::fprintf(stderr, "Test failed at %s:%d\n", file, line); abort(); } } @@ -226,7 +226,7 @@ main(int argc, char* argv[]) } // i } // maxBufferSize - std::cerr << argv[0] << ": all " << gTestsPassed << " tests passed" << std::endl; + std::fprintf(stderr, "%s: all %d tests passed\n", argv[0], gTestsPassed); return 0; } diff --git a/dom/cellbroadcast/tests/marionette/head.js b/dom/cellbroadcast/tests/marionette/head.js index ed8de04460..0917791b4a 100644 --- a/dom/cellbroadcast/tests/marionette/head.js +++ b/dom/cellbroadcast/tests/marionette/head.js @@ -39,6 +39,9 @@ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_USER_2] = "user-2"; const CB_MESSAGE_SIZE_GSM = 88; const CB_MESSAGE_SIZE_ETWS = 56; +const CB_UMTS_MESSAGE_TYPE_CBS = 1; +const CB_UMTS_MESSAGE_PAGE_SIZE = 82; + const CB_GSM_MESSAGEID_ETWS_BEGIN = 0x1100; const CB_GSM_MESSAGEID_ETWS_END = 0x1107; @@ -189,7 +192,7 @@ function decodeGsmDataCodingScheme(aDcs) { * * @return A deferred promise. */ -let cbManager; +var cbManager; function ensureCellBroadcast() { let deferred = Promise.defer(); @@ -201,18 +204,28 @@ function ensureCellBroadcast() { SpecialPowers.pushPermissions(permissions, function() { ok(true, "permissions pushed: " + JSON.stringify(permissions)); - cbManager = window.navigator.mozCellBroadcast; - if (cbManager) { - log("navigator.mozCellBroadcast is instance of " + cbManager.constructor); - } else { - log("navigator.mozCellBroadcast is undefined."); - } + // Permission changes can't change existing Navigator.prototype + // objects, so grab our objects from a new Navigator. + let workingFrame = document.createElement("iframe"); + workingFrame.addEventListener("load", function load() { + workingFrame.removeEventListener("load", load); - if (cbManager instanceof window.MozCellBroadcast) { - deferred.resolve(cbManager); - } else { - deferred.reject(); - } + cbManager = workingFrame.contentWindow.navigator.mozCellBroadcast; + + if (cbManager) { + log("navigator.mozCellBroadcast is instance of " + cbManager.constructor); + } else { + log("navigator.mozCellBroadcast is undefined."); + } + + if (cbManager instanceof window.MozCellBroadcast) { + deferred.resolve(cbManager); + } else { + deferred.reject(); + } + }); + + document.body.appendChild(workingFrame); }); return deferred.promise; @@ -233,7 +246,7 @@ function ensureCellBroadcast() { * * @return A deferred promise. */ -let pendingEmulatorCmdCount = 0; +var pendingEmulatorCmdCount = 0; function runEmulatorCmdSafe(aCommand) { let deferred = Promise.defer(); diff --git a/dom/cellbroadcast/tests/marionette/manifest.ini b/dom/cellbroadcast/tests/marionette/manifest.ini index dc649fa172..8b440e887d 100644 --- a/dom/cellbroadcast/tests/marionette/manifest.ini +++ b/dom/cellbroadcast/tests/marionette/manifest.ini @@ -5,5 +5,9 @@ qemu = true [test_cellbroadcast_etws.js] [test_cellbroadcast_gsm.js] +[test_cellbroadcast_gsm_language_and_body.js] +disabled = Bug 1231462 [test_cellbroadcast_multi_sim.js] -[test_cellbroadcast_umts.js] \ No newline at end of file +[test_cellbroadcast_umts.js] +[test_cellbroadcast_umts_language_and_body.js] +disabled = Bug 1224992 diff --git a/dom/cellbroadcast/tests/marionette/test_cellbroadcast_etws.js b/dom/cellbroadcast/tests/marionette/test_cellbroadcast_etws.js index 557bcbc08b..fe4b75ac86 100644 --- a/dom/cellbroadcast/tests/marionette/test_cellbroadcast_etws.js +++ b/dom/cellbroadcast/tests/marionette/test_cellbroadcast_etws.js @@ -1,7 +1,7 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ -MARIONETTE_TIMEOUT = 20000; +MARIONETTE_TIMEOUT = 60000; MARIONETTE_HEAD_JS = 'head.js'; function testReceiving_ETWS_MessageAttributes() { @@ -91,7 +91,7 @@ function testReceiving_ETWS_MessageId() { // Message Identifier has 16 bits, but no bitwise operation is needed. // Test some selected values only. let messageIds = [ - 0x0000, 0x0001, 0x0010, 0x0100, 0x1000, 0x1111, 0x8888, 0x8811, + 0x0000, 0x0001, 0x0010, 0x0100, 0x1111, 0x8888, 0x8811, ]; let verifyCBMessage = (aMessage, aMessageId) => { diff --git a/dom/cellbroadcast/tests/marionette/test_cellbroadcast_gsm.js b/dom/cellbroadcast/tests/marionette/test_cellbroadcast_gsm.js index 97d826f2ae..8b749737fb 100644 --- a/dom/cellbroadcast/tests/marionette/test_cellbroadcast_gsm.js +++ b/dom/cellbroadcast/tests/marionette/test_cellbroadcast_gsm.js @@ -90,7 +90,7 @@ function testReceiving_GSM_MessageId() { // Message Identifier has 16 bits, but no bitwise operation is needed. // Test some selected values only. let messageIds = [ - 0x0000, 0x0001, 0x0010, 0x0100, 0x1000, 0x1111, 0x8888, 0x8811, + 0x0000, 0x0001, 0x0010, 0x0100, 0x1111, 0x8888, 0x8811, ]; let verifyCBMessage = (aMessage, aMessageId) => { @@ -110,62 +110,6 @@ function testReceiving_GSM_MessageId() { return promise; } -function testReceiving_GSM_Language_and_Body() { - log("Test receiving GSM Cell Broadcast - Language & Body"); - - let promise = Promise.resolve(); - - let testDcs = []; - let dcs = 0; - while (dcs <= 0xFF) { - try { - let dcsInfo = { dcs: dcs }; - [ dcsInfo.encoding, dcsInfo.language, - dcsInfo.indicator, dcsInfo.messageClass ] = decodeGsmDataCodingScheme(dcs); - testDcs.push(dcsInfo); - } catch (e) { - // Unsupported coding group, skip. - dcs = (dcs & PDU_DCS_CODING_GROUP_BITS) + 0x10; - } - dcs++; - } - - let verifyCBMessage = (aMessage, aDcsInfo) => { - if (aDcsInfo.language) { - is(aMessage.language, aDcsInfo.language, "aMessage.language"); - } else if (aDcsInfo.indicator) { - is(aMessage.language, "@@", "aMessage.language"); - } else { - ok(aMessage.language == null, "aMessage.language"); - } - - switch (aDcsInfo.encoding) { - case PDU_DCS_MSG_CODING_7BITS_ALPHABET: - is(aMessage.body, aDcsInfo.indicator ? DUMMY_BODY_7BITS_IND : DUMMY_BODY_7BITS, "aMessage.body"); - break; - case PDU_DCS_MSG_CODING_8BITS_ALPHABET: - ok(aMessage.body == null, "aMessage.body"); - break; - case PDU_DCS_MSG_CODING_16BITS_ALPHABET: - is(aMessage.body, aDcsInfo.indicator ? DUMMY_BODY_UCS2_IND : DUMMY_BODY_UCS2, "aMessage.body"); - break; - } - - is(aMessage.messageClass, aDcsInfo.messageClass, "aMessage.messageClass"); - }; - - testDcs.forEach(function(aDcsInfo) { - let pdu = buildHexStr(0, 8) - + buildHexStr(aDcsInfo.dcs, 2) - + buildHexStr(0, (CB_MESSAGE_SIZE_GSM - 5) * 2); - promise = promise - .then(() => sendMultipleRawCbsToEmulatorAndWait([pdu])) - .then((aMessage) => verifyCBMessage(aMessage, aDcsInfo)); - }); - - return promise; -} - function testReceiving_GSM_Timestamp() { log("Test receiving GSM Cell Broadcast - Timestamp"); @@ -351,7 +295,6 @@ startTestCommon(function testCaseMain() { .then(() => testReceiving_GSM_GeographicalScope()) .then(() => testReceiving_GSM_MessageCode()) .then(() => testReceiving_GSM_MessageId()) - .then(() => testReceiving_GSM_Language_and_Body()) .then(() => testReceiving_GSM_Timestamp()) .then(() => testReceiving_GSM_WarningType()) .then(() => testReceiving_GSM_EmergencyUserAlert()) diff --git a/dom/cellbroadcast/tests/marionette/test_cellbroadcast_gsm_language_and_body.js b/dom/cellbroadcast/tests/marionette/test_cellbroadcast_gsm_language_and_body.js new file mode 100644 index 0000000000..7953532340 --- /dev/null +++ b/dom/cellbroadcast/tests/marionette/test_cellbroadcast_gsm_language_and_body.js @@ -0,0 +1,64 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_TIMEOUT = 90000; +MARIONETTE_HEAD_JS = 'head.js'; + +function testReceiving_GSM_Language_and_Body() { + log("Test receiving GSM Cell Broadcast - Language & Body"); + + let promise = Promise.resolve(); + + let testDcs = []; + let dcs = 0; + while (dcs <= 0xFF) { + try { + let dcsInfo = { dcs: dcs }; + [ dcsInfo.encoding, dcsInfo.language, + dcsInfo.indicator, dcsInfo.messageClass ] = decodeGsmDataCodingScheme(dcs); + testDcs.push(dcsInfo); + } catch (e) { + // Unsupported coding group, skip. + dcs = (dcs & PDU_DCS_CODING_GROUP_BITS) + 0x10; + } + dcs++; + } + + let verifyCBMessage = (aMessage, aDcsInfo) => { + if (aDcsInfo.language) { + is(aMessage.language, aDcsInfo.language, "aMessage.language"); + } else if (aDcsInfo.indicator) { + is(aMessage.language, "@@", "aMessage.language"); + } else { + ok(aMessage.language == null, "aMessage.language"); + } + + switch (aDcsInfo.encoding) { + case PDU_DCS_MSG_CODING_7BITS_ALPHABET: + is(aMessage.body, aDcsInfo.indicator ? DUMMY_BODY_7BITS_IND : DUMMY_BODY_7BITS, "aMessage.body"); + break; + case PDU_DCS_MSG_CODING_8BITS_ALPHABET: + ok(aMessage.body == null, "aMessage.body"); + break; + case PDU_DCS_MSG_CODING_16BITS_ALPHABET: + is(aMessage.body, aDcsInfo.indicator ? DUMMY_BODY_UCS2_IND : DUMMY_BODY_UCS2, "aMessage.body"); + break; + } + + is(aMessage.messageClass, aDcsInfo.messageClass, "aMessage.messageClass"); + }; + + ok(testDcs.length, "testDcs.length"); + testDcs.forEach(function(aDcsInfo) { + let pdu = buildHexStr(0, 8) + + buildHexStr(aDcsInfo.dcs, 2) + + buildHexStr(0, (CB_MESSAGE_SIZE_GSM - 5) * 2); + promise = promise + .then(() => sendMultipleRawCbsToEmulatorAndWait([pdu])) + .then((aMessage) => verifyCBMessage(aMessage, aDcsInfo)); + }); + + return promise; +} + +startTestCommon(() => testReceiving_GSM_Language_and_Body()); diff --git a/dom/cellbroadcast/tests/marionette/test_cellbroadcast_multi_sim.js b/dom/cellbroadcast/tests/marionette/test_cellbroadcast_multi_sim.js index eb322d0395..e759cd47a4 100644 --- a/dom/cellbroadcast/tests/marionette/test_cellbroadcast_multi_sim.js +++ b/dom/cellbroadcast/tests/marionette/test_cellbroadcast_multi_sim.js @@ -1,7 +1,7 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ -MARIONETTE_TIMEOUT = 10000; +MARIONETTE_TIMEOUT = 60000; MARIONETTE_HEAD_JS = 'head.js'; function testReceiving_MultiSIM() { diff --git a/dom/cellbroadcast/tests/marionette/test_cellbroadcast_umts.js b/dom/cellbroadcast/tests/marionette/test_cellbroadcast_umts.js index d08a565ba3..f9e85c1c38 100644 --- a/dom/cellbroadcast/tests/marionette/test_cellbroadcast_umts.js +++ b/dom/cellbroadcast/tests/marionette/test_cellbroadcast_umts.js @@ -1,9 +1,9 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + MARIONETTE_TIMEOUT = 90000; MARIONETTE_HEAD_JS = 'head.js'; -const CB_UMTS_MESSAGE_TYPE_CBS = 1; -const CB_UMTS_MESSAGE_PAGE_SIZE = 82; - function testReceiving_UMTS_MessageAttributes() { log("Test receiving UMTS Cell Broadcast - Message Attributes"); @@ -102,7 +102,7 @@ function testReceiving_UMTS_MessageId() { // Message Identifier has 16 bits, but no bitwise operation is needed. // Test some selected values only. let messageIds = [ - 0x0000, 0x0001, 0x0010, 0x0100, 0x1000, 0x1111, 0x8888, 0x8811, + 0x0000, 0x0001, 0x0010, 0x0100, 0x1111, 0x8888, 0x8811, ]; let verifyCBMessage = (aMessage, aMessageId) => { @@ -126,70 +126,6 @@ function testReceiving_UMTS_MessageId() { return promise; } -function testReceiving_UMTS_Language_and_Body() { - log("Test receiving UMTS Cell Broadcast - Language & Body"); - - let promise = Promise.resolve(); - - let testDcs = []; - let dcs = 0; - while (dcs <= 0xFF) { - try { - let dcsInfo = { dcs: dcs }; - [ dcsInfo.encoding, dcsInfo.language, - dcsInfo.indicator, dcsInfo.messageClass ] = decodeGsmDataCodingScheme(dcs); - testDcs.push(dcsInfo); - } catch (e) { - // Unsupported coding group, skip. - dcs = (dcs & PDU_DCS_CODING_GROUP_BITS) + 0x10; - } - dcs++; - } - - let verifyCBMessage = (aMessage, aDcsInfo) => { - if (aDcsInfo.language) { - is(aMessage.language, aDcsInfo.language, "aMessage.language"); - } else if (aDcsInfo.indicator) { - is(aMessage.language, "@@", "aMessage.language"); - } else { - ok(aMessage.language == null, "aMessage.language"); - } - - switch (aDcsInfo.encoding) { - case PDU_DCS_MSG_CODING_7BITS_ALPHABET: - is(aMessage.body, - aDcsInfo.indicator ? DUMMY_BODY_7BITS_IND : DUMMY_BODY_7BITS, - "aMessage.body"); - break; - case PDU_DCS_MSG_CODING_8BITS_ALPHABET: - ok(aMessage.body == null, "aMessage.body"); - break; - case PDU_DCS_MSG_CODING_16BITS_ALPHABET: - is(aMessage.body, - aDcsInfo.indicator ? DUMMY_BODY_UCS2_IND : DUMMY_BODY_UCS2, - "aMessage.body"); - break; - } - - is(aMessage.messageClass, aDcsInfo.messageClass, "aMessage.messageClass"); - }; - - testDcs.forEach(function(aDcsInfo) { - let pdu = buildHexStr(CB_UMTS_MESSAGE_TYPE_CBS, 2) // msg_type - + buildHexStr(0, 4) // skip msg_id - + buildHexStr(0, 4) // skip SN - + buildHexStr(aDcsInfo.dcs, 2) // set dcs - + buildHexStr(1, 2) // set num_of_pages to 1 - + buildHexStr(0, CB_UMTS_MESSAGE_PAGE_SIZE * 2) - + buildHexStr(CB_UMTS_MESSAGE_PAGE_SIZE, 2); // msg_info_length - promise = promise - .then(() => sendMultipleRawCbsToEmulatorAndWait([pdu])) - .then((aMessage) => verifyCBMessage(aMessage, aDcsInfo)); - }); - - return promise; -} - function testReceiving_UMTS_Timestamp() { log("Test receiving UMTS Cell Broadcast - Timestamp"); @@ -441,7 +377,6 @@ startTestCommon(function testCaseMain() { .then(() => testReceiving_UMTS_GeographicalScope()) .then(() => testReceiving_UMTS_MessageCode()) .then(() => testReceiving_UMTS_MessageId()) - .then(() => testReceiving_UMTS_Language_and_Body()) .then(() => testReceiving_UMTS_Timestamp()) .then(() => testReceiving_UMTS_WarningType()) .then(() => testReceiving_UMTS_EmergencyUserAlert()) diff --git a/dom/cellbroadcast/tests/marionette/test_cellbroadcast_umts_language_and_body.js b/dom/cellbroadcast/tests/marionette/test_cellbroadcast_umts_language_and_body.js new file mode 100644 index 0000000000..f7846452a7 --- /dev/null +++ b/dom/cellbroadcast/tests/marionette/test_cellbroadcast_umts_language_and_body.js @@ -0,0 +1,71 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_TIMEOUT = 90000; +MARIONETTE_HEAD_JS = 'head.js'; + +function testReceiving_UMTS_Language_and_Body() { + log("Test receiving UMTS Cell Broadcast - Language & Body"); + + let promise = Promise.resolve(); + + let testDcs = []; + let dcs = 0; + while (dcs <= 0xFF) { + try { + let dcsInfo = { dcs: dcs }; + [ dcsInfo.encoding, dcsInfo.language, + dcsInfo.indicator, dcsInfo.messageClass ] = decodeGsmDataCodingScheme(dcs); + testDcs.push(dcsInfo); + } catch (e) { + // Unsupported coding group, skip. + dcs = (dcs & PDU_DCS_CODING_GROUP_BITS) + 0x10; + } + dcs++; + } + + let verifyCBMessage = (aMessage, aDcsInfo) => { + if (aDcsInfo.language) { + is(aMessage.language, aDcsInfo.language, "aMessage.language"); + } else if (aDcsInfo.indicator) { + is(aMessage.language, "@@", "aMessage.language"); + } else { + ok(aMessage.language == null, "aMessage.language"); + } + + switch (aDcsInfo.encoding) { + case PDU_DCS_MSG_CODING_7BITS_ALPHABET: + is(aMessage.body, + aDcsInfo.indicator ? DUMMY_BODY_7BITS_IND : DUMMY_BODY_7BITS, + "aMessage.body"); + break; + case PDU_DCS_MSG_CODING_8BITS_ALPHABET: + ok(aMessage.body == null, "aMessage.body"); + break; + case PDU_DCS_MSG_CODING_16BITS_ALPHABET: + is(aMessage.body, + aDcsInfo.indicator ? DUMMY_BODY_UCS2_IND : DUMMY_BODY_UCS2, + "aMessage.body"); + break; + } + + is(aMessage.messageClass, aDcsInfo.messageClass, "aMessage.messageClass"); + }; + + testDcs.forEach(function(aDcsInfo) { + let pdu = buildHexStr(CB_UMTS_MESSAGE_TYPE_CBS, 2) // msg_type + + buildHexStr(0, 4) // skip msg_id + + buildHexStr(0, 4) // skip SN + + buildHexStr(aDcsInfo.dcs, 2) // set dcs + + buildHexStr(1, 2) // set num_of_pages to 1 + + buildHexStr(0, CB_UMTS_MESSAGE_PAGE_SIZE * 2) + + buildHexStr(CB_UMTS_MESSAGE_PAGE_SIZE, 2); // msg_info_length + promise = promise + .then(() => sendMultipleRawCbsToEmulatorAndWait([pdu])) + .then((aMessage) => verifyCBMessage(aMessage, aDcsInfo)); + }); + + return promise; +} + +startTestCommon(() => testReceiving_UMTS_Language_and_Body()); diff --git a/dom/contacts/tests/test_migration.html b/dom/contacts/tests/test_migration.html index 18141899c1..ff0c39cdd1 100644 --- a/dom/contacts/tests/test_migration.html +++ b/dom/contacts/tests/test_migration.html @@ -1,209 +1,28 @@ - + - Migration tests - - - + + -

migration tests

-

- +
-
-
 
diff --git a/dom/html/nsGenericHTMLElement.cpp b/dom/html/nsGenericHTMLElement.cpp index 7ddfadd3d5..9985b82d1d 100644 --- a/dom/html/nsGenericHTMLElement.cpp +++ b/dom/html/nsGenericHTMLElement.cpp @@ -109,6 +109,7 @@ #include "nsComputedDOMStyle.h" #include "mozilla/StyleSetHandle.h" #include "mozilla/StyleSetHandleInlines.h" +#include "ReferrerPolicy.h" using namespace mozilla; using namespace mozilla::dom; @@ -1275,9 +1276,11 @@ nsGenericHTMLElement::ParseReferrerAttribute(const nsAString& aString, nsAttrValue& aResult) { static const nsAttrValue::EnumTable kReferrerTable[] = { - { "no-referrer", net::RP_No_Referrer }, - { "origin", net::RP_Origin }, - { "unsafe-url", net::RP_Unsafe_URL }, + { net::kRPS_No_Referrer, net::RP_No_Referrer }, + { net::kRPS_Origin, net::RP_Origin }, + { net::kRPS_Origin_When_Cross_Origin, net::RP_Origin_When_Crossorigin }, + { net::kRPS_No_Referrer_When_Downgrade, net::RP_No_Referrer_When_Downgrade }, + { net::kRPS_Unsafe_URL, net::RP_Unsafe_URL }, { 0 } }; return aResult.ParseEnumValue(aString, kReferrerTable, false); diff --git a/dom/html/nsHTMLDNSPrefetch.cpp b/dom/html/nsHTMLDNSPrefetch.cpp index 82cb1e097e..331a005c6a 100644 --- a/dom/html/nsHTMLDNSPrefetch.cpp +++ b/dom/html/nsHTMLDNSPrefetch.cpp @@ -320,7 +320,7 @@ nsHTMLDNSPrefetch::nsDeferrals::SubmitQueue() if (link && link->HasDeferredDNSPrefetchRequest()) { nsCOMPtr hrefURI(link ? link->GetURI() : nullptr); bool isLocalResource = false; - nsresult rv; + nsresult rv = NS_OK; hostName.Truncate(); if (hrefURI) { diff --git a/dom/html/nsHTMLDocument.cpp b/dom/html/nsHTMLDocument.cpp index d0ee7bc626..8763af5280 100644 --- a/dom/html/nsHTMLDocument.cpp +++ b/dom/html/nsHTMLDocument.cpp @@ -934,23 +934,22 @@ nsHTMLDocument::SetDomain(const nsAString& aDomain, ErrorResult& rv) return; } - nsAutoCString newURIString; - if (NS_FAILED(uri->GetScheme(newURIString))) { - rv.Throw(NS_ERROR_FAILURE); - return; - } - nsAutoCString path; - if (NS_FAILED(uri->GetPath(path))) { - rv.Throw(NS_ERROR_FAILURE); - return; - } - newURIString.AppendLiteral("://"); - AppendUTF16toUTF8(aDomain, newURIString); - newURIString.Append(path); - nsCOMPtr newURI; - if (NS_FAILED(NS_NewURI(getter_AddRefs(newURI), newURIString))) { - rv.Throw(NS_ERROR_FAILURE); + nsresult rv2 = uri->Clone(getter_AddRefs(newURI)); + if (NS_FAILED(rv2)) { + rv.Throw(rv2); + return; + } + + rv2 = newURI->SetUserPass(EmptyCString()); + if (NS_FAILED(rv2)) { + rv.Throw(rv2); + return; + } + + rv2 = newURI->SetHostPort(NS_ConvertUTF16toUTF8(aDomain)); + if (NS_FAILED(rv2)) { + rv.Throw(rv2); return; } @@ -989,6 +988,7 @@ nsHTMLDocument::SetDomain(const nsAString& aDomain, ErrorResult& rv) return; } + NS_TryToSetImmutable(newURI); rv = NodePrincipal()->SetDomain(newURI); } diff --git a/dom/html/nsTextEditorState.cpp b/dom/html/nsTextEditorState.cpp index ca74571a1e..7683fe0274 100644 --- a/dom/html/nsTextEditorState.cpp +++ b/dom/html/nsTextEditorState.cpp @@ -1918,7 +1918,7 @@ nsTextEditorState::GetValue(nsAString& aValue, bool aIgnoreWrap) const if (!mTextCtrlElement->ValueChanged() || !mValue) { mTextCtrlElement->GetDefaultValueFromContent(aValue); } else { - aValue = NS_ConvertUTF8toUTF16(*mValue); + aValue = *mValue; } } } @@ -2139,7 +2139,7 @@ nsTextEditorState::SetValue(const nsAString& aValue, uint32_t aFlags) } } else { if (!mValue) { - mValue = new nsCString; + mValue.emplace(); } nsString value; if (!value.Assign(newValue, fallible)) { @@ -2148,7 +2148,7 @@ nsTextEditorState::SetValue(const nsAString& aValue, uint32_t aFlags) if (!nsContentUtils::PlatformToDOMLineBreaks(value, fallible)) { return false; } - if (!CopyUTF16toUTF8(value, *mValue, fallible)) { + if (!mValue->Assign(value, fallible)) { return false; } diff --git a/dom/html/nsTextEditorState.h b/dom/html/nsTextEditorState.h index 06aefcabad..7a15c04528 100644 --- a/dom/html/nsTextEditorState.h +++ b/dom/html/nsTextEditorState.h @@ -14,6 +14,7 @@ #include "nsCycleCollectionParticipant.h" #include "mozilla/dom/Element.h" #include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" #include "mozilla/WeakPtr.h" class nsTextInputListener; @@ -292,7 +293,7 @@ private: nsCOMPtr mPlaceholderDiv; nsTextControlFrame* mBoundFrame; RefPtr mTextListener; - nsAutoPtr mValue; + mozilla::Maybe mValue; RefPtr mMutationObserver; mutable nsString mCachedValue; // Caches non-hard-wrapped value on a multiline control. // mValueBeingSet is available only while SetValue() is requesting to commit diff --git a/dom/mobileconnection/tests/marionette/head_chrome.js b/dom/mobileconnection/tests/marionette/head_chrome.js new file mode 100644 index 0000000000..6b29d6864e --- /dev/null +++ b/dom/mobileconnection/tests/marionette/head_chrome.js @@ -0,0 +1,144 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_CONTEXT = "chrome"; + +var XPCOMUtils = Cu.import("resource://gre/modules/XPCOMUtils.jsm").XPCOMUtils; +var Promise = Cu.import("resource://gre/modules/Promise.jsm").Promise; + +var mobileConnectionService = + Cc["@mozilla.org/mobileconnection/gonkmobileconnectionservice;1"] + .getService(Ci.nsIMobileConnectionService); +ok(mobileConnectionService, + "mobileConnectionService.constructor is " + mobileConnectionService.constructor); + +var _pendingEmulatorShellCmdCount = 0; + +/** + * Send emulator shell command with safe guard. + * + * We should only call |finish()| after all emulator shell command transactions + * end, so here comes with the pending counter. Resolve when the emulator + * shell gives response. Never reject. + * + * Fulfill params: + * result -- an array of emulator shell response lines. + * + * @param aCommands + * A string array commands to be passed to emulator through adb shell. + * + * @return A deferred promise. + */ +function runEmulatorShellCmdSafe(aCommands) { + return new Promise(function(aResolve, aReject) { + ++_pendingEmulatorShellCmdCount; + runEmulatorShell(aCommands, function(aResult) { + --_pendingEmulatorShellCmdCount; + + ok(true, "Emulator shell response: " + JSON.stringify(aResult)); + aResolve(aResult); + }); + }); +} + +/** + * Get nsIMobileConnection by clientId + * + * @param aClient [optional] + * A numeric DSDS client id. Default: 0. + * + * @return A nsIMobileConnection. + */ +function getMobileConnection(aClientId = 0) { + let mobileConnection = mobileConnectionService.getItemByServiceId(0); + ok(mobileConnection, + "mobileConnection.constructor is " + mobileConnection.constructor); + return mobileConnection; +} + +/** + * Get Neighboring Cell Ids. + * + * Fulfill params: + * An array of nsINeighboringCellInfo. + * Reject params: + * 'RadioNotAvailable', 'RequestNotSupported', or 'GenericFailure' + * + * @param aClient [optional] + * A numeric DSDS client id. Default: 0. + * + * @return A deferred promise. + */ +function getNeighboringCellIds(aClientId = 0) { + let mobileConnection = getMobileConnection(aClientId); + return new Promise(function(aResolve, aReject) { + ok(true, "getNeighboringCellIds"); + mobileConnection.getNeighboringCellIds({ + QueryInterface: XPCOMUtils.generateQI([Ci.nsINeighboringCellIdsCallback]), + notifyGetNeighboringCellIds: function(aCount, aResults) { + aResolve(aResults); + }, + notifyGetNeighboringCellIdsFailed: function(aErrorMsg) { + aReject(aErrorMsg); + }, + }); + }); +} + +/** + * Get Cell Info List. + * + * Fulfill params: + * An array of nsICellInfo. + * Reject params: + * 'RadioNotAvailable', 'RequestNotSupported', or 'GenericFailure' + * + * @param aClient [optional] + * A numeric DSDS client id. Default: 0. + * + * @return A deferred promise. + */ +function getCellInfoList(aClientId = 0) { + let mobileConnection = getMobileConnection(aClientId); + return new Promise(function(aResolve, aReject) { + ok(true, "getCellInfoList"); + mobileConnection.getCellInfoList({ + QueryInterface: XPCOMUtils.generateQI([Ci.nsICellInfoListCallback]), + notifyGetCellInfoList: function(aCount, aResults) { + aResolve(aResults); + }, + notifyGetCellInfoListFailed: function(aErrorMsg) { + aReject(aErrorMsg); + }, + }); + }); +} + +/** + * Wait for pending emulator transactions and call |finish()|. + */ +function cleanUp() { + // Use ok here so that we have at least one test run. + ok(true, ":: CLEANING UP ::"); + + waitFor(finish, function() { + return _pendingEmulatorShellCmdCount === 0; + }); +} + +/** + * Basic test routine helper. + * + * This helper does nothing but clean-ups. + * + * @param aTestCaseMain + * A function that takes no parameter. + */ +function startTestBase(aTestCaseMain) { + return Promise.resolve() + .then(aTestCaseMain) + .catch((aException) => { + ok(false, "promise rejects during test: " + aException); + }) + .then(cleanUp); +} diff --git a/dom/mobileconnection/tests/marionette/manifest.ini b/dom/mobileconnection/tests/marionette/manifest.ini index 5879c3ee05..7780d633f6 100644 --- a/dom/mobileconnection/tests/marionette/manifest.ini +++ b/dom/mobileconnection/tests/marionette/manifest.ini @@ -8,6 +8,7 @@ qemu = true [test_mobile_voice_location.js] [test_mobile_operator_names.js] [test_mobile_operator_names_plmnlist.js] +disabled = Bug 1234746 [test_mobile_operator_names_roaming.js] [test_mobile_preferred_network_type.js] [test_mobile_preferred_network_type_radio_off.js] @@ -15,6 +16,7 @@ qemu = true [test_mobile_data_location.js] [test_mobile_data_state.js] [test_mobile_roaming_preference.js] +[test_call_barring_basic_operations.js] [test_call_barring_get_error.js] [test_call_barring_set_error.js] [test_call_barring_change_password.js] @@ -25,7 +27,6 @@ qemu = true [test_mobile_connections_array_uninitialized.js] [test_mobile_signal_strength.js] [test_mobile_data_ipv6.js] -disabled = Bug 979137 [test_mobile_supported_network_types.js] [test_mobile_call_forwarding.js] [test_mobile_call_forwarding_set_error.js] @@ -35,3 +36,5 @@ disabled = Bug 979137 [test_mobile_clir.js] [test_mobile_clir_radio_off.js] [test_mobile_neighboring_cell_ids.js] +[test_mobile_cell_Info_list.js] +skip-if = android_version < '19' diff --git a/dom/mobileconnection/tests/marionette/test_call_barring_basic_operations.js b/dom/mobileconnection/tests/marionette/test_call_barring_basic_operations.js new file mode 100644 index 0000000000..07781fbb84 --- /dev/null +++ b/dom/mobileconnection/tests/marionette/test_call_barring_basic_operations.js @@ -0,0 +1,130 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_TIMEOUT = 60000; +MARIONETTE_HEAD_JS = "head.js"; + +const AO = MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING; +const OI = MozMobileConnection.CALL_BARRING_PROGRAM_OUTGOING_INTERNATIONAL; +const OX = MozMobileConnection.CALL_BARRING_PROGRAM_OUTGOING_INTERNATIONAL_EXCEPT_HOME; +var outgoingPrograms = [null, AO, OI, OX]; + +const AI = MozMobileConnection.CALL_BARRING_PROGRAM_ALL_INCOMING; +const IR = MozMobileConnection.CALL_BARRING_PROGRAM_INCOMING_ROAMING; +var incomingPrograms = [null, AI, IR]; + +const SERVICE_CLASS_VOICE = MozMobileConnection.ICC_SERVICE_CLASS_VOICE; + +function getProgram(aProgram, aEnabled) { + if (aProgram === null) { + return Promise.resolve(); + } + + return Promise.resolve() + .then(() => getCallBarringOption({ + program: aProgram, + serviceClass: SERVICE_CLASS_VOICE + })) + + .then(result => { + is(result.program, aProgram, "Program"); + is(result.enabled, aEnabled, "Enabled"); + is(result.serviceClass, SERVICE_CLASS_VOICE, "ServiceClass"); + }); +} + +function setProgram(aProgram, aEnabled) { + if (aProgram === null) { + return Promise.resolve(); + } + + return Promise.resolve() + .then(() => setCallBarringOption({ + program: aProgram, + serviceClass: SERVICE_CLASS_VOICE, + enabled: aEnabled, + password: "0000" // The dafault call barring password of the emulator + })); +} + +function setAndCheckProgram(aActiveProgram, aAllPrograms) { + let promise = Promise.resolve(); + + if (aActiveProgram !== null) { + promise = promise.then(() => setProgram(aActiveProgram, true)); + } else { + // Deactive all barring programs in |aAllPrograms|. + promise = aAllPrograms.reduce((previousPromise, program) => { + return previousPromise.then(() => setProgram(program, false)); + }, promise); + } + + // Make sure |aActiveProgram| is the only active program in |aAllPrograms|. + promise = aAllPrograms.reduce((previousPromise, program) => { + return previousPromise.then(() => getProgram(program, program === aActiveProgram)); + }, promise); + + return promise; +} + +function testSingleProgram(aOriginProgram, aTargetPrograms, aOriginPrograms) { + let promise = setAndCheckProgram(aOriginProgram, aOriginPrograms); + + return aTargetPrograms.reduce((previousPromise, targetProgram) => { + return previousPromise + .then(() => log(aOriginProgram + " <-> " + targetProgram)) + .then(() => setAndCheckProgram(targetProgram, aOriginPrograms)) + .then(() => setAndCheckProgram(aOriginProgram, aOriginPrograms)); + }, promise); +} + +function testAllPrograms(aPrograms) { + let targetPrograms = aPrograms.slice(); + + return aPrograms.reduce((previousPromise, program) => { + return previousPromise + .then(() => { + targetPrograms.shift(); + return testSingleProgram(program, targetPrograms, aPrograms); + }); + }, Promise.resolve()); +} + +function testUnsupportedPrograms() { + // Emulator now doesn't support these three programs. + let unsupportedPrograms = + [MozMobileConnection.CALL_BARRING_PROGRAM_ALL_SERVICE, + MozMobileConnection.CALL_BARRING_PROGRAM_OUTGOING_SERVICE, + MozMobileConnection.CALL_BARRING_PROGRAM_INCOMING_SERVICE]; + + return unsupportedPrograms.reduce((previousPromise, program) => { + return previousPromise + .then(() => log("Test " + program)) + .then(() => setProgram(program, false)) + .then(() => { + ok(false, "Should be rejected"); + }, error => { + is(error.name, "RequestNotSupported", + "Failed to setProgram: "+ error.name); + }); + }, Promise.resolve()); +} + +// Start tests +startTestCommon(function() { + return Promise.resolve() + .then(() => setAndCheckProgram(null, outgoingPrograms)) + .then(() => setAndCheckProgram(null, incomingPrograms)) + + .then(() => log("=== Test outgoing call barring programs ===")) + .then(() => testAllPrograms(outgoingPrograms)) + .then(() => log("=== Test incoming call barring programs ===")) + .then(() => testAllPrograms(incomingPrograms)) + + .then(() => log("=== Test unsupported call barring programs ===")) + .then(() => testUnsupportedPrograms()) + + .catch(aError => ok(false, "promise rejects during test: " + aError)) + .then(() => setAndCheckProgram(null, outgoingPrograms)) + .then(() => setAndCheckProgram(null, incomingPrograms)); +}); diff --git a/dom/mobileconnection/tests/marionette/test_call_barring_change_password.js b/dom/mobileconnection/tests/marionette/test_call_barring_change_password.js index 707be64a7d..9ca0a3d622 100644 --- a/dom/mobileconnection/tests/marionette/test_call_barring_change_password.js +++ b/dom/mobileconnection/tests/marionette/test_call_barring_change_password.js @@ -4,33 +4,11 @@ MARIONETTE_TIMEOUT = 60000; MARIONETTE_HEAD_JS = "head.js"; -const TEST_DATA = [ - // [, , ] +function testChangeCallBarringPassword(aExpectedError, aOptions) { + log("Test changing call barring password from " + + aOptions.pin + " to " + aOptions.newPin); - // Test passing an invalid pin or newPin. - [null, "0000", "InvalidPassword"], - ["0000", null, "InvalidPassword"], - [null, null, "InvalidPassword"], - - // Test passing mismatched newPin. - ["000", "0000", "InvalidPassword"], - ["00000", "1111", "InvalidPassword"], - ["abcd", "efgh", "InvalidPassword"], - - // TODO: Bug 906603 - B2G RIL: Support Change Call Barring Password on Emulator. - // Currently emulator doesn't support REQUEST_CHANGE_BARRING_PASSWORD, so we - // expect to get a 'RequestNotSupported' error here. - ["1234", "1234", "RequestNotSupported"] -]; - -function testChangeCallBarringPassword(aPin, aNewPin, aExpectedError) { - log("Test changing call barring password to " + aPin + "/" + aNewPin); - - let options = { - pin: aPin, - newPin: aNewPin - }; - return changeCallBarringPassword(options) + return changeCallBarringPassword(aOptions) .then(function resolve() { ok(!aExpectedError, "changeCallBarringPassword success"); }, function reject(aError) { @@ -40,11 +18,67 @@ function testChangeCallBarringPassword(aPin, aNewPin, aExpectedError) { // Start tests startTestCommon(function() { - let promise = Promise.resolve(); - for (let i = 0; i < TEST_DATA.length; i++) { - let data = TEST_DATA[i]; - promise = - promise.then(() => testChangeCallBarringPassword(data[0], data[1], data[2])); - } - return promise; + return Promise.resolve() + + // According to TS.22.004 clause 5.2, the password should consist of four + // digits in the range 0000 to 9999. + + // Test passing an invalid pin. + .then(() => testChangeCallBarringPassword("InvalidPassword", { + pin: null, + newPin: "0000" + })) + + .then(() => testChangeCallBarringPassword("InvalidPassword", { + pin: "000", + newPin: "0000" + })) + + .then(() => testChangeCallBarringPassword("InvalidPassword", { + pin: "00000", + newPin: "0000" + })) + + .then(() => testChangeCallBarringPassword("InvalidPassword", { + pin: "abcd", + newPin: "0000" + })) + + // Test passing an invalid newPin + .then(() => testChangeCallBarringPassword("InvalidPassword", { + pin: "0000", + newPin: null + })) + + .then(() => testChangeCallBarringPassword("InvalidPassword", { + pin: "0000", + newPin: "000" + })) + + .then(() => testChangeCallBarringPassword("InvalidPassword", { + pin: "0000", + newPin: "00000" + })) + + .then(() => testChangeCallBarringPassword("InvalidPassword", { + pin: "0000", + newPin: "abcd" + })) + + // Test passing an incorrect password, where the default password is "0000". + .then(() => testChangeCallBarringPassword("IncorrectPassword", { + pin: "1111", + newPin: "2222" + })) + + // Sucessful + .then(() => testChangeCallBarringPassword(null, { + pin: "0000", + newPin: "2222" + })) + + .then(() => testChangeCallBarringPassword(null, { + pin: "2222", + newPin: "0000" + })); }); diff --git a/dom/mobileconnection/tests/marionette/test_call_barring_get_error.js b/dom/mobileconnection/tests/marionette/test_call_barring_get_error.js index 5b6eb2c46b..a561484503 100644 --- a/dom/mobileconnection/tests/marionette/test_call_barring_get_error.js +++ b/dom/mobileconnection/tests/marionette/test_call_barring_get_error.js @@ -4,59 +4,12 @@ MARIONETTE_TIMEOUT = 60000; MARIONETTE_HEAD_JS = "head.js"; -const TEST_DATA = [ - // Test passing invalid program. - { - options: { - program: 5, /* Invalid program */ - serviceClass: 0 - }, - expectedError: "InvalidParameter" - }, { - options: { - program: null, - serviceClass: 0 - }, - expectedError: "InvalidParameter" - }, { - options: { - /* Undefined program */ - serviceClass: 0 - }, - expectedError: "InvalidParameter" - }, - // Test passing invalid serviceClass. - { - options: { - program: MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING, - serviceClass: null - }, - expectedError: "InvalidParameter" - }, { - options: { - program: MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING, - /* Undefined serviceClass */ - }, - expectedError: "InvalidParameter" - }, - // TODO: Bug 1027546 - [B2G][Emulator] Support call barring - // Currently emulator doesn't support call barring, so we expect to get a - // 'RequestNotSupported' error here. - { - options: { - program: MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING, - serviceClass: 0 - }, - expectedError: "RequestNotSupported" - } -]; - -function testGetCallBarringOption(aOptions, aExpectedError) { - log("Test getting call barring to " + JSON.stringify(aOptions)); +function testGetCallBarringOption(aExpectedError, aOptions) { + log("Test getCallBarringOption with " + JSON.stringify(aOptions)); return getCallBarringOption(aOptions) .then(function resolve(aResult) { - ok(false, "should not success"); + ok(false, "should be rejected"); }, function reject(aError) { is(aError.name, aExpectedError, "failed to getCallBarringOption"); }); @@ -64,11 +17,32 @@ function testGetCallBarringOption(aOptions, aExpectedError) { // Start tests startTestCommon(function() { - let promise = Promise.resolve(); - for (let i = 0; i < TEST_DATA.length; i++) { - let data = TEST_DATA[i]; - promise = promise.then(() => testGetCallBarringOption(data.options, - data.expectedError)); - } - return promise; + return Promise.resolve() + + // Test program + .then(() => testGetCallBarringOption("InvalidParameter", { + program: 8, /* Invalid program */ + serviceClass: 0 + })) + + .then(() => testGetCallBarringOption("InvalidParameter", { + program: null, /* Invalid program */ + serviceClass: 0 + })) + + .then(() => testGetCallBarringOption("InvalidParameter", { + /* Undefined program */ + serviceClass: 0 + })) + + // Test serviceClass + .then(() => testGetCallBarringOption("InvalidParameter", { + program: MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING, + serviceClass: null /* Invalid serviceClass */ + })) + + .then(() => testGetCallBarringOption("InvalidParameter", { + program: MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING, + /* Undefined serviceClass */ + })); }); diff --git a/dom/mobileconnection/tests/marionette/test_call_barring_set_error.js b/dom/mobileconnection/tests/marionette/test_call_barring_set_error.js index 33d7cd22c7..0dbd4e521d 100644 --- a/dom/mobileconnection/tests/marionette/test_call_barring_set_error.js +++ b/dom/mobileconnection/tests/marionette/test_call_barring_set_error.js @@ -4,107 +4,12 @@ MARIONETTE_TIMEOUT = 60000; MARIONETTE_HEAD_JS = "head.js"; -const TEST_DATA = [ - // Test passing invalid program. - { - options: { - "program": 5, /* Invalid program */ - "enabled": true, - "password": "0000", - "serviceClass": 0 - }, - expectedError: "InvalidParameter" - }, { - options: { - "program": null, - "enabled": true, - "password": "0000", - "serviceClass": 0 - }, - expectedError: "InvalidParameter" - }, { - options: { - /* Undefined program */ - "enabled": true, - "password": "0000", - "serviceClass": 0 - }, - expectedError: "InvalidParameter" - }, - // Test passing invalid enabled. - { - options: { - "program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING, - "enabled": null, - "password": "0000", - "serviceClass": 0 - }, - expectedError: "InvalidParameter" - }, { - options: { - "program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING, - /* Undefined enabled */ - "password": "0000", - "serviceClass": 0 - }, - expectedError: "InvalidParameter" - }, - // Test passing invalid password. - { - options: { - "program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING, - "enabled": true, - "password": null, - "serviceClass": 0 - }, - expectedError: "InvalidParameter" - }, { - options: { - "program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING, - "enabled": true, - /* Undefined password */ - "serviceClass": 0 - }, - expectedError: "InvalidParameter" - }, - // Test passing invalid serviceClass. - { - options: { - "program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING, - "enabled": true, - "password": "0000", - "serviceClass": null - }, - expectedError: "InvalidParameter" - }, { - options: { - "program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING, - "enabled": true, - "password": "0000", - /* Undefined serviceClass */ - }, - expectedError: "InvalidParameter" - }, - // TODO: Bug 1027546 - [B2G][Emulator] Support call barring - // Currently emulator doesn't support call barring, so we expect to get a - // 'RequestNotSupported' error here. - { - options: { - "program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING, - "enabled": true, - "password": "0000", - "serviceClass": 0 - }, - expectedError: "RequestNotSupported" - } -]; - -function testSetCallBarringOption(aOptions, aExpectedError) { - log("Test setting call barring to " + JSON.stringify(aOptions)); +function testSetCallBarringOption(aExpectedError, aOptions) { + log("Test setCallBarringOption with " + JSON.stringify(aOptions)); return setCallBarringOption(aOptions) .then(function resolve() { - ok(false, "changeCallBarringPassword success"); + ok(false, "should be rejected"); }, function reject(aError) { is(aError.name, aExpectedError, "failed to changeCallBarringPassword"); }); @@ -112,11 +17,79 @@ function testSetCallBarringOption(aOptions, aExpectedError) { // Start tests startTestCommon(function() { - let promise = Promise.resolve(); - for (let i = 0; i < TEST_DATA.length; i++) { - let data = TEST_DATA[i]; - promise = promise.then(() => testSetCallBarringOption(data.options, - data.expectedError)); - } - return promise; + return Promise.resolve() + + // Test program + .then(() => testSetCallBarringOption("InvalidParameter", { + "program": 8, /* Invalid program */ + "enabled": true, + "password": "0000", + "serviceClass": 0 + })) + + .then(() => testSetCallBarringOption("InvalidParameter", { + "program": null, + "enabled": true, + "password": "0000", + "serviceClass": 0 + })) + + .then(() => testSetCallBarringOption("InvalidParameter", { + /* Undefined program */ + "enabled": true, + "password": "0000", + "serviceClass": 0 + })) + + // Test enabled + .then(() => testSetCallBarringOption("InvalidParameter", { + "program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING, + "enabled": null, /* Invalid enabled */ + "password": "0000", + "serviceClass": 0 + })) + + .then(() => testSetCallBarringOption("InvalidParameter", { + "program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING, + /* Undefined enabled */ + "password": "0000", + "serviceClass": 0 + })) + + // Test password + .then(() => testSetCallBarringOption("InvalidParameter", { + "program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING, + "enabled": true, + "password": null, /* Invalid password */ + "serviceClass": 0 + })) + + .then(() => testSetCallBarringOption("InvalidParameter", { + "program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING, + "enabled": true, + /* Undefined password */ + "serviceClass": 0 + })) + + .then(() => testSetCallBarringOption("IncorrectPassword", { + "program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING, + "enabled": true, + "password": "1111", /* Incorrect password */ + "serviceClass": 0 + })) + + // Test serviceClass + .then(() => testSetCallBarringOption("InvalidParameter", { + "program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING, + "enabled": true, + "password": "0000", + "serviceClass": null /* Invalid serviceClass */ + })) + + .then(() => testSetCallBarringOption("InvalidParameter", { + "program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING, + "enabled": true, + "password": "0000", + /* Undefined serviceClass */ + })) }); diff --git a/dom/mobileconnection/tests/marionette/test_dsds_mobile_data_connection.js b/dom/mobileconnection/tests/marionette/test_dsds_mobile_data_connection.js index 4a9bafccc0..7fa08266d2 100644 --- a/dom/mobileconnection/tests/marionette/test_dsds_mobile_data_connection.js +++ b/dom/mobileconnection/tests/marionette/test_dsds_mobile_data_connection.js @@ -6,9 +6,9 @@ MARIONETTE_HEAD_JS = "head.js"; const SETTINGS_KEY_DATA_DEFAULT_ID = "ril.data.defaultServiceId"; -let connections; -let numOfRadioInterfaces; -let currentDataDefaultId = 0; +var connections; +var numOfRadioInterfaces; +var currentDataDefaultId = 0; function muxModem(id) { return runEmulatorCmdSafe("mux modem " + id); diff --git a/dom/mobileconnection/tests/marionette/test_mobile_cell_Info_list.js b/dom/mobileconnection/tests/marionette/test_mobile_cell_Info_list.js new file mode 100644 index 0000000000..6c4dc9246a --- /dev/null +++ b/dom/mobileconnection/tests/marionette/test_mobile_cell_Info_list.js @@ -0,0 +1,25 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_TIMEOUT = 60000; +MARIONETTE_HEAD_JS = "head_chrome.js"; + +// Start test. +startTestBase(function() { + return getCellInfoList() + .then((aResults) => { + // Cell Info are hard-coded in hardware/ril/reference-ril/reference-ril.c. + is(aResults.length, 1, "Check number of cell Info"); + + let cell = aResults[0]; + is(cell.type, Ci.nsICellInfo.CELL_INFO_TYPE_GSM, "Check cell.type"); + is(cell.registered, true, "Check cell.registered"); + + ok(cell instanceof Ci.nsIGsmCellInfo, + "cell.constructor is " + cell.constructor); + + // The data hard-coded in hardware/ril/reference-ril/reference-ril.c + // isn't correct (missing timeStampType), so we skip to check other + // attributes first until we fix it. + }); +}); \ No newline at end of file diff --git a/dom/mobileconnection/tests/marionette/test_mobile_data_ipv6.js b/dom/mobileconnection/tests/marionette/test_mobile_data_ipv6.js index af5238bf59..7d49979f24 100644 --- a/dom/mobileconnection/tests/marionette/test_mobile_data_ipv6.js +++ b/dom/mobileconnection/tests/marionette/test_mobile_data_ipv6.js @@ -11,7 +11,7 @@ MARIONETTE_HEAD_JS = "head.js"; * * 1) set "ril.data.apnSettings" to a given settings object, * 2) enable data connection and wait for a "datachange" event, - * 3) check the IP address type of the active network interface, + * 3) check the IP address type of the active network info, * 4) disable data connection. * * Fulfill params: (none) @@ -30,12 +30,12 @@ function doTest(aApnSettings, aHaveV4Address, aHaveV6Address) { .then(() => setDataEnabledAndWait(true)) .then(function() { let nm = getNetworkManager(); - let active = nm.active; - ok(active, "Active network interface"); - log(" Interface: " + active.name); + let networkInfo = nm.activeNetworkInfo; + ok(networkInfo, "Active network info"); + log(" Interface: " + networkInfo.name); let ips = {}, prefixLengths = {}; - let num = active.getAddresses(ips, prefixLengths); + let num = networkInfo.getAddresses(ips, prefixLengths); log(" Num addresses: " + num); log(" Addresses: " + JSON.stringify(ips.value)); log(" PrefixLengths: " + JSON.stringify(prefixLengths.value)); @@ -43,12 +43,12 @@ function doTest(aApnSettings, aHaveV4Address, aHaveV6Address) { if (aHaveV4Address) { ok(ips.value.reduce(function(aFound, aAddress) { return aFound || aAddress.indexOf(":") < 0; - }), "IPv4 address"); + }, false), "IPv4 address"); } if (aHaveV6Address) { ok(ips.value.reduce(function(aFound, aAddress) { return aFound || aAddress.indexOf(":") > 0; - }), "IPv6 address"); + }, false), "IPv6 address"); } }) .then(() => setDataEnabledAndWait(false)); @@ -106,14 +106,16 @@ startTestCommon(function() { .then(() => doTestHome(aApnSettings, "NoSuchProtocol")) .then(() => doTestHome(aApnSettings, "IP")) - .then(() => doTestHome(aApnSettings, "IPV4V6")) + // TODO: Bug 979137 - B2G Emulator: Support the IPV4V6 + //.then(() => doTestHome(aApnSettings, "IPV4V6")) .then(() => doTestHome(aApnSettings, "IPV6")) .then(() => setEmulatorRoamingAndWait(true)) .then(() => doTestRoaming(aApnSettings, "NoSuchProtocol")) .then(() => doTestRoaming(aApnSettings, "IP")) - .then(() => doTestRoaming(aApnSettings, "IPV4V6")) + // TODO: Bug 979137 - B2G Emulator: Support the IPV4V6 + //.then(() => doTestRoaming(aApnSettings, "IPV4V6")) .then(() => doTestRoaming(aApnSettings, "IPV6")) .then(() => setEmulatorRoamingAndWait(false)); diff --git a/dom/mobileconnection/tests/marionette/test_mobile_neighboring_cell_ids.js b/dom/mobileconnection/tests/marionette/test_mobile_neighboring_cell_ids.js index 7904ec68d7..ec4fbb3e44 100644 --- a/dom/mobileconnection/tests/marionette/test_mobile_neighboring_cell_ids.js +++ b/dom/mobileconnection/tests/marionette/test_mobile_neighboring_cell_ids.js @@ -2,37 +2,17 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ MARIONETTE_TIMEOUT = 30000; -// This test must run in chrome context. -MARIONETTE_CONTEXT = "chrome"; +MARIONETTE_HEAD_JS = "head_chrome.js"; -let Promise = Cu.import("resource://gre/modules/Promise.jsm").Promise; - -let service = Cc["@mozilla.org/mobileconnection/mobileconnectionservice;1"] - .getService(Ci.nsIMobileConnectionService); -ok(service, "service.constructor is " + service.constructor); - -let mobileConnection = service.getItemByServiceId(0); -ok(mobileConnection, "mobileConnection.constructor is " + mobileConnection.constrctor); - -function testGetNeighboringCellIds() { - log("Test getting mobile neighboring cell ids"); - let deferred = Promise.defer(); - - mobileConnection.getNeighboringCellIds({ - notifyGetNeighboringCellIds: function(aResult) { - deferred.resolve(aResult); - }, - notifyGetNeighboringCellIdsFailed: function(aError) { - deferred.reject(aError); - } - }); - return deferred.promise; -} - -// Start tests -testGetNeighboringCellIds() - .then(function resolve(aResult) { - ok(false, "getNeighboringCellIds should not success"); - }, function reject(aError) { - is(aError, "RequestNotSupported", "failed to getNeighboringCellIds"); - }).then(finish); \ No newline at end of file +// Start test. +startTestBase(function() { + // Emulator doesn't support REQUEST_GET_NEIGHBORING_CELL_IDS, so we expect to + // get an 'RequestNotSupported' error here. + return getNeighboringCellIds() + .then(() => { + ok(false, "should not success"); + }, (aErrorMsg) => { + is(aErrorMsg, "RequestNotSupported", + "Failed to getNeighboringCellIds: " + aErrorMsg); + }); +}); diff --git a/dom/mobileconnection/tests/marionette/test_mobile_voice_location.js b/dom/mobileconnection/tests/marionette/test_mobile_voice_location.js index 9501bf1378..e82010e237 100644 --- a/dom/mobileconnection/tests/marionette/test_mobile_voice_location.js +++ b/dom/mobileconnection/tests/marionette/test_mobile_voice_location.js @@ -10,6 +10,12 @@ function verifyVoiceCellLocationInfo(aLac, aCid) { is(cell.gsmLocationAreaCode, aLac, "check voice.cell.gsmLocationAreaCode"); is(cell.gsmCellId, aCid, "check voice.cell.gsmCellId"); + + // TODO: Since gecko doesn't reset these values below to their invalid values, + // the tests below will fail after we once change to CDMA mode. Please refer + // to Bug 1190274 for more information. + + /* is(cell.cdmaBaseStationId, -1, "check voice.cell.cdmaBaseStationId"); is(cell.cdmaBaseStationLatitude, -2147483648, "check voice.cell.cdmaBaseStationLatitude"); @@ -17,6 +23,7 @@ function verifyVoiceCellLocationInfo(aLac, aCid) { "check voice.cell.cdmaBaseStationLongitude"); is(cell.cdmaSystemId, -1, "check voice.cell.cdmaSystemId"); is(cell.cdmaNetworkId, -1, "check voice.cell.cdmaNetworkId"); + */ } /* Test Voice Cell Location Info Change */ diff --git a/dom/mobileconnection/tests/marionette/test_mobile_voice_state.js b/dom/mobileconnection/tests/marionette/test_mobile_voice_state.js index b3aa3103d6..86438e8153 100644 --- a/dom/mobileconnection/tests/marionette/test_mobile_voice_state.js +++ b/dom/mobileconnection/tests/marionette/test_mobile_voice_state.js @@ -4,25 +4,6 @@ MARIONETTE_TIMEOUT = 60000; MARIONETTE_HEAD_JS = "head.js"; -const INITIAL_STATES = { - state: "registered", - connected: true, - emergencyCallsOnly: false, - roaming: false, - signalStrength: -99, - relSignalStrength: 44, - - cell: { - gsmLocationAreaCode: 0, - gsmCellId: 0, - cdmaBaseStationId: -1, - cdmaBaseStationLatitude: -2147483648, - cdmaBaseStationLongitude: -2147483648, - cdmaSystemId: -1, - cdmaNetworkId: -1, - } -}; - const TEST_DATA = [{ // Test state becomes to "unregistered" state: "unregistered", @@ -126,8 +107,6 @@ function testVoiceStateUpdate(aNewState, aExpected) { startTestCommon(function() { log("Test initial voice connection info"); - verifyVoiceInfo(INITIAL_STATES); - let promise = Promise.resolve(); for (let i = 0; i < TEST_DATA.length; i++) { let entry = TEST_DATA[i]; diff --git a/extensions/spellcheck/hunspell/glue/moz.build b/extensions/spellcheck/hunspell/glue/moz.build index d2c0cb7f6d..ea26420aaf 100644 --- a/extensions/spellcheck/hunspell/glue/moz.build +++ b/extensions/spellcheck/hunspell/glue/moz.build @@ -11,12 +11,14 @@ UNIFIED_SOURCES += [ 'RemoteSpellCheckEngineParent.cpp', ] -CXXFLAGS += CONFIG['MOZ_HUNSPELL_CFLAGS'] - FINAL_LIBRARY = 'xul' +if CONFIG['MOZ_SYSTEM_HUNSPELL']: + CXXFLAGS += CONFIG['MOZ_HUNSPELL_CFLAGS'] +else: + LOCAL_INCLUDES += ['../src'] + LOCAL_INCLUDES += [ - '../src', '/dom/base', '/editor/libeditor', '/extensions/spellcheck/src', diff --git a/extensions/spellcheck/src/moz.build b/extensions/spellcheck/src/moz.build index 916382ee77..951f0d14fd 100644 --- a/extensions/spellcheck/src/moz.build +++ b/extensions/spellcheck/src/moz.build @@ -17,9 +17,13 @@ SOURCES += [ FINAL_LIBRARY = 'xul' +if CONFIG['MOZ_SYSTEM_HUNSPELL']: + CXXFLAGS += CONFIG['MOZ_HUNSPELL_CFLAGS'] +else: + LOCAL_INCLUDES += ['../hunspell/src'] + LOCAL_INCLUDES += [ '../hunspell/glue', - '../hunspell/src', '/dom/base', '/editor/libeditor', ] diff --git a/gfx/2d/DrawTargetCairo.cpp b/gfx/2d/DrawTargetCairo.cpp index 55096c0084..3e6a1ab2ec 100644 --- a/gfx/2d/DrawTargetCairo.cpp +++ b/gfx/2d/DrawTargetCairo.cpp @@ -1178,7 +1178,7 @@ DrawTargetCairo::ClearRect(const Rect& aRect) AutoPrepareForDrawing prep(this, mContext); - if (!mContext || aRect.Width() <= 0 || aRect.Height() <= 0 || + if (!mContext || aRect.Width() < 0 || aRect.Height() < 0 || !IsFinite(aRect.X()) || !IsFinite(aRect.Width()) || !IsFinite(aRect.Y()) || !IsFinite(aRect.Height())) { gfxCriticalNote << "ClearRect with invalid argument " << gfx::hexa(mContext) << " with " << aRect.Width() << "x" << aRect.Height() << " [" << aRect.X() << ", " << aRect.Y() << "]"; diff --git a/gfx/2d/DrawTargetD2D1.cpp b/gfx/2d/DrawTargetD2D1.cpp index fb79d72a69..8a1a092611 100644 --- a/gfx/2d/DrawTargetD2D1.cpp +++ b/gfx/2d/DrawTargetD2D1.cpp @@ -363,7 +363,7 @@ DrawTargetD2D1::CopySurface(SourceSurface *aSurface, mDC->SetTransform(D2D1::IdentityMatrix()); mTransformDirty = true; - Matrix mat; + Matrix mat = Matrix::Translation(aDestination.x - aSourceRect.x, aDestination.y - aSourceRect.y); RefPtr image = GetImageForSurface(aSurface, mat, ExtendMode::CLAMP); if (!image) { @@ -371,11 +371,17 @@ DrawTargetD2D1::CopySurface(SourceSurface *aSurface, return; } - if (!mat.IsIdentity()) { - gfxDebug() << *this << ": At this point complex partial uploads are not supported for CopySurface."; + if (mat.HasNonIntegerTranslation()) { + gfxDebug() << *this << ": At this point scaled partial uploads are not supported for CopySurface."; return; } + IntRect sourceRect = aSourceRect; + sourceRect.x += (aDestination.x - aSourceRect.x) - mat._31; + sourceRect.width -= (aDestination.x - aSourceRect.x) - mat._31; + sourceRect.y += (aDestination.y - aSourceRect.y) - mat._32; + sourceRect.height -= (aDestination.y - aSourceRect.y) - mat._32; + RefPtr bitmap; image->QueryInterface((ID2D1Bitmap**)getter_AddRefs(bitmap)); @@ -391,7 +397,7 @@ DrawTargetD2D1::CopySurface(SourceSurface *aSurface, return; } - Rect srcRect(Float(aSourceRect.x), Float(aSourceRect.y), + Rect srcRect(Float(sourceRect.x), Float(sourceRect.y), Float(aSourceRect.width), Float(aSourceRect.height)); Rect dstRect(Float(aDestination.x), Float(aDestination.y), diff --git a/gfx/2d/DrawTargetD2D1.h b/gfx/2d/DrawTargetD2D1.h index 7e69c62039..0d1256eb3d 100644 --- a/gfx/2d/DrawTargetD2D1.h +++ b/gfx/2d/DrawTargetD2D1.h @@ -131,6 +131,9 @@ public: bool Init(ID3D11Texture2D* aTexture, SurfaceFormat aFormat); uint32_t GetByteSize() const; + // This function will get an image for a surface, it may adjust the source + // transform for any transformation of the resulting image relative to the + // oritingal SourceSurface. already_AddRefed GetImageForSurface(SourceSurface *aSurface, Matrix &aSourceTransform, ExtendMode aExtendMode, const IntRect* aSourceRect = nullptr); diff --git a/gfx/2d/DrawTargetRecording.cpp b/gfx/2d/DrawTargetRecording.cpp index 0411f3d5c2..6d72f82d4a 100644 --- a/gfx/2d/DrawTargetRecording.cpp +++ b/gfx/2d/DrawTargetRecording.cpp @@ -389,10 +389,6 @@ DrawTargetRecording::FillGlyphs(ScaledFont *aFont, { EnsurePatternDependenciesStored(aPattern); - if (aFont->GetType() != FontType::DWRITE && aFont->GetType() != FontType::GDI) { - gfxDevCrash(LogReason::GetFontFileDataFailed) << "Unexpected ScaledFont type " << (int)aFont->GetType(); - } - if (!aFont->GetUserData(reinterpret_cast(mRecorder.get()))) { // TODO support font in b2g recordings #ifndef MOZ_WIDGET_GONK diff --git a/gfx/2d/DrawTargetSkia.cpp b/gfx/2d/DrawTargetSkia.cpp index 39290c4beb..8f2ba503d0 100644 --- a/gfx/2d/DrawTargetSkia.cpp +++ b/gfx/2d/DrawTargetSkia.cpp @@ -416,7 +416,8 @@ DrawTargetSkia::DrawSurfaceWithShadow(SourceSurface *aSurface, Float aSigma, CompositionOp aOperator) { - if (!(aSurface->GetType() == SurfaceType::SKIA || aSurface->GetType() == SurfaceType::DATA)) { + if (!(aSurface->GetType() == SurfaceType::SKIA || aSurface->GetType() == SurfaceType::DATA) || + aSurface->GetSize().IsEmpty()) { return; } @@ -636,10 +637,19 @@ DrawTargetSkia::FillGlyphs(ScaledFont *aFont, // SkFontHost_cairo does not support subpixel text, so only enable it for other font hosts. paint.mPaint.setSubpixelText(true); - if (aFont->GetType() == FontType::MAC) { + if (aFont->GetType() == FontType::MAC && + (shouldLCDRenderText || aOptions.mAntialiasMode == AntialiasMode::GRAY)) { + // SkFontHost_mac only enables CG Font Smoothing if hinting is disabled. + // CG Font Smoothing normally enables subpixel AA in CG, but Skia supports + // font smoothing with grayscale AA. // SkFontHost_mac only supports subpixel antialiasing when hinting is turned off. - // For grayscale AA, we want to disable font smoothing as the only time we should - // use grayscale AA is with explicit -moz-osx-font-smoothing + // We can get grayscale AA if we have -moz-osx-font-smoothing: grayscale + // explicitly enabled or for transparent surfaces. + // If we have AA grayscale explicit through the draw options, + // then we want to disable font smoothing. + // If we have a transparent surface, shouldLCDRenderText will be false. But unless + // grayscale font smoothing is explicitly requested, we still want Skia to use + // CG Font smoothing. paint.mPaint.setHinting(SkPaint::kNo_Hinting); } else { paint.mPaint.setHinting(SkPaint::kNormal_Hinting); diff --git a/gfx/2d/Path.cpp b/gfx/2d/Path.cpp index ed88ddd16f..b4ed643723 100644 --- a/gfx/2d/Path.cpp +++ b/gfx/2d/Path.cpp @@ -10,30 +10,42 @@ namespace mozilla { namespace gfx { -static float CubicRoot(float aValue) { +static double CubicRoot(double aValue) { if (aValue < 0.0) { return -CubicRoot(-aValue); } else { - return powf(aValue, 1.0f / 3.0f); + return pow(aValue, 1.0 / 3.0); } } +struct PointD : public BasePoint { + typedef BasePoint Super; + + PointD() : Super() {} + PointD(double aX, double aY) : Super(aX, aY) {} + MOZ_IMPLICIT PointD(const Point& aPoint) : Super(aPoint.x, aPoint.y) {} + + Point ToPoint() const { + return Point(static_cast(x), static_cast(y)); + } +}; + struct BezierControlPoints { BezierControlPoints() {} - BezierControlPoints(const Point &aCP1, const Point &aCP2, - const Point &aCP3, const Point &aCP4) + BezierControlPoints(const PointD &aCP1, const PointD &aCP2, + const PointD &aCP3, const PointD &aCP4) : mCP1(aCP1), mCP2(aCP2), mCP3(aCP3), mCP4(aCP4) { } - Point mCP1, mCP2, mCP3, mCP4; + PointD mCP1, mCP2, mCP3, mCP4; }; void FlattenBezier(const BezierControlPoints &aPoints, - PathSink *aSink, Float aTolerance); + PathSink *aSink, double aTolerance); Path::Path() @@ -205,18 +217,18 @@ static void SplitBezier(const BezierControlPoints &aControlPoints, BezierControlPoints *aFirstSegmentControlPoints, BezierControlPoints *aSecondSegmentControlPoints, - Float t) + double t) { MOZ_ASSERT(aSecondSegmentControlPoints); *aSecondSegmentControlPoints = aControlPoints; - Point cp1a = aControlPoints.mCP1 + (aControlPoints.mCP2 - aControlPoints.mCP1) * t; - Point cp2a = aControlPoints.mCP2 + (aControlPoints.mCP3 - aControlPoints.mCP2) * t; - Point cp1aa = cp1a + (cp2a - cp1a) * t; - Point cp3a = aControlPoints.mCP3 + (aControlPoints.mCP4 - aControlPoints.mCP3) * t; - Point cp2aa = cp2a + (cp3a - cp2a) * t; - Point cp1aaa = cp1aa + (cp2aa - cp1aa) * t; + PointD cp1a = aControlPoints.mCP1 + (aControlPoints.mCP2 - aControlPoints.mCP1) * t; + PointD cp2a = aControlPoints.mCP2 + (aControlPoints.mCP3 - aControlPoints.mCP2) * t; + PointD cp1aa = cp1a + (cp2a - cp1a) * t; + PointD cp3a = aControlPoints.mCP3 + (aControlPoints.mCP4 - aControlPoints.mCP3) * t; + PointD cp2aa = cp2a + (cp3a - cp2a) * t; + PointD cp1aaa = cp1aa + (cp2aa - cp1aa) * t; aSecondSegmentControlPoints->mCP4 = aControlPoints.mCP4; if(aFirstSegmentControlPoints) { @@ -233,7 +245,7 @@ SplitBezier(const BezierControlPoints &aControlPoints, static void FlattenBezierCurveSegment(const BezierControlPoints &aControlPoints, PathSink *aSink, - Float aTolerance) + double aTolerance) { /* The algorithm implemented here is based on: * http://cis.usouthal.edu/~hain/general/Publications/Bezier/Bezier%20Offset%20Curves.pdf @@ -245,47 +257,46 @@ FlattenBezierCurveSegment(const BezierControlPoints &aControlPoints, */ BezierControlPoints currentCP = aControlPoints; - Float t = 0; - while (t < 1.0f) { - Point cp21 = currentCP.mCP2 - currentCP.mCP3; - Point cp31 = currentCP.mCP3 - currentCP.mCP1; + double t = 0; + while (t < 1.0) { + PointD cp21 = currentCP.mCP2 - currentCP.mCP3; + PointD cp31 = currentCP.mCP3 - currentCP.mCP1; /* To remove divisions and check for divide-by-zero, this is optimized from: * Float s3 = (cp31.x * cp21.y - cp31.y * cp21.x) / hypotf(cp21.x, cp21.y); * t = 2 * Float(sqrt(aTolerance / (3. * std::abs(s3)))); */ - Float cp21x31 = cp31.x * cp21.y - cp31.y * cp21.x; - Float h = hypotf(cp21.x, cp21.y); + double cp21x31 = cp31.x * cp21.y - cp31.y * cp21.x; + double h = hypot(cp21.x, cp21.y); if (cp21x31 * h == 0) { break; } - Float s3inv = h / cp21x31; - t = 2 * Float(sqrt(aTolerance * std::abs(s3inv) / 3.)); - if (t >= 1.0f) { + double s3inv = h / cp21x31; + t = 2 * sqrt(aTolerance * std::abs(s3inv) / 3.); + if (t >= 1.0) { break; } - Point prevCP2, prevCP3, nextCP1, nextCP2, nextCP3; SplitBezier(currentCP, nullptr, ¤tCP, t); - aSink->LineTo(currentCP.mCP1); + aSink->LineTo(currentCP.mCP1.ToPoint()); } - aSink->LineTo(currentCP.mCP4); + aSink->LineTo(currentCP.mCP4.ToPoint()); } static inline void FindInflectionApproximationRange(BezierControlPoints aControlPoints, - Float *aMin, Float *aMax, Float aT, - Float aTolerance) + double *aMin, double *aMax, double aT, + double aTolerance) { SplitBezier(aControlPoints, nullptr, &aControlPoints, aT); - Point cp21 = aControlPoints.mCP2 - aControlPoints.mCP1; - Point cp41 = aControlPoints.mCP4 - aControlPoints.mCP1; + PointD cp21 = aControlPoints.mCP2 - aControlPoints.mCP1; + PointD cp41 = aControlPoints.mCP4 - aControlPoints.mCP1; - if (cp21.x == 0.f && cp21.y == 0.f) { + if (cp21.x == 0. && cp21.y == 0.) { // In this case s3 becomes lim[n->0] (cp41.x * n) / n - (cp41.y * n) / n = cp41.x - cp41.y. // Use the absolute value so that Min and Max will correspond with the @@ -295,18 +306,18 @@ FindInflectionApproximationRange(BezierControlPoints aControlPoints, return; } - Float s3 = (cp41.x * cp21.y - cp41.y * cp21.x) / hypotf(cp21.x, cp21.y); + double s3 = (cp41.x * cp21.y - cp41.y * cp21.x) / hypot(cp21.x, cp21.y); if (s3 == 0) { // This means within the precision we have it can be approximated // infinitely by a linear segment. Deal with this by specifying the // approximation range as extending beyond the entire curve. - *aMin = -1.0f; - *aMax = 2.0f; + *aMin = -1.0; + *aMax = 2.0; return; } - Float tf = CubicRoot(std::abs(aTolerance / s3)); + double tf = CubicRoot(std::abs(aTolerance / s3)); *aMin = aT - tf * (1 - aT); *aMax = aT + tf * (1 - aT); @@ -365,18 +376,18 @@ FindInflectionApproximationRange(BezierControlPoints aControlPoints, */ static inline void FindInflectionPoints(const BezierControlPoints &aControlPoints, - Float *aT1, Float *aT2, uint32_t *aCount) + double *aT1, double *aT2, uint32_t *aCount) { // Find inflection points. // See www.faculty.idc.ac.il/arik/quality/appendixa.html for an explanation // of this approach. - Point A = aControlPoints.mCP2 - aControlPoints.mCP1; - Point B = aControlPoints.mCP3 - (aControlPoints.mCP2 * 2) + aControlPoints.mCP1; - Point C = aControlPoints.mCP4 - (aControlPoints.mCP3 * 3) + (aControlPoints.mCP2 * 3) - aControlPoints.mCP1; + PointD A = aControlPoints.mCP2 - aControlPoints.mCP1; + PointD B = aControlPoints.mCP3 - (aControlPoints.mCP2 * 2) + aControlPoints.mCP1; + PointD C = aControlPoints.mCP4 - (aControlPoints.mCP3 * 3) + (aControlPoints.mCP2 * 3) - aControlPoints.mCP1; - Float a = Float(B.x) * C.y - Float(B.y) * C.x; - Float b = Float(A.x) * C.y - Float(A.y) * C.x; - Float c = Float(A.x) * B.y - Float(A.y) * B.x; + double a = B.x * C.y - B.y * C.x; + double b = A.x * C.y - A.y * C.x; + double c = A.x * B.y - A.y * B.x; if (a == 0) { // Not a quadratic equation. @@ -400,7 +411,7 @@ FindInflectionPoints(const BezierControlPoints &aControlPoints, *aCount = 1; return; } else { - Float discriminant = b * b - 4 * a * c; + double discriminant = b * b - 4 * a * c; if (discriminant < 0) { // No inflection points. @@ -415,13 +426,13 @@ FindInflectionPoints(const BezierControlPoints &aControlPoints, * t1 = q / a * t2 = c / q */ - Float q = sqrtf(discriminant); + double q = sqrt(discriminant); if (b < 0) { q = b - q; } else { q = b + q; } - q *= Float(-1./2); + q *= -1./2; *aT1 = q / a; *aT2 = c / q; @@ -437,10 +448,10 @@ FindInflectionPoints(const BezierControlPoints &aControlPoints, void FlattenBezier(const BezierControlPoints &aControlPoints, - PathSink *aSink, Float aTolerance) + PathSink *aSink, double aTolerance) { - Float t1; - Float t2; + double t1; + double t2; uint32_t count; FindInflectionPoints(aControlPoints, &t1, &t2, &count); @@ -451,7 +462,7 @@ FlattenBezier(const BezierControlPoints &aControlPoints, return; } - Float t1min = t1, t1max = t1, t2min = t2, t2max = t2; + double t1min = t1, t1max = t1, t2min = t2, t2max = t2; BezierControlPoints remainingCP = aControlPoints; @@ -470,7 +481,7 @@ FlattenBezier(const BezierControlPoints &aControlPoints, // segments. if (count == 1 && t1min <= 0 && t1max >= 1.0) { // The whole range can be approximated by a line segment. - aSink->LineTo(aControlPoints.mCP4); + aSink->LineTo(aControlPoints.mCP4.ToPoint()); return; } @@ -487,7 +498,7 @@ FlattenBezier(const BezierControlPoints &aControlPoints, // subsequently flatten up until the end or the next inflection point. SplitBezier(aControlPoints, nullptr, &nextCPs, t1max); - aSink->LineTo(nextCPs.mCP1); + aSink->LineTo(nextCPs.mCP1.ToPoint()); if (count == 1 || (count > 1 && t2min >= 1.0)) { // No more inflection points to deal with, flatten the rest of the curve. @@ -497,7 +508,7 @@ FlattenBezier(const BezierControlPoints &aControlPoints, // We've already concluded t2min <= t1max, so if this is true the // approximation range for the first inflection point runs past the // end of the curve, draw a line to the end and we're done. - aSink->LineTo(aControlPoints.mCP4); + aSink->LineTo(aControlPoints.mCP4.ToPoint()); return; } @@ -506,12 +517,12 @@ FlattenBezier(const BezierControlPoints &aControlPoints, // In this case the t2 approximation range starts inside the t1 // approximation range. SplitBezier(aControlPoints, nullptr, &nextCPs, t1max); - aSink->LineTo(nextCPs.mCP1); + aSink->LineTo(nextCPs.mCP1.ToPoint()); } else if (t2min > 0 && t1max > 0) { SplitBezier(aControlPoints, nullptr, &nextCPs, t1max); // Find a control points describing the portion of the curve between t1max and t2min. - Float t2mina = (t2min - t1max) / (1 - t1max); + double t2mina = (t2min - t1max) / (1 - t1max); SplitBezier(nextCPs, &prevCPs, &nextCPs, t2mina); FlattenBezierCurveSegment(prevCPs, aSink, aTolerance); } else if (t2min > 0) { @@ -525,11 +536,11 @@ FlattenBezier(const BezierControlPoints &aControlPoints, // Draw a line to the start, this is the approximation between t2min and // t2max. - aSink->LineTo(nextCPs.mCP1); + aSink->LineTo(nextCPs.mCP1.ToPoint()); FlattenBezierCurveSegment(nextCPs, aSink, aTolerance); } else { // Our approximation range extends beyond the end of the curve. - aSink->LineTo(aControlPoints.mCP4); + aSink->LineTo(aControlPoints.mCP4.ToPoint()); return; } } diff --git a/gfx/2d/PathD2D.cpp b/gfx/2d/PathD2D.cpp index eaa0c2e08b..8d81eb7843 100644 --- a/gfx/2d/PathD2D.cpp +++ b/gfx/2d/PathD2D.cpp @@ -90,6 +90,31 @@ private: bool mNeedsFigureEnded; }; +class MOZ_STACK_CLASS AutoRestoreFP +{ +public: + AutoRestoreFP() + { + // save the current floating point control word + _controlfp_s(&savedFPSetting, 0, 0); + UINT unused; + // set the floating point control word to its default value + _controlfp_s(&unused, _CW_DEFAULT, MCW_PC); + } + ~AutoRestoreFP() + { + UINT unused; + // restore the saved floating point control word + _controlfp_s(&unused, savedFPSetting, MCW_PC); + } +private: + UINT savedFPSetting; +}; + +// Note that overrides of ID2D1SimplifiedGeometrySink methods in this class may +// get called from D2D with nonstandard floating point settings (see comments in +// bug 1134549) - use AutoRestoreFP to reset the floating point control word to +// what we expect class StreamingGeometrySink : public ID2D1SimplifiedGeometrySink { public: @@ -129,11 +154,18 @@ public: STDMETHOD_(void, SetFillMode)(D2D1_FILL_MODE aMode) { return; } STDMETHOD_(void, BeginFigure)(D2D1_POINT_2F aPoint, D2D1_FIGURE_BEGIN aBegin) - { mSink->MoveTo(ToPoint(aPoint)); } + { + AutoRestoreFP resetFloatingPoint; + mSink->MoveTo(ToPoint(aPoint)); + } STDMETHOD_(void, AddLines)(const D2D1_POINT_2F *aLines, UINT aCount) - { for (UINT i = 0; i < aCount; i++) { mSink->LineTo(ToPoint(aLines[i])); } } + { + AutoRestoreFP resetFloatingPoint; + for (UINT i = 0; i < aCount; i++) { mSink->LineTo(ToPoint(aLines[i])); } + } STDMETHOD_(void, AddBeziers)(const D2D1_BEZIER_SEGMENT *aSegments, UINT aCount) { + AutoRestoreFP resetFloatingPoint; for (UINT i = 0; i < aCount; i++) { mSink->BezierTo(ToPoint(aSegments[i].point1), ToPoint(aSegments[i].point2), ToPoint(aSegments[i].point3)); } @@ -145,6 +177,7 @@ public: STDMETHOD_(void, EndFigure)(D2D1_FIGURE_END aEnd) { + AutoRestoreFP resetFloatingPoint; if (aEnd == D2D1_FIGURE_END_CLOSED) { return mSink->Close(); } diff --git a/gfx/2d/RecordedEvent.h b/gfx/2d/RecordedEvent.h index cc81e58f04..e4eabe1f04 100644 --- a/gfx/2d/RecordedEvent.h +++ b/gfx/2d/RecordedEvent.h @@ -659,7 +659,7 @@ private: class RecordedPopLayer : public RecordedDrawingEvent { public: - RecordedPopLayer(DrawTarget* aDT) + MOZ_IMPLICIT RecordedPopLayer(DrawTarget* aDT) : RecordedDrawingEvent(POPLAYER, aDT) { } diff --git a/gfx/2d/SFNTData.cpp b/gfx/2d/SFNTData.cpp index 2e0fa6e0fe..fd29f6694d 100644 --- a/gfx/2d/SFNTData.cpp +++ b/gfx/2d/SFNTData.cpp @@ -116,7 +116,7 @@ SFNTData::Create(const uint8_t *aFontData, uint32_t aDataLength) // Check to see if this is a font collection. if (aDataLength < sizeof(TTCHeader)) { - gfxDevCrash(LogReason::GetFontFileDataFailed) << "Font data too short: length = " << aDataLength; + gfxWarning() << "Font data too short."; return nullptr; } @@ -124,7 +124,7 @@ SFNTData::Create(const uint8_t *aFontData, uint32_t aDataLength) if (ttcHeader->ttcTag == TRUETYPE_TAG('t', 't', 'c', 'f')) { uint32_t numFonts = ttcHeader->numFonts; if (aDataLength < sizeof(TTCHeader) + (numFonts * sizeof(BigEndianUint32))) { - gfxDevCrash(LogReason::GetFontFileDataFailed) << "Font data too short to contain full TTC Header: numFonts = " << numFonts << "; length = " << aDataLength; + gfxWarning() << "Font data too short to contain full TTC Header."; return nullptr; } @@ -134,7 +134,6 @@ SFNTData::Create(const uint8_t *aFontData, uint32_t aDataLength) const BigEndianUint32* endOfOffsets = offset + numFonts; while (offset != endOfOffsets) { if (!sfntData->AddFont(aFontData, aDataLength, *offset)) { - gfxDevCrash(LogReason::GetFontFileDataFailed) << "Failed to add font data from TTC"; return nullptr; } ++offset; @@ -145,7 +144,6 @@ SFNTData::Create(const uint8_t *aFontData, uint32_t aDataLength) UniquePtr sfntData(new SFNTData); if (!sfntData->AddFont(aFontData, aDataLength, 0)) { - gfxDevCrash(LogReason::GetFontFileDataFailed) << "Failed to add single font data"; return nullptr; } @@ -206,11 +204,20 @@ SFNTData::GetU16FullNames(Vector& aU16FullNames) bool SFNTData::GetIndexForU16Name(const mozilla::u16string& aU16FullName, - uint32_t* aIndex) + uint32_t* aIndex, size_t aTruncatedLen) { for (size_t i = 0; i < mFonts.length(); ++i) { mozilla::u16string name; - if (mFonts[i]->GetU16FullName(name) && name == aU16FullName) { + if (!mFonts[i]->GetU16FullName(name)) { + continue; + } + + if (aTruncatedLen) { + MOZ_ASSERT(aU16FullName.length() <= aTruncatedLen); + name = name.substr(0, aTruncatedLen); + } + + if (name == aU16FullName) { *aIndex = i; return true; } @@ -225,7 +232,7 @@ SFNTData::AddFont(const uint8_t *aFontData, uint32_t aDataLength, { uint32_t remainingLength = aDataLength - aOffset; if (remainingLength < sizeof(OffsetTable)) { - gfxCriticalError() << "Font data too short to contain OffsetTable: offset = " << aOffset << "; length = " << aDataLength; + gfxWarning() << "Font data too short to contain OffsetTable " << aOffset; return false; } @@ -233,7 +240,7 @@ SFNTData::AddFont(const uint8_t *aFontData, uint32_t aDataLength, reinterpret_cast(aFontData + aOffset); if (remainingLength < sizeof(OffsetTable) + (offsetTable->numTables * sizeof(TableDirEntry))) { - gfxCriticalError() << "Font data too short to contain tables. numTables = " << offsetTable->numTables << "; offset = " << aOffset << "; length = " << aDataLength; + gfxWarning() << "Font data too short to contain tables."; return false; } diff --git a/gfx/2d/SFNTData.h b/gfx/2d/SFNTData.h index 5205dfa1c4..e10d63caed 100644 --- a/gfx/2d/SFNTData.h +++ b/gfx/2d/SFNTData.h @@ -70,9 +70,11 @@ public: * * @param aU16FullName full name to find. * @param aIndex out param for the index if found. + * @param aTruncatedLen length to truncate the compared font name to. * @return true if the full name is successfully read. */ - bool GetIndexForU16Name(const mozilla::u16string& aU16FullName, uint32_t* aIndex); + bool GetIndexForU16Name(const mozilla::u16string& aU16FullName, uint32_t* aIndex, + size_t aTruncatedLen = 0); private: diff --git a/gfx/2d/ScaledFontDWrite.cpp b/gfx/2d/ScaledFontDWrite.cpp index a9c9e8f805..06cfb6da2d 100644 --- a/gfx/2d/ScaledFontDWrite.cpp +++ b/gfx/2d/ScaledFontDWrite.cpp @@ -115,12 +115,42 @@ ScaledFontDWrite::GetPathForGlyphs(const GlyphBuffer &aBuffer, const DrawTarget #ifdef USE_SKIA +// This can happen if we have mixed backends which create DWrite +// fonts in a mixed environment. e.g. a cairo content backend +// but Skia canvas backend. +void +ScaledFontDWrite::GetFontDataFromSystemFonts(IDWriteFactory* aFactory) +{ + MOZ_ASSERT(mFontFace); + RefPtr systemFonts; + HRESULT hr = aFactory->GetSystemFontCollection(getter_AddRefs(systemFonts)); + if (FAILED(hr)) { + gfxWarning() << "Failed to get system font collection from file data. Code: " << hexa(hr); + return; + } + + hr = systemFonts->GetFontFromFontFace(mFontFace, getter_AddRefs(mFont)); + if (FAILED(hr)) { + gfxWarning() << "Failed to get system font from font face. Code: " << hexa(hr); + return; + } + + hr = mFont->GetFontFamily(getter_AddRefs(mFontFamily)); + if (FAILED(hr)) { + gfxWarning() << "Failed to get font family from font face. Code: " << hexa(hr); + return; + } +} + SkTypeface* ScaledFontDWrite::GetSkTypeface() { - MOZ_ASSERT(mFont); if (!mTypeface) { IDWriteFactory *factory = DrawTargetD2D1::GetDWriteFactory(); + if (!mFont || !mFontFamily) { + GetFontDataFromSystemFonts(factory); + } + mTypeface = SkCreateTypefaceFromDWriteFont(factory, mFontFace, mFont, mFontFamily); } return mTypeface; diff --git a/gfx/2d/ScaledFontDWrite.h b/gfx/2d/ScaledFontDWrite.h index 65f25d89ff..9a4d16264e 100644 --- a/gfx/2d/ScaledFontDWrite.h +++ b/gfx/2d/ScaledFontDWrite.h @@ -46,8 +46,10 @@ public: #ifdef USE_SKIA virtual SkTypeface* GetSkTypeface(); + void GetFontDataFromSystemFonts(IDWriteFactory* aFactory); #endif + // The font and font family are only used with Skia RefPtr mFont; RefPtr mFontFamily; RefPtr mFontFace; diff --git a/gfx/2d/ScaledFontWin.cpp b/gfx/2d/ScaledFontWin.cpp index 4395c76dda..abcbfed36a 100644 --- a/gfx/2d/ScaledFontWin.cpp +++ b/gfx/2d/ScaledFontWin.cpp @@ -40,7 +40,6 @@ ScaledFontWin::GetFontFileData(FontFileDataOutput aDataCallback, void *aBaton) table = 0; tableSize = ::GetFontData(dc.GetDC(), table, 0, nullptr, 0); if (tableSize == GDI_ERROR) { - gfxDevCrash(LogReason::GetFontFileDataFailed) << "Failed to get font data from GDI"; return false; } } @@ -50,7 +49,6 @@ ScaledFontWin::GetFontFileData(FontFileDataOutput aDataCallback, void *aBaton) uint32_t sizeGot = ::GetFontData(dc.GetDC(), table, 0, fontData.get(), tableSize); if (sizeGot != tableSize) { - gfxDevCrash(LogReason::GetFontFileDataFailed) << "GDI did not return enough data for font: wanted " << tableSize << ", got " << sizeGot; return false; } @@ -60,15 +58,17 @@ ScaledFontWin::GetFontFileData(FontFileDataOutput aDataCallback, void *aBaton) UniquePtr sfntData = SFNTData::Create(fontData.get(), tableSize); if (!sfntData) { - gfxDevCrash(LogReason::GetFontFileDataFailed) << "Failed to create SFNTData for GetFontFileData."; + gfxWarning() << "Failed to create SFNTData for GetFontFileData."; return false; } // We cast here because for VS2015 char16_t != wchar_t, even though they are // both 16 bit. if (!sfntData->GetIndexForU16Name( - reinterpret_cast(mLogFont.lfFaceName), &index)) { - gfxDevCrash(LogReason::GetFontFileDataFailed) << "Failed to get index for face name."; + reinterpret_cast(mLogFont.lfFaceName), &index, LF_FACESIZE - 1)) { + gfxWarning() << "Failed to get index for face name."; + gfxDevCrash(LogReason::GetFontFileDataFailed) << + "Failed to get index for face name |" << mLogFont.lfFaceName << "|."; return false; } } diff --git a/gfx/2d/SourceSurfaceCairo.cpp b/gfx/2d/SourceSurfaceCairo.cpp index 2ead9510f3..ba8498b844 100644 --- a/gfx/2d/SourceSurfaceCairo.cpp +++ b/gfx/2d/SourceSurfaceCairo.cpp @@ -22,6 +22,8 @@ CairoFormatToSurfaceFormat(cairo_format_t format) return SurfaceFormat::B8G8R8A8; case CAIRO_FORMAT_RGB24: return SurfaceFormat::B8G8R8X8; + case CAIRO_FORMAT_RGB16_565: + return SurfaceFormat::R5G6B5_UINT16; case CAIRO_FORMAT_A8: return SurfaceFormat::A8; default: diff --git a/gfx/2d/convolver.cpp b/gfx/2d/convolver.cpp index b4a23133f3..0c3416f768 100644 --- a/gfx/2d/convolver.cpp +++ b/gfx/2d/convolver.cpp @@ -322,13 +322,13 @@ void ConvolveHorizontally(const unsigned char* src_data, bool has_alpha, bool use_simd) { int width = filter.num_values(); int processed = 0; -#if defined(USE_SSE2) +#if defined(USE_SSE2) || defined(_MIPS_ARCH_LOONGSON3A) int simd_width = width & ~3; if (use_simd && simd_width) { // SIMD implementation works with 4 pixels at a time. // Therefore we process as much as we can using SSE and then use // C implementation for leftovers - ConvolveHorizontally_SSE2(src_data, filter, out_row); + ConvolveHorizontally_SIMD(src_data, filter, out_row); processed = simd_width; } #endif diff --git a/gfx/2d/convolverLS3.cpp b/gfx/2d/convolverLS3.cpp index 8c6b26cafd..795c1b8901 100644 --- a/gfx/2d/convolverLS3.cpp +++ b/gfx/2d/convolverLS3.cpp @@ -99,7 +99,8 @@ void ConvolveHorizontally_LS3(const unsigned char* src_data, ".set arch=loongson3a \n\t" // Load 4 coefficients => duplicate 1st and 2nd of them for all channels. // [16] xx xx xx xx c3 c2 c1 c0 - "ldc1 %[coeffl], (%[fval]) \n\t" + "gsldlc1 %[coeffl], 7(%[fval]) \n\t" + "gsldrc1 %[coeffl], (%[fval]) \n\t" "xor %[coeffh], %[coeffh], %[coeffh] \n\t" // [16] xx xx xx xx c1 c1 c0 c0 _mm_pshuflh(coeff16, coeff, shuf_50) @@ -170,7 +171,8 @@ void ConvolveHorizontally_LS3(const unsigned char* src_data, asm volatile ( ".set push \n\t" ".set arch=loongson3a \n\t" - "ldc1 %[coeffl], (%[fval]) \n\t" + "gsldlc1 %[coeffl], 7(%[fval]) \n\t" + "gsldrc1 %[coeffl], (%[fval]) \n\t" "xor %[coeffh], %[coeffh], %[coeffh] \n\t" // Mask out extra filter taps. "and %[coeffl], %[coeffl], %[mask] \n\t" @@ -305,7 +307,8 @@ void ConvolveHorizontally4_LS3(const unsigned char* src_data[4], ".set push \n\t" ".set arch=loongson3a \n\t" // [16] xx xx xx xx c3 c2 c1 c0 - "ldc1 %[coeffl], (%[fval]) \n\t" + "gsldlc1 %[coeffl], 7(%[fval]) \n\t" + "gsldrc1 %[coeffl], (%[fval]) \n\t" "xor %[coeffh], %[coeffh], %[coeffh] \n\t" // [16] xx xx xx xx c1 c1 c0 c0 _mm_pshuflh(coeff16lo, coeff, shuf_50) @@ -374,7 +377,8 @@ void ConvolveHorizontally4_LS3(const unsigned char* src_data[4], asm volatile ( ".set push \n\t" ".set arch=loongson3a \n\t" - "ldc1 %[coeffl], (%[fval]) \n\t" + "gsldlc1 %[coeffl], 7(%[fval]) \n\t" + "gsldrc1 %[coeffl], (%[fval]) \n\t" "xor %[coeffh], %[coeffh], %[coeffh] \n\t" // Mask out extra filter taps. "and %[coeffl], %[coeffl], %[mask] \n\t" @@ -500,7 +504,8 @@ void ConvolveVertically_LS3_impl(const ConvolutionFilter1D::Fixed* filter_values ".set arch=loongson3a \n\t" // Duplicate the filter coefficient 8 times. // [16] cj cj cj cj cj cj cj cj - "mtc1 %[fval], %[coeff16l] \n\t" + "gsldlc1 %[coeff16l], 7+%[fval] \n\t" + "gsldrc1 %[coeff16l], %[fval] \n\t" "pshufh %[coeff16l], %[coeff16l], %[zerol] \n\t" "mov.d %[coeff16h], %[coeff16l] \n\t" // Load four pixels (16 bytes) together. @@ -537,7 +542,7 @@ void ConvolveVertically_LS3_impl(const ConvolutionFilter1D::Fixed* filter_values [accum1h]"+f"(accum1h), [accum1l]"+f"(accum1l), [coeff16h]"=&f"(coeff16h), [coeff16l]"=&f"(coeff16l) :[zeroh]"f"(zero), [zerol]"f"(zero), - [fval]"r"(filter_values[filter_y]), + [fval]"m"(filter_values[filter_y]), [src]"r"(src) ); @@ -675,7 +680,8 @@ void ConvolveVertically_LS3_impl(const ConvolutionFilter1D::Fixed* filter_values asm volatile ( ".set push \n\t" ".set arch=loongson3a \n\t" - "mtc1 %[fval], %[coeff16l] \n\t" + "gsldlc1 %[coeff16l], 7+%[fval] \n\t" + "gsldrc1 %[coeff16l], %[fval] \n\t" "pshufh %[coeff16l], %[coeff16l], %[zerol] \n\t" "mov.d %[coeff16h], %[coeff16l] \n\t" // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0 @@ -711,7 +717,7 @@ void ConvolveVertically_LS3_impl(const ConvolutionFilter1D::Fixed* filter_values [accum2h]"+f"(accum2h), [accum2l]"+f"(accum2l), [coeff16h]"=&f"(coeff16h), [coeff16l]"=&f"(coeff16l) :[zeroh]"f"(zero), [zerol]"f"(zero), - [fval]"r"(filter_values[filter_y]), + [fval]"m"(filter_values[filter_y]), [src]"r"(src) ); } diff --git a/gfx/angle/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp b/gfx/angle/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp index aa34fd4de8..e01464a95b 100644 --- a/gfx/angle/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp +++ b/gfx/angle/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp @@ -582,7 +582,9 @@ gl::Error StateManager11::setDepthStencilState(const gl::State &glState) gl::Error StateManager11::setRasterizerState(const gl::RasterizerState &rasterState) { - if (!mRasterizerStateIsDirty) + // TODO: Remove pointDrawMode and multiSample from gl::RasterizerState. + if (!mRasterizerStateIsDirty && rasterState.pointDrawMode == mCurRasterState.pointDrawMode && + rasterState.multiSample == mCurRasterState.multiSample) { return gl::Error(GL_NO_ERROR); } diff --git a/gfx/angle/src/tests/gl_tests/PointSpritesTest.cpp b/gfx/angle/src/tests/gl_tests/PointSpritesTest.cpp index 6b7f26555b..3fc974b9c4 100644 --- a/gfx/angle/src/tests/gl_tests/PointSpritesTest.cpp +++ b/gfx/angle/src/tests/gl_tests/PointSpritesTest.cpp @@ -434,6 +434,84 @@ TEST_P(PointSpritesTest, PointSizeDeclaredButUnused) glDeleteProgram(program); } +// Test to cover a bug where the D3D11 rasterizer state would not be update when switching between +// draw types. This causes the cull face to potentially be incorrect when drawing emulated point +// spites. +TEST_P(PointSpritesTest, PointSpriteAlternatingDrawTypes) +{ + // clang-format off + const std::string pointFS = SHADER_SOURCE + ( + precision mediump float; + void main() + { + gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); + } + ); + + const std::string pointVS = SHADER_SOURCE + ( + void main() + { + gl_PointSize = 16.0; + gl_Position = vec4(0.0, 0.0, 0.0, 1.0); + } + ); + + const std::string quadFS = SHADER_SOURCE + ( + precision mediump float; + void main() + { + gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); + } + ); + + const std::string quadVS = SHADER_SOURCE + ( + precision mediump float; + attribute vec4 pos; + void main() + { + gl_Position = pos; + } + ); + // clang-format on + + GLuint pointProgram = CompileProgram(pointVS, pointFS); + ASSERT_NE(pointProgram, 0u); + ASSERT_GL_NO_ERROR(); + + GLuint quadProgram = CompileProgram(quadVS, quadFS); + ASSERT_NE(pointProgram, 0u); + ASSERT_GL_NO_ERROR(); + + glEnable(GL_CULL_FACE); + glCullFace(GL_FRONT); + + const GLfloat quadVertices[] = { + -1.0f, 1.0f, 0.5f, 1.0f, -1.0f, 0.5f, -1.0f, -1.0f, 0.5f, + + -1.0f, 1.0f, 0.5f, 1.0f, 1.0f, 0.5f, 1.0f, -1.0f, 0.5f, + }; + + glUseProgram(quadProgram); + GLint positionLocation = glGetAttribLocation(quadProgram, "pos"); + glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, quadVertices); + glEnableVertexAttribArray(positionLocation); + glDrawArrays(GL_TRIANGLES, 0, 6); + + glUseProgram(pointProgram); + glDrawArrays(GL_POINTS, 0, 1); + ASSERT_GL_NO_ERROR(); + + // expect the center pixel to be green + EXPECT_PIXEL_EQ(getWindowWidth() / 2, getWindowHeight() / 2, 0, 255, 0, 255); + + glDeleteProgram(pointProgram); + glDeleteProgram(quadProgram); +} + // Use this to select which configurations (e.g. which renderer, which GLES // major version) these tests should be run against. // diff --git a/gfx/doc/B2GInputFlow.svg b/gfx/doc/B2GInputFlow.svg new file mode 100644 index 0000000000..ee6f4332c2 --- /dev/null +++ b/gfx/doc/B2GInputFlow.svg @@ -0,0 +1,349 @@ + + + Touch input event flow on B2G + + + + diff --git a/gfx/doc/LayersHistory.md b/gfx/doc/LayersHistory.md new file mode 100644 index 0000000000..2833aa3c5b --- /dev/null +++ b/gfx/doc/LayersHistory.md @@ -0,0 +1,60 @@ +This is an overview of the major events in the history of our Layers infrastructure. + +- iPhone released in July 2007 (Built on a toolkit called LayerKit) + +- Core Animation (October 2007) LayerKit was publicly renamed to OS X 10.5 + +- Webkit CSS 3d transforms (July 2009) + +- Original layers API (March 2010) Introduced the idea of a layer manager that + would composite. One of the first use cases for this was hardware accelerated + YUV conversion for video. + +- Retained layers (July 7 2010 - Bug 564991) +This was an important concept that introduced the idea of persisting the layer +content across paints in gecko controlled buffers instead of just by the OS. This introduced +the concept of buffer rotation to deal with scrolling instead of using the +native scrolling APIs like ScrollWindowEx + +- Layers IPC (July 2010 - Bug 570294) +This introduced shadow layers and edit lists and was originally done for e10s v1 + +- 3d transforms (September 2011 - Bug 505115) + +- OMTC (December 2011 - Bug 711168) +This was prototyped on OS X but shipped first for Fennec + +- Tiling v1 (April 2012 - Bug 739679) +Originally done for Fennec. +This was done to avoid situations where we had to do a bunch of work for +scrolling a small amount. i.e. buffer rotation. It allowed us to have a +variety of interesting features like progressive painting and lower resolution +painting. + +- C++ Async pan zoom controller (July 2012 - Bug 750974) +The existing APZ code was in Java for Fennec so this was reimplemented. + +- Streaming WebGL Buffers (February 2013 - Bug 716859) +Infrastructure to allow OMTC WebGL and avoid the need to glFinish() every +frame. + +- Compositor API (April 2013 - Bug 825928) +The planning for this started around November 2012. +Layers refactoring created a compositor API that abstracted away the differences between the +D3D vs OpenGL. The main piece of API is DrawQuad. + +- Tiling v2 (Mar 7 2014 - Bug 963073) +Tiling for B2G. This work is mainly porting tiled layers to new textures, +implementing double-buffered tiles and implementing a texture client pool, to +be used by tiled content clients. + + A large motivation for the pool was the very slow performance of allocating tiles because +of the sync messages to the compositor. + + The slow performance of allocating was directly addressed by bug 959089 which allowed us +to allocate gralloc buffers without sync messages to the compositor thread. + +- B2G WebGL performance (May 2014 - Bug 1006957, 1001417, 1024144) +This work improved the synchronization mechanism between the compositor +and the producer. + diff --git a/gfx/gl/GLContext.cpp b/gfx/gl/GLContext.cpp index 1b45639f35..96d118f247 100644 --- a/gfx/gl/GLContext.cpp +++ b/gfx/gl/GLContext.cpp @@ -155,6 +155,7 @@ static const char* const sExtensionNames[] = { "GL_NV_geometry_program4", "GL_NV_half_float", "GL_NV_instanced_arrays", + "GL_NV_texture_barrier", "GL_NV_transform_feedback", "GL_NV_transform_feedback2", "GL_OES_EGL_image", @@ -1556,6 +1557,14 @@ GLContext::LoadMoreSymbols(const char* prefix, bool trygl) fnLoadForExt(symbols, NV_fence); } + if (IsExtensionSupported(NV_texture_barrier)) { + const SymLoadStruct symbols[] = { + { (PRFuncPtr*) &mSymbols.fTextureBarrier, { "TextureBarrierNV", nullptr } }, + END_SYMBOLS + }; + fnLoadForExt(symbols, NV_texture_barrier); + } + if (IsSupported(GLFeature::read_buffer)) { const SymLoadStruct symbols[] = { { (PRFuncPtr*) &mSymbols.fReadBuffer, { "ReadBuffer", nullptr } }, diff --git a/gfx/gl/GLContext.h b/gfx/gl/GLContext.h index edef184e12..284b9b1b03 100644 --- a/gfx/gl/GLContext.h +++ b/gfx/gl/GLContext.h @@ -478,6 +478,7 @@ public: NV_geometry_program4, NV_half_float, NV_instanced_arrays, + NV_texture_barrier, NV_transform_feedback, NV_transform_feedback2, OES_EGL_image, @@ -2879,6 +2880,17 @@ public: AFTER_GL_CALL; } +// ----------------------------------------------------------------------------- +// Extension NV_texture_barrier +public: + void fTextureBarrier() + { + ASSERT_SYMBOL_PRESENT(fTextureBarrier); + BEFORE_GL_CALL; + mSymbols.fTextureBarrier(); + AFTER_GL_CALL; + } + // Core GL & Extension ARB_copy_buffer public: void fCopyBufferSubData(GLenum readtarget, GLenum writetarget, diff --git a/gfx/gl/GLContextSymbols.h b/gfx/gl/GLContextSymbols.h index efed07959d..aec5d2617e 100644 --- a/gfx/gl/GLContextSymbols.h +++ b/gfx/gl/GLContextSymbols.h @@ -690,6 +690,10 @@ struct GLContextSymbols // APPLE_framebuffer_multisample typedef void (GLAPIENTRY * PFNRESOLVEMULTISAMPLEFRAMEBUFFERAPPLE) (void); PFNRESOLVEMULTISAMPLEFRAMEBUFFERAPPLE fResolveMultisampleFramebufferAPPLE; + + // NV_texture_barrier + typedef void (GLAPIENTRY * PFNTEXTUREBARRIERPROC) (void); + PFNTEXTUREBARRIERPROC fTextureBarrier; }; } // namespace gl diff --git a/gfx/gl/GLLibraryEGL.h b/gfx/gl/GLLibraryEGL.h index ca1a2e396a..e792f0eb10 100644 --- a/gfx/gl/GLLibraryEGL.h +++ b/gfx/gl/GLLibraryEGL.h @@ -108,9 +108,52 @@ public: GLLibraryEGL() : mInitialized(false), mEGLLibrary(nullptr), + mEGLDisplay(EGL_NO_DISPLAY), mIsANGLE(false), mIsWARP(false) { + ClearSymbols(); + } + + void ClearSymbols() { + mSymbols.fGetDisplay = nullptr; + mSymbols.fGetPlatformDisplayEXT = nullptr; + mSymbols.fTerminate = nullptr; + mSymbols.fGetCurrentSurface = nullptr; + mSymbols.fGetCurrentContext = nullptr; + mSymbols.fMakeCurrent = nullptr; + mSymbols.fDestroyContext = nullptr; + mSymbols.fCreateContext = nullptr; + mSymbols.fDestroySurface = nullptr; + mSymbols.fCreateWindowSurface = nullptr; + mSymbols.fCreatePbufferSurface = nullptr; + mSymbols.fCreatePixmapSurface = nullptr; + mSymbols.fBindAPI = nullptr; + mSymbols.fInitialize = nullptr; + mSymbols.fChooseConfig = nullptr; + mSymbols.fGetError = nullptr; + mSymbols.fGetConfigAttrib = nullptr; + mSymbols.fGetConfigs = nullptr; + mSymbols.fWaitNative = nullptr; + mSymbols.fGetProcAddress = nullptr; + mSymbols.fSwapBuffers = nullptr; + mSymbols.fCopyBuffers = nullptr; + mSymbols.fQueryString = nullptr; + mSymbols.fQueryStringImplementationANDROID = nullptr; + mSymbols.fQueryContext = nullptr; + mSymbols.fBindTexImage = nullptr; + mSymbols.fReleaseTexImage = nullptr; + mSymbols.fCreateImage = nullptr; + mSymbols.fDestroyImage = nullptr; + mSymbols.fLockSurface = nullptr; + mSymbols.fUnlockSurface = nullptr; + mSymbols.fQuerySurface = nullptr; + mSymbols.fQuerySurfacePointerANGLE = nullptr; + mSymbols.fCreateSync = nullptr; + mSymbols.fDestroySync = nullptr; + mSymbols.fClientWaitSync = nullptr; + mSymbols.fGetSyncAttrib = nullptr; + mSymbols.fDupNativeFenceFDANDROID = nullptr; } void InitClientExtensions(); diff --git a/gfx/gl/GLScreenBuffer.cpp b/gfx/gl/GLScreenBuffer.cpp index 22011ccacb..a3ca56bc37 100755 --- a/gfx/gl/GLScreenBuffer.cpp +++ b/gfx/gl/GLScreenBuffer.cpp @@ -854,7 +854,7 @@ DrawBuffer::Create(GLContext* const gl, DrawBuffer::~DrawBuffer() { - if(!mGL->MakeCurrent()) + if (!mGL->MakeCurrent()) return; GLuint fb = mFB; @@ -932,7 +932,7 @@ ReadBuffer::Create(GLContext* gl, ReadBuffer::~ReadBuffer() { - if(!mGL->MakeCurrent()) + if (!mGL->MakeCurrent()) return; GLuint fb = mFB; diff --git a/gfx/gl/GLXLibrary.h b/gfx/gl/GLXLibrary.h index 1ebe90a72c..7ce332fa7d 100644 --- a/gfx/gl/GLXLibrary.h +++ b/gfx/gl/GLXLibrary.h @@ -32,13 +32,36 @@ namespace gl { class GLXLibrary { public: - GLXLibrary() : mInitialized(false), mTriedInitializing(false), - mUseTextureFromPixmap(false), mDebug(false), - mHasRobustness(false), mHasCreateContextAttribs(false), - mIsATI(false), mIsNVIDIA(false), - mClientIsMesa(false), mGLXMajorVersion(0), - mGLXMinorVersion(0), - mOGLLibrary(nullptr) {} + MOZ_CONSTEXPR GLXLibrary() + : xDestroyContextInternal(nullptr) + , xMakeCurrentInternal(nullptr) + , xGetCurrentContextInternal(nullptr) + , xGetProcAddressInternal(nullptr) + , xChooseFBConfigInternal(nullptr) + , xGetFBConfigsInternal(nullptr) + , xCreateNewContextInternal(nullptr) + , xGetFBConfigAttribInternal(nullptr) + , xSwapBuffersInternal(nullptr) + , xQueryExtensionsStringInternal(nullptr) + , xGetClientStringInternal(nullptr) + , xQueryServerStringInternal(nullptr) + , xCreatePixmapInternal(nullptr) + , xCreateGLXPixmapWithConfigInternal(nullptr) + , xDestroyPixmapInternal(nullptr) + , xQueryVersionInternal(nullptr) + , xBindTexImageInternal(nullptr) + , xReleaseTexImageInternal(nullptr) + , xWaitGLInternal(nullptr) + , xWaitXInternal(nullptr) + , xCreateContextAttribsInternal(nullptr) + , mInitialized(false), mTriedInitializing(false) + , mUseTextureFromPixmap(false), mDebug(false) + , mHasRobustness(false), mHasCreateContextAttribs(false) + , mIsATI(false), mIsNVIDIA(false) + , mClientIsMesa(false), mGLXMajorVersion(0) + , mGLXMinorVersion(0) + , mOGLLibrary(nullptr) + {} void xDestroyContext(Display* display, GLXContext context); Bool xMakeCurrent(Display* display, diff --git a/gfx/gl/SharedSurfaceEGL.cpp b/gfx/gl/SharedSurfaceEGL.cpp index e911f3f3ec..abd94bd5b7 100644 --- a/gfx/gl/SharedSurfaceEGL.cpp +++ b/gfx/gl/SharedSurfaceEGL.cpp @@ -39,7 +39,7 @@ SharedSurface_EGLImage::Create(GLContext* prodGL, return Move(ret); } - EGLClientBuffer buffer = reinterpret_cast(prodTex); + EGLClientBuffer buffer = reinterpret_cast(uintptr_t(prodTex)); EGLImage image = egl->fCreateImage(egl->Display(), context, LOCAL_EGL_GL_TEXTURE_2D, buffer, nullptr); diff --git a/gfx/ipc/GfxMessageUtils.h b/gfx/ipc/GfxMessageUtils.h index 0507ca0ec8..22476cf8bb 100644 --- a/gfx/ipc/GfxMessageUtils.h +++ b/gfx/ipc/GfxMessageUtils.h @@ -906,7 +906,7 @@ struct ParamTraits : public ContiguousEnumSerializer< mozilla::StereoMode, mozilla::StereoMode::MONO, - mozilla::StereoMode::TOP_BOTTOM> + mozilla::StereoMode::MAX> {}; template <> diff --git a/gfx/layers/ImageTypes.h b/gfx/layers/ImageTypes.h index 9619f29724..3cd4e96cc5 100644 --- a/gfx/layers/ImageTypes.h +++ b/gfx/layers/ImageTypes.h @@ -99,7 +99,8 @@ enum class StereoMode { LEFT_RIGHT, RIGHT_LEFT, BOTTOM_TOP, - TOP_BOTTOM + TOP_BOTTOM, + MAX, }; } // namespace mozilla diff --git a/gfx/layers/LayerTreeInvalidation.cpp b/gfx/layers/LayerTreeInvalidation.cpp index 77b99835d5..215ebb1236 100644 --- a/gfx/layers/LayerTreeInvalidation.cpp +++ b/gfx/layers/LayerTreeInvalidation.cpp @@ -135,7 +135,7 @@ struct LayerPropertiesBase : public LayerProperties , mPostXScale(aLayer->GetPostXScale()) , mPostYScale(aLayer->GetPostYScale()) , mOpacity(aLayer->GetLocalOpacity()) - , mUseClipRect(!!aLayer->GetEffectiveClipRect()) + , mUseClipRect(!!aLayer->GetLocalClipRect()) { MOZ_COUNT_CTOR(LayerPropertiesBase); if (aLayer->GetMaskLayer()) { @@ -146,7 +146,7 @@ struct LayerPropertiesBase : public LayerProperties mAncestorMaskLayers.AppendElement(CloneLayerTreePropertiesInternal(maskLayer, true)); } if (mUseClipRect) { - mClipRect = *aLayer->GetEffectiveClipRect(); + mClipRect = *aLayer->GetLocalClipRect(); } mTransform = GetTransformForInvalidation(aLayer); } @@ -173,7 +173,7 @@ struct LayerPropertiesBase : public LayerProperties bool transformChanged = !mTransform.FuzzyEqual(GetTransformForInvalidation(mLayer)) || mLayer->GetPostXScale() != mPostXScale || mLayer->GetPostYScale() != mPostYScale; - const Maybe& otherClip = mLayer->GetEffectiveClipRect(); + const Maybe& otherClip = mLayer->GetLocalClipRect(); nsIntRegion result; bool ancestorMaskChanged = mAncestorMaskLayers.Length() != mLayer->GetAncestorMaskLayerCount(); @@ -191,7 +191,7 @@ struct LayerPropertiesBase : public LayerProperties ancestorMaskChanged || (mUseClipRect != !!otherClip) || mLayer->GetLocalOpacity() != mOpacity || - transformChanged) + transformChanged) { aGeometryChanged = true; result = OldTransformedBounds(); @@ -220,7 +220,7 @@ struct LayerPropertiesBase : public LayerProperties if (mUseClipRect && otherClip) { if (!mClipRect.IsEqualInterior(*otherClip)) { aGeometryChanged = true; - nsIntRegion tmp; + nsIntRegion tmp; tmp.Xor(mClipRect.ToUnknownRect(), otherClip->ToUnknownRect()); AddRegion(result, tmp); } @@ -474,7 +474,7 @@ struct ImageLayerProperties : public LayerPropertiesBase bool& aGeometryChanged) { ImageLayer* imageLayer = static_cast(mLayer.get()); - + if (!imageLayer->GetLocalVisibleRegion().ToUnknownRegion().IsEqual(mVisibleRegion)) { aGeometryChanged = true; IntRect result = NewTransformedBounds(); @@ -585,7 +585,7 @@ LayerProperties::CloneFrom(Layer* aRoot) return CloneLayerTreePropertiesInternal(aRoot); } -/* static */ void +/* static */ void LayerProperties::ClearInvalidations(Layer *aLayer) { aLayer->ClearInvalidRect(); diff --git a/gfx/layers/Layers.cpp b/gfx/layers/Layers.cpp index b75983d781..2f8f57242a 100644 --- a/gfx/layers/Layers.cpp +++ b/gfx/layers/Layers.cpp @@ -600,7 +600,7 @@ Layer::CanUseOpaqueSurface() // NB: eventually these methods will be defined unconditionally, and // can be moved into Layers.h const Maybe& -Layer::GetEffectiveClipRect() +Layer::GetLocalClipRect() { if (LayerComposite* shadow = AsLayerComposite()) { return shadow->GetShadowClipRect(); @@ -786,7 +786,7 @@ Layer::CalculateScissorRect(const RenderTargetIntRect& aCurrentScissorRect) // ContainerState::SetupScrollingMetadata() may install a clip on // the layer. Layer *clipLayer = - containerChild && containerChild->GetEffectiveClipRect() ? + containerChild && containerChild->GetLocalClipRect() ? containerChild : this; // Establish initial clip rect: it's either the one passed in, or @@ -798,7 +798,7 @@ Layer::CalculateScissorRect(const RenderTargetIntRect& aCurrentScissorRect) currentClip = aCurrentScissorRect; } - if (!clipLayer->GetEffectiveClipRect()) { + if (!clipLayer->GetLocalClipRect()) { return currentClip; } @@ -815,7 +815,7 @@ Layer::CalculateScissorRect(const RenderTargetIntRect& aCurrentScissorRect) } const RenderTargetIntRect clipRect = - ViewAs(*clipLayer->GetEffectiveClipRect(), + ViewAs(*clipLayer->GetLocalClipRect(), PixelCastJustification::RenderTargetIsParentLayerForRoot); if (clipRect.IsEmpty()) { // We might have a non-translation transform in the container so we can't @@ -904,18 +904,10 @@ Layer::GetTransformTyped() const const Matrix4x4 Layer::GetLocalTransform() { - Matrix4x4 transform; if (LayerComposite* shadow = AsLayerComposite()) - transform = shadow->GetShadowBaseTransform(); + return shadow->GetShadowTransform(); else - transform = mTransform; - - transform.PostScale(GetPostXScale(), GetPostYScale(), 1.0f); - if (ContainerLayer* c = AsContainerLayer()) { - transform.PreScale(c->GetPreXScale(), c->GetPreYScale(), 1.0f); - } - - return transform; + return GetTransform(); } const LayerToParentLayerMatrix4x4 @@ -981,7 +973,7 @@ Layer::GetEffectiveOpacity() } return opacity; } - + CompositionOp Layer::GetEffectiveMixBlendMode() { @@ -1061,8 +1053,8 @@ Layer::GetVisibleRegionRelativeToRootLayer(nsIntRegion& aResult, // If the parent layer clips its lower layers, clip the visible region // we're accumulating. - if (layer->GetEffectiveClipRect()) { - aResult.AndWith(layer->GetEffectiveClipRect()->ToUnknownRect()); + if (layer->GetLocalClipRect()) { + aResult.AndWith(layer->GetLocalClipRect()->ToUnknownRect()); } // Now we need to walk across the list of siblings for this parent layer, @@ -1086,7 +1078,7 @@ Layer::GetVisibleRegionRelativeToRootLayer(nsIntRegion& aResult, siblingVisibleRegion.MoveBy(-siblingOffset.x, -siblingOffset.y); // Apply the sibling's clip. // Layer clip rects are not affected by the layer's transform. - Maybe clipRect = sibling->GetEffectiveClipRect(); + Maybe clipRect = sibling->GetLocalClipRect(); if (clipRect) { siblingVisibleRegion.AndWith(clipRect->ToUnknownRect()); } @@ -1327,7 +1319,7 @@ ContainerLayer::HasMultipleChildren() { uint32_t count = 0; for (Layer* child = GetFirstChild(); child; child = child->GetNextSibling()) { - const Maybe& clipRect = child->GetEffectiveClipRect(); + const Maybe& clipRect = child->GetLocalClipRect(); if (clipRect && clipRect->IsEmpty()) continue; if (child->GetLocalVisibleRegion().IsEmpty()) @@ -1447,7 +1439,7 @@ ContainerLayer::DefaultComputeEffectiveTransforms(const Matrix4x4& aTransformToS if (checkClipRect || checkMaskLayers) { for (Layer* child = GetFirstChild(); child; child = child->GetNextSibling()) { - const Maybe& clipRect = child->GetEffectiveClipRect(); + const Maybe& clipRect = child->GetLocalClipRect(); /* We can't (easily) forward our transform to children with a non-empty clip * rect since it would need to be adjusted for the transform. See * the calculations performed by CalculateScissorRect above. @@ -1578,7 +1570,7 @@ RefLayer::FillSpecificAttributes(SpecificLayerAttributes& aAttrs) aAttrs = RefLayerAttributes(GetReferentId(), mEventRegionsOverride); } -/** +/** * StartFrameTimeRecording, together with StopFrameTimeRecording * enable recording of frame intervals. * diff --git a/gfx/layers/Layers.h b/gfx/layers/Layers.h index eb4d516614..11707b5830 100644 --- a/gfx/layers/Layers.h +++ b/gfx/layers/Layers.h @@ -1505,7 +1505,7 @@ public: // These getters can be used anytime. They return the effective // values that should be used when drawing this layer to screen, // accounting for this layer possibly being a shadow. - const Maybe& GetEffectiveClipRect(); + const Maybe& GetLocalClipRect(); const LayerIntRegion& GetLocalVisibleRegion(); bool Extend3DContext() { diff --git a/gfx/layers/ReadbackProcessor.cpp b/gfx/layers/ReadbackProcessor.cpp index de8042efb5..80693c78f3 100644 --- a/gfx/layers/ReadbackProcessor.cpp +++ b/gfx/layers/ReadbackProcessor.cpp @@ -79,7 +79,7 @@ FindBackgroundLayer(ReadbackLayer* aLayer, nsIntPoint* aOffset) } // cliprects are post-transform - const Maybe& clipRect = l->GetEffectiveClipRect(); + const Maybe& clipRect = l->GetLocalClipRect(); if (clipRect && !clipRect->Contains(ViewAs(IntRect(transformOffset, aLayer->GetSize())))) return nullptr; diff --git a/gfx/layers/apz/src/APZCTreeManager.cpp b/gfx/layers/apz/src/APZCTreeManager.cpp index a09407fa79..45efbc5e4d 100644 --- a/gfx/layers/apz/src/APZCTreeManager.cpp +++ b/gfx/layers/apz/src/APZCTreeManager.cpp @@ -781,6 +781,16 @@ APZCTreeManager::ReceiveInputEvent(InputData& aEvent, return result; } + // If/when we enable support for pan inputs off-main-thread, we'll need + // to duplicate this EventStateManager code or something. See the other + // call to GetUserPrefsForWheelEvent in this file for why these fields + // are stored separately. + MOZ_ASSERT(NS_IsMainThread()); + WidgetWheelEvent wheelEvent = panInput.ToWidgetWheelEvent(nullptr); + EventStateManager::GetUserPrefsForWheelEvent(&wheelEvent, + &panInput.mUserDeltaMultiplierX, + &panInput.mUserDeltaMultiplierY); + RefPtr apzc = GetTargetAPZC(panInput.mPanStartPoint, &hitResult); if (apzc) { diff --git a/gfx/layers/apz/src/AsyncPanZoomController.cpp b/gfx/layers/apz/src/AsyncPanZoomController.cpp index 04da8ed97b..885a35298f 100644 --- a/gfx/layers/apz/src/AsyncPanZoomController.cpp +++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp @@ -1750,7 +1750,7 @@ AsyncPanZoomController::CanScroll(const InputData& aEvent) const delta = GetScrollWheelDelta(aEvent.AsScrollWheelInput()); } else if (aEvent.mInputType == PANGESTURE_INPUT) { const PanGestureInput& panInput = aEvent.AsPanGestureInput(); - delta = ToParentLayerCoordinates(panInput.mPanDisplacement, panInput.mPanStartPoint); + delta = ToParentLayerCoordinates(panInput.UserMultipliedPanDisplacement(), panInput.mPanStartPoint); } if (!delta.x && !delta.y) { return false; @@ -1896,6 +1896,7 @@ nsEventStatus AsyncPanZoomController::OnScrollWheel(const ScrollWheelInput& aEve // If we're scroll snapping, use a smooth scroll animation to get // the desired physics. Note that SmoothScrollTo() will re-use an // existing smooth scroll animation if there is one. + APZC_LOG("%p wheel scrolling to snap point %s\n", this, Stringify(startPosition).c_str()); SmoothScrollTo(startPosition); break; } @@ -2018,19 +2019,27 @@ nsEventStatus AsyncPanZoomController::OnPan(const PanGestureInput& aEvent, bool return OnPanBegin(aEvent); } + // Note that there is a multiplier that applies onto the "physical" pan + // displacement (how much the user's fingers moved) that produces the "logical" + // pan displacement (how much the page should move). For some of the code + // below it makes more sense to use the physical displacement rather than + // the logical displacement, and vice-versa. + ScreenPoint physicalPanDisplacement = aEvent.mPanDisplacement; + ParentLayerPoint logicalPanDisplacement = aEvent.UserMultipliedLocalPanDisplacement(); + // We need to update the axis velocity in order to get a useful display port // size and position. We need to do so even if this is a momentum pan (i.e. // aFingersOnTouchpad == false); in that case the "with touch" part is not // really appropriate, so we may want to rethink this at some point. - mX.UpdateWithTouchAtDevicePoint(aEvent.mLocalPanStartPoint.x, aEvent.mLocalPanDisplacement.x, aEvent.mTime); - mY.UpdateWithTouchAtDevicePoint(aEvent.mLocalPanStartPoint.y, aEvent.mLocalPanDisplacement.y, aEvent.mTime); + mX.UpdateWithTouchAtDevicePoint(aEvent.mLocalPanStartPoint.x, logicalPanDisplacement.x, aEvent.mTime); + mY.UpdateWithTouchAtDevicePoint(aEvent.mLocalPanStartPoint.y, logicalPanDisplacement.y, aEvent.mTime); - HandlePanningUpdate(aEvent.mPanDisplacement); + HandlePanningUpdate(physicalPanDisplacement); mozilla::Telemetry::Accumulate(mozilla::Telemetry::SCROLL_INPUT_METHODS, (uint32_t) ScrollInputMethod::ApzPanGesture); - ScreenPoint panDistance(fabs(aEvent.mPanDisplacement.x), fabs(aEvent.mPanDisplacement.y)); + ScreenPoint panDistance(fabs(physicalPanDisplacement.x), fabs(physicalPanDisplacement.y)); OverscrollHandoffState handoffState( *CurrentPanGestureBlock()->GetOverscrollHandoffChain(), panDistance, @@ -2044,7 +2053,7 @@ nsEventStatus AsyncPanZoomController::OnPan(const PanGestureInput& aEvent, bool // the motion of the scrolled contents, not of the scroll position, they need // to move in the opposite direction of the pan displacement. ParentLayerPoint startPoint = aEvent.mLocalPanStartPoint; - ParentLayerPoint endPoint = aEvent.mLocalPanStartPoint - aEvent.mLocalPanDisplacement; + ParentLayerPoint endPoint = aEvent.mLocalPanStartPoint - logicalPanDisplacement; CallDispatchScroll(startPoint, endPoint, handoffState); return nsEventStatus_eConsumeNoDefault; @@ -3852,11 +3861,14 @@ AsyncPanZoomController::GetZoomConstraints() const void AsyncPanZoomController::PostDelayedTask(already_AddRefed aTask, int aDelayMs) { APZThreadUtils::AssertOnControllerThread(); + RefPtr task = aTask; RefPtr controller = GetGeckoContentController(); if (controller) { - controller->PostDelayedTask(Move(aTask), aDelayMs); + controller->PostDelayedTask(task.forget(), aDelayMs); } - // XXX khuey what is supposed to happen if there's no controller? We were leaking tasks ... + // If there is no controller, that means this APZC has been destroyed, and + // we probably don't need to run the task. It will get destroyed when the + // RefPtr goes out of scope. } bool AsyncPanZoomController::Matches(const ScrollableLayerGuid& aGuid) @@ -3965,6 +3977,7 @@ void AsyncPanZoomController::ScrollSnapNear(const CSSPoint& aDestination) { if (Maybe snapPoint = FindSnapPointNear(aDestination, nsIScrollableFrame::DEVICE_PIXELS)) { if (*snapPoint != mFrameMetrics.GetScrollOffset()) { + APZC_LOG("%p smooth scrolling to snap point %s\n", this, Stringify(*snapPoint).c_str()); SmoothScrollTo(*snapPoint); } } diff --git a/gfx/layers/apz/src/InputQueue.cpp b/gfx/layers/apz/src/InputQueue.cpp index 72180c3d7b..0909465d6a 100644 --- a/gfx/layers/apz/src/InputQueue.cpp +++ b/gfx/layers/apz/src/InputQueue.cpp @@ -400,7 +400,7 @@ InputQueue::CancelAnimationsForNewBlock(CancelableBlockState* aBlock) // being processed) we only do this animation-cancellation if there are no older // touch blocks still in the queue. if (aBlock == CurrentBlock()) { - aBlock->GetOverscrollHandoffChain()->CancelAnimations(ExcludeOverscroll); + aBlock->GetOverscrollHandoffChain()->CancelAnimations(ExcludeOverscroll | ScrollSnap); } } diff --git a/gfx/layers/apz/src/WheelScrollAnimation.cpp b/gfx/layers/apz/src/WheelScrollAnimation.cpp index a17f7d8937..d7cb338e6d 100644 --- a/gfx/layers/apz/src/WheelScrollAnimation.cpp +++ b/gfx/layers/apz/src/WheelScrollAnimation.cpp @@ -55,7 +55,10 @@ WheelScrollAnimation::DoSample(FrameMetrics& aFrameMetrics, const TimeDuration& ParentLayerPoint displacement = (CSSPoint::FromAppUnits(sampledDest) - aFrameMetrics.GetScrollOffset()) * zoom; - if (!IsZero(displacement)) { + if (finished) { + mApzc.mX.SetVelocity(0); + mApzc.mY.SetVelocity(0); + } else if (!IsZero(displacement)) { // Velocity is measured in ParentLayerCoords / Milliseconds float xVelocity = displacement.x / aDelta.ToMilliseconds(); float yVelocity = displacement.y / aDelta.ToMilliseconds(); diff --git a/gfx/layers/apz/test/gtest/APZTestCommon.h b/gfx/layers/apz/test/gtest/APZTestCommon.h index 6bec600805..aa12dfb342 100644 --- a/gfx/layers/apz/test/gtest/APZTestCommon.h +++ b/gfx/layers/apz/test/gtest/APZTestCommon.h @@ -66,6 +66,11 @@ private: &(gfxPrefs::Set##prefBase), \ prefValue) +static TimeStamp GetStartupTime() { + static TimeStamp sStartupTime = TimeStamp::Now(); + return sStartupTime; +} + class MockContentController : public GeckoContentController { public: MOCK_METHOD1(RequestContentRepaint, void(const FrameMetrics&)); @@ -85,7 +90,7 @@ public: class MockContentControllerDelayed : public MockContentController { public: MockContentControllerDelayed() - : mTime(TimeStamp::Now()) + : mTime(GetStartupTime()) { } @@ -265,11 +270,6 @@ public: mWaitForMainThread = true; } - static TimeStamp GetStartupTime() { - static TimeStamp sStartupTime = TimeStamp::Now(); - return sStartupTime; - } - private: bool mWaitForMainThread; MockContentControllerDelayed* mcc; @@ -300,7 +300,7 @@ TestFrameMetrics() uint32_t MillisecondsSinceStartup(TimeStamp aTime) { - return (aTime - TestAsyncPanZoomController::GetStartupTime()).ToMilliseconds(); + return (aTime - GetStartupTime()).ToMilliseconds(); } #endif // mozilla_layers_APZTestCommon_h diff --git a/gfx/layers/apz/test/gtest/TestGestureDetector.cpp b/gfx/layers/apz/test/gtest/TestGestureDetector.cpp index 15ad393ee3..635fe72de1 100644 --- a/gfx/layers/apz/test/gtest/TestGestureDetector.cpp +++ b/gfx/layers/apz/test/gtest/TestGestureDetector.cpp @@ -605,6 +605,25 @@ TEST_F(APZCGestureDetectorTester, LongPressInterruptedByWheel) { Wheel(apzc, ScreenIntPoint(10, 10), ScreenPoint(0, -10), mcc->Time(), &wheelBlockId); EXPECT_NE(touchBlockId, wheelBlockId); mcc->AdvanceByMillis(1000); - - +} + +TEST_F(APZCGestureDetectorTester, TapTimeoutInterruptedByWheel) { + // In this test, even though the wheel block comes right after the tap, the + // tap should still be dispatched because it completes fully before the wheel + // block arrived. + EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1); + + // We make the APZC zoomable so the gesture detector needs to wait to + // distinguish between tap and double-tap. During that timeout is when we + // insert the wheel event. + MakeApzcZoomable(); + + uint64_t touchBlockId = 0; + uint64_t wheelBlockId = 0; + Tap(apzc, ScreenIntPoint(10, 10), mcc, TimeDuration::FromMilliseconds(100), + nullptr, &touchBlockId); + mcc->AdvanceByMillis(10); + Wheel(apzc, ScreenIntPoint(10, 10), ScreenPoint(0, -10), mcc->Time(), &wheelBlockId); + EXPECT_NE(touchBlockId, wheelBlockId); + while (mcc->RunThroughDelayedTasks()); } diff --git a/gfx/layers/apz/test/reftest/frame-reconstruction-scroll-clamping-ref.html b/gfx/layers/apz/test/reftest/frame-reconstruction-scroll-clamping-ref.html new file mode 100644 index 0000000000..3db9f2969e --- /dev/null +++ b/gfx/layers/apz/test/reftest/frame-reconstruction-scroll-clamping-ref.html @@ -0,0 +1,27 @@ + + + + +
+ This is the top of the page. +
+ This is the bottom of the page. + diff --git a/gfx/layers/apz/test/reftest/frame-reconstruction-scroll-clamping.html b/gfx/layers/apz/test/reftest/frame-reconstruction-scroll-clamping.html new file mode 100644 index 0000000000..479363f3fb --- /dev/null +++ b/gfx/layers/apz/test/reftest/frame-reconstruction-scroll-clamping.html @@ -0,0 +1,53 @@ + + + + + +
+ This is the top of the page. +
+ This is the bottom of the page. + diff --git a/gfx/layers/apz/test/reftest/reftest.list b/gfx/layers/apz/test/reftest/reftest.list index 5dc127bc26..e7aca21364 100644 --- a/gfx/layers/apz/test/reftest/reftest.list +++ b/gfx/layers/apz/test/reftest/reftest.list @@ -15,3 +15,5 @@ skip-if(!asyncZoom) fuzzy-if(B2G,94,146) == async-scrollbar-zoom-2.html async-sc # Meta-viewport tag support skip-if(!asyncZoom) == initial-scale-1.html initial-scale-1-ref.html + +skip-if(!asyncPan) == frame-reconstruction-scroll-clamping.html frame-reconstruction-scroll-clamping-ref.html diff --git a/gfx/layers/apz/util/APZEventState.cpp b/gfx/layers/apz/util/APZEventState.cpp index a88c1e7453..d8078388d5 100644 --- a/gfx/layers/apz/util/APZEventState.cpp +++ b/gfx/layers/apz/util/APZEventState.cpp @@ -10,6 +10,7 @@ #include "gfxPrefs.h" #include "LayersLogging.h" #include "mozilla/BasicEvents.h" +#include "mozilla/IntegerPrintfMacros.h" #include "mozilla/Move.h" #include "mozilla/Preferences.h" #include "mozilla/TouchEvents.h" diff --git a/gfx/layers/apz/util/APZThreadUtils.cpp b/gfx/layers/apz/util/APZThreadUtils.cpp index 96fbc53aca..d9fb0a4d9e 100644 --- a/gfx/layers/apz/util/APZThreadUtils.cpp +++ b/gfx/layers/apz/util/APZThreadUtils.cpp @@ -80,23 +80,6 @@ APZThreadUtils::RunOnControllerThread(already_AddRefed aTask) #endif } -/*static*/ void -APZThreadUtils::RunDelayedTaskOnCurrentThread(already_AddRefed aTask, - const TimeDuration& aDelay) -{ - if (MessageLoop* messageLoop = MessageLoop::current()) { - messageLoop->PostDelayedTask(Move(aTask), aDelay.ToMilliseconds()); - } else { -#ifdef MOZ_ANDROID_APZ - // Fennec does not have a MessageLoop::current() on the controller thread. - AndroidBridge::Bridge()->PostTaskToUiThread(Move(aTask), aDelay.ToMilliseconds()); -#else - // Other platforms should. - MOZ_RELEASE_ASSERT(false, "This non-Fennec platform should have a MessageLoop::current()"); -#endif - } -} - /*static*/ bool APZThreadUtils::IsControllerThread() { diff --git a/gfx/layers/apz/util/APZThreadUtils.h b/gfx/layers/apz/util/APZThreadUtils.h index 0accd19afc..b16c14789a 100644 --- a/gfx/layers/apz/util/APZThreadUtils.h +++ b/gfx/layers/apz/util/APZThreadUtils.h @@ -7,7 +7,6 @@ #define mozilla_layers_APZThreadUtils_h #include "base/message_loop.h" -#include "mozilla/TimeStamp.h" // for TimeDuration #include "nsITimer.h" namespace mozilla { @@ -53,12 +52,6 @@ public: */ static void RunOnControllerThread(already_AddRefed aTask); - /** - * Runs the given task on the current thread after a delay of |aDelay|. - */ - static void RunDelayedTaskOnCurrentThread(already_AddRefed aTask, - const TimeDuration& aDelay); - /** * Returns true if currently on APZ "controller thread". */ diff --git a/gfx/layers/basic/BasicLayerManager.cpp b/gfx/layers/basic/BasicLayerManager.cpp index 9f1a0ffac5..9fe56e036d 100644 --- a/gfx/layers/basic/BasicLayerManager.cpp +++ b/gfx/layers/basic/BasicLayerManager.cpp @@ -413,7 +413,7 @@ MarkLayersHidden(Layer* aLayer, const IntRect& aClipRect, } { - const Maybe& clipRect = aLayer->GetEffectiveClipRect(); + const Maybe& clipRect = aLayer->GetLocalClipRect(); if (clipRect) { IntRect cr = clipRect->ToUnknownRect(); // clipRect is in the container's coordinate system. Get it into the @@ -492,7 +492,7 @@ ApplyDoubleBuffering(Layer* aLayer, const IntRect& aVisibleRect) IntRect newVisibleRect(aVisibleRect); { - const Maybe& clipRect = aLayer->GetEffectiveClipRect(); + const Maybe& clipRect = aLayer->GetLocalClipRect(); if (clipRect) { IntRect cr = clipRect->ToUnknownRect(); // clipRect is in the container's coordinate system. Get it into the @@ -771,7 +771,7 @@ BasicLayerManager::FlushGroup(PaintLayerContext& aPaintContext, bool aNeedsClipT static void InstallLayerClipPreserves3D(gfxContext* aTarget, Layer* aLayer) { - const Maybe &clipRect = aLayer->GetEffectiveClipRect(); + const Maybe &clipRect = aLayer->GetLocalClipRect(); if (!clipRect) { return; @@ -823,7 +823,7 @@ BasicLayerManager::PaintLayer(gfxContext* aTarget, RenderTraceScope trace("BasicLayerManager::PaintLayer", "707070"); - const Maybe& clipRect = aLayer->GetEffectiveClipRect(); + const Maybe& clipRect = aLayer->GetLocalClipRect(); BasicContainerLayer* container = static_cast(aLayer->AsContainerLayer()); bool needsGroup = container && container->UseIntermediateSurface(); @@ -871,7 +871,7 @@ BasicLayerManager::PaintLayer(gfxContext* aTarget, // Don't need to clip to visible region again needsClipToVisibleRegion = false; } - + if (is2D) { paintLayerContext.AnnotateOpaqueRect(); } diff --git a/gfx/layers/client/CanvasClient.cpp b/gfx/layers/client/CanvasClient.cpp index 64a8b19916..d03786c3c0 100644 --- a/gfx/layers/client/CanvasClient.cpp +++ b/gfx/layers/client/CanvasClient.cpp @@ -390,6 +390,9 @@ CanvasClientSharedSurface::UpdateRenderer(gfx::IntSize aSize, Renderer& aRendere } } else { mShSurfClient = gl->Screen()->Front(); + if (mShSurfClient && mShSurfClient->GetAllocator() != GetForwarder()) { + mShSurfClient = CloneSurface(mShSurfClient->Surf(), gl->Screen()->Factory()); + } if (!mShSurfClient) { return; } diff --git a/gfx/layers/client/ClientLayerManager.cpp b/gfx/layers/client/ClientLayerManager.cpp index 20bbd9ea1b..cb819b8243 100644 --- a/gfx/layers/client/ClientLayerManager.cpp +++ b/gfx/layers/client/ClientLayerManager.cpp @@ -352,10 +352,6 @@ ClientLayerManager::EndTransaction(DrawPaintedLayerCallback aCallback, MakeSnapshotIfRequired(); } - for (size_t i = 0; i < mTexturePools.Length(); i++) { - mTexturePools[i]->ReturnDeferredClients(); - } - mInTransaction = false; mTransactionStart = TimeStamp(); } @@ -432,6 +428,10 @@ ClientLayerManager::DidComposite(uint64_t aTransactionId, for (size_t i = 0; i < mDidCompositeObservers.Length(); i++) { mDidCompositeObservers[i]->DidComposite(); } + + for (size_t i = 0; i < mTexturePools.Length(); i++) { + mTexturePools[i]->ReturnDeferredClients(); + } } void diff --git a/gfx/layers/client/SingleTiledContentClient.h b/gfx/layers/client/SingleTiledContentClient.h index 42b5359a63..d8239ea045 100644 --- a/gfx/layers/client/SingleTiledContentClient.h +++ b/gfx/layers/client/SingleTiledContentClient.h @@ -33,7 +33,7 @@ public: // TextureClientAllocator already_AddRefed GetTextureClient() override; - void ReturnTextureClientDeferred(TextureClient* aClient) override {} + void ReturnTextureClientDeferred(TextureClient* aClient, gfxSharedReadLock* aLock) override {} void ReportClientLost() override {} // ClientTiledLayerBuffer diff --git a/gfx/layers/client/TextureClientPool.cpp b/gfx/layers/client/TextureClientPool.cpp index aec63e311b..8502de933a 100644 --- a/gfx/layers/client/TextureClientPool.cpp +++ b/gfx/layers/client/TextureClientPool.cpp @@ -6,6 +6,7 @@ #include "TextureClientPool.h" #include "CompositableClient.h" #include "mozilla/layers/CompositableForwarder.h" +#include "mozilla/layers/TiledContentClient.h" #include "gfxPrefs.h" @@ -157,16 +158,17 @@ TextureClientPool::ReturnTextureClient(TextureClient *aClient) } void -TextureClientPool::ReturnTextureClientDeferred(TextureClient *aClient) +TextureClientPool::ReturnTextureClientDeferred(TextureClient *aClient, gfxSharedReadLock* aLock) { if (!aClient) { return; } + MOZ_ASSERT(aLock); #ifdef GFX_DEBUG_TRACK_CLIENTS_IN_POOL DebugOnly ok = TestClientPool("defer", aClient, this); MOZ_ASSERT(ok); #endif - mTextureClientsDeferred.push(aClient); + mTextureClientsDeferred.push_back(TextureClientHolder(aClient, aLock)); TCP_LOG("TexturePool %p had client %p defer-returned, size %u outstanding %u\n", this, aClient, mTextureClientsDeferred.size(), mOutstandingClients); ShrinkToMaximumSize(); @@ -188,9 +190,9 @@ TextureClientPool::ShrinkToMaximumSize() MOZ_ASSERT(mOutstandingClients > 0); mOutstandingClients--; TCP_LOG("TexturePool %p dropped deferred client %p; %u remaining\n", - this, mTextureClientsDeferred.top().get(), + this, mTextureClientsDeferred.front().mTextureClient.get(), mTextureClientsDeferred.size() - 1); - mTextureClientsDeferred.pop(); + mTextureClientsDeferred.pop_front(); } else { if (!mTextureClients.size()) { // Getting here means we're over our desired number of TextureClients @@ -211,6 +213,16 @@ TextureClientPool::ShrinkToMaximumSize() void TextureClientPool::ShrinkToMinimumSize() { + ReturnUnlockedClients(); + + while (!mTextureClientsDeferred.empty()) { + MOZ_ASSERT(mOutstandingClients > 0); + mOutstandingClients--; + TCP_LOG("TexturePool %p releasing deferred client %p\n", + this, mTextureClientsDeferred.front().mTextureClient.get()); + mTextureClientsDeferred.pop_front(); + } + TCP_LOG("TexturePool %p shrinking to minimum size %u\n", this, sMinCacheSize); while (mTextureClients.size() > sMinCacheSize) { TCP_LOG("TexturePool %p popped %p; shrunk to %u\n", @@ -224,13 +236,12 @@ TextureClientPool::ReturnDeferredClients() { TCP_LOG("TexturePool %p returning %u deferred clients to pool\n", this, mTextureClientsDeferred.size()); - while (!mTextureClientsDeferred.empty()) { - mTextureClients.push(mTextureClientsDeferred.top()); - mTextureClientsDeferred.pop(); - MOZ_ASSERT(mOutstandingClients > 0); - mOutstandingClients--; + if (mTextureClientsDeferred.empty()) { + return; } + + ReturnUnlockedClients(); ShrinkToMaximumSize(); // Kick off the pool shrinking timer if there are still more unused texture @@ -242,6 +253,24 @@ TextureClientPool::ReturnDeferredClients() } } +void +TextureClientPool::ReturnUnlockedClients() +{ + for (auto it = mTextureClientsDeferred.begin(); it != mTextureClientsDeferred.end();) { + MOZ_ASSERT((*it).mLock->GetReadCount() >= 1); + // Last count is held by the lock itself. + if ((*it).mLock->GetReadCount() == 1) { + mTextureClients.push((*it).mTextureClient); + it = mTextureClientsDeferred.erase(it); + + MOZ_ASSERT(mOutstandingClients > 0); + mOutstandingClients--; + } else { + it++; + } + } +} + void TextureClientPool::ReportClientLost() { @@ -264,8 +293,8 @@ TextureClientPool::Clear() MOZ_ASSERT(mOutstandingClients > 0); mOutstandingClients--; TCP_LOG("TexturePool %p releasing deferred client %p\n", - this, mTextureClientsDeferred.top().get()); - mTextureClientsDeferred.pop(); + this, mTextureClientsDeferred.front().mTextureClient.get()); + mTextureClientsDeferred.pop_front(); } } diff --git a/gfx/layers/client/TextureClientPool.h b/gfx/layers/client/TextureClientPool.h index 9c72030055..e6b764a3ca 100644 --- a/gfx/layers/client/TextureClientPool.h +++ b/gfx/layers/client/TextureClientPool.h @@ -12,12 +12,14 @@ #include "TextureClient.h" #include "nsITimer.h" #include +#include namespace mozilla { namespace layers { class ISurfaceAllocator; class CompositableForwarder; +class gfxSharedReadLock; class TextureClientAllocator { @@ -32,7 +34,7 @@ public: * Return a TextureClient that is not yet ready to be reused, but will be * imminently. */ - virtual void ReturnTextureClientDeferred(TextureClient *aClient) = 0; + virtual void ReturnTextureClientDeferred(TextureClient *aClient, gfxSharedReadLock* aLock) = 0; virtual void ReportClientLost() = 0; }; @@ -70,7 +72,7 @@ public: * Return a TextureClient that is not yet ready to be reused, but will be * imminently. */ - void ReturnTextureClientDeferred(TextureClient *aClient) override; + void ReturnTextureClientDeferred(TextureClient *aClient, gfxSharedReadLock* aLock) override; /** * Attempt to shrink the pool so that there are no more than @@ -111,6 +113,8 @@ public: void Destroy(); private: + void ReturnUnlockedClients(); + // The minimum size of the pool (the number of tiles that will be kept after // shrinking). static const uint32_t sMinCacheSize = 0; @@ -137,11 +141,21 @@ private: /// existence is always mOutstandingClients + the size of mTextureClients. uint32_t mOutstandingClients; + struct TextureClientHolder { + RefPtr mTextureClient; + RefPtr mLock; + + TextureClientHolder(TextureClient* aTextureClient, gfxSharedReadLock* aLock) + : mTextureClient(aTextureClient), mLock(aLock) + {} + }; + // On b2g gonk, std::queue might be a better choice. // On ICS, fence wait happens implicitly before drawing. // Since JB, fence wait happens explicitly when fetching a client from the pool. std::stack > mTextureClients; - std::stack > mTextureClientsDeferred; + + std::list mTextureClientsDeferred; RefPtr mTimer; RefPtr mSurfaceAllocator; }; diff --git a/gfx/layers/client/TiledContentClient.cpp b/gfx/layers/client/TiledContentClient.cpp index 213e6a60f0..f767e10949 100644 --- a/gfx/layers/client/TiledContentClient.cpp +++ b/gfx/layers/client/TiledContentClient.cpp @@ -358,7 +358,8 @@ gfxMemorySharedReadLock::gfxMemorySharedReadLock() gfxMemorySharedReadLock::~gfxMemorySharedReadLock() { - MOZ_ASSERT(mReadCount == 0); + // One read count that is added in constructor. + MOZ_ASSERT(mReadCount == 1); MOZ_COUNT_DTOR(gfxMemorySharedReadLock); } @@ -405,6 +406,12 @@ gfxShmSharedReadLock::gfxShmSharedReadLock(ClientIPCAllocator* aAllocator) gfxShmSharedReadLock::~gfxShmSharedReadLock() { + auto fwd = mAllocator->AsLayerForwarder(); + if (fwd) { + // Release one read count that is added in constructor. + // The count is kept for calling GetReadCount() by TextureClientPool. + ReadUnlock(); + } MOZ_COUNT_DTOR(gfxShmSharedReadLock); } @@ -673,12 +680,11 @@ TileClient::DiscardFrontBuffer() mFrontBuffer->RemoveFromCompositable(mCompositableClient); } - mAllocator->ReturnTextureClientDeferred(mFrontBuffer); + mAllocator->ReturnTextureClientDeferred(mFrontBuffer, mFrontLock); if (mFrontBufferOnWhite) { mFrontBufferOnWhite->RemoveFromCompositable(mCompositableClient); - mAllocator->ReturnTextureClientDeferred(mFrontBufferOnWhite); + mAllocator->ReturnTextureClientDeferred(mFrontBufferOnWhite, mFrontLock); } - mFrontLock->ReadUnlock(); if (mFrontBuffer->IsLocked()) { mFrontBuffer->Unlock(); } @@ -705,12 +711,11 @@ TileClient::DiscardBackBuffer() mAllocator->ReportClientLost(); } } else { - mAllocator->ReturnTextureClientDeferred(mBackBuffer); + mAllocator->ReturnTextureClientDeferred(mBackBuffer, mBackLock); if (mBackBufferOnWhite) { - mAllocator->ReturnTextureClientDeferred(mBackBufferOnWhite); + mAllocator->ReturnTextureClientDeferred(mBackBufferOnWhite, mBackLock); } } - mBackLock->ReadUnlock(); if (mBackBuffer->IsLocked()) { mBackBuffer->Unlock(); } @@ -744,11 +749,6 @@ TileClient::GetBackBuffer(const nsIntRegion& aDirtyRegion, if (!mBackBuffer || mBackLock->GetReadCount() > 1) { - if (mBackLock) { - // Before we Replacing the lock by another one we need to unlock it! - mBackLock->ReadUnlock(); - } - if (mBackBuffer) { // Our current back-buffer is still locked by the compositor. This can occur // when the client is producing faster than the compositor can consume. In diff --git a/gfx/layers/composite/ContainerLayerComposite.cpp b/gfx/layers/composite/ContainerLayerComposite.cpp index 9f9ab69465..557ec6185b 100755 --- a/gfx/layers/composite/ContainerLayerComposite.cpp +++ b/gfx/layers/composite/ContainerLayerComposite.cpp @@ -542,8 +542,8 @@ RenderMinimap(ContainerT* aContainer, LayerManagerComposite* aManager, float scaleFactorX; float scaleFactorY; Rect dest = Rect(aClipRect.ToUnknownRect()); - if (aLayer->GetEffectiveClipRect()) { - dest = Rect(aLayer->GetEffectiveClipRect().value().ToUnknownRect()); + if (aLayer->GetLocalClipRect()) { + dest = Rect(aLayer->GetLocalClipRect().value().ToUnknownRect()); } else { dest = aContainer->GetEffectiveTransform().Inverse().TransformBounds(dest); } diff --git a/gfx/layers/composite/ImageHost.cpp b/gfx/layers/composite/ImageHost.cpp index e72e0663b7..369076405c 100644 --- a/gfx/layers/composite/ImageHost.cpp +++ b/gfx/layers/composite/ImageHost.cpp @@ -332,10 +332,6 @@ ImageHost::Composite(LayerComposite* aLayer, TimedImage* img = &mImages[imageIndex]; img->mTextureHost->SetCompositor(GetCompositor()); SetCurrentTextureHost(img->mTextureHost); - // Make sure the front buffer has a compositor - if (mCurrentTextureSource) { - mCurrentTextureSource->SetCompositor(GetCompositor()); - } { AutoLockCompositableHost autoLock(this); diff --git a/gfx/layers/composite/LayerManagerComposite.cpp b/gfx/layers/composite/LayerManagerComposite.cpp index 35ce432398..a49450e015 100644 --- a/gfx/layers/composite/LayerManagerComposite.cpp +++ b/gfx/layers/composite/LayerManagerComposite.cpp @@ -164,11 +164,11 @@ void LayerManagerComposite::BeginTransaction() { mInTransaction = true; - + if (!mCompositor->Ready()) { return; } - + mIsCompositorReady = true; } @@ -176,7 +176,7 @@ void LayerManagerComposite::BeginTransactionWithDrawTarget(DrawTarget* aTarget, const IntRect& aRect) { mInTransaction = true; - + if (!mCompositor->Ready()) { return; } @@ -257,7 +257,7 @@ LayerManagerComposite::PostProcessLayers(Layer* aLayer, localOpaque.MoveBy(-*integerTranslation); } } - + // Compute a clip that's the combination of our layer clip with the clip // from our ancestors. LayerComposite* composite = aLayer->AsLayerComposite(); @@ -1565,6 +1565,19 @@ LayerComposite::GetFullyRenderedRegion() { } } +const Matrix4x4 +LayerComposite::GetShadowTransform() { + Matrix4x4 transform = mShadowTransform; + Layer* layer = GetLayer(); + + transform.PostScale(layer->GetPostXScale(), layer->GetPostYScale(), 1.0f); + if (const ContainerLayer* c = layer->AsContainerLayer()) { + transform.PreScale(c->GetPreXScale(), c->GetPreYScale(), 1.0f); + } + + return transform; +} + bool LayerComposite::HasStaleCompositor() const { diff --git a/gfx/layers/composite/LayerManagerComposite.h b/gfx/layers/composite/LayerManagerComposite.h index e5f30143ac..e6fbfd6f2d 100644 --- a/gfx/layers/composite/LayerManagerComposite.h +++ b/gfx/layers/composite/LayerManagerComposite.h @@ -541,6 +541,7 @@ public: const Maybe& GetShadowClipRect() { return mShadowClipRect; } const LayerIntRegion& GetShadowVisibleRegion() { return mShadowVisibleRegion; } const gfx::Matrix4x4& GetShadowBaseTransform() { return mShadowTransform; } + const gfx::Matrix4x4 GetShadowTransform(); bool GetShadowTransformSetByAnimation() { return mShadowTransformSetByAnimation; } bool HasLayerBeenComposited() { return mLayerComposited; } gfx::IntRect GetClearRect() { return mClearRect; } diff --git a/gfx/layers/composite/TextureHost.cpp b/gfx/layers/composite/TextureHost.cpp index e66b9cfda3..c552cf1e14 100644 --- a/gfx/layers/composite/TextureHost.cpp +++ b/gfx/layers/composite/TextureHost.cpp @@ -558,7 +558,8 @@ BufferTextureHost::PrepareTextureSource(CompositableTextureSourceRef& aTexture) || (mFormat == gfx::SurfaceFormat::YUV && mCompositor && mCompositor->SupportsEffect(EffectTypes::YCBCR) - && texture->GetNextSibling()) + && texture->GetNextSibling() + && texture->GetNextSibling()->GetNextSibling()) || (mFormat == gfx::SurfaceFormat::YUV && mCompositor && !mCompositor->SupportsEffect(EffectTypes::YCBCR) @@ -573,6 +574,15 @@ BufferTextureHost::PrepareTextureSource(CompositableTextureSourceRef& aTexture) mFirstSource = texture; mFirstSource->SetOwner(this); mNeedsFullUpdate = true; + + // It's possible that texture belonged to a different compositor, + // so make sure we update it (and all of its siblings) to the + // current one. + RefPtr it = mFirstSource; + while (it) { + it->SetCompositor(mCompositor); + it = it->GetNextSibling(); + } } } @@ -784,7 +794,8 @@ ShmemTextureHost::ShmemTextureHost(const ipc::Shmem& aShmem, // available, even though we did on the child process. // As a result this texture will be in an invalid state and Lock will // always fail. - gfxCriticalError() << "Failed to create a valid ShmemTextureHost"; + + gfxCriticalNote << "Failed to create a valid ShmemTextureHost"; } MOZ_COUNT_CTOR(ShmemTextureHost); diff --git a/gfx/layers/opengl/CompositorOGL.cpp b/gfx/layers/opengl/CompositorOGL.cpp index 257be9d487..3aef5cfc5e 100644 --- a/gfx/layers/opengl/CompositorOGL.cpp +++ b/gfx/layers/opengl/CompositorOGL.cpp @@ -1092,6 +1092,7 @@ CompositorOGL::DrawQuad(const Rect& aRect, aOpacity = 1.f; } + bool createdMixBlendBackdropTexture = false; GLuint mixBlendBackdrop = 0; gfx::CompositionOp blendMode = gfx::CompositionOp::OP_OVER; @@ -1124,9 +1125,19 @@ CompositorOGL::DrawQuad(const Rect& aRect, if (BlendOpIsMixBlendMode(blendMode)) { gfx::Matrix4x4 backdropTransform; - gfx::IntRect rect = ComputeBackdropCopyRect(aRect, aClipRect, aTransform, &backdropTransform); - mixBlendBackdrop = CreateTexture(rect, true, mCurrentRenderTarget->GetFBO()); + if (gl()->IsExtensionSupported(GLContext::NV_texture_barrier)) { + // The NV_texture_barrier extension lets us read directly from the + // backbuffer. Let's do that. + // We need to tell OpenGL about this, so that it can make sure everything + // on the GPU is happening in the right order. + gl()->fTextureBarrier(); + mixBlendBackdrop = mCurrentRenderTarget->GetTextureHandle(); + } else { + gfx::IntRect rect = ComputeBackdropCopyRect(aRect, aClipRect, aTransform, &backdropTransform); + mixBlendBackdrop = CreateTexture(rect, true, mCurrentRenderTarget->GetFBO()); + createdMixBlendBackdropTexture = true; + } program->SetBackdropTransform(backdropTransform); } @@ -1443,7 +1454,7 @@ CompositorOGL::DrawQuad(const Rect& aRect, gl()->fBlendFuncSeparate(LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA, LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA); } - if (mixBlendBackdrop) { + if (createdMixBlendBackdropTexture) { gl()->fDeleteTextures(1, &mixBlendBackdrop); } diff --git a/gfx/tests/crashtests/1134549-1.svg b/gfx/tests/crashtests/1134549-1.svg new file mode 100644 index 0000000000..1d0d5484a8 --- /dev/null +++ b/gfx/tests/crashtests/1134549-1.svg @@ -0,0 +1,14 @@ + + + + + + + + Eisack + + + diff --git a/gfx/tests/crashtests/crashtests.list b/gfx/tests/crashtests/crashtests.list index b577715a31..ccce929571 100644 --- a/gfx/tests/crashtests/crashtests.list +++ b/gfx/tests/crashtests/crashtests.list @@ -116,3 +116,4 @@ load 944579.svg load 944579.html pref(security.fileuri.strict_origin_policy,false) load 950000.html load 1034403-1.html +load 1134549-1.svg diff --git a/gfx/thebes/gfxFontEntry.cpp b/gfx/thebes/gfxFontEntry.cpp index 7863274930..91e0e0f53a 100644 --- a/gfx/thebes/gfxFontEntry.cpp +++ b/gfx/thebes/gfxFontEntry.cpp @@ -1098,6 +1098,44 @@ gfxFontEntry::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, aSizes->mFontTableCacheSize += mFontTableCache->SizeOfIncludingThis(aMallocSizeOf); } + + // If the font has UVS data, we count that as part of the character map. + if (mUVSData) { + aSizes->mCharMapsSize += aMallocSizeOf(mUVSData.get()); + } + + // The following, if present, are essentially cached forms of font table + // data, so we'll accumulate them together with the basic table cache. + if (mUserFontData) { + aSizes->mFontTableCacheSize += + mUserFontData->SizeOfIncludingThis(aMallocSizeOf); + } + if (mSVGGlyphs) { + aSizes->mFontTableCacheSize += + mSVGGlyphs->SizeOfIncludingThis(aMallocSizeOf); + } + if (mMathTable) { + aSizes->mFontTableCacheSize += + mMathTable->SizeOfIncludingThis(aMallocSizeOf); + } + if (mSupportedFeatures) { + aSizes->mFontTableCacheSize += + mSupportedFeatures->ShallowSizeOfIncludingThis(aMallocSizeOf); + } + if (mFeatureInputs) { + aSizes->mFontTableCacheSize += + mFeatureInputs->ShallowSizeOfIncludingThis(aMallocSizeOf); + for (auto iter = mFeatureInputs->ConstIter(); !iter.Done(); + iter.Next()) { + // There's no API to get the real size of an hb_set, so we'll use + // an approximation based on knowledge of the implementation. + aSizes->mFontTableCacheSize += 8192; // vector of 64K bits + } + } + // We don't include the size of mCOLR/mCPAL here, because (depending on the + // font backend implementation) they will either wrap blocks of data owned + // by the system (and potentially shared), or tables that are in our font + // table cache and therefore already counted. } void diff --git a/gfx/thebes/gfxMathTable.cpp b/gfx/thebes/gfxMathTable.cpp index c08d5d4c69..b95a76a35d 100644 --- a/gfx/thebes/gfxMathTable.cpp +++ b/gfx/thebes/gfxMathTable.cpp @@ -474,3 +474,13 @@ gfxMathTable::SelectGlyphConstruction(uint32_t aGlyphID, bool aVertical) mGlyphConstruction = reinterpret_cast(start + offset); } + +size_t +gfxMathTable::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const +{ + // We don't include the size of mMathTable here, because (depending on the + // font backend implementation) it will either wrap a block of data owned + // by the system (and potentially shared), or a table that's in our font + // table cache and therefore already counted. + return aMallocSizeOf(this); +} diff --git a/gfx/thebes/gfxMathTable.h b/gfx/thebes/gfxMathTable.h index 55bd6686a8..4745c0802f 100644 --- a/gfx/thebes/gfxMathTable.h +++ b/gfx/thebes/gfxMathTable.h @@ -76,6 +76,8 @@ public: bool GetMathVariantsParts(uint32_t aGlyphID, bool aVertical, uint32_t aGlyphs[4]); + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + protected: friend class gfxFontEntry; // This allows gfxFontEntry to verify the validity of the main headers diff --git a/gfx/thebes/gfxSVGGlyphs.cpp b/gfx/thebes/gfxSVGGlyphs.cpp index a38b44ac9a..f53f19daa0 100644 --- a/gfx/thebes/gfxSVGGlyphs.cpp +++ b/gfx/thebes/gfxSVGGlyphs.cpp @@ -256,6 +256,22 @@ gfxSVGGlyphs::HasSVGGlyph(uint32_t aGlyphId) return !!GetGlyphElement(aGlyphId); } +size_t +gfxSVGGlyphs::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const +{ + // We don't include the size of mSVGData here, because (depending on the + // font backend implementation) it will either wrap a block of data owned + // by the system (and potentially shared), or a table that's in our font + // table cache and therefore already counted. + size_t result = aMallocSizeOf(this) + + mGlyphDocs.ShallowSizeOfExcludingThis(aMallocSizeOf) + + mGlyphIdMap.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (auto iter = mGlyphDocs.ConstIter(); !iter.Done(); iter.Next()) { + result += iter.Data()->SizeOfIncludingThis(aMallocSizeOf); + } + return result; +} + Element * gfxSVGGlyphsDocument::GetGlyphElement(uint32_t aGlyphId) { @@ -437,6 +453,15 @@ gfxSVGGlyphsDocument::InsertGlyphId(Element *aGlyphElement) mGlyphIdMap.Put(id, aGlyphElement); } +size_t +gfxSVGGlyphsDocument::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const +{ + return aMallocSizeOf(this) + + mGlyphIdMap.ShallowSizeOfExcludingThis(aMallocSizeOf) + + mSVGGlyphsDocumentURI.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + +} + void gfxTextContextPaint::InitStrokeGeometry(gfxContext *aContext, float devUnitsPerSVGUnit) diff --git a/gfx/thebes/gfxSVGGlyphs.h b/gfx/thebes/gfxSVGGlyphs.h index 1a81cd0107..34c6898ac2 100644 --- a/gfx/thebes/gfxSVGGlyphs.h +++ b/gfx/thebes/gfxSVGGlyphs.h @@ -49,6 +49,8 @@ public: virtual void DidRefresh() override; + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + private: nsresult ParseDocument(const uint8_t *aBuffer, uint32_t aBufLen); @@ -128,6 +130,8 @@ public: bool GetGlyphExtents(uint32_t aGlyphId, const gfxMatrix& aSVGToAppSpace, gfxRect *aResult); + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + private: Element *GetGlyphElement(uint32_t aGlyphId); diff --git a/gfx/thebes/gfxUserFontSet.cpp b/gfx/thebes/gfxUserFontSet.cpp index ad3b86c358..3d416e0d71 100644 --- a/gfx/thebes/gfxUserFontSet.cpp +++ b/gfx/thebes/gfxUserFontSet.cpp @@ -52,11 +52,11 @@ public: NS_Free(mPtr); } - // return the buffer, and give up ownership of it - // so the caller becomes responsible to call NS_Free - // when finished with it + // Return the buffer, resized to fit its contents (as it may have been + // over-allocated during growth), and give up ownership of it so the + // caller becomes responsible to call free() when finished with it. void* forget() { - void* p = mPtr; + void* p = moz_xrealloc(mPtr, mOff); mPtr = nullptr; return p; } @@ -249,14 +249,14 @@ gfxUserFontEntry::SanitizeOpenTypeData(const uint8_t* aData, ExpandingMemoryStream output(lengthHint, 1024 * 1024 * 256); gfxOTSContext otsContext(this); - - if (otsContext.Process(&output, aData, aLength)) { - aSaneLength = output.Tell(); - return static_cast(output.forget()); - } else { + if (!otsContext.Process(&output, aData, aLength)) { + // Failed to decode/sanitize the font, so discard it. aSaneLength = 0; return nullptr; } + + aSaneLength = output.Tell(); + return static_cast(output.forget()); } void @@ -295,6 +295,16 @@ gfxUserFontEntry::StoreUserFontData(gfxFontEntry* aFontEntry, } } +size_t +gfxUserFontData::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const +{ + return aMallocSizeOf(this) + + mMetadata.ShallowSizeOfExcludingThis(aMallocSizeOf) + + mLocalName.SizeOfExcludingThisIfUnshared(aMallocSizeOf) + + mRealName.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + // Not counting mURI and mPrincipal, as those will be shared. +} + void gfxUserFontEntry::GetFamilyNameAndURIForLogging(nsACString& aFamilyName, nsACString& aURI) diff --git a/gfx/thebes/gfxUserFontSet.h b/gfx/thebes/gfxUserFontSet.h index d9be9a9648..d6a67fe489 100644 --- a/gfx/thebes/gfxUserFontSet.h +++ b/gfx/thebes/gfxUserFontSet.h @@ -101,6 +101,8 @@ public: { } virtual ~gfxUserFontData() { } + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + nsTArray mMetadata; // woff metadata block (compressed), if any nsCOMPtr mURI; // URI of the source, if it was url() nsCOMPtr mPrincipal; // principal for the download, if url() diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..e8c6bf7bb4 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..ca653b435c --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +#Tue Apr 12 09:52:06 CEST 2016 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip +distributionSha256Sum=496d60c331f8666f99b66d08ff67a880697a7e85a9d9b76ff08814cf97f61a4c diff --git a/gradlew b/gradlew new file mode 100644 index 0000000000..97fac783e1 --- /dev/null +++ b/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/hal/gonk/GonkDiskSpaceWatcher.cpp b/hal/gonk/GonkDiskSpaceWatcher.cpp index 777619bc9f..048fccf137 100644 --- a/hal/gonk/GonkDiskSpaceWatcher.cpp +++ b/hal/gonk/GonkDiskSpaceWatcher.cpp @@ -7,17 +7,16 @@ #include #include #include +#include "base/message_loop.h" +#include "DiskSpaceWatcher.h" +#include "fanotify.h" #include "nsIObserverService.h" #include "nsIDiskSpaceWatcher.h" -#include "mozilla/ModuleUtils.h" -#include "nsAutoPtr.h" #include "nsThreadUtils.h" -#include "base/message_loop.h" +#include "nsXULAppAPI.h" +#include "mozilla/ModuleUtils.h" #include "mozilla/Preferences.h" #include "mozilla/Services.h" -#include "nsXULAppAPI.h" -#include "fanotify.h" -#include "DiskSpaceWatcher.h" using namespace mozilla; @@ -177,15 +176,17 @@ GonkDiskSpaceWatcher::DoStart() NS_ASSERTION(XRE_GetIOMessageLoop() == MessageLoopForIO::current(), "Not on the correct message loop"); - mFd = fanotify_init(FAN_CLASS_NOTIF, FAN_CLOEXEC); + mFd = fanotify_init(FAN_CLASS_NOTIF, FAN_CLOEXEC | O_LARGEFILE); if (mFd == -1) { if (errno == ENOSYS) { - NS_WARNING("Warning: No fanotify support in this device's kernel.\n"); + // Don't change these printf_stderr since we need these logs even + // in opt builds. + printf_stderr("Warning: No fanotify support in this device's kernel.\n"); #if ANDROID_VERSION >= 19 MOZ_CRASH("Fanotify support must be enabled in the kernel."); #endif } else { - NS_WARNING("Error calling fanotify_init()"); + printf_stderr("Error calling fanotify_init()"); } return; } diff --git a/hal/gonk/GonkHal.cpp b/hal/gonk/GonkHal.cpp index 05fe4a6c14..d395526185 100644 --- a/hal/gonk/GonkHal.cpp +++ b/hal/gonk/GonkHal.cpp @@ -1125,7 +1125,7 @@ EnableAlarm() return false; } - nsAutoPtr alarmData(new AlarmData(alarmFd)); + UniquePtr alarmData = MakeUnique(alarmFd); struct sigaction actions; memset(&actions, 0, sizeof(actions)); @@ -1144,7 +1144,7 @@ EnableAlarm() int status = pthread_create(&sAlarmFireWatcherThread, &attr, WaitForAlarm, alarmData.get()); if (status) { - alarmData = nullptr; + alarmData.reset(); HAL_LOG("Failed to create alarm-watcher thread. Status: %d.", status); return false; } @@ -1152,7 +1152,7 @@ EnableAlarm() pthread_attr_destroy(&attr); // The thread owns this now. We only hold a pointer. - sAlarmData = alarmData.forget(); + sAlarmData = alarmData.release(); return true; } @@ -1295,7 +1295,7 @@ OomVictimLogger::Observe( // deprecated the old klog defs. // Our current bionic does not hit this // change yet so handle the future change. - // (ICS doesn't have KLOG_SIZE_BUFFER but + // (ICS doesn't have KLOG_SIZE_BUFFER but // JB and onwards does.) #define KLOG_SIZE_BUFFER KLOG_WRITE #endif @@ -1461,7 +1461,7 @@ private: nsCString cgroupName = mGroup; /* If mGroup is empty, our cgroup.procs file is the root procs file, - * located at /sys/fs/cgroup/memory/cgroup.procs. Otherwise our procs + * located at /sys/fs/cgroup/memory/cgroup.procs. Otherwise our procs * file is /sys/fs/cgroup/memory/NAME/cgroup.procs. */ if (!mGroup.IsEmpty()) { diff --git a/hal/gonk/GonkSensorsInterface.cpp b/hal/gonk/GonkSensorsInterface.cpp index c6c7658065..51e1ff50c5 100644 --- a/hal/gonk/GonkSensorsInterface.cpp +++ b/hal/gonk/GonkSensorsInterface.cpp @@ -259,7 +259,7 @@ GonkSensorsInterface::Connect(GonkSensorsNotificationHandler* aNotificationHandl mResultHandlerQ.AppendElement(aRes); if (!mProtocol) { - mProtocol = new GonkSensorsProtocol(); + mProtocol = MakeUnique(); } if (!mListenSocket) { @@ -269,7 +269,7 @@ GonkSensorsInterface::Connect(GonkSensorsNotificationHandler* aNotificationHandl // Init, step 1: Listen for data channel... */ if (!mDataSocket) { - mDataSocket = new DaemonSocket(mProtocol, this, DATA_SOCKET); + mDataSocket = new DaemonSocket(mProtocol.get(), this, DATA_SOCKET); } else if (mDataSocket->GetConnectionStatus() == SOCKET_CONNECTED) { // Command channel should not be open; let's close it. mDataSocket->Close(); @@ -334,24 +334,24 @@ GonkSensorsRegistryInterface* GonkSensorsInterface::GetSensorsRegistryInterface() { if (mRegistryInterface) { - return mRegistryInterface; + return mRegistryInterface.get(); } - mRegistryInterface = new GonkSensorsRegistryInterface(mProtocol); + mRegistryInterface = MakeUnique(mProtocol.get()); - return mRegistryInterface; + return mRegistryInterface.get(); } GonkSensorsPollInterface* GonkSensorsInterface::GetSensorsPollInterface() { if (mPollInterface) { - return mPollInterface; + return mPollInterface.get(); } - mPollInterface = new GonkSensorsPollInterface(mProtocol); + mPollInterface = MakeUnique(mProtocol.get()); - return mPollInterface; + return mPollInterface.get(); } GonkSensorsInterface::GonkSensorsInterface() diff --git a/hal/gonk/GonkSensorsInterface.h b/hal/gonk/GonkSensorsInterface.h index 44126ce876..6e356dc364 100644 --- a/hal/gonk/GonkSensorsInterface.h +++ b/hal/gonk/GonkSensorsInterface.h @@ -17,6 +17,7 @@ #include #include #include +#include #include "SensorsTypes.h" namespace mozilla { @@ -174,14 +175,14 @@ private: nsCString mListenSocketName; RefPtr mListenSocket; RefPtr mDataSocket; - nsAutoPtr mProtocol; + UniquePtr mProtocol; nsTArray > mResultHandlerQ; GonkSensorsNotificationHandler* mNotificationHandler; - nsAutoPtr mRegistryInterface; - nsAutoPtr mPollInterface; + UniquePtr mRegistryInterface; + UniquePtr mPollInterface; }; } // namespace hal diff --git a/hal/gonk/GonkSensorsPollInterface.cpp b/hal/gonk/GonkSensorsPollInterface.cpp index 010deb656a..d4edc2e7af 100644 --- a/hal/gonk/GonkSensorsPollInterface.cpp +++ b/hal/gonk/GonkSensorsPollInterface.cpp @@ -6,6 +6,7 @@ #include "GonkSensorsPollInterface.h" #include "HalLog.h" +#include namespace mozilla { namespace hal { @@ -124,18 +125,18 @@ GonkSensorsPollModule::EnableSensorCmd(int32_t aId, GonkSensorsPollResultHandler { MOZ_ASSERT(NS_IsMainThread()); - nsAutoPtr pdu( - new DaemonSocketPDU(SERVICE_ID, OPCODE_ENABLE_SENSOR, 0)); + UniquePtr pdu = + MakeUnique(SERVICE_ID, OPCODE_ENABLE_SENSOR, 0); nsresult rv = PackPDU(aId, *pdu); if (NS_FAILED(rv)) { return rv; } - rv = Send(pdu, aRes); + rv = Send(pdu.get(), aRes); if (NS_FAILED(rv)) { return rv; } - Unused << pdu.forget(); + Unused << pdu.release(); return NS_OK; } @@ -144,18 +145,18 @@ GonkSensorsPollModule::DisableSensorCmd(int32_t aId, GonkSensorsPollResultHandle { MOZ_ASSERT(NS_IsMainThread()); - nsAutoPtr pdu( - new DaemonSocketPDU(SERVICE_ID, OPCODE_DISABLE_SENSOR, 0)); + UniquePtr pdu = + MakeUnique(SERVICE_ID, OPCODE_DISABLE_SENSOR, 0); nsresult rv = PackPDU(aId, *pdu); if (NS_FAILED(rv)) { return rv; } - rv = Send(pdu, aRes); + rv = Send(pdu.get(), aRes); if (NS_FAILED(rv)) { return rv; } - Unused << pdu.forget(); + Unused << pdu.release(); return NS_OK; } @@ -165,8 +166,8 @@ GonkSensorsPollModule::SetPeriodCmd(int32_t aId, uint64_t aPeriod, { MOZ_ASSERT(NS_IsMainThread()); - nsAutoPtr pdu( - new DaemonSocketPDU(SERVICE_ID, OPCODE_SET_PERIOD, 0)); + UniquePtr pdu = + MakeUnique(SERVICE_ID, OPCODE_SET_PERIOD, 0); nsresult rv = PackPDU(aId, *pdu); if (NS_FAILED(rv)) { @@ -176,11 +177,11 @@ GonkSensorsPollModule::SetPeriodCmd(int32_t aId, uint64_t aPeriod, if (NS_FAILED(rv)) { return rv; } - rv = Send(pdu, aRes); + rv = Send(pdu.get(), aRes); if (NS_FAILED(rv)) { return rv; } - Unused << pdu.forget(); + Unused << pdu.release(); return NS_OK; } diff --git a/hal/gonk/GonkSensorsPollInterface.h b/hal/gonk/GonkSensorsPollInterface.h index d25fed4f3e..89381a9bdb 100644 --- a/hal/gonk/GonkSensorsPollInterface.h +++ b/hal/gonk/GonkSensorsPollInterface.h @@ -273,7 +273,8 @@ private: class GonkSensorsPollInterface final { public: - friend class GonkSensorsInterface; + GonkSensorsPollInterface(GonkSensorsPollModule* aModule); + ~GonkSensorsPollInterface(); /** * This method sets the notification handler for poll notifications. Call @@ -326,11 +327,7 @@ public: */ void SetPeriod(int32_t aId, uint64_t aPeriod, GonkSensorsPollResultHandler* aRes); - ~GonkSensorsPollInterface(); - private: - GonkSensorsPollInterface(GonkSensorsPollModule* aModule); - void DispatchError(GonkSensorsPollResultHandler* aRes, SensorsError aError); void DispatchError(GonkSensorsPollResultHandler* aRes, nsresult aRv); diff --git a/hal/gonk/GonkSensorsRegistryInterface.cpp b/hal/gonk/GonkSensorsRegistryInterface.cpp index 0fc318fe2a..601dc7a2a6 100644 --- a/hal/gonk/GonkSensorsRegistryInterface.cpp +++ b/hal/gonk/GonkSensorsRegistryInterface.cpp @@ -7,6 +7,7 @@ #include "GonkSensorsRegistryInterface.h" #include "GonkSensorsHelpers.h" #include "HalLog.h" +#include namespace mozilla { namespace hal { @@ -80,18 +81,18 @@ GonkSensorsRegistryModule::RegisterModuleCmd( { MOZ_ASSERT(NS_IsMainThread()); - nsAutoPtr pdu( - new DaemonSocketPDU(SERVICE_ID, OPCODE_REGISTER_MODULE, 0)); + UniquePtr pdu = + MakeUnique(SERVICE_ID, OPCODE_REGISTER_MODULE, 0); nsresult rv = PackPDU(aId, *pdu); if (NS_FAILED(rv)) { return rv; } - rv = Send(pdu, aRes); + rv = Send(pdu.get(), aRes); if (NS_FAILED(rv)) { return rv; } - Unused << pdu.forget(); + Unused << pdu.release(); return NS_OK; } @@ -101,18 +102,18 @@ GonkSensorsRegistryModule::UnregisterModuleCmd( { MOZ_ASSERT(NS_IsMainThread()); - nsAutoPtr pdu( - new DaemonSocketPDU(SERVICE_ID, OPCODE_UNREGISTER_MODULE, 0)); + UniquePtr pdu = + MakeUnique(SERVICE_ID, OPCODE_UNREGISTER_MODULE, 0); nsresult rv = PackPDU(aId, *pdu); if (NS_FAILED(rv)) { return rv; } - rv = Send(pdu, aRes); + rv = Send(pdu.get(), aRes); if (NS_FAILED(rv)) { return rv; } - Unused << pdu.forget(); + Unused << pdu.release(); return NS_OK; } diff --git a/hal/gonk/GonkSensorsRegistryInterface.h b/hal/gonk/GonkSensorsRegistryInterface.h index e7d64cb1d8..a9d98d653f 100644 --- a/hal/gonk/GonkSensorsRegistryInterface.h +++ b/hal/gonk/GonkSensorsRegistryInterface.h @@ -145,7 +145,8 @@ protected: class GonkSensorsRegistryInterface final { public: - friend class GonkSensorsInterface; + GonkSensorsRegistryInterface(GonkSensorsRegistryModule* aModule); + ~GonkSensorsRegistryInterface(); /** * Sends a RegisterModule command to the Sensors daemon. When the @@ -166,11 +167,7 @@ public: */ void UnregisterModule(uint8_t aId, GonkSensorsRegistryResultHandler* aRes); - ~GonkSensorsRegistryInterface(); - private: - GonkSensorsRegistryInterface(GonkSensorsRegistryModule* aModule); - void DispatchError(GonkSensorsRegistryResultHandler* aRes, SensorsError aError); void DispatchError(GonkSensorsRegistryResultHandler* aRes, diff --git a/layout/generic/nsGfxScrollFrame.cpp b/layout/generic/nsGfxScrollFrame.cpp index 53bce6ab4d..277f4a6318 100644 --- a/layout/generic/nsGfxScrollFrame.cpp +++ b/layout/generic/nsGfxScrollFrame.cpp @@ -2174,6 +2174,11 @@ ScrollFrameHelper::ScrollToWithOrigin(nsPoint aScrollPosition, nsRect scrollRange = GetScrollRangeForClamping(); mDestination = scrollRange.ClampPoint(aScrollPosition); + if (mDestination != aScrollPosition && aOrigin == nsGkAtoms::restore) { + // If we're doing a restore but the scroll position is clamped, promote + // the origin from one that APZ can clobber to one that it can't clobber. + aOrigin = nsGkAtoms::other; + } nsRect range = aRange ? *aRange : nsRect(aScrollPosition, nsSize(0, 0)); @@ -2743,25 +2748,29 @@ ScrollFrameHelper::ScrollToImpl(nsPoint aPt, const nsRect& aRange, nsIAtom* aOri usingDisplayPort, displayPort.IsEqualEdges(oldDisplayPort), mScrollableByAPZ, HasPluginFrames()); if (usingDisplayPort && displayPort.IsEqualEdges(oldDisplayPort)) { - if (LastScrollOrigin() == nsGkAtoms::apz) { - schedulePaint = false; - PAINT_SKIP_LOG("Skipping due to APZ scroll\n"); - } else if (mScrollableByAPZ && !HasPluginFrames()) { - nsIWidget* widget = presContext->GetNearestWidget(); - LayerManager* manager = widget ? widget->GetLayerManager() : nullptr; - if (manager) { - mozilla::layers::FrameMetrics::ViewID id; - DebugOnly success = nsLayoutUtils::FindIDFor(content, &id); - MOZ_ASSERT(success); // we have a displayport, we better have an ID - - // Schedule an empty transaction to carry over the scroll offset update, - // instead of a full transaction. This empty transaction might still get - // squashed into a full transaction if something happens to trigger one. + bool haveScrollLinkedEffects = content->GetComposedDoc()->HasScrollLinkedEffect(); + bool apzDisabled = haveScrollLinkedEffects && gfxPrefs::APZDisableForScrollLinkedEffects(); + if (!apzDisabled) { + if (LastScrollOrigin() == nsGkAtoms::apz) { schedulePaint = false; - manager->SetPendingScrollUpdateForNextTransaction(id, - { mScrollGeneration, CSSPoint::FromAppUnits(GetScrollPosition()) }); - mOuter->SchedulePaint(nsIFrame::PAINT_COMPOSITE_ONLY); - PAINT_SKIP_LOG("Skipping due to APZ-forwarded main-thread scroll\n"); + PAINT_SKIP_LOG("Skipping due to APZ scroll\n"); + } else if (mScrollableByAPZ && !HasPluginFrames()) { + nsIWidget* widget = presContext->GetNearestWidget(); + LayerManager* manager = widget ? widget->GetLayerManager() : nullptr; + if (manager) { + mozilla::layers::FrameMetrics::ViewID id; + DebugOnly success = nsLayoutUtils::FindIDFor(content, &id); + MOZ_ASSERT(success); // we have a displayport, we better have an ID + + // Schedule an empty transaction to carry over the scroll offset update, + // instead of a full transaction. This empty transaction might still get + // squashed into a full transaction if something happens to trigger one. + schedulePaint = false; + manager->SetPendingScrollUpdateForNextTransaction(id, + { mScrollGeneration, CSSPoint::FromAppUnits(GetScrollPosition()) }); + mOuter->SchedulePaint(nsIFrame::PAINT_COMPOSITE_ONLY); + PAINT_SKIP_LOG("Skipping due to APZ-forwarded main-thread scroll\n"); + } } } } diff --git a/layout/generic/nsGridContainerFrame.cpp b/layout/generic/nsGridContainerFrame.cpp index da78dd1bee..6f88a03df7 100644 --- a/layout/generic/nsGridContainerFrame.cpp +++ b/layout/generic/nsGridContainerFrame.cpp @@ -4022,14 +4022,17 @@ nsGridContainerFrame::LineRange::ToPositionAndLengthForAbsPos( // done } else { const nscoord endPos = *aPos + *aLength; - nscoord startPos = - aTracks.GridLineEdge(mStart, GridLineSide::eAfterGridGap); + auto side = mStart == aTracks.mSizes.Length() ? GridLineSide::eBeforeGridGap + : GridLineSide::eAfterGridGap; + nscoord startPos = aTracks.GridLineEdge(mStart, side); *aPos = aGridOrigin + startPos; *aLength = std::max(endPos - *aPos, 0); } } else { if (mStart == kAutoLine) { - nscoord endPos = aTracks.GridLineEdge(mEnd, GridLineSide::eBeforeGridGap); + auto side = mEnd == 0 ? GridLineSide::eAfterGridGap + : GridLineSide::eBeforeGridGap; + nscoord endPos = aTracks.GridLineEdge(mEnd, side); *aLength = std::max(aGridOrigin + endPos, 0); } else { nscoord pos; diff --git a/layout/generic/nsHTMLReflowState.cpp b/layout/generic/nsHTMLReflowState.cpp index fb2bc63674..15fc76ae09 100644 --- a/layout/generic/nsHTMLReflowState.cpp +++ b/layout/generic/nsHTMLReflowState.cpp @@ -643,6 +643,29 @@ nsHTMLReflowState::InitResizeFlags(nsPresContext* aPresContext, nsIAtom* aFrameT // Possibly; in that case we should at least be checking // NS_SUBTREE_DIRTY, I'd think. SetBResize(mCBReflowState->IsBResize()); + } else if (mCBReflowState && !nsLayoutUtils::GetAsBlock(frame)) { + // Some non-block frames (e.g. table frames) aggressively optimize out their + // BSize recomputation when they don't have the BResize flag set. This + // means that if they go from having a computed non-auto height to having an + // auto height and don't have that flag set, they will not actually compute + // their auto height and will just remain at whatever size they already + // were. We can end up in that situation if the child has a percentage + // specified height and the parent changes from non-auto height to auto + // height. When that happens, the parent will typically have the BResize + // flag set, and we want to propagate that flag to the kid. + // + // Ideally it seems like we'd do this for blocks too, of course... but we'd + // really want to restrict it to the percentage height case or something, to + // avoid extra reflows in common cases. Maybe we should be examining + // mStylePosition->BSize(wm).GetUnit() for that purpose? + // + // Note that we _also_ need to set the BResize flag if we have auto + // ComputedBSize() and a dirty subtree, since that might require us to + // change BSize due to kids having been added or removed. + SetBResize(mCBReflowState->IsBResize()); + if (ComputedBSize() == NS_AUTOHEIGHT) { + SetBResize(IsBResize() || NS_SUBTREE_DIRTY(frame)); + } } else if (ComputedBSize() == NS_AUTOHEIGHT) { if (eCompatibility_NavQuirks == aPresContext->CompatibilityMode() && mCBReflowState) { diff --git a/layout/reftests/bugs/1263845-ref.html b/layout/reftests/bugs/1263845-ref.html new file mode 100644 index 0000000000..65c67a95a7 --- /dev/null +++ b/layout/reftests/bugs/1263845-ref.html @@ -0,0 +1,10 @@ + +
+ + + + +
+ This is some text +
+
diff --git a/layout/reftests/bugs/1263845.html b/layout/reftests/bugs/1263845.html new file mode 100644 index 0000000000..3bc9c28b99 --- /dev/null +++ b/layout/reftests/bugs/1263845.html @@ -0,0 +1,15 @@ + +
+ + + + +
+ This is some text +
+
+ diff --git a/layout/reftests/bugs/reftest.list b/layout/reftests/bugs/reftest.list index ca9a4e8f64..8f34f7c6cf 100644 --- a/layout/reftests/bugs/reftest.list +++ b/layout/reftests/bugs/reftest.list @@ -1951,4 +1951,5 @@ random-if(OSX==1006) == 1238243-2.html 1238243-2-ref.html # fails on 10.6 with d fuzzy(100,2000) == 1239564.html 1239564-ref.html == 1242172-1.html 1242172-1-ref.html == 1242172-2.html 1242172-2-ref.html +== 1263845.html 1263845-ref.html == 1260543-1.html 1260543-1-ref.html diff --git a/layout/reftests/canvas/reftest.list b/layout/reftests/canvas/reftest.list index 6672e62dac..4a10279130 100644 --- a/layout/reftests/canvas/reftest.list +++ b/layout/reftests/canvas/reftest.list @@ -28,7 +28,7 @@ random-if(cocoaWidget||(Android&&AndroidVersion==15)) == subpixel-1.html about:b == text-rtl-alignment-test.html text-rtl-alignment-ref.html fuzzy-if((B2G||Mulet)&&azureSkiaGL,1,256) == text-horzline-with-bottom.html text-horzline.html # Initial mulet triage: parity with B2G/B2G Desktop -fuzzy-if((B2G||Mulet)&&azureSkiaGL,1,256) fails-if(azureSkia&&OSX>=1008) == text-horzline-with-top.html text-horzline.html # Initial mulet triage: parity with B2G/B2G Desktop, Skia OS X failure tracked in bug 1152044 +fuzzy-if((B2G||Mulet)&&azureSkiaGL,1,256) fails-if(azureSkia&&OSX>=1008) == text-horzline-with-top.html text-horzline.html # Initial mulet triage: parity with B2G/B2G Desktop != text-big-stroke.html text-blank.html != text-big-stroke.html text-big-fill.html diff --git a/layout/reftests/css-grid/grid-abspos-items-013-ref.html b/layout/reftests/css-grid/grid-abspos-items-013-ref.html index 9104efb42a..3e4ef381b1 100644 --- a/layout/reftests/css-grid/grid-abspos-items-013-ref.html +++ b/layout/reftests/css-grid/grid-abspos-items-013-ref.html @@ -96,7 +96,7 @@ x:nth-of-type(4) { left:365px; }
- +
@@ -106,12 +106,12 @@ x:nth-of-type(4) { left:365px; }
- +
- +
@@ -126,7 +126,7 @@ x:nth-of-type(4) { left:365px; }
- +
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 3c9999f850..3bc177eb27 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 @@ -98,7 +98,7 @@ span {
- +
diff --git a/layout/reftests/css-grid/grid-repeat-auto-fill-fit-007-ref.html b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-007-ref.html index 81ba8aff8f..5f3cebf6a7 100644 --- a/layout/reftests/css-grid/grid-repeat-auto-fill-fit-007-ref.html +++ b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-007-ref.html @@ -100,7 +100,7 @@ float { float:left; margin-right:20px; } .x2 { grid-template-columns: repeat(2,20px); } .t1.x5 a { grid-column:5/auto; } -.c2.t1.x5 a , .c3.t1.x5 a { display:none; } +.c2.t1.x5 a , .c3.t1.x5 a { grid-column-start:-2; left:-2px; } .c1.t1.x4 a { grid-column:4/auto; } .c1.t1.x3 a { grid-column:3/auto; } .c1.t1.x2 a { grid-column:2/auto; } diff --git a/netwerk/base/Predictor.cpp b/netwerk/base/Predictor.cpp index a48912ff05..3197202dc9 100644 --- a/netwerk/base/Predictor.cpp +++ b/netwerk/base/Predictor.cpp @@ -2375,7 +2375,8 @@ Predictor::UpdateCacheability(nsIURI *sourceURI, nsIURI *targetURI, RefPtr self = sSelf; if (self) { - const nsCString method = requestHead.Method(); + nsAutoCString method; + requestHead.Method(method); self->UpdateCacheabilityInternal(sourceURI, targetURI, httpStatus, method); } diff --git a/netwerk/base/ProxyAutoConfig.cpp b/netwerk/base/ProxyAutoConfig.cpp index c91f475c7c..19b0597235 100644 --- a/netwerk/base/ProxyAutoConfig.cpp +++ b/netwerk/base/ProxyAutoConfig.cpp @@ -109,7 +109,8 @@ static const char *sPacUtils = " var wd1 = getDay(arguments[0]);\n" " var wd2 = (argc == 2) ? getDay(arguments[1]) : wd1;\n" " return (wd1 == -1 || wd2 == -1) ? false\n" - " : (wd1 <= wday && wday <= wd2);\n" + " : (wd1 <= wd2) ? (wd1 <= wday && wday <= wd2)\n" + " : (wd2 >= wday || wday >= wd1);\n" "}\n" "" "function dateRange() {\n" @@ -184,7 +185,8 @@ static const char *sPacUtils = " tmp.setSeconds(date.getUTCSeconds());\n" " date = tmp;\n" " }\n" - " return ((date1 <= date) && (date <= date2));\n" + " return (date1 <= date2) ? (date1 <= date) && (date <= date2)\n" + " : (date2 >= date) || (date >= date1);\n" "}\n" "" "function timeRange() {\n" @@ -237,7 +239,9 @@ static const char *sPacUtils = " date.setMinutes(date.getUTCMinutes());\n" " date.setSeconds(date.getUTCSeconds());\n" " }\n" - " return ((date1 <= date) && (date <= date2));\n" + " return (date1 <= date2) ? (date1 <= date) && (date <= date2)\n" + " : (date2 >= date) || (date >= date1);\n" + "\n" "}\n" ""; diff --git a/netwerk/base/ReferrerPolicy.h b/netwerk/base/ReferrerPolicy.h index bf8e92047e..efd09136ad 100644 --- a/netwerk/base/ReferrerPolicy.h +++ b/netwerk/base/ReferrerPolicy.h @@ -28,7 +28,7 @@ enum ReferrerPolicy { RP_Unsafe_URL = nsIHttpChannel::REFERRER_POLICY_UNSAFE_URL, /* referrer policy is not set */ - RP_Unset = nsIHttpChannel::REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE + RP_Unset = nsIHttpChannel::REFERRER_POLICY_UNSET, }; /* spec tokens: never no-referrer */ @@ -96,10 +96,10 @@ IsValidReferrerPolicy(const nsAString& content) inline bool IsValidAttributeReferrerPolicy(const nsAString& aContent) { - // Spec allows only these three policies at the moment - // See bug 1178337 return aContent.LowerCaseEqualsLiteral(kRPS_No_Referrer) || aContent.LowerCaseEqualsLiteral(kRPS_Origin) + || aContent.LowerCaseEqualsLiteral(kRPS_No_Referrer_When_Downgrade) + || aContent.LowerCaseEqualsLiteral(kRPS_Origin_When_Cross_Origin) || aContent.LowerCaseEqualsLiteral(kRPS_Unsafe_URL); } diff --git a/netwerk/protocol/http/ConnectionDiagnostics.cpp b/netwerk/protocol/http/ConnectionDiagnostics.cpp index d1ab03e843..184adf95d2 100644 --- a/netwerk/protocol/http/ConnectionDiagnostics.cpp +++ b/netwerk/protocol/http/ConnectionDiagnostics.cpp @@ -236,8 +236,9 @@ nsHttpTransaction::PrintDiagnostics(nsCString &log) if (!mRequestHead) return; - log.AppendPrintf(" ::: uri = %s\n", - nsAutoCString(mRequestHead->RequestURI()).get()); + nsAutoCString requestURI; + mRequestHead->RequestURI(requestURI); + log.AppendPrintf(" ::: uri = %s\n", requestURI.get()); log.AppendPrintf(" caps = 0x%x\n", mCaps); log.AppendPrintf(" priority = %d\n", mPriority); log.AppendPrintf(" restart count = %u\n", mRestartCount); diff --git a/netwerk/protocol/http/Http2Session.cpp b/netwerk/protocol/http/Http2Session.cpp index 724bff95c2..c15ae65bd2 100644 --- a/netwerk/protocol/http/Http2Session.cpp +++ b/netwerk/protocol/http/Http2Session.cpp @@ -2126,7 +2126,7 @@ Http2Session::RecvAltSvc(Http2Session *self) return NS_OK; } - origin.Assign(self->mInputFrameDataStream->Transaction()->RequestHead()->Origin()); + self->mInputFrameDataStream->Transaction()->RequestHead()->Origin(origin); } else if (!self->mInputFrameID) { // ID 0 streams must supply their own origin if (origin.IsEmpty()) { diff --git a/netwerk/protocol/http/Http2Stream.cpp b/netwerk/protocol/http/Http2Stream.cpp index 7c7993c55d..bad33199db 100644 --- a/netwerk/protocol/http/Http2Stream.cpp +++ b/netwerk/protocol/http/Http2Stream.cpp @@ -397,7 +397,7 @@ Http2Stream::ParseHttpRequestHeaders(const char *buf, this, avail, mUpstreamState)); mFlatHttpRequestHeaders.Append(buf, avail); - const nsHttpRequestHead *head = mTransaction->RequestHead(); + nsHttpRequestHead *head = mTransaction->RequestHead(); // We can use the simple double crlf because firefox is the // only client we are parsing @@ -424,9 +424,11 @@ Http2Stream::ParseHttpRequestHeaders(const char *buf, nsAutoCString hashkey; head->GetHeader(nsHttp::Host, authorityHeader); + nsAutoCString requestURI; + head->RequestURI(requestURI); CreatePushHashKey(nsDependentCString(head->IsHTTPS() ? "https" : "http"), authorityHeader, mSession->Serial(), - head->RequestURI(), + requestURI, mOrigin, hashkey); // check the push cache for GET @@ -496,9 +498,11 @@ Http2Stream::GenerateOpen() mOpenGenerated = 1; - const nsHttpRequestHead *head = mTransaction->RequestHead(); + nsHttpRequestHead *head = mTransaction->RequestHead(); + nsAutoCString requestURI; + head->RequestURI(requestURI); LOG3(("Http2Stream %p Stream ID 0x%X [session=%p] for URI %s\n", - this, mStreamID, mSession, nsCString(head->RequestURI()).get())); + this, mStreamID, mSession, requestURI.get())); if (mStreamID >= 0x80000000) { // streamID must fit in 31 bits. Evading This is theoretically possible @@ -538,9 +542,13 @@ Http2Stream::GenerateOpen() authorityHeader.AppendInt(ci->OriginPort()); } + nsAutoCString method; + nsAutoCString path; + head->Method(method); + head->Path(path); mSession->Compressor()->EncodeHeaderBlock(mFlatHttpRequestHeaders, - head->Method(), - head->Path(), + method, + path, authorityHeader, scheme, head->IsConnect(), @@ -606,7 +614,7 @@ Http2Stream::GenerateOpen() LOG3(("Http2Stream %p Generating %d bytes of HEADERS for stream 0x%X with " "priority weight %u dep 0x%X frames %u uri=%s\n", this, mTxInlineFrameUsed, mStreamID, mPriorityWeight, - mPriorityDependency, numFrames, nsCString(head->RequestURI()).get())); + mPriorityDependency, numFrames, requestURI.get())); uint32_t outputOffset = 0; uint32_t compressedDataOffset = 0; @@ -650,7 +658,7 @@ Http2Stream::GenerateOpen() // The size of the input headers is approximate uint32_t ratio = compressedData.Length() * 100 / - (11 + head->RequestURI().Length() + + (11 + requestURI.Length() + mFlatHttpRequestHeaders.Length()); mFlatHttpRequestHeaders.Truncate(); diff --git a/netwerk/protocol/http/HttpBaseChannel.cpp b/netwerk/protocol/http/HttpBaseChannel.cpp index b55872ec45..ee9b1cfa7b 100644 --- a/netwerk/protocol/http/HttpBaseChannel.cpp +++ b/netwerk/protocol/http/HttpBaseChannel.cpp @@ -52,6 +52,7 @@ #include "nsIURL.h" #include "nsIConsoleService.h" #include "mozilla/BinarySearch.h" +#include "nsIHttpHeaderVisitor.h" #include @@ -189,7 +190,7 @@ HttpBaseChannel::Init(nsIURI *aURI, rv = mRequestHead.SetHeader(nsHttp::Host, hostLine); if (NS_FAILED(rv)) return rv; - rv = gHttpHandler->AddStandardRequestHeaders(&mRequestHead.Headers(), isHTTPS); + rv = gHttpHandler->AddStandardRequestHeaders(&mRequestHead, isHTTPS); if (NS_FAILED(rv)) return rv; nsAutoCString type; @@ -1172,7 +1173,7 @@ HttpBaseChannel::GetEncodedBodySize(uint64_t *aEncodedBodySize) NS_IMETHODIMP HttpBaseChannel::GetRequestMethod(nsACString& aMethod) { - aMethod = mRequestHead.Method(); + mRequestHead.Method(aMethod); return NS_OK; } @@ -1601,14 +1602,14 @@ HttpBaseChannel::SetEmptyRequestHeader(const nsACString& aHeader) NS_IMETHODIMP HttpBaseChannel::VisitRequestHeaders(nsIHttpHeaderVisitor *visitor) { - return mRequestHead.Headers().VisitHeaders(visitor); + return mRequestHead.VisitHeaders(visitor); } NS_IMETHODIMP HttpBaseChannel::VisitNonDefaultRequestHeaders(nsIHttpHeaderVisitor *visitor) { - return mRequestHead.Headers().VisitHeaders( - visitor, nsHttpHeaderArray::eFilterSkipDefault); + return mRequestHead.VisitHeaders(visitor, + nsHttpHeaderArray::eFilterSkipDefault); } NS_IMETHODIMP @@ -2728,6 +2729,35 @@ bool IsHeaderBlacklistedForRedirectCopy(nsHttpAtom const& aHeader) HttpAtomComparator(aHeader), &unused); } +class SetupReplacementChannelHeaderVisitor final : public nsIHttpHeaderVisitor +{ +public: + NS_DECL_ISUPPORTS + + explicit SetupReplacementChannelHeaderVisitor(nsIHttpChannel *aChannel) + : mChannel(aChannel) + { + } + + NS_IMETHOD VisitHeader(const nsACString& aHeader, + const nsACString& aValue) override + { + nsHttpAtom atom = nsHttp::ResolveAtom(aHeader); + if (!IsHeaderBlacklistedForRedirectCopy(atom)) { + mChannel->SetRequestHeader(aHeader, aValue, false); + } + return NS_OK; + } +private: + ~SetupReplacementChannelHeaderVisitor() + { + } + + nsCOMPtr mChannel; +}; + +NS_IMPL_ISUPPORTS(SetupReplacementChannelHeaderVisitor, nsIHttpHeaderVisitor) + nsresult HttpBaseChannel::SetupReplacementChannel(nsIURI *newURI, nsIChannel *newChannel, @@ -2806,31 +2836,35 @@ HttpBaseChannel::SetupReplacementChannel(nsIURI *newURI, // replicate original call to SetUploadStream... if (uploadChannel2) { - const char *ctype = mRequestHead.PeekHeader(nsHttp::Content_Type); - if (!ctype) - ctype = ""; - const char *clen = mRequestHead.PeekHeader(nsHttp::Content_Length); - int64_t len = clen ? nsCRT::atoll(clen) : -1; + nsAutoCString ctype; + // If header is not present mRequestHead.HasHeaderValue will truncated + // it. + mRequestHead.GetHeader(nsHttp::Content_Type, ctype); + nsAutoCString clen; + mRequestHead.GetHeader(nsHttp::Content_Length, clen); + nsAutoCString method; + mRequestHead.Method(method); + int64_t len = clen.IsEmpty() ? -1 : nsCRT::atoll(clen.get()); uploadChannel2->ExplicitSetUploadStream( - mUploadStream, nsDependentCString(ctype), len, - mRequestHead.Method(), + mUploadStream, ctype, len, + method, mUploadStreamHasHeaders); } else { if (mUploadStreamHasHeaders) { uploadChannel->SetUploadStream(mUploadStream, EmptyCString(), -1); } else { - const char *ctype = - mRequestHead.PeekHeader(nsHttp::Content_Type); - const char *clen = - mRequestHead.PeekHeader(nsHttp::Content_Length); - if (!ctype) { - ctype = "application/octet-stream"; + nsAutoCString ctype; + if (NS_FAILED(mRequestHead.GetHeader(nsHttp::Content_Type, ctype))) { + ctype = NS_LITERAL_CSTRING("application/octet-stream"); } - if (clen) { + nsAutoCString clen; + if (NS_SUCCEEDED(mRequestHead.GetHeader(nsHttp::Content_Length, clen)) + && + !clen.IsEmpty()) { uploadChannel->SetUploadStream(mUploadStream, - nsDependentCString(ctype), - nsCRT::atoll(clen)); + ctype, + nsCRT::atoll(clen.get())); } } } @@ -2840,7 +2874,9 @@ HttpBaseChannel::SetupReplacementChannel(nsIURI *newURI, // we set the upload stream above. This means SetRequestMethod() will // be called twice if ExplicitSetUploadStream() gets called above. - httpChannel->SetRequestMethod(mRequestHead.Method()); + nsAutoCString method; + mRequestHead.Method(method); + httpChannel->SetRequestMethod(method); } // convey the referrer if one was used for this channel to the next one if (mReferrer) @@ -2970,18 +3006,9 @@ HttpBaseChannel::SetupReplacementChannel(nsIURI *newURI, if (redirectFlags & (nsIChannelEventSink::REDIRECT_INTERNAL | nsIChannelEventSink::REDIRECT_STS_UPGRADE)) { // Copy non-origin related headers to the new channel. - nsHttpHeaderArray& requestHeaders = mRequestHead.Headers(); - uint32_t requestHeaderCount = requestHeaders.Count(); - for (uint32_t i = 0; i < requestHeaderCount; ++i) { - nsHttpAtom header; - const char *val = requestHeaders.PeekHeaderAt(i, header); - if (!val || IsHeaderBlacklistedForRedirectCopy(header)) { - continue; - } - - httpChannel->SetRequestHeader(nsDependentCString(header.get()), - nsDependentCString(val), false); - } + nsCOMPtr visitor = + new SetupReplacementChannelHeaderVisitor(httpChannel); + mRequestHead.VisitHeaders(visitor); } // This channel has been redirected. Don't report timing info. diff --git a/netwerk/protocol/http/HttpChannelChild.cpp b/netwerk/protocol/http/HttpChannelChild.cpp index da2936c7f1..f54dbe9dfb 100644 --- a/netwerk/protocol/http/HttpChannelChild.cpp +++ b/netwerk/protocol/http/HttpChannelChild.cpp @@ -451,7 +451,7 @@ HttpChannelChild::OnStartRequest(const nsresult& channelStatus, mCacheKey = container; // replace our request headers with what actually got sent in the parent - mRequestHead.Headers() = requestHeaders; + mRequestHead.SetHeaders(requestHeaders); // Note: this is where we would notify "http-on-examine-response" observers. // We have deliberately disabled this for child processes (see bug 806753) @@ -1741,9 +1741,9 @@ HttpChannelChild::AsyncOpen(nsIStreamListener *listener, nsISupports *aContext) if (NS_FAILED(rv)) return rv; - const char *cookieHeader = mRequestHead.PeekHeader(nsHttp::Cookie); - if (cookieHeader) { - mUserSetCookieHeader = cookieHeader; + nsAutoCString cookie; + if (NS_SUCCEEDED(mRequestHead.GetHeader(nsHttp::Cookie, cookie))) { + mUserSetCookieHeader = cookie; } AddCookiesToRequest(); @@ -1854,7 +1854,7 @@ HttpChannelChild::ContinueAsyncOpen() SerializeURI(mAPIRedirectToURI, openArgs.apiRedirectTo()); openArgs.loadFlags() = mLoadFlags; openArgs.requestHeaders() = mClientSetRequestHeaders; - openArgs.requestMethod() = mRequestHead.Method(); + mRequestHead.Method(openArgs.requestMethod()); nsTArray fds; SerializeInputStream(mUploadStream, openArgs.uploadStream(), fds); diff --git a/netwerk/protocol/http/HttpChannelParent.cpp b/netwerk/protocol/http/HttpChannelParent.cpp index a8c14c1ebf..c6bed6cd92 100644 --- a/netwerk/protocol/http/HttpChannelParent.cpp +++ b/netwerk/protocol/http/HttpChannelParent.cpp @@ -1057,6 +1057,9 @@ HttpChannelParent::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext) } } + // !!! We need to lock headers and please don't forget to unlock them !!! + requestHead->Lock(); + nsresult rv = NS_OK; if (mIPCClosed || !SendOnStartRequest(channelStatus, responseHead ? *responseHead : nsHttpResponseHead(), @@ -1069,9 +1072,10 @@ HttpChannelParent::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext) redirectCount, cacheKeyValue)) { - return NS_ERROR_UNEXPECTED; + rv = NS_ERROR_UNEXPECTED; } - return NS_OK; + requestHead->Unlock(); + return rv; } NS_IMETHODIMP diff --git a/netwerk/protocol/http/SpdyStream31.cpp b/netwerk/protocol/http/SpdyStream31.cpp index 75ff4f0e29..247f965c70 100644 --- a/netwerk/protocol/http/SpdyStream31.cpp +++ b/netwerk/protocol/http/SpdyStream31.cpp @@ -296,10 +296,11 @@ SpdyStream31::ParseHttpRequestHeaders(const char *buf, nsAutoCString hostHeader; nsAutoCString hashkey; mTransaction->RequestHead()->GetHeader(nsHttp::Host, hostHeader); - + nsAutoCString requestURI; + mTransaction->RequestHead()->RequestURI(requestURI); CreatePushHashKey(nsDependentCString(mTransaction->RequestHead()->IsHTTPS() ? "https" : "http"), hostHeader, mSession->Serial(), - mTransaction->RequestHead()->RequestURI(), + requestURI, mOrigin, hashkey); // check the push cache for GET @@ -418,7 +419,7 @@ SpdyStream31::GenerateSynFrame() // even though we are parsing the actual text stream because // it is legit to append headers. nsClassHashtable - hdrHash(mTransaction->RequestHead()->Headers().Count()); + hdrHash(mTransaction->RequestHead()->HeaderCount()); const char *beginBuffer = mFlatHttpRequestHeaders.BeginReading(); @@ -482,8 +483,9 @@ SpdyStream31::GenerateSynFrame() // contain auth. The http transaction already logs the sanitized request // headers at this same level so it is not necessary to do so here. - const char *methodHeader = mTransaction->RequestHead()->Method().get(); - LOG3(("Stream method %p 0x%X %s\n", this, mStreamID, methodHeader)); + nsAutoCString method; + mTransaction->RequestHead()->Method(method); + LOG3(("Stream method %p 0x%X %s\n", this, mStreamID, method.get())); // The header block length uint16_t count = hdrHash.Count() + 4; /* :method, :path, :version, :host */ @@ -497,11 +499,13 @@ SpdyStream31::GenerateSynFrame() // :method, :path, :version comprise a HTTP/1 request line, so send those first // to make life easy for any gateways CompressToFrame(NS_LITERAL_CSTRING(":method")); - CompressToFrame(methodHeader, strlen(methodHeader)); + CompressToFrame(method); CompressToFrame(NS_LITERAL_CSTRING(":path")); if (!mTransaction->RequestHead()->IsConnect()) { - CompressToFrame(mTransaction->RequestHead()->Path()); + nsAutoCString path; + mTransaction->RequestHead()->Path(path); + CompressToFrame(path); } else { MOZ_ASSERT(mTransaction->QuerySpdyConnectTransaction()); mIsTunnel = true; @@ -568,11 +572,12 @@ SpdyStream31::GenerateSynFrame() mTxInlineFrame[4] = SpdySession31::kFlag_Data_FIN; } + nsAutoCString requestURI; + mTransaction->RequestHead()->RequestURI(requestURI); // The size of the input headers is approximate uint32_t ratio = (mTxInlineFrameUsed - 18) * 100 / - (11 + mTransaction->RequestHead()->RequestURI().Length() + - mFlatHttpRequestHeaders.Length()); + (11 + requestURI.Length() + mFlatHttpRequestHeaders.Length()); return NS_OK; } diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp index 3ed350d1ba..49834643c4 100644 --- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -702,9 +702,11 @@ nsHttpChannel::SetupTransaction() // (4) request method is non-idempotent // (5) request is marked slow (e.g XHR) // + nsAutoCString method; + mRequestHead.Method(method); if (!mAllowPipelining || (mLoadFlags & (LOAD_INITIAL_DOCUMENT_URI | INHIBIT_PIPELINE)) || - !SafeForPipelining(mRequestHead.ParsedMethod(), mRequestHead.Method())) { + !SafeForPipelining(mRequestHead.ParsedMethod(), method)) { LOG((" pipelining disallowed\n")); mCaps &= ~NS_HTTP_ALLOW_PIPELINING; } @@ -2359,7 +2361,7 @@ nsHttpChannel::ResolveProxy() } bool -nsHttpChannel::ResponseWouldVary(nsICacheEntry* entry) const +nsHttpChannel::ResponseWouldVary(nsICacheEntry* entry) { nsresult rv; nsAutoCString buf, metaKey; @@ -2403,33 +2405,37 @@ nsHttpChannel::ResponseWouldVary(nsICacheEntry* entry) const // Look for value of "Cookie" in the request headers nsHttpAtom atom = nsHttp::ResolveAtom(token); - const char *newVal = mRequestHead.PeekHeader(atom); + nsAutoCString newVal; + bool hasHeader = NS_SUCCEEDED(mRequestHead.GetHeader(atom, + newVal)); if (!lastVal.IsEmpty()) { // value for this header in cache, but no value in request - if (!newVal) + if (!hasHeader) { return true; // yes - response would vary + } // If this is a cookie-header, stored metadata is not // the value itself but the hash. So we also hash the // outgoing value here in order to compare the hashes nsAutoCString hash; if (atom == nsHttp::Cookie) { - rv = Hash(newVal, hash); + rv = Hash(newVal.get(), hash); // If hash failed, be conservative (the cached hash // exists at this point) and claim response would vary if (NS_FAILED(rv)) return true; - newVal = hash.get(); + newVal = hash; LOG(("nsHttpChannel::ResponseWouldVary [this=%p] " \ "set-cookie value hashed to %s\n", - this, newVal)); + this, newVal.get())); } - if (strcmp(newVal, lastVal)) + if (!newVal.Equals(lastVal)) { return true; // yes, response would vary + } - } else if (newVal) { // old value is empty, but newVal is set + } else if (hasHeader) { // old value is empty, but newVal is set return true; } @@ -2497,10 +2503,11 @@ nsHttpChannel::EnsureAssocReq() return NS_OK; // check the method - int32_t methodlen = strlen(mRequestHead.Method().get()); - if ((methodlen != (endofmethod - method)) || + nsAutoCString methodHead; + mRequestHead.Method(methodHead); + if ((((int32_t)methodHead.Length()) != (endofmethod - method)) || PL_strncmp(method, - mRequestHead.Method().get(), + methodHead.get(), endofmethod - method)) { LOG((" Assoc-Req failure Method %s", method)); if (mConnectionInfo) @@ -2518,7 +2525,7 @@ nsHttpChannel::EnsureAssocReq() mResponseHead->PeekHeader(nsHttp::Assoc_Req), message); message += NS_LITERAL_STRING(" expected method "); - AppendASCIItoUTF16(mRequestHead.Method().get(), message); + AppendASCIItoUTF16(methodHead, message); consoleService->LogStringMessage(message.get()); } @@ -3010,10 +3017,10 @@ nsHttpChannel::ContinueProcessFallback(nsresult rv) static bool IsSubRangeRequest(nsHttpRequestHead &aRequestHead) { - if (!aRequestHead.PeekHeader(nsHttp::Range)) - return false; nsAutoCString byteRange; - aRequestHead.GetHeader(nsHttp::Range, byteRange); + if (NS_FAILED(aRequestHead.GetHeader(nsHttp::Range, byteRange))) { + return false; + } return !byteRange.EqualsLiteral("bytes=0-"); } @@ -3283,11 +3290,11 @@ nsHttpChannel::OnCacheEntryCheck(nsICacheEntry* entry, nsIApplicationCache* appC // Remember the request is a custom conditional request so that we can // process any 304 response correctly. mCustomConditionalRequest = - mRequestHead.PeekHeader(nsHttp::If_Modified_Since) || - mRequestHead.PeekHeader(nsHttp::If_None_Match) || - mRequestHead.PeekHeader(nsHttp::If_Unmodified_Since) || - mRequestHead.PeekHeader(nsHttp::If_Match) || - mRequestHead.PeekHeader(nsHttp::If_Range); + mRequestHead.HasHeader(nsHttp::If_Modified_Since) || + mRequestHead.HasHeader(nsHttp::If_None_Match) || + mRequestHead.HasHeader(nsHttp::If_Unmodified_Since) || + mRequestHead.HasHeader(nsHttp::If_Match) || + mRequestHead.HasHeader(nsHttp::If_Range); // Be pessimistic: assume the cache entry has no useful data. *aResult = ENTRY_WANTED; @@ -3517,13 +3524,13 @@ nsHttpChannel::OnCacheEntryCheck(nsICacheEntry* entry, nsIApplicationCache* appC doValidation = true; } - if (!doValidation && mRequestHead.PeekHeader(nsHttp::If_Match) && + nsAutoCString requestedETag; + if (!doValidation && + NS_SUCCEEDED(mRequestHead.GetHeader(nsHttp::If_Match, requestedETag)) && (methodWasGet || methodWasHead)) { - const char *requestedETag, *cachedETag; - cachedETag = mCachedResponseHead->PeekHeader(nsHttp::ETag); - requestedETag = mRequestHead.PeekHeader(nsHttp::If_Match); + const char *cachedETag = mCachedResponseHead->PeekHeader(nsHttp::ETag); if (cachedETag && (!strncmp(cachedETag, "W/", 2) || - strcmp(requestedETag, cachedETag))) { + !requestedETag.Equals(cachedETag))) { // User has defined If-Match header, if the cached entry is not // matching the provided header value or the cached ETag is weak, // force validation. @@ -3546,7 +3553,7 @@ nsHttpChannel::OnCacheEntryCheck(nsICacheEntry* entry, nsIApplicationCache* appC entry->GetMetaDataElement("auth", getter_Copies(buf)); doValidation = (fromPreviousSession && !buf.IsEmpty()) || - (buf.IsEmpty() && mRequestHead.PeekHeader(nsHttp::Authorization)); + (buf.IsEmpty() && mRequestHead.HasHeader(nsHttp::Authorization)); } // Bug #561276: We maintain a chain of cache-keys which returns cached @@ -4454,8 +4461,9 @@ DoAddCacheEntryHeaders(nsHttpChannel *self, // Store the HTTP request method with the cache entry so we can distinguish // for example GET and HEAD responses. - rv = entry->SetMetaDataElement("request-method", - requestHead->Method().get()); + nsAutoCString method; + requestHead->Method(method); + rv = entry->SetMetaDataElement("request-method", method.get()); if (NS_FAILED(rv)) return rv; // Store the HTTP authorization scheme used if any... @@ -4486,27 +4494,28 @@ DoAddCacheEntryHeaders(nsHttpChannel *self, "processing %s", self, token)); if (*token != '*') { nsHttpAtom atom = nsHttp::ResolveAtom(token); - const char *val = requestHead->PeekHeader(atom); + nsAutoCString val; nsAutoCString hash; - if (val) { + if (NS_SUCCEEDED(requestHead->GetHeader(atom, val))) { // If cookie-header, store a hash of the value if (atom == nsHttp::Cookie) { LOG(("nsHttpChannel::AddCacheEntryHeaders [this=%p] " \ - "cookie-value %s", self, val)); - rv = Hash(val, hash); + "cookie-value %s", self, val.get())); + rv = Hash(val.get(), hash); // If hash failed, store a string not very likely // to be the result of subsequent hashes - if (NS_FAILED(rv)) - val = ""; - else - val = hash.get(); + if (NS_FAILED(rv)) { + val = NS_LITERAL_CSTRING(""); + } else { + val = hash; + } - LOG((" hashed to %s\n", val)); + LOG((" hashed to %s\n", val.get())); } // build cache meta data key and set meta data element... metaKey = prefix + nsDependentCString(token); - entry->SetMetaDataElement(metaKey.get(), val); + entry->SetMetaDataElement(metaKey.get(), val.get()); } else { LOG(("nsHttpChannel::AddCacheEntryHeaders [this=%p] " \ "clearing metadata for %s", self, token)); @@ -4555,13 +4564,14 @@ nsresult StoreAuthorizationMetaData(nsICacheEntry *entry, nsHttpRequestHead *requestHead) { // Not applicable to proxy authorization... - const char *val = requestHead->PeekHeader(nsHttp::Authorization); - if (!val) + nsAutoCString val; + if (NS_FAILED(requestHead->GetHeader(nsHttp::Authorization, val))) { return NS_OK; + } // eg. [Basic realm="wally world"] nsAutoCString buf; - GetAuthType(val, buf); + GetAuthType(val.get(), buf); return entry->SetMetaDataElement("auth", buf.get()); } @@ -5195,8 +5205,8 @@ nsHttpChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *context) } // Remember the cookie header that was set, if any - const char *cookieHeader = mRequestHead.PeekHeader(nsHttp::Cookie); - if (cookieHeader) { + nsAutoCString cookieHeader; + if (NS_SUCCEEDED(mRequestHead.GetHeader(nsHttp::Cookie, cookieHeader))) { mUserSetCookieHeader = cookieHeader; } @@ -5229,7 +5239,7 @@ nsHttpChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *context) // Remember we have Authorization header set here. We need to check on it // just once and early, AsyncOpen is the best place. - mCustomAuthHeader = !!mRequestHead.PeekHeader(nsHttp::Authorization); + mCustomAuthHeader = mRequestHead.HasHeader(nsHttp::Authorization); // the only time we would already know the proxy information at this // point would be if we were proxying a non-http protocol like ftp @@ -5442,7 +5452,7 @@ nsHttpChannel::BeginConnect() } // if this somehow fails we can go on without it - gHttpHandler->AddConnectionHeader(&mRequestHead.Headers(), mCaps); + gHttpHandler->AddConnectionHeader(&mRequestHead, mCaps); if (mLoadFlags & VALIDATE_ALWAYS || BYPASS_LOCAL_CACHE(mLoadFlags)) mCaps |= NS_HTTP_REFRESH_DNS; diff --git a/netwerk/protocol/http/nsHttpChannel.h b/netwerk/protocol/http/nsHttpChannel.h index 73ec19fbed..52c18b691b 100644 --- a/netwerk/protocol/http/nsHttpChannel.h +++ b/netwerk/protocol/http/nsHttpChannel.h @@ -418,7 +418,7 @@ private: void UpdateAggregateCallbacks(); static bool HasQueryString(nsHttpRequestHead::ParsedMethodType method, nsIURI * uri); - bool ResponseWouldVary(nsICacheEntry* entry) const; + bool ResponseWouldVary(nsICacheEntry* entry); bool IsResumable(int64_t partialLen, int64_t contentLength, bool ignoreMissingPartialLen = false) const; nsresult MaybeSetupByteRangeRequest(int64_t partialLen, int64_t contentLength, diff --git a/netwerk/protocol/http/nsHttpConnection.cpp b/netwerk/protocol/http/nsHttpConnection.cpp index cf3f12dd60..84eff8fcaf 100644 --- a/netwerk/protocol/http/nsHttpConnection.cpp +++ b/netwerk/protocol/http/nsHttpConnection.cpp @@ -1133,21 +1133,25 @@ nsHttpConnection::OnHeadersAvailable(nsAHttpTransaction *trans, } } - const char *upgradeReq = requestHead->PeekHeader(nsHttp::Upgrade); + nsAutoCString upgradeReq; + bool hasUpgradeReq = NS_SUCCEEDED(requestHead->GetHeader(nsHttp::Upgrade, + upgradeReq)); // Don't use persistent connection for Upgrade unless there's an auth failure: // some proxies expect to see auth response on persistent connection. - if (upgradeReq && responseStatus != 401 && responseStatus != 407) { + if (hasUpgradeReq && responseStatus != 401 && responseStatus != 407) { LOG(("HTTP Upgrade in play - disable keepalive\n")); DontReuse(); } if (responseStatus == 101) { const char *upgradeResp = responseHead->PeekHeader(nsHttp::Upgrade); - if (!upgradeReq || !upgradeResp || - !nsHttp::FindToken(upgradeResp, upgradeReq, + if (!hasUpgradeReq || !upgradeResp || + !nsHttp::FindToken(upgradeResp, upgradeReq.get(), HTTP_HEADER_VALUE_SEPS)) { LOG(("HTTP 101 Upgrade header mismatch req = %s, resp = %s\n", - upgradeReq, upgradeResp)); + upgradeReq.get(), + upgradeResp ? upgradeResp : + "RESPONSE's nsHttp::Upgrade is empty")); Close(NS_ERROR_ABORT); } else { @@ -1968,11 +1972,13 @@ nsHttpConnection::MakeConnectString(nsAHttpTransaction *trans, // may seem redundant in this case; see bug 82388). request->SetHeader(nsHttp::Host, result); - const char *val = trans->RequestHead()->PeekHeader(nsHttp::Proxy_Authorization); - if (val) { + nsAutoCString val; + if (NS_SUCCEEDED(trans->RequestHead()->GetHeader( + nsHttp::Proxy_Authorization, + val))) { // we don't know for sure if this authorization is intended for the // SSL proxy, so we add it just in case. - request->SetHeader(nsHttp::Proxy_Authorization, nsDependentCString(val)); + request->SetHeader(nsHttp::Proxy_Authorization, val); } result.Truncate(); diff --git a/netwerk/protocol/http/nsHttpHandler.cpp b/netwerk/protocol/http/nsHttpHandler.cpp index f81f175ed4..5581c932c9 100644 --- a/netwerk/protocol/http/nsHttpHandler.cpp +++ b/netwerk/protocol/http/nsHttpHandler.cpp @@ -434,7 +434,7 @@ nsHttpHandler::InitConnectionMgr() } nsresult -nsHttpHandler::AddStandardRequestHeaders(nsHttpHeaderArray *request, bool isSecure) +nsHttpHandler::AddStandardRequestHeaders(nsHttpRequestHead *request, bool isSecure) { nsresult rv; @@ -487,7 +487,7 @@ nsHttpHandler::AddStandardRequestHeaders(nsHttpHeaderArray *request, bool isSecu } nsresult -nsHttpHandler::AddConnectionHeader(nsHttpHeaderArray *request, +nsHttpHandler::AddConnectionHeader(nsHttpRequestHead *request, uint32_t caps) { // RFC2616 section 19.6.2 states that the "Connection: keep-alive" diff --git a/netwerk/protocol/http/nsHttpHandler.h b/netwerk/protocol/http/nsHttpHandler.h index 178fd431c6..ebe42dc73f 100644 --- a/netwerk/protocol/http/nsHttpHandler.h +++ b/netwerk/protocol/http/nsHttpHandler.h @@ -67,8 +67,8 @@ public: nsHttpHandler(); nsresult Init(); - nsresult AddStandardRequestHeaders(nsHttpHeaderArray *, bool isSecure); - nsresult AddConnectionHeader(nsHttpHeaderArray *, + nsresult AddStandardRequestHeaders(nsHttpRequestHead *, bool isSecure); + nsresult AddConnectionHeader(nsHttpRequestHead *, uint32_t capabilities); bool IsAcceptableEncoding(const char *encoding, bool isSecure); diff --git a/netwerk/protocol/http/nsHttpHeaderArray.cpp b/netwerk/protocol/http/nsHttpHeaderArray.cpp index d557efa8b3..ee3baa05e6 100644 --- a/netwerk/protocol/http/nsHttpHeaderArray.cpp +++ b/netwerk/protocol/http/nsHttpHeaderArray.cpp @@ -137,6 +137,14 @@ nsHttpHeaderArray::GetHeader(nsHttpAtom header, nsACString &result) const return NS_OK; } +bool +nsHttpHeaderArray::HasHeader(nsHttpAtom header) const +{ + const nsEntry *entry = nullptr; + LookupEntry(header, &entry); + return entry; +} + nsresult nsHttpHeaderArray::VisitHeaders(nsIHttpHeaderVisitor *visitor, nsHttpHeaderArray::VisitorFilter filter) { diff --git a/netwerk/protocol/http/nsHttpHeaderArray.h b/netwerk/protocol/http/nsHttpHeaderArray.h index 9cd4bf3da8..b3d99c1143 100644 --- a/netwerk/protocol/http/nsHttpHeaderArray.h +++ b/netwerk/protocol/http/nsHttpHeaderArray.h @@ -59,6 +59,8 @@ public: return FindHeaderValue(header, value) != nullptr; } + bool HasHeader(nsHttpAtom header) const; + enum VisitorFilter { eFilterAll, diff --git a/netwerk/protocol/http/nsHttpRequestHead.cpp b/netwerk/protocol/http/nsHttpRequestHead.cpp index aa58ce0211..165163e559 100644 --- a/netwerk/protocol/http/nsHttpRequestHead.cpp +++ b/netwerk/protocol/http/nsHttpRequestHead.cpp @@ -7,6 +7,7 @@ #include "HttpLog.h" #include "nsHttpRequestHead.h" +#include "nsIHttpHeaderVisitor.h" //----------------------------------------------------------------------------- // nsHttpRequestHead @@ -20,6 +21,7 @@ nsHttpRequestHead::nsHttpRequestHead() , mVersion(NS_HTTP_VERSION_1_1) , mParsedMethod(kMethod_Get) , mHTTPS(false) + , mLock("nsHttpRequestHead.mLock") { MOZ_COUNT_CTOR(nsHttpRequestHead); } @@ -29,9 +31,201 @@ nsHttpRequestHead::~nsHttpRequestHead() MOZ_COUNT_DTOR(nsHttpRequestHead); } +// Don't use this function. It is only used by HttpChannelParent to avoid +// copying of request headers!!! +const nsHttpHeaderArray & +nsHttpRequestHead::Headers() const +{ + mLock.AssertCurrentThreadOwns(); + return mHeaders; +} + +void +nsHttpRequestHead::SetHeaders(const nsHttpHeaderArray& aHeaders) +{ + mHeaders = aHeaders; +} + +void +nsHttpRequestHead::SetVersion(nsHttpVersion version) +{ + MutexAutoLock lock(mLock); + mVersion = version; +} + +void +nsHttpRequestHead::SetRequestURI(const nsCSubstring &s) +{ + MutexAutoLock lock(mLock); + mRequestURI = s; +} + +void +nsHttpRequestHead::SetPath(const nsCSubstring &s) +{ + MutexAutoLock lock(mLock); + mPath = s; +} + +uint32_t +nsHttpRequestHead::HeaderCount() +{ + MutexAutoLock lock(mLock); + return mHeaders.Count(); +} + +nsresult +nsHttpRequestHead::VisitHeaders(nsIHttpHeaderVisitor *visitor, + nsHttpHeaderArray::VisitorFilter filter /* = nsHttpHeaderArray::eFilterAll*/) +{ + MutexAutoLock lock(mLock); + return mHeaders.VisitHeaders(visitor, filter); +} + +void +nsHttpRequestHead::Method(nsACString &aMethod) +{ + MutexAutoLock lock(mLock); + aMethod = mMethod; +} + +nsHttpVersion +nsHttpRequestHead::Version() +{ + MutexAutoLock lock(mLock); + return mVersion; +} + +void +nsHttpRequestHead::RequestURI(nsACString &aRequestURI) +{ + MutexAutoLock lock(mLock); + aRequestURI = mRequestURI; +} + +void +nsHttpRequestHead::Path(nsACString &aPath) +{ + MutexAutoLock lock(mLock); + aPath = mPath.IsEmpty() ? mRequestURI : mPath; +} + +void +nsHttpRequestHead::SetHTTPS(bool val) +{ + MutexAutoLock lock(mLock); + mHTTPS = val; +} + +void +nsHttpRequestHead::Origin(nsACString &aOrigin) +{ + MutexAutoLock lock(mLock); + aOrigin = mOrigin; +} + +nsresult +nsHttpRequestHead::SetHeader(nsHttpAtom h, const nsACString &v, + bool m /*= false*/) +{ + MutexAutoLock lock(mLock); + return mHeaders.SetHeader(h, v, m); +} + +nsresult +nsHttpRequestHead::SetHeader(nsHttpAtom h, const nsACString &v, bool m, + nsHttpHeaderArray::HeaderVariety variety) +{ + MutexAutoLock lock(mLock); + return mHeaders.SetHeader(h, v, m, variety); +} + +nsresult +nsHttpRequestHead::SetEmptyHeader(nsHttpAtom h) +{ + MutexAutoLock lock(mLock); + return mHeaders.SetEmptyHeader(h); +} + +nsresult +nsHttpRequestHead::GetHeader(nsHttpAtom h, nsACString &v) +{ + v.Truncate(); + MutexAutoLock lock(mLock); + return mHeaders.GetHeader(h, v); +} + +void +nsHttpRequestHead::ClearHeader(nsHttpAtom h) +{ + MutexAutoLock lock(mLock); + mHeaders.ClearHeader(h); +} + +void +nsHttpRequestHead::ClearHeaders() +{ + MutexAutoLock lock(mLock); + mHeaders.Clear(); +} + +bool +nsHttpRequestHead::HasHeader(nsHttpAtom h) +{ + MutexAutoLock lock(mLock); + return mHeaders.HasHeader(h); +} + +bool +nsHttpRequestHead::HasHeaderValue(nsHttpAtom h, const char *v) +{ + MutexAutoLock lock(mLock); + return mHeaders.HasHeaderValue(h, v); +} + +nsresult +nsHttpRequestHead::SetHeaderOnce(nsHttpAtom h, const char *v, + bool merge /*= false */) +{ + MutexAutoLock lock(mLock); + if (!merge || !mHeaders.HasHeaderValue(h, v)) { + return mHeaders.SetHeader(h, nsDependentCString(v), merge); + } + return NS_OK; +} + +nsHttpRequestHead::ParsedMethodType +nsHttpRequestHead::ParsedMethod() +{ + MutexAutoLock lock(mLock); + return mParsedMethod; +} + +bool +nsHttpRequestHead::EqualsMethod(ParsedMethodType aType) +{ + MutexAutoLock lock(mLock); + return mParsedMethod == aType; +} + +void +nsHttpRequestHead::ParseHeaderSet(char *buffer) +{ + MutexAutoLock lock(mLock); + mHeaders.ParseHeaderSet(buffer); +} + +bool +nsHttpRequestHead::IsHTTPS() +{ + MutexAutoLock lock(mLock); + return mHTTPS; +} + void nsHttpRequestHead::SetMethod(const nsACString &method) { + MutexAutoLock lock(mLock); mParsedMethod = kMethod_Custom; mMethod = method; if (!strcmp(mMethod.get(), "GET")) { @@ -52,8 +246,10 @@ nsHttpRequestHead::SetMethod(const nsACString &method) } void -nsHttpRequestHead::SetOrigin(const nsACString &scheme, const nsACString &host, int32_t port) +nsHttpRequestHead::SetOrigin(const nsACString &scheme, const nsACString &host, + int32_t port) { + MutexAutoLock lock(mLock); mOrigin.Assign(scheme); mOrigin.Append(NS_LITERAL_CSTRING("://")); mOrigin.Append(host); @@ -64,11 +260,14 @@ nsHttpRequestHead::SetOrigin(const nsACString &scheme, const nsACString &host, i } bool -nsHttpRequestHead::IsSafeMethod() const +nsHttpRequestHead::IsSafeMethod() { - // This code will need to be extended for new safe methods, otherwise - // they'll default to "not safe". - if (IsGet() || IsHead() || IsOptions() || IsTrace()) { + MutexAutoLock lock(mLock); + // This code will need to be extended for new safe methods, otherwise + // they'll default to "not safe". + if ((mParsedMethod == kMethod_Get) || (mParsedMethod == kMethod_Head) || + (mParsedMethod == kMethod_Options) || (mParsedMethod == kMethod_Trace) + ) { return true; } @@ -84,6 +283,7 @@ nsHttpRequestHead::IsSafeMethod() const void nsHttpRequestHead::Flatten(nsACString &buf, bool pruneProxyHeaders) { + MutexAutoLock lock(mLock); // note: the first append is intentional. buf.Append(mMethod); diff --git a/netwerk/protocol/http/nsHttpRequestHead.h b/netwerk/protocol/http/nsHttpRequestHead.h index 755750e83d..d58ee5e748 100644 --- a/netwerk/protocol/http/nsHttpRequestHead.h +++ b/netwerk/protocol/http/nsHttpRequestHead.h @@ -9,6 +9,9 @@ #include "nsHttp.h" #include "nsHttpHeaderArray.h" #include "nsString.h" +#include "mozilla/Mutex.h" + +class nsIHttpHeaderVisitor; namespace mozilla { namespace net { @@ -23,57 +26,56 @@ public: nsHttpRequestHead(); ~nsHttpRequestHead(); + // The following function is only used in HttpChannelParent to avoid + // copying headers. If you use it be careful to do it only under + // nsHttpRequestHead lock!!! + const nsHttpHeaderArray &Headers() const; + void Lock() { mLock.Lock(); } + void Unlock() { mLock.Unlock(); } + + void SetHeaders(const nsHttpHeaderArray& aHeaders); + void SetMethod(const nsACString &method); - void SetVersion(nsHttpVersion version) { mVersion = version; } - void SetRequestURI(const nsCSubstring &s) { mRequestURI = s; } - void SetPath(const nsCSubstring &s) { mPath = s; } + void SetVersion(nsHttpVersion version); + void SetRequestURI(const nsCSubstring &s); + void SetPath(const nsCSubstring &s); + uint32_t HeaderCount(); - const nsHttpHeaderArray &Headers() const { return mHeaders; } - nsHttpHeaderArray & Headers() { return mHeaders; } - const nsCString &Method() const { return mMethod; } - nsHttpVersion Version() const { return mVersion; } - const nsCSubstring &RequestURI() const { return mRequestURI; } - const nsCSubstring &Path() const { return mPath.IsEmpty() ? mRequestURI : mPath; } + // Using this function it is possible to itereate through all headers + // automatically under one lock. + nsresult VisitHeaders(nsIHttpHeaderVisitor *visitor, + nsHttpHeaderArray::VisitorFilter filter = + nsHttpHeaderArray::eFilterAll); + void Method(nsACString &aMethod); + nsHttpVersion Version(); + void RequestURI(nsACString &RequestURI); + void Path(nsACString &aPath); + void SetHTTPS(bool val); + bool IsHTTPS(); - void SetHTTPS(bool val) { mHTTPS = val; } - bool IsHTTPS() const { return mHTTPS; } + void SetOrigin(const nsACString &scheme, const nsACString &host, + int32_t port); + void Origin(nsACString &aOrigin); - void SetOrigin(const nsACString &scheme, const nsACString &host, int32_t port); - const nsCString &Origin() const { return mOrigin; } + nsresult SetHeader(nsHttpAtom h, const nsACString &v, bool m=false); + nsresult SetHeader(nsHttpAtom h, const nsACString &v, bool m, + nsHttpHeaderArray::HeaderVariety variety); + nsresult SetEmptyHeader(nsHttpAtom h); + nsresult GetHeader(nsHttpAtom h, nsACString &v); - const char *PeekHeader(nsHttpAtom h) const - { - return mHeaders.PeekHeader(h); - } - nsresult SetHeader(nsHttpAtom h, const nsACString &v, bool m=false) { return mHeaders.SetHeader(h, v, m); } - nsresult SetEmptyHeader(nsHttpAtom h) { return mHeaders.SetEmptyHeader(h); } - nsresult GetHeader(nsHttpAtom h, nsACString &v) const - { - return mHeaders.GetHeader(h, v); - } - void ClearHeader(nsHttpAtom h) { mHeaders.ClearHeader(h); } - void ClearHeaders() { mHeaders.Clear(); } - - const char *FindHeaderValue(nsHttpAtom h, const char *v) const - { - return mHeaders.FindHeaderValue(h, v); - } - bool HasHeaderValue(nsHttpAtom h, const char *v) const - { - return mHeaders.HasHeaderValue(h, v); - } + void ClearHeader(nsHttpAtom h); + void ClearHeaders(); + bool HasHeaderValue(nsHttpAtom h, const char *v); + // This function returns true if header is set even if it is an empty + // header. + bool HasHeader(nsHttpAtom h); void Flatten(nsACString &, bool pruneProxyHeaders = false); // Don't allow duplicate values - nsresult SetHeaderOnce(nsHttpAtom h, const char *v, bool merge = false) - { - if (!merge || !HasHeaderValue(h, v)) - return mHeaders.SetHeader(h, nsDependentCString(v), merge); - return NS_OK; - } + nsresult SetHeaderOnce(nsHttpAtom h, const char *v, bool merge = false); - bool IsSafeMethod() const; + bool IsSafeMethod(); enum ParsedMethodType { @@ -87,17 +89,16 @@ public: kMethod_Trace }; - ParsedMethodType ParsedMethod() const { return mParsedMethod; } - bool EqualsMethod(ParsedMethodType aType) const { return mParsedMethod == aType; } - bool IsGet() const { return EqualsMethod(kMethod_Get); } - bool IsPost() const { return EqualsMethod(kMethod_Post); } - bool IsOptions() const { return EqualsMethod(kMethod_Options); } - bool IsConnect() const { return EqualsMethod(kMethod_Connect); } - bool IsHead() const { return EqualsMethod(kMethod_Head); } - bool IsPut() const { return EqualsMethod(kMethod_Put); } - bool IsTrace() const { return EqualsMethod(kMethod_Trace); } - void ParseHeaderSet(char *buffer) { mHeaders.ParseHeaderSet(buffer); } - + ParsedMethodType ParsedMethod(); + bool EqualsMethod(ParsedMethodType aType); + bool IsGet() { return EqualsMethod(kMethod_Get); } + bool IsPost() { return EqualsMethod(kMethod_Post); } + bool IsOptions() { return EqualsMethod(kMethod_Options); } + bool IsConnect() { return EqualsMethod(kMethod_Connect); } + bool IsHead() { return EqualsMethod(kMethod_Head); } + bool IsPut() { return EqualsMethod(kMethod_Put); } + bool IsTrace() { return EqualsMethod(kMethod_Trace); } + void ParseHeaderSet(char *buffer); private: // All members must be copy-constructable and assignable nsHttpHeaderArray mHeaders; @@ -112,6 +113,8 @@ private: nsCString mOrigin; ParsedMethodType mParsedMethod; bool mHTTPS; + + Mutex mLock; }; } // namespace net diff --git a/netwerk/protocol/http/nsHttpTransaction.cpp b/netwerk/protocol/http/nsHttpTransaction.cpp index 00d79daed9..2b4d7dc0b8 100644 --- a/netwerk/protocol/http/nsHttpTransaction.cpp +++ b/netwerk/protocol/http/nsHttpTransaction.cpp @@ -183,27 +183,32 @@ nsHttpTransaction::Classify() if (!(mCaps & NS_HTTP_ALLOW_PIPELINING)) return (mClassification = CLASS_SOLO); - if (mRequestHead->PeekHeader(nsHttp::If_Modified_Since) || - mRequestHead->PeekHeader(nsHttp::If_None_Match)) + if (mRequestHead->HasHeader(nsHttp::If_Modified_Since) || + mRequestHead->HasHeader(nsHttp::If_None_Match)) return (mClassification = CLASS_REVALIDATION); - const char *accept = mRequestHead->PeekHeader(nsHttp::Accept); - if (accept && !PL_strncmp(accept, "image/", 6)) + nsAutoCString accept; + bool hasAccept = NS_SUCCEEDED(mRequestHead->GetHeader(nsHttp::Accept, accept)); + if (hasAccept && StringBeginsWith(accept, NS_LITERAL_CSTRING("image/"))) { return (mClassification = CLASS_IMAGE); + } - if (accept && !PL_strncmp(accept, "text/css", 8)) + if (hasAccept && StringBeginsWith(accept, NS_LITERAL_CSTRING("text/css"))) { return (mClassification = CLASS_SCRIPT); + } mClassification = CLASS_GENERAL; - int32_t queryPos = mRequestHead->RequestURI().FindChar('?'); + nsAutoCString requestURI; + mRequestHead->RequestURI(requestURI); + int32_t queryPos = requestURI.FindChar('?'); if (queryPos == kNotFound) { - if (StringEndsWith(mRequestHead->RequestURI(), + if (StringEndsWith(requestURI, NS_LITERAL_CSTRING(".js"))) mClassification = CLASS_SCRIPT; } else if (queryPos >= 3 && - Substring(mRequestHead->RequestURI(), queryPos - 3, 3). + Substring(requestURI, queryPos - 3, 3). EqualsLiteral(".js")) { mClassification = CLASS_SCRIPT; } @@ -301,7 +306,7 @@ nsHttpTransaction::Init(uint32_t caps, // containing a message-body MUST include a valid Content-Length header // field unless the server is known to be HTTP/1.1 compliant. if ((requestHead->IsPost() || requestHead->IsPut()) && - !requestBody && !requestHead->PeekHeader(nsHttp::Transfer_Encoding)) { + !requestBody && !requestHead->HasHeader(nsHttp::Transfer_Encoding)) { requestHead->SetHeader(nsHttp::Content_Length, NS_LITERAL_CSTRING("0")); } diff --git a/netwerk/protocol/http/nsIHttpChannel.idl b/netwerk/protocol/http/nsIHttpChannel.idl index a2b4735f02..b73e51deab 100644 --- a/netwerk/protocol/http/nsIHttpChannel.idl +++ b/netwerk/protocol/http/nsIHttpChannel.idl @@ -59,6 +59,11 @@ interface nsIHttpChannel : nsIChannel * Referrer policies. See ReferrerPolicy.h for more details. */ + + /* The undefined state, usually used to check the need of + overruling document-wide referrer policy. */ + const unsigned long REFERRER_POLICY_UNSET = 4294967295; // UINT32_MAX + /* default state, a shorter name for no-referrer-when-downgrade */ const unsigned long REFERRER_POLICY_DEFAULT = 0; /* default state, doesn't send referrer from https->http */ diff --git a/netwerk/test/unit/test_protocolproxyservice.js b/netwerk/test/unit/test_protocolproxyservice.js index b8fd682e02..2629bd5c03 100644 --- a/netwerk/test/unit/test_protocolproxyservice.js +++ b/netwerk/test/unit/test_protocolproxyservice.js @@ -604,6 +604,36 @@ function run_pac4_test() { prefs.setIntPref("network.proxy.type", 2); prefs.setCharPref("network.proxy.autoconfig_url", pac); + var req = pps.asyncResolve(channel, 0, new TestResolveCallback("http", run_pac5_test)); +} + +function run_pac5_test() { + // Bug 1251332 + let wRange = [ + ["SUN", "MON", "SAT", "MON"], // for Sun + ["SUN", "TUE", "SAT", "TUE"], // for Mon + ["MON", "WED", "SAT", "WED"], // for Tue + ["TUE", "THU", "SAT", "THU"], // for Wed + ["WED", "FRI", "WED", "SUN"], // for Thu + ["THU", "SAT", "THU", "SUN"], // for Fri + ["FRI", "SAT", "FRI", "SUN"], // for Sat + ]; + let today = (new Date()).getDay(); + var pac = 'data:text/plain,' + + 'function FindProxyForURL(url, host) {' + + ' if (weekdayRange("' + wRange[today][0] + '", "' + wRange[today][1] + '") &&' + + ' weekdayRange("' + wRange[today][2] + '", "' + wRange[today][3] + '")) {' + + ' return "PROXY foopy:8080; DIRECT";' + + ' }' + + '}'; + var channel = NetUtil.newChannel({ + uri: "http://www.mozilla.org/", + loadUsingSystemPrincipal: true + }); + // Configure PAC + + prefs.setIntPref("network.proxy.type", 2); + prefs.setCharPref("network.proxy.autoconfig_url", pac); var req = pps.asyncResolve(channel, 0, new TestResolveCallback("http", finish_pac_test)); } diff --git a/python/mozboot/mozboot/centosfedora.py b/python/mozboot/mozboot/centosfedora.py index 153309991b..3e1ada9b74 100644 --- a/python/mozboot/mozboot/centosfedora.py +++ b/python/mozboot/mozboot/centosfedora.py @@ -132,6 +132,5 @@ class CentOSFedoraBootstrapper(BaseBootstrapper): def suggest_mobile_android_artifact_mode_mozconfig(self): self.suggest_mobile_android_mozconfig(artifact_mode=True) - def upgrade_mercurial(self): + def upgrade_mercurial(self, current): self.dnf_update('mercurial') - diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000000..839f856fd2 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,43 @@ +// You might think topsrcdir is '.', but that's not true when the Gradle build +// is launched from within IntelliJ. +def topsrcdir = rootProject.projectDir.absolutePath + +def commandLine = ["${topsrcdir}/mach", "environment", "--format", "json", "--verbose"] +def proc = commandLine.execute(null, new File(topsrcdir)) +def standardOutput = new ByteArrayOutputStream() +proc.consumeProcessOutput(standardOutput, standardOutput) +proc.waitFor() + +// Only show the output if something went wrong. +if (proc.exitValue() != 0) { + throw new GradleException("Process '${commandLine}' finished with non-zero exit value ${proc.exitValue()}:\n\n${standardOutput.toString()}") +} + +import groovy.json.JsonSlurper +def slurper = new JsonSlurper() +def json = slurper.parseText(standardOutput.toString()) + +include ':app' +include ':base' +include ':omnijar' +include ':thirdparty' + +def gradleRoot = new File("${json.topobjdir}/mobile/android/gradle") +project(':app').projectDir = new File(gradleRoot, 'app') +project(':base').projectDir = new File(gradleRoot, 'base') +project(':omnijar').projectDir = new File(gradleRoot, 'omnijar') +project(':thirdparty').projectDir = new File(gradleRoot, 'thirdparty') + +if (json.substs.MOZ_INSTALL_TRACKING) { + include ':thirdparty_adjust_sdk' + project(':thirdparty_adjust_sdk').projectDir = new File(gradleRoot, 'thirdparty_adjust_sdk') +} + +// The Gradle instance is shared between settings.gradle and all the +// other build.gradle files (see +// http://forums.gradle.org/gradle/topics/define_extension_properties_from_settings_xml). +// We use this ext property to pass the per-object-directory mozconfig +// between scripts. This lets us execute set-up code before we gradle +// tries to configure the project even once, and as a side benefit +// saves invoking |mach environment| multiple times. +gradle.ext.mozconfig = json diff --git a/toolkit/components/telemetry/Telemetry.h b/toolkit/components/telemetry/Telemetry.h index 1b16d0ed74..cfdb445905 100644 --- a/toolkit/components/telemetry/Telemetry.h +++ b/toolkit/components/telemetry/Telemetry.h @@ -273,7 +273,7 @@ class ProcessedStack; * @param aFirefoxUptime - Firefox uptime at the time of the hang, in minutes * @param aAnnotations - Any annotations to be added to the report */ -#if defined(MOZ_ENABLE_PROFILER_SPS) && !defined(MOZILLA_XPCOMRT_API) +#if defined(MOZ_ENABLE_PROFILER_SPS) void RecordChromeHang(uint32_t aDuration, ProcessedStack &aStack, int32_t aSystemUptime, diff --git a/toolkit/components/telemetry/TelemetryController.jsm b/toolkit/components/telemetry/TelemetryController.jsm index eb848f623b..5b4cae6900 100644 --- a/toolkit/components/telemetry/TelemetryController.jsm +++ b/toolkit/components/telemetry/TelemetryController.jsm @@ -49,7 +49,7 @@ const PING_FORMAT_VERSION = 4; // Delay before intializing telemetry (ms) const TELEMETRY_DELAY = Preferences.get("toolkit.telemetry.initDelay", 60) * 1000; // Delay before initializing telemetry if we're testing (ms) -const TELEMETRY_TEST_DELAY = 100; +const TELEMETRY_TEST_DELAY = 1; // Ping types. const PING_TYPE_MAIN = "main"; diff --git a/toolkit/components/telemetry/TelemetrySend.jsm b/toolkit/components/telemetry/TelemetrySend.jsm index 1820c72c13..64e0cbf644 100644 --- a/toolkit/components/telemetry/TelemetrySend.jsm +++ b/toolkit/components/telemetry/TelemetrySend.jsm @@ -911,6 +911,7 @@ var TelemetrySendImpl = { request.open("POST", url, true); request.overrideMimeType("text/plain"); request.setRequestHeader("Content-Type", "application/json; charset=UTF-8"); + request.setRequestHeader("Date", Policy.now().toUTCString()); this._pendingPingRequests.set(id, request); diff --git a/toolkit/components/telemetry/TelemetrySession.jsm b/toolkit/components/telemetry/TelemetrySession.jsm index 53238d779e..877bcc8c29 100644 --- a/toolkit/components/telemetry/TelemetrySession.jsm +++ b/toolkit/components/telemetry/TelemetrySession.jsm @@ -78,7 +78,7 @@ const TELEMETRY_INTERVAL = 60 * 1000; // Delay before intializing telemetry (ms) const TELEMETRY_DELAY = Preferences.get("toolkit.telemetry.initDelay", 60) * 1000; // Delay before initializing telemetry if we're testing (ms) -const TELEMETRY_TEST_DELAY = 100; +const TELEMETRY_TEST_DELAY = 1; // Execute a scheduler tick every 5 minutes. const SCHEDULER_TICK_INTERVAL_MS = Preferences.get("toolkit.telemetry.scheduler.tickInterval", 5 * 60) * 1000; // When user is idle, execute a scheduler tick every 60 minutes. @@ -311,13 +311,41 @@ var TelemetryScheduler = { this._log.trace("init"); this._shuttingDown = false; this._isUserIdle = false; + // Initialize the last daily ping and aborted session last due times to the current time. // Otherwise, we might end up sending daily pings even if the subsession is not long enough. let now = Policy.now(); this._lastDailyPingTime = now.getTime(); this._lastSessionCheckpointTime = now.getTime(); this._rescheduleTimeout(); + idleService.addIdleObserver(this, IDLE_TIMEOUT_SECONDS); + Services.obs.addObserver(this, "wake_notification", false); + }, + + /** + * Stops the scheduler. + */ + shutdown: function() { + if (this._shuttingDown) { + if (this._log) { + this._log.error("shutdown - Already shut down"); + } else { + Cu.reportError("TelemetryScheduler.shutdown - Already shut down"); + } + return; + } + + this._log.trace("shutdown"); + if (this._schedulerTimer) { + Policy.clearSchedulerTickTimeout(this._schedulerTimer); + this._schedulerTimer = null; + } + + idleService.removeIdleObserver(this, IDLE_TIMEOUT_SECONDS); + Services.obs.removeObserver(this, "wake_notification"); + + this._shuttingDown = true; }, _clearTimeout: function() { @@ -415,6 +443,13 @@ var TelemetryScheduler = { this._isUserIdle = false; return this._onSchedulerTick(); break; + case "wake_notification": + // The machine woke up from sleep, trigger a tick to avoid sessions + // spanning more than a day. + // This is needed because sleep time does not count towards timeouts + // on Mac & Linux - see bug 1262386, bug 1204823 et al. + return this._onSchedulerTick(); + break; } return undefined; }, @@ -517,30 +552,6 @@ var TelemetryScheduler = { this._rescheduleTimeout(); }, - - /** - * Stops the scheduler. - */ - shutdown: function() { - if (this._shuttingDown) { - if (this._log) { - this._log.error("shutdown - Already shut down"); - } else { - Cu.reportError("TelemetryScheduler.shutdown - Already shut down"); - } - return; - } - - this._log.trace("shutdown"); - if (this._schedulerTimer) { - Policy.clearSchedulerTickTimeout(this._schedulerTimer); - this._schedulerTimer = null; - } - - idleService.removeIdleObserver(this, IDLE_TIMEOUT_SECONDS); - - this._shuttingDown = true; - } }; this.EXPORTED_SYMBOLS = ["TelemetrySession"]; diff --git a/toolkit/components/telemetry/TelemetryStopwatch.jsm b/toolkit/components/telemetry/TelemetryStopwatch.jsm index 19c19c6b66..ee3d899480 100644 --- a/toolkit/components/telemetry/TelemetryStopwatch.jsm +++ b/toolkit/components/telemetry/TelemetryStopwatch.jsm @@ -8,14 +8,110 @@ const Cu = Components.utils; this.EXPORTED_SYMBOLS = ["TelemetryStopwatch"]; +Cu.import("resource://gre/modules/Log.jsm", this); var Telemetry = Cc["@mozilla.org/base/telemetry;1"] .getService(Ci.nsITelemetry); -// simpleTimers are directly associated with a histogram -// name. objectTimers are associated with an object _and_ -// a histogram name. -var simpleTimers = {}; -var objectTimers = new WeakMap(); +// Weak map does not allow using null objects as keys. These objects are used +// as 'null' placeholders. +const NULL_OBJECT = {}; +const NULL_KEY = {}; + +/** + * Timers is a variation of a Map used for storing information about running + * Stopwatches. Timers has the following data structure: + * + * { + * "HISTOGRAM_NAME": WeakMap { + * Object || NULL_OBJECT: Map { + * "KEY" || NULL_KEY: startTime + * ... + * } + * ... + * } + * ... + * } + * + * + * @example + * // Stores current time for a keyed histogram "PLAYING_WITH_CUTE_ANIMALS". + * Timers.put("PLAYING_WITH_CUTE_ANIMALS", null, "CATS", Date.now()); + * + * @example + * // Returns information about a simple Stopwatch. + * let startTime = Timers.get("PLAYING_WITH_CUTE_ANIMALS", null, "CATS"); + */ +let Timers = { + _timers: new Map(), + + _validTypes: function(histogram, obj, key) { + let nonEmptyString = value => { + return typeof value === "string" && value !== "" && value.length > 0; + }; + return nonEmptyString(histogram) && + typeof obj == "object" && + (key === NULL_KEY || nonEmptyString(key)); + }, + + get: function(histogram, obj, key) { + key = key === null ? NULL_KEY : key; + obj = obj || NULL_OBJECT; + + if (!this.has(histogram, obj, key)) { + return null; + } + + return this._timers.get(histogram).get(obj).get(key); + }, + + put: function(histogram, obj, key, startTime) { + key = key === null ? NULL_KEY : key; + obj = obj || NULL_OBJECT; + + if (!this._validTypes(histogram, obj, key)) { + return false; + } + + let objectMap = this._timers.get(histogram) || new WeakMap(); + let keyedInfo = objectMap.get(obj) || new Map(); + keyedInfo.set(key, startTime); + objectMap.set(obj, keyedInfo); + this._timers.set(histogram, objectMap); + return true; + }, + + has: function(histogram, obj, key) { + key = key === null ? NULL_KEY : key; + obj = obj || NULL_OBJECT; + + return this._timers.has(histogram) && + this._timers.get(histogram).has(obj) && + this._timers.get(histogram).get(obj).has(key); + }, + + delete: function(histogram, obj, key) { + key = key === null ? NULL_KEY : key; + obj = obj || NULL_OBJECT; + + if(!this.has(histogram, obj, key)) { + return false; + } + let objectMap = this._timers.get(histogram); + let keyedInfo = objectMap.get(obj); + if (keyedInfo.size > 1) { + keyedInfo.delete(key); + return true; + } + objectMap.delete(obj); + // NOTE: + // We never delete empty objecMaps from this._timers because there is no + // nice solution for tracking the number of objects in a WeakMap. + // WeakMap is not enumerable, so we can't deterministically say when it's + // empty. We accept that trade-off here, given that entries for short-lived + // objects will go away when they are no longer referenced + return true; + } +}; this.TelemetryStopwatch = { /** @@ -23,39 +119,21 @@ this.TelemetryStopwatch = { * directly associated with a histogram, or with a pair of a histogram and * an object. * - * @param aHistogram a string which must be a valid histogram name - * from TelemetryHistograms.h + * @param {String} aHistogram - a string which must be a valid histogram name. * - * @param aObj Optional parameter. If specified, the timer is associated - * with this object, meaning that multiple timers for a same - * histogram may be run concurrently, as long as they are - * associated with different objects. + * @param {Object} aObj - Optional parameter. If specified, the timer is + * associated with this object, meaning that multiple + * timers for the same histogram may be run + * concurrently, as long as they are associated with + * different objects. * - * @return true if the timer was successfully started, false otherwise. If a - * timer already exists, it can't be started again, and the existing - * one will be cleared in order to avoid measurements errors. + * @returns {Boolean} True if the timer was successfully started, false + * otherwise. If a timer already exists, it can't be + * started again, and the existing one will be cleared in + * order to avoid measurements errors. */ start: function(aHistogram, aObj) { - if (!validTypes(aHistogram, aObj)) - return false; - - let timers; - if (aObj) { - timers = objectTimers.get(aObj) || {}; - objectTimers.set(aObj, timers); - } else { - timers = simpleTimers; - } - - if (timers.hasOwnProperty(aHistogram)) { - delete timers[aHistogram]; - Cu.reportError("TelemetryStopwatch: key \"" + - aHistogram + "\" was already initialized"); - return false; - } - - timers[aHistogram] = Components.utils.now(); - return true; + return TelemetryStopwatchImpl.start(aHistogram, aObj, null); }, /** @@ -64,57 +142,37 @@ this.TelemetryStopwatch = { * an object. Important: Only use this method when a legitimate cancellation * should be done. * - * @param aHistogram a string which must be a valid histogram name - * from TelemetryHistograms.json + * @param {String} aHistogram - a string which must be a valid histogram name. * - * @param aObj Optional parameter. If specified, the timer is associated - * with this object, meaning that multiple timers for a same - * histogram may be run concurrently, as long as they are - * associated with different objects. + * @param {Object} aObj - Optional parameter. If specified, the timer is + * associated with this object, meaning that multiple + * timers or a same histogram may be run concurrently, + * as long as they are associated with different + * objects. * - * @return true if the timer exist and it was cleared, false otherwise. + * @returns {Boolean} True if the timer exist and it was cleared, False + * otherwise. */ - cancel: function ts_cancel(aHistogram, aObj) { - if (!validTypes(aHistogram, aObj)) - return false; - - let timers = aObj - ? objectTimers.get(aObj) || {} - : simpleTimers; - - if (timers.hasOwnProperty(aHistogram)) { - delete timers[aHistogram]; - return true; - } - - return false; + cancel: function(aHistogram, aObj) { + return TelemetryStopwatchImpl.cancel(aHistogram, aObj, null); }, /** * Returns the elapsed time for a particular stopwatch. Primarily for * debugging purposes. Must be called prior to finish. * - * @param aHistogram a string which must be a valid histogram name - * from TelemetryHistograms.h. If an invalid name - * is given, the function will throw. + * @param {String} aHistogram - a string which must be a valid histogram name. + * If an invalid name is given, the function will + * throw. * - * @param aObj Optional parameter which associates the histogram timer with - * the given object. + * @param (Object) aObj - Optional parameter which associates the histogram + * timer with the given object. * - * @return time in milliseconds or -1 if the stopwatch was not found. + * @returns {Integer} time in milliseconds or -1 if the stopwatch was not + * found. */ timeElapsed: function(aHistogram, aObj) { - if (!validTypes(aHistogram, aObj)) - return -1; - let timers = aObj - ? objectTimers.get(aObj) || {} - : simpleTimers; - let start = timers[aHistogram]; - if (start) { - let delta = Components.utils.now() - start; - return Math.round(delta); - } - return -1; + return TelemetryStopwatchImpl.timeElapsed(aHistogram, aObj, null); }, /** @@ -122,40 +180,156 @@ this.TelemetryStopwatch = { * calculates the time delta between start and finish, and adds the value * to the histogram. * - * @param aHistogram a string which must be a valid histogram name - * from TelemetryHistograms.h. If an invalid name - * is given, the function will throw. + * @param {String} aHistogram - a string which must be a valid histogram name. * - * @param aObj Optional parameter which associates the histogram timer with - * the given object. + * @param {Object} aObj - Optional parameter which associates the histogram + * timer with the given object. * - * @return true if the timer was succesfully stopped and the data was - * added to the histogram, false otherwise. + * @returns {Boolean} True if the timer was succesfully stopped and the data + * was added to the histogram, False otherwise. */ finish: function(aHistogram, aObj) { - if (!validTypes(aHistogram, aObj)) + return TelemetryStopwatchImpl.finish(aHistogram, aObj, null); + }, + + /** + * Starts a timer associated with a keyed telemetry histogram. The timer can + * be directly associated with a histogram and its key. Similarly to + * @see{TelemetryStopwatch.stat} the histogram and its key can be associated + * with an object. Each key may have multiple associated objects and each + * object can be associated with multiple keys. + * + * @param {String} aHistogram - a string which must be a valid histogram name. + * + * @param {String} aKey - a string which must be a valid histgram key. + * + * @param {Object} aObj - Optional parameter. If specified, the timer is + * associated with this object, meaning that multiple + * timers for the same histogram may be run + * concurrently,as long as they are associated with + * different objects. + * + * @returns {Boolean} True if the timer was successfully started, false + * otherwise. If a timer already exists, it can't be + * started again, and the existing one will be cleared in + * order to avoid measurements errors. + */ + startKeyed: function(aHistogram, aKey, aObj) { + return TelemetryStopwatchImpl.start(aHistogram, aObj, aKey); + }, + + /** + * Deletes the timer associated with a keyed histogram. Important: Only use + * this method when a legitimate cancellation should be done. + * + * @param {String} aHistogram - a string which must be a valid histogram name. + * + * @param {String} aKey - a string which must be a valid histgram key. + * + * @param {Object} aObj - Optional parameter. If specified, the timer + * associated with this object is deleted. + * + * @return {Boolean} True if the timer exist and it was cleared, False + * otherwise. + */ + cancelKeyed: function(aHistogram, aKey, aObj) { + return TelemetryStopwatchImpl.cancel(aHistogram, aObj, aKey); + }, + + /** + * Returns the elapsed time for a particular stopwatch. Primarily for + * debugging purposes. Must be called prior to finish. + * + * @param {String} aHistogram - a string which must be a valid histogram name. + * + * @param {String} aKey - a string which must be a valid histgram key. + * + * @param {Object} aObj - Optional parameter. If specified, the timer + * associated with this object is used to calculate + * the elapsed time. + * + * @return {Integer} time in milliseconds or -1 if the stopwatch was not + * found. + */ + timeElapsedKeyed: function(aHistogram, aKey, aObj) { + return TelemetryStopwatchImpl.timeElapsed(aHistogram, aObj, aKey); + }, + + /** + * Stops the timer associated with the given keyed histogram (and object), + * calculates the time delta between start and finish, and adds the value + * to the keyed histogram. + * + * @param {String} aHistogram - a string which must be a valid histogram name. + * + * @param {String} aKey - a string which must be a valid histgram key. + * + * @param {Object} aObj - optional parameter which associates the histogram + * timer with the given object. + * + * @returns {Boolean} True if the timer was succesfully stopped and the data + * was added to the histogram, False otherwise. + */ + finishKeyed: function(aHistogram, aKey, aObj) { + return TelemetryStopwatchImpl.finish(aHistogram, aObj, aKey); + } +}; + +this.TelemetryStopwatchImpl = { + start: function(histogram, object, key) { + if (Timers.has(histogram, object, key)) { + Timers.delete(histogram, object, key); + Cu.reportError(`TelemetryStopwatch: key "${histogram}" was already ` + + "initialized"); return false; - - let timers = aObj - ? objectTimers.get(aObj) || {} - : simpleTimers; - - let start = timers[aHistogram]; - delete timers[aHistogram]; - - if (start) { - let delta = Components.utils.now() - start; - delta = Math.round(delta); - let histogram = Telemetry.getHistogramById(aHistogram); - histogram.add(delta); - return true; } - return false; + return Timers.put(histogram, object, key, Components.utils.now()); + }, + + cancel: function (histogram, object, key) { + return Timers.delete(histogram, object, key); + }, + + timeElapsed: function(histogram, object, key) { + let startTime = Timers.get(histogram, object, key); + if (startTime === null) { + Cu.reportError("TelemetryStopwatch: requesting elapsed time for " + + `nonexisting stopwatch. Histogram: "${histogram}", ` + + `key: "${key}"`); + return -1; + } + + try { + let delta = Components.utils.now() - startTime + return Math.round(delta); + } catch (e) { + Cu.reportError("TelemetryStopwatch: failed to calculate elapsed time " + + `for Histogram: "${histogram}", key: "${key}", ` + + `exception: ${Log.exceptionStr(e)}`); + return -1; + } + }, + + finish: function(histogram, object, key) { + let delta = this.timeElapsed(histogram, object, key); + if (delta == -1) { + return false; + } + + try { + if (key) { + Telemetry.getKeyedHistogramById(histogram).add(key, delta); + } else { + Telemetry.getHistogramById(histogram).add(delta); + } + } catch (e) { + Cu.reportError("TelemetryStopwatch: failed to update the Histogram " + + `"${histogram}", using key: "${key}", ` + + `exception: ${Log.exceptionStr(e)}`); + return false; + } + + return Timers.delete(histogram, object, key); } } - -function validTypes(aKey, aObj) { - return (typeof aKey == "string") && - (aObj === undefined || typeof aObj == "object"); -} diff --git a/toolkit/components/telemetry/TelemetryStorage.jsm b/toolkit/components/telemetry/TelemetryStorage.jsm index 8d0422c78c..27cacbe466 100644 --- a/toolkit/components/telemetry/TelemetryStorage.jsm +++ b/toolkit/components/telemetry/TelemetryStorage.jsm @@ -1579,10 +1579,6 @@ var TelemetryStorageImpl = { let ping; try { ping = JSON.parse(string); - // The ping's payload used to be stringified JSON. Deal with that. - if (typeof(ping.payload) == "string") { - ping.payload = JSON.parse(ping.payload); - } } catch (e) { this._log.trace("loadPingfile - unparseable ping " + aFilePath, e); yield OS.File.remove(aFilePath).catch((ex) => { diff --git a/toolkit/components/telemetry/docs/core-ping.rst b/toolkit/components/telemetry/docs/core-ping.rst new file mode 100644 index 0000000000..a2101749ac --- /dev/null +++ b/toolkit/components/telemetry/docs/core-ping.rst @@ -0,0 +1,105 @@ + +"core" ping +============ + +This mobile-specific ping is intended to provide the most critical +data in a concise format, allowing for frequent uploads. + +Since this ping is used to measure retention, it should be sent +each time the browser is opened. + +Submission will be per the Edge server specification:: + + /submit/telemetry/docId/docType/appName/appVersion/appUpdateChannel/appBuildID + +* ``docId`` is a UUID for deduping +* ``docType`` is “core” +* ``appName`` is “Fennec” +* ``appVersion`` is the version of the application (e.g. "46.0a1") +* ``appUpdateChannel`` is “release”, “beta”, etc. +* ``appBuildID`` is the build number + +Note: Counts below (e.g. search & usage times) are “since the last +ping”, not total for the whole application lifetime. + +Structure:: + + { + "v": 3, // ping format version + "clientId": , // client id, e.g. + // "c641eacf-c30c-4171-b403-f077724e848a" + "seq": , // running ping counter, e.g. 3 + "locale": , // application locale, e.g. "en-US" + "os": , // OS name. + "osversion": , // OS version. + "device": , // Build.MANUFACTURER + " - " + Build.MODEL + // where manufacturer is truncated to 12 characters + // & model is truncated to 19 characters + "arch": , // e.g. "arm", "x86" + "profileDate": , // Profile creation date in days since + // UNIX epoch. + "defaultSearch": , // Identifier of the default search engine, + // e.g. "yahoo". + + "experiments": [, …], // Optional, array of identifiers + // for the active experiments + } + +Field details +------------- + +device +~~~~~~ +The ``device`` field is filled in with information specified by the hardware +manufacturer. As such, it could be excessively long and use excessive amounts +of limited user data. To avoid this, we limit the length of the field. We're +more likely have collisions for models within a manufacturer (e.g. "Galaxy S5" +vs. "Galaxy Note") than we are for shortened manufacturer names so we provide +more characters for the model than the manufacturer. + +defaultSearch +~~~~~~~~~~~~~ +On Android, this field may be ``null``. To get the engine, we rely on +``SearchEngineManager#getDefaultEngine``, which searches in several places in +order to find the search engine identifier: + +* Shared Preferences +* The distribution (if it exists) +* The localized default engine + +If the identifier could not be retrieved, this field is ``null``. If the +identifier is retrieved, we attempt to create an instance of the search +engine from the search plugins (in order): + +* In the distribution +* From the localized plugins shipped with the browser +* The third-party plugins that are installed in the profile directory + +If the plugins fail to create a search engine instance, this field is also +``null``. + +This field can also be ``null`` when a custom search engine is set as the +default. + +profileDate +~~~~~~~~~~~ +On Android, this value is created at profile creation time and retrieved or, +for legacy profiles, taken from the package install time (note: this is not the +same exact metric as profile creation time but we compromised in favor of ease +of implementation). + +Additionally on Android, this field may be ``null`` in the unlikely event that +all of the following events occur: + +#. The times.json file does not exist +#. The package install date could not be persisted to disk + +The reason we don't just return the package install time even if the date could +not be persisted to disk is to ensure the value doesn't change once we start +sending it: we only want to send consistent values. + +Version history +--------------- +* v3: ``profileDate`` will return package install time when times.json is not available +* v2: added ``defaultSearch`` +* v1: initial version diff --git a/toolkit/components/telemetry/docs/deletion-ping.rst b/toolkit/components/telemetry/docs/deletion-ping.rst new file mode 100644 index 0000000000..a28f47cb97 --- /dev/null +++ b/toolkit/components/telemetry/docs/deletion-ping.rst @@ -0,0 +1,17 @@ + +"deletion" ping +=============== + +This ping is generated when a user turns off FHR upload from the Preferences panel, changing the related ``datareporting.healthreport.uploadEnabled`` preference. This requests that all associated data from that user be deleted. + +This ping contains the client id and no environment data. + +Structure:: + + { + version: 4, + type: "deletion", + ... common ping data + clientId: , + payload: { } + } \ No newline at end of file diff --git a/toolkit/components/telemetry/docs/index.rst b/toolkit/components/telemetry/docs/index.rst index 2c49b3ff5c..3e7a47af59 100644 --- a/toolkit/components/telemetry/docs/index.rst +++ b/toolkit/components/telemetry/docs/index.rst @@ -19,4 +19,7 @@ Client-side, this consists of: common-ping environment main-ping + core-ping + deletion-ping + uitour-ping preferences diff --git a/toolkit/components/telemetry/docs/pings.rst b/toolkit/components/telemetry/docs/pings.rst index 00031c241c..3ba42a4b98 100644 --- a/toolkit/components/telemetry/docs/pings.rst +++ b/toolkit/components/telemetry/docs/pings.rst @@ -43,9 +43,10 @@ Ping types * :doc:`main ` - contains the information collected by Telemetry (Histograms, hang stacks, ...) * :doc:`saved-session ` - has the same format as a main ping, but it contains the *"classic"* Telemetry payload with measurements covering the whole browser session. This is only a separate type to make storage of saved-session easier server-side. This is temporary and will be removed soon. * :doc:`crash ` - a ping that is captured and sent after Firefox crashes. +* :doc:`uitour-ping` - a ping submitted via the UITour API * ``activation`` - *planned* - sent right after installation or profile creation * ``upgrade`` - *planned* - sent right after an upgrade -* ``deletion`` - *planned* - on opt-out we may have to tell the server to delete user data +* :doc:`deletion ` - sent when FHR upload is disabled, requesting deletion of the data associated with this user Archiving ========= diff --git a/toolkit/components/telemetry/docs/uitour-ping.rst b/toolkit/components/telemetry/docs/uitour-ping.rst new file mode 100644 index 0000000000..e56a605e23 --- /dev/null +++ b/toolkit/components/telemetry/docs/uitour-ping.rst @@ -0,0 +1,24 @@ + +"uitour-tag" ping +================= + +This ping is submitted via the UITour setTreatmentTag API. It may be used by +the tour to record what settings were made by a user or to track the result of +A/B experiments. + +The client ID is submitted with this ping. + +Structure:: + + { + version: 1, + type: "uitour-tag", + clientId: , + payload: { + tagName: , + tagValue: + } + } + +See also: :doc:`common ping fields ` + diff --git a/toolkit/components/telemetry/tests/unit/.eslintrc b/toolkit/components/telemetry/tests/unit/.eslintrc new file mode 100644 index 0000000000..8a895f93bd --- /dev/null +++ b/toolkit/components/telemetry/tests/unit/.eslintrc @@ -0,0 +1,5 @@ +{ + "extends": [ + "../../../../../testing/xpcshell/xpcshell.eslintrc" + ] +} diff --git a/toolkit/components/telemetry/tests/unit/head.js b/toolkit/components/telemetry/tests/unit/head.js index 145b8aca58..a5dc9023c9 100644 --- a/toolkit/components/telemetry/tests/unit/head.js +++ b/toolkit/components/telemetry/tests/unit/head.js @@ -133,7 +133,7 @@ function decodeRequestPayload(request) { unicodeConverter.charset = "UTF-8"; let utf8string = unicodeConverter.ConvertToUnicode(observer.buffer); utf8string += unicodeConverter.Finish(); - payload = decoder.decode(utf8string); + payload = JSON.parse(utf8string); } else { payload = decoder.decodeFromStream(s, s.available()); } @@ -172,58 +172,6 @@ function loadAddonManager(ID, name, version, platformVersion) { startupManager(); } -/** - * Decode the payload of an HTTP request into a ping. - * @param {Object} request The data representing an HTTP request (nsIHttpRequest). - * @return {Object} The decoded ping payload. - */ -function decodeRequestPayload(request) { - let s = request.bodyInputStream; - let payload = null; - let decoder = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON) - - if (request.getHeader("content-encoding") == "gzip") { - let observer = { - buffer: "", - onStreamComplete: function(loader, context, status, length, result) { - this.buffer = String.fromCharCode.apply(this, result); - } - }; - - let scs = Cc["@mozilla.org/streamConverters;1"] - .getService(Ci.nsIStreamConverterService); - let listener = Cc["@mozilla.org/network/stream-loader;1"] - .createInstance(Ci.nsIStreamLoader); - listener.init(observer); - let converter = scs.asyncConvertData("gzip", "uncompressed", - listener, null); - converter.onStartRequest(null, null); - converter.onDataAvailable(null, null, s, 0, s.available()); - converter.onStopRequest(null, null, null); - let unicodeConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"] - .createInstance(Ci.nsIScriptableUnicodeConverter); - unicodeConverter.charset = "UTF-8"; - let utf8string = unicodeConverter.ConvertToUnicode(observer.buffer); - utf8string += unicodeConverter.Finish(); - payload = decoder.decode(utf8string); - } else { - payload = decoder.decodeFromStream(s, s.available()); - } - - return payload; -} - -function loadAddonManager(id, name, version, platformVersion) { - let ns = {}; - Cu.import("resource://gre/modules/Services.jsm", ns); - let head = "../../../../mozapps/extensions/test/xpcshell/head_addons.js"; - let file = do_get_file(head); - let uri = ns.Services.io.newFileURI(file); - ns.Services.scriptloader.loadSubScript(uri.spec, gGlobalScope); - createAppInfo(id, name, version, platformVersion); - startupManager(); -} - var gAppInfo = null; function createAppInfo(ID, name, version, platformVersion) { @@ -312,6 +260,17 @@ function promiseRejects(promise) { return promise.then(() => false, () => true); } +// Generates a random string of at least a specific length. +function generateRandomString(length) { + let string = ""; + + while (string.length < length) { + string += Math.random().toString(36); + } + + return string.substring(0, length); +} + // Short-hand for retrieving the histogram with that id. function getHistogram(histogramId) { return Telemetry.getHistogramById(histogramId); @@ -329,6 +288,8 @@ if (runningInParent) { Services.prefs.setBoolPref("toolkit.telemetry.archive.enabled", true); // Telemetry xpcshell tests cannot show the infobar. Services.prefs.setBoolPref("datareporting.policy.dataSubmissionPolicyBypassNotification", true); + // FHR uploads should be enabled. + Services.prefs.setBoolPref("datareporting.healthreport.uploadEnabled", true); fakePingSendTimer((callback, timeout) => { Services.tm.mainThread.dispatch(() => callback(), Ci.nsIThread.DISPATCH_NORMAL); @@ -338,6 +299,8 @@ if (runningInParent) { do_register_cleanup(() => TelemetrySend.shutdown()); } +TelemetryController.initLogging(); + // Avoid timers interrupting test behavior. fakeSchedulerTimer(() => {}, () => {}); // Make pind sending predictable. diff --git a/toolkit/components/telemetry/tests/unit/test_PingAPI.js b/toolkit/components/telemetry/tests/unit/test_PingAPI.js index 6ba160fb7a..2ed67a0661 100644 --- a/toolkit/components/telemetry/tests/unit/test_PingAPI.js +++ b/toolkit/components/telemetry/tests/unit/test_PingAPI.js @@ -195,6 +195,23 @@ add_task(function* test_archiveCleanup() { // Empty the archive. yield OS.File.removeDir(gPingsArchivePath); + Telemetry.getHistogramById("TELEMETRY_ARCHIVE_SCAN_PING_COUNT").clear(); + Telemetry.getHistogramById("TELEMETRY_ARCHIVE_DIRECTORIES_COUNT").clear(); + // Also reset these histograms to make sure normal sized pings don't get counted. + Telemetry.getHistogramById("TELEMETRY_PING_SIZE_EXCEEDED_ARCHIVED").clear(); + Telemetry.getHistogramById("TELEMETRY_DISCARDED_ARCHIVED_PINGS_SIZE_MB").clear(); + + // Build the cache. Nothing should be evicted as there's no ping directory. + yield TelemetryController.reset(); + yield TelemetryStorage.testCleanupTaskPromise(); + yield TelemetryArchive.promiseArchivedPingList(); + + let h = Telemetry.getHistogramById("TELEMETRY_ARCHIVE_SCAN_PING_COUNT").snapshot(); + Assert.equal(h.sum, 0, "Telemetry must report 0 pings scanned if no archive dir exists."); + // One directory out of four was removed as well. + h = Telemetry.getHistogramById("TELEMETRY_ARCHIVE_EVICTED_OLD_DIRS").snapshot(); + Assert.equal(h.sum, 0, "Telemetry must report 0 evicted dirs if no archive dir exists."); + let expectedPrunedInfo = []; let expectedNotPrunedInfo = []; @@ -215,15 +232,17 @@ add_task(function* test_archiveCleanup() { } }); + Telemetry.getHistogramById("TELEMETRY_ARCHIVE_SESSION_PING_COUNT").clear(); + // Create a ping which should be pruned because it is past the retention period. let date = fakeNow(2010, 1, 1, 1, 0, 0); let pingId = yield TelemetryController.submitExternalPing(PING_TYPE, {}, {}); expectedPrunedInfo.push({ id: pingId, creationDate: date }); // Create a ping which should be kept because it is within the retention period. - date = fakeNow(2010, 2, 1, 1, 0, 0); + const oldestDirectoryDate = fakeNow(2010, 2, 1, 1, 0, 0); pingId = yield TelemetryController.submitExternalPing(PING_TYPE, {}, {}); - expectedNotPrunedInfo.push({ id: pingId, creationDate: date }); + expectedNotPrunedInfo.push({ id: pingId, creationDate: oldestDirectoryDate }); // Create 20 other pings which are within the retention period, but would be affected // by the disk quota. @@ -235,8 +254,15 @@ add_task(function* test_archiveCleanup() { } } + // We expect all the pings we archived to be in this histogram. + h = Telemetry.getHistogramById("TELEMETRY_ARCHIVE_SESSION_PING_COUNT"); + Assert.equal(h.snapshot().sum, 22, "All the pings must be live-accumulated in the histogram."); + // Reset the histogram that will be populated by the archive scan. + Telemetry.getHistogramById("TELEMETRY_ARCHIVE_EVICTED_OLD_DIRS").clear(); + Telemetry.getHistogramById("TELEMETRY_ARCHIVE_OLDEST_DIRECTORY_AGE").clear(); + // Move the current date 180 days ahead of the first ping. - fakeNow(2010, 7, 1, 1, 0, 0); + let now = fakeNow(2010, 7, 1, 1, 0, 0); // Reset TelemetryArchive and TelemetryController to start the startup cleanup. yield TelemetryController.reset(); // Wait for the cleanup to finish. @@ -247,6 +273,27 @@ add_task(function* test_archiveCleanup() { // Check that the archive is in the correct state. yield checkArchive(); + // Make sure the ping count is correct after the scan (one ping was removed). + h = Telemetry.getHistogramById("TELEMETRY_ARCHIVE_SCAN_PING_COUNT").snapshot(); + Assert.equal(h.sum, 21, "The histogram must count all the pings in the archive."); + // One directory out of four was removed as well. + h = Telemetry.getHistogramById("TELEMETRY_ARCHIVE_EVICTED_OLD_DIRS").snapshot(); + Assert.equal(h.sum, 1, "Telemetry must correctly report removed archive directories."); + // Check that the remaining directories are correctly counted. + h = Telemetry.getHistogramById("TELEMETRY_ARCHIVE_DIRECTORIES_COUNT").snapshot(); + Assert.equal(h.sum, 3, "Telemetry must correctly report the remaining archive directories."); + // Check that the remaining directories are correctly counted. + const oldestAgeInMonths = 5; + h = Telemetry.getHistogramById("TELEMETRY_ARCHIVE_OLDEST_DIRECTORY_AGE").snapshot(); + Assert.equal(h.sum, oldestAgeInMonths, + "Telemetry must correctly report age of the oldest directory in the archive."); + + // We need to test the archive size before we hit the quota, otherwise a special + // value is recorded. + Telemetry.getHistogramById("TELEMETRY_ARCHIVE_SIZE_MB").clear(); + Telemetry.getHistogramById("TELEMETRY_ARCHIVE_EVICTED_OVER_QUOTA").clear(); + Telemetry.getHistogramById("TELEMETRY_ARCHIVE_EVICTING_OVER_QUOTA_MS").clear(); + // Move the current date 180 days ahead of the second ping. fakeNow(2010, 8, 1, 1, 0, 0); // Reset TelemetryController and TelemetryArchive. @@ -262,9 +309,19 @@ add_task(function* test_archiveCleanup() { yield checkArchive(); // Find how much disk space the archive takes. - const archivedPings = yield getArchivedPingsInfo(); + const archivedPingsInfo = yield getArchivedPingsInfo(); let archiveSizeInBytes = - archivedPings.reduce((lastResult, element) => lastResult + element.size, 0); + archivedPingsInfo.reduce((lastResult, element) => lastResult + element.size, 0); + + // Check that the correct values for quota probes are reported when no quota is hit. + h = Telemetry.getHistogramById("TELEMETRY_ARCHIVE_SIZE_MB").snapshot(); + Assert.equal(h.sum, Math.round(archiveSizeInBytes / 1024 / 1024), + "Telemetry must report the correct archive size."); + h = Telemetry.getHistogramById("TELEMETRY_ARCHIVE_EVICTED_OVER_QUOTA").snapshot(); + Assert.equal(h.sum, 0, "Telemetry must report 0 evictions if quota is not hit."); + h = Telemetry.getHistogramById("TELEMETRY_ARCHIVE_EVICTING_OVER_QUOTA_MS").snapshot(); + Assert.equal(h.sum, 0, "Telemetry must report a null elapsed time if quota is not hit."); + // Set the quota to 80% of the space. const testQuotaInBytes = archiveSizeInBytes * 0.8; fakeStorageQuota(testQuotaInBytes); @@ -272,7 +329,6 @@ add_task(function* test_archiveCleanup() { // The storage prunes archived pings until we reach 90% of the requested storage quota. // Based on that, find how many pings should be kept. const safeQuotaSize = testQuotaInBytes * 0.9; - const archivedPingsInfo = yield getArchivedPingsInfo(); let sizeInBytes = 0; let pingsWithinQuota = []; let pingsOutsideQuota = []; @@ -296,11 +352,50 @@ add_task(function* test_archiveCleanup() { // Check that the archive is in the correct state. yield checkArchive(); + h = Telemetry.getHistogramById("TELEMETRY_ARCHIVE_EVICTED_OVER_QUOTA").snapshot(); + Assert.equal(h.sum, pingsOutsideQuota.length, + "Telemetry must correctly report the over quota pings evicted from the archive."); + h = Telemetry.getHistogramById("TELEMETRY_ARCHIVE_SIZE_MB").snapshot(); + Assert.equal(h.sum, 300, "Archive quota was hit, a special size must be reported."); + // Trigger a cleanup again and make sure we're not removing anything. yield TelemetryController.reset(); yield TelemetryStorage.testCleanupTaskPromise(); yield TelemetryArchive.promiseArchivedPingList(); yield checkArchive(); + + const OVERSIZED_PING_ID = "9b21ec8f-f762-4d28-a2c1-44e1c4694f24"; + // Create and archive an oversized, uncompressed, ping. + const OVERSIZED_PING = { + id: OVERSIZED_PING_ID, + type: PING_TYPE, + creationDate: (new Date()).toISOString(), + // Generate a ~2MB string to use as the payload. + payload: generateRandomString(2 * 1024 * 1024) + }; + yield TelemetryArchive.promiseArchivePing(OVERSIZED_PING); + + // Get the size of the archived ping. + const oversizedPingPath = + TelemetryStorage._testGetArchivedPingPath(OVERSIZED_PING.id, new Date(OVERSIZED_PING.creationDate), PING_TYPE) + "lz4"; + const archivedPingSizeMB = Math.floor((yield OS.File.stat(oversizedPingPath)).size / 1024 / 1024); + + // We expect the oversized ping to be pruned when scanning the archive. + expectedPrunedInfo.push({ id: OVERSIZED_PING_ID, creationDate: new Date(OVERSIZED_PING.creationDate) }); + + // Scan the archive. + yield TelemetryController.reset(); + yield TelemetryStorage.testCleanupTaskPromise(); + yield TelemetryArchive.promiseArchivedPingList(); + // The following also checks that non oversized pings are not removed. + yield checkArchive(); + + // Make sure we're correctly updating the related histograms. + h = Telemetry.getHistogramById("TELEMETRY_PING_SIZE_EXCEEDED_ARCHIVED").snapshot(); + Assert.equal(h.sum, 1, "Telemetry must report 1 oversized ping in the archive."); + h = Telemetry.getHistogramById("TELEMETRY_DISCARDED_ARCHIVED_PINGS_SIZE_MB").snapshot(); + Assert.equal(h.counts[archivedPingSizeMB], 1, + "Telemetry must report the correct size for the oversized ping."); }); add_task(function* test_clientId() { diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetryController.js b/toolkit/components/telemetry/tests/unit/test_TelemetryController.js index 1b5db0204a..4514ce7e9d 100644 --- a/toolkit/components/telemetry/tests/unit/test_TelemetryController.js +++ b/toolkit/components/telemetry/tests/unit/test_TelemetryController.js @@ -20,6 +20,7 @@ Cu.import("resource://gre/modules/Promise.jsm", this); Cu.import("resource://gre/modules/Preferences.jsm"); const PING_FORMAT_VERSION = 4; +const DELETION_PING_TYPE = "deletion"; const TEST_PING_TYPE = "test-ping-type"; const PLATFORM_VERSION = "1.9.2"; @@ -140,6 +141,64 @@ add_task(function* test_simplePing() { checkPingFormat(ping, TEST_PING_TYPE, false, false); }); +add_task(function* test_disableDataUpload() { + const isUnified = Preferences.get(PREF_UNIFIED, false); + if (!isUnified) { + // Skipping the test if unified telemetry is off, as no deletion ping will + // be generated. + return; + } + + const PREF_TELEMETRY_SERVER = "toolkit.telemetry.server"; + + // Disable FHR upload: this should trigger a deletion ping. + Preferences.set(PREF_FHR_UPLOAD_ENABLED, false); + + let ping = yield PingServer.promiseNextPing(); + checkPingFormat(ping, DELETION_PING_TYPE, true, false); + // Wait on ping activity to settle. + yield TelemetrySend.testWaitOnOutgoingPings(); + + // Restore FHR Upload. + Preferences.set(PREF_FHR_UPLOAD_ENABLED, true); + + // Simulate a failure in sending the deletion ping by disabling the HTTP server. + yield PingServer.stop(); + + // Try to send a ping. It will be saved as pending and get deleted when disabling upload. + TelemetryController.submitExternalPing(TEST_PING_TYPE, {}); + + // Disable FHR upload to send a deletion ping again. + Preferences.set(PREF_FHR_UPLOAD_ENABLED, false); + + // Wait on sending activity to settle, as |TelemetryController.reset()| doesn't do that. + yield TelemetrySend.testWaitOnOutgoingPings(); + // Wait for the pending pings to be deleted. Resetting TelemetryController doesn't + // trigger the shutdown, so we need to call it ourselves. + yield TelemetryStorage.shutdown(); + // Simulate a restart, and spin the send task. + yield TelemetryController.reset(); + + // Disabling Telemetry upload must clear out all the pending pings. + let pendingPings = yield TelemetryStorage.loadPendingPingList(); + Assert.equal(pendingPings.length, 1, + "All the pending pings but the deletion ping should have been deleted"); + + // Enable the ping server again. + PingServer.start(); + // We set the new server using the pref, otherwise it would get reset with + // |TelemetryController.reset|. + Preferences.set(PREF_TELEMETRY_SERVER, "http://localhost:" + PingServer.port); + + // Reset the controller to spin the ping sending task. + yield TelemetryController.reset(); + ping = yield PingServer.promiseNextPing(); + checkPingFormat(ping, DELETION_PING_TYPE, true, false); + + // Restore FHR Upload. + Preferences.set(PREF_FHR_UPLOAD_ENABLED, true); +}); + add_task(function* test_pingHasClientId() { // Send a ping with a clientId. yield sendPing(true, false); @@ -186,8 +245,15 @@ add_task(function* test_archivePings() { const uploadPref = isUnified ? PREF_FHR_UPLOAD_ENABLED : PREF_ENABLED; Preferences.set(uploadPref, false); + // If we're using unified telemetry, disabling ping upload will generate a "deletion" + // ping. Catch it. + if (isUnified) { + let ping = yield PingServer.promiseNextPing(); + checkPingFormat(ping, DELETION_PING_TYPE, true, false); + } + // Register a new Ping Handler that asserts if a ping is received, then send a ping. - registerPingHandler(() => Assert.ok(false, "Telemetry must not send pings if not allowed to.")); + PingServer.registerPingHandler(() => Assert.ok(false, "Telemetry must not send pings if not allowed to.")); let pingId = yield sendPing(true, true); // Check that the ping was archived, even with upload disabled. @@ -207,7 +273,7 @@ add_task(function* test_archivePings() { Preferences.set(uploadPref, true); Preferences.set(PREF_ARCHIVE_ENABLED, true); - now = new Date(2014, 06, 18, 22, 0, 0); + now = new Date(2014, 6, 18, 22, 0, 0); fakeNow(now); // Restore the non asserting ping handler. PingServer.resetPingHandler(); @@ -225,7 +291,7 @@ add_task(function* test_archivePings() { add_task(function* test_midnightPingSendFuzzing() { const fuzzingDelay = 60 * 60 * 1000; fakeMidnightPingFuzzingDelay(fuzzingDelay); - let now = new Date(2030, 5, 1, 11, 00, 0); + let now = new Date(2030, 5, 1, 11, 0, 0); fakeNow(now); let waitForTimer = () => new Promise(resolve => { @@ -237,21 +303,20 @@ add_task(function* test_midnightPingSendFuzzing() { PingServer.clearRequests(); yield TelemetryController.reset(); - // A ping just before the end of the fuzzing delay should not get sent. - now = new Date(2030, 5, 2, 0, 59, 59); + // A ping after midnight within the fuzzing delay should not get sent. + now = new Date(2030, 5, 2, 0, 40, 0); fakeNow(now); PingServer.registerPingHandler((req, res) => { Assert.ok(false, "No ping should be received yet."); }); let timerPromise = waitForTimer(); yield sendPing(true, true); - let [timerCallback, timerTimeout] = yield timerPromise; Assert.ok(!!timerCallback); Assert.deepEqual(futureDate(now, timerTimeout), new Date(2030, 5, 2, 1, 0, 0)); - // A ping after midnight within the fuzzing delay should also not get sent. - now = new Date(2030, 5, 2, 0, 40, 0); + // A ping just before the end of the fuzzing delay should not get sent. + now = new Date(2030, 5, 2, 0, 59, 59); fakeNow(now); timerPromise = waitForTimer(); yield sendPing(true, true); @@ -282,8 +347,7 @@ add_task(function* test_midnightPingSendFuzzing() { // Check that pings shortly before midnight are immediately sent. now = fakeNow(2030, 5, 3, 23, 59, 0); yield sendPing(true, true); - request = yield gRequestIterator.next(); - ping = decodeRequestPayload(request); + ping = yield PingServer.promiseNextPing(); checkPingFormat(ping, TEST_PING_TYPE, true, true); yield TelemetrySend.testWaitOnOutgoingPings(); @@ -292,6 +356,52 @@ add_task(function* test_midnightPingSendFuzzing() { fakePingSendTimer(() => {}, () => {}); }); +add_task(function* test_changePingAfterSubmission() { + // Submit a ping with a custom payload. + let payload = { canary: "test" }; + let pingPromise = TelemetryController.submitExternalPing(TEST_PING_TYPE, payload, options); + + // Change the payload with a predefined value. + payload.canary = "changed"; + + // Wait for the ping to be archived. + const pingId = yield pingPromise; + + // Make sure our changes didn't affect the submitted payload. + let archivedCopy = yield TelemetryArchive.promiseArchivedPingById(pingId); + Assert.equal(archivedCopy.payload.canary, "test", + "The payload must not be changed after being submitted."); +}); + +add_task(function* test_telemetryEnabledUnexpectedValue(){ + // Remove the default value for toolkit.telemetry.enabled from the default prefs. + // Otherwise, we wouldn't be able to set the pref to a string. + let defaultPrefBranch = Services.prefs.getDefaultBranch(null); + defaultPrefBranch.deleteBranch(PREF_ENABLED); + + // Set the preferences controlling the Telemetry status to a string. + Preferences.set(PREF_ENABLED, "false"); + // Check that Telemetry is not enabled. + yield TelemetryController.reset(); + Assert.equal(Telemetry.canRecordExtended, false, + "Invalid values must not enable Telemetry recording."); + + // Delete the pref again. + defaultPrefBranch.deleteBranch(PREF_ENABLED); + + // Make sure that flipping it to true works. + Preferences.set(PREF_ENABLED, true); + yield TelemetryController.reset(); + Assert.equal(Telemetry.canRecordExtended, true, + "True must enable Telemetry recording."); + + // Also check that the false works as well. + Preferences.set(PREF_ENABLED, false); + yield TelemetryController.reset(); + Assert.equal(Telemetry.canRecordExtended, false, + "False must disable Telemetry recording."); +}); + add_task(function* test_telemetryCleanFHRDatabase(){ const FHR_DBNAME_PREF = "datareporting.healthreport.dbName"; const CUSTOM_DB_NAME = "unlikely.to.be.used.sqlite"; diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetryControllerShutdown.js b/toolkit/components/telemetry/tests/unit/test_TelemetryControllerShutdown.js index 923a8640f7..533d460314 100644 --- a/toolkit/components/telemetry/tests/unit/test_TelemetryControllerShutdown.js +++ b/toolkit/components/telemetry/tests/unit/test_TelemetryControllerShutdown.js @@ -50,11 +50,13 @@ add_task(function* test_sendTimeout() { yield TelemetryController.setup(); TelemetrySend.setServer("http://localhost:" + httpServer.identity.primaryPort); - yield TelemetryController.submitExternalPing("test-ping-type", {}); + let submissionPromise = TelemetryController.submitExternalPing("test-ping-type", {}); // Trigger the AsyncShutdown phase TelemetryController hangs off. AsyncShutdown.profileBeforeChange._trigger(); AsyncShutdown.sendTelemetry._trigger(); + // Now wait for the ping submission. + yield submissionPromise; // If we get here, we didn't time out in the shutdown routines. Assert.ok(true, "Didn't time out on shutdown."); diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetryLateWrites.js b/toolkit/components/telemetry/tests/unit/test_TelemetryLateWrites.js index 1131f67eff..f2b2b3bba4 100644 --- a/toolkit/components/telemetry/tests/unit/test_TelemetryLateWrites.js +++ b/toolkit/components/telemetry/tests/unit/test_TelemetryLateWrites.js @@ -9,7 +9,7 @@ Cu.import("resource://gre/modules/Services.jsm", this); const PR_WRONLY = 0x2; const PR_CREATE_FILE = 0x8; const PR_TRUNCATE = 0x20; -const RW_OWNER = 0600; +const RW_OWNER = parseInt("0600", 8); const STACK_SUFFIX1 = "stack1.txt"; const STACK_SUFFIX2 = "stack2.txt"; diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetryLockCount.js b/toolkit/components/telemetry/tests/unit/test_TelemetryLockCount.js index e41b074987..808f2f3ec9 100644 --- a/toolkit/components/telemetry/tests/unit/test_TelemetryLockCount.js +++ b/toolkit/components/telemetry/tests/unit/test_TelemetryLockCount.js @@ -12,7 +12,7 @@ const N_FAILED_LOCKS = 10; const PR_WRONLY = 0x2; const PR_CREATE_FILE = 0x8; const PR_TRUNCATE = 0x20; -const RW_OWNER = 0600; +const RW_OWNER = parseInt("0600", 8); function write_string_to_file(file, contents) { let ostream = Cc["@mozilla.org/network/safe-file-output-stream;1"] diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetryLog.js b/toolkit/components/telemetry/tests/unit/test_TelemetryLog.js index 44d73b296d..4f9d385485 100644 --- a/toolkit/components/telemetry/tests/unit/test_TelemetryLog.js +++ b/toolkit/components/telemetry/tests/unit/test_TelemetryLog.js @@ -24,8 +24,9 @@ function check_event(event, id, data) } } -function* run_test() +add_task(function* () { + do_get_profile(); yield TelemetrySession.setup(); TelemetryLog.log(TEST_PREFIX + "1", ["val", 123, undefined]); @@ -45,4 +46,4 @@ function* run_test() do_check_true(log[1][1] <= log[2][1]); yield TelemetrySession.shutdown(); -} +}); diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetryReportingPolicy.js b/toolkit/components/telemetry/tests/unit/test_TelemetryReportingPolicy.js index b9e57bc7ae..0f9c1eff6d 100644 --- a/toolkit/components/telemetry/tests/unit/test_TelemetryReportingPolicy.js +++ b/toolkit/components/telemetry/tests/unit/test_TelemetryReportingPolicy.js @@ -14,6 +14,7 @@ Cu.import("resource://gre/modules/TelemetryReportingPolicy.jsm", this); Cu.import("resource://gre/modules/TelemetryUtils.jsm", this); Cu.import("resource://gre/modules/Timer.jsm", this); Cu.import("resource://gre/modules/XPCOMUtils.jsm", this); +Cu.import("resource://gre/modules/UpdateUtils.jsm", this); const PREF_BRANCH = "toolkit.telemetry."; const PREF_SERVER = PREF_BRANCH + "server"; @@ -40,6 +41,21 @@ function fakeResetAcceptedPolicy() { Preferences.reset(PREF_ACCEPTED_POLICY_VERSION); } +function setMinimumPolicyVersion(aNewPolicyVersion) { + const CHANNEL_NAME = UpdateUtils.getUpdateChannel(false); + // We might have channel-dependent minimum policy versions. + const CHANNEL_DEPENDENT_PREF = PREF_MINIMUM_POLICY_VERSION + ".channel-" + CHANNEL_NAME; + + // Does the channel-dependent pref exist? If so, set its value. + if (Preferences.get(CHANNEL_DEPENDENT_PREF, undefined)) { + Preferences.set(CHANNEL_DEPENDENT_PREF, aNewPolicyVersion); + return; + } + + // We don't have a channel specific minimu, so set the common one. + Preferences.set(PREF_MINIMUM_POLICY_VERSION, aNewPolicyVersion); +} + function run_test() { // Addon manager needs a profile directory do_get_profile(true); @@ -109,7 +125,7 @@ add_task(function* test_prefs() { // Set a new minimum policy version and check that user is no longer notified. let newMinimum = Preferences.get(PREF_CURRENT_POLICY_VERSION, 1) + 1; - Preferences.set(PREF_MINIMUM_POLICY_VERSION, newMinimum); + setMinimumPolicyVersion(newMinimum); Assert.ok(!TelemetryReportingPolicy.testIsUserNotified(), "A greater minimum policy version must invalidate the policy and disable upload."); diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetrySend.js b/toolkit/components/telemetry/tests/unit/test_TelemetrySend.js index 410926cc51..2e7bbdde97 100644 --- a/toolkit/components/telemetry/tests/unit/test_TelemetrySend.js +++ b/toolkit/components/telemetry/tests/unit/test_TelemetrySend.js @@ -170,6 +170,21 @@ add_task(function* test_sendPendingPings() { countByType = countPingTypes(pings); Assert.equal(countByType.get(TEST_TYPE_A), 5, "Should have received the correct amount of type A pings"); + + yield TelemetrySend.testWaitOnOutgoingPings(); + PingServer.resetPingHandler(); +}); + +add_task(function* test_sendDateHeader() { + let now = fakeNow(new Date(Date.UTC(2011, 1, 1, 11, 0, 0))); + yield TelemetrySend.reset(); + + let pingId = yield TelemetryController.submitExternalPing("test-send-date-header", {}); + let req = yield PingServer.promiseNextRequest(); + let ping = decodeRequestPayload(req); + Assert.equal(req.getHeader("Date"), "Tue, 01 Feb 2011 11:00:00 GMT", + "Telemetry should send the correct Date header with requests."); + Assert.equal(ping.id, pingId, "Should have received the correct ping id."); }); // Test the backoff timeout behavior after send failures. @@ -235,6 +250,69 @@ add_task(function* test_backoffTimeout() { Assert.equal(TelemetrySend.pendingPingCount, 0, "Should have no pending pings left"); }); +add_task(function* test_discardBigPings() { + const TEST_PING_TYPE = "test-ping-type"; + + // Generate a 2MB string and create an oversized payload. + const OVERSIZED_PAYLOAD = generateRandomString(2 * 1024 * 1024); + + // Reset the histograms. + Telemetry.getHistogramById("TELEMETRY_PING_SIZE_EXCEEDED_SEND").clear(); + Telemetry.getHistogramById("TELEMETRY_DISCARDED_SEND_PINGS_SIZE_MB").clear(); + + // Submit a ping of a normal size and check that we don't count it in the histogram. + yield TelemetryController.submitExternalPing(TEST_PING_TYPE, { test: "test" }); + yield TelemetrySend.testWaitOnOutgoingPings(); + let h = Telemetry.getHistogramById("TELEMETRY_PING_SIZE_EXCEEDED_SEND").snapshot(); + Assert.equal(h.sum, 0, "Telemetry must report no oversized ping submitted."); + h = Telemetry.getHistogramById("TELEMETRY_DISCARDED_SEND_PINGS_SIZE_MB").snapshot(); + Assert.equal(h.sum, 0, "Telemetry must report no oversized pings."); + + // Submit an oversized ping and check that it gets discarded. + yield TelemetryController.submitExternalPing(TEST_PING_TYPE, OVERSIZED_PAYLOAD); + yield TelemetrySend.testWaitOnOutgoingPings(); + h = Telemetry.getHistogramById("TELEMETRY_PING_SIZE_EXCEEDED_SEND").snapshot(); + Assert.equal(h.sum, 1, "Telemetry must report 1 oversized ping submitted."); + h = Telemetry.getHistogramById("TELEMETRY_DISCARDED_SEND_PINGS_SIZE_MB").snapshot(); + Assert.equal(h.counts[2], 1, "Telemetry must report a 2MB, oversized, ping submitted."); +}); + +add_task(function* test_evictedOnServerErrors() { + const TEST_TYPE = "test-evicted"; + + yield TelemetrySend.reset(); + + // Write a custom ping handler which will return 403. This will trigger ping eviction + // on client side. + PingServer.registerPingHandler((req, res) => { + res.setStatusLine(null, 403, "Forbidden"); + res.processAsync(); + res.finish(); + }); + + // Clear the histogram and submit a ping. + Telemetry.getHistogramById("TELEMETRY_PING_EVICTED_FOR_SERVER_ERRORS").clear(); + let pingId = yield TelemetryController.submitExternalPing(TEST_TYPE, {}); + yield TelemetrySend.testWaitOnOutgoingPings(); + + let h = Telemetry.getHistogramById("TELEMETRY_PING_EVICTED_FOR_SERVER_ERRORS").snapshot(); + Assert.equal(h.sum, 1, "Telemetry must report a ping evicted due to server errors"); + + // The ping should not be persisted. + yield Assert.rejects(TelemetryStorage.loadPendingPing(pingId), "The ping must not be persisted."); + + // Reset the ping handler and submit a new ping. + PingServer.resetPingHandler(); + pingId = yield TelemetryController.submitExternalPing(TEST_TYPE, {}); + + let ping = yield PingServer.promiseNextPings(1); + Assert.equal(ping[0].id, pingId, "The correct ping must be received"); + + // We should not have updated the error histogram. + h = Telemetry.getHistogramById("TELEMETRY_PING_EVICTED_FOR_SERVER_ERRORS").snapshot(); + Assert.equal(h.sum, 1, "Telemetry must report a ping evicted due to server errors"); +}); + // Test that the current, non-persisted pending pings are properly saved on shutdown. add_task(function* test_persistCurrentPingsOnShutdown() { const TEST_TYPE = "test-persistCurrentPingsOnShutdown"; diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetrySendOldPings.js b/toolkit/components/telemetry/tests/unit/test_TelemetrySendOldPings.js index 6d85245219..52d6c3511f 100644 --- a/toolkit/components/telemetry/tests/unit/test_TelemetrySendOldPings.js +++ b/toolkit/components/telemetry/tests/unit/test_TelemetrySendOldPings.js @@ -17,7 +17,7 @@ Cu.import("resource://gre/modules/TelemetryController.jsm", this); Cu.import("resource://gre/modules/TelemetrySend.jsm", this); Cu.import("resource://gre/modules/Task.jsm", this); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -let {OS: {File, Path, Constants}} = Cu.import("resource://gre/modules/osfile.jsm", {}); +var {OS: {File, Path, Constants}} = Cu.import("resource://gre/modules/osfile.jsm", {}); // We increment TelemetryStorage's MAX_PING_FILE_AGE and // OVERDUE_PING_FILE_AGE by 1 minute so that our test pings exceed @@ -33,8 +33,10 @@ const RECENT_PINGS = 4; const TOTAL_EXPECTED_PINGS = OVERDUE_PINGS + RECENT_PINGS + OLD_FORMAT_PINGS; -let gCreatedPings = 0; -let gSeenPings = 0; +const PREF_FHR_UPLOAD = "datareporting.healthreport.uploadEnabled"; + +var gCreatedPings = 0; +var gSeenPings = 0; /** * Creates some Telemetry pings for the and saves them to disk. Each ping gets a @@ -48,13 +50,13 @@ let gSeenPings = 0; * @returns Promise * @resolve an Array with the created pings ids. */ -let createSavedPings = Task.async(function* (aPingInfos) { +var createSavedPings = Task.async(function* (aPingInfos) { let pingIds = []; let now = Date.now(); for (let type in aPingInfos) { let num = aPingInfos[type].num; - let age = now - aPingInfos[type].age; + let age = now - (aPingInfos[type].age || 0); for (let i = 0; i < num; ++i) { let pingId = yield TelemetryController.addPendingPing("test-ping", {}, { overwrite: true }); if (aPingInfos[type].age) { @@ -77,7 +79,7 @@ let createSavedPings = Task.async(function* (aPingInfos) { * @param aPingIds an Array of ping ids to delete. * @returns Promise */ -let clearPings = Task.async(function* (aPingIds) { +var clearPings = Task.async(function* (aPingIds) { for (let pingId of aPingIds) { yield TelemetryStorage.removePendingPing(pingId); } @@ -118,7 +120,7 @@ function assertReceivedPings(aExpectedNum) { * @param aPingIds an Array of pings ids to check. * @returns Promise */ -let assertNotSaved = Task.async(function* (aPingIds) { +var assertNotSaved = Task.async(function* (aPingIds) { let saved = 0; for (let id of aPingIds) { let filePath = getSavePathForPingId(id); @@ -145,7 +147,7 @@ function pingHandler(aRequest) { /** * Clear out all pending pings. */ -let clearPendingPings = Task.async(function*() { +var clearPendingPings = Task.async(function*() { const pending = yield TelemetryStorage.loadPendingPingList(); for (let p of pending) { yield TelemetryStorage.removePendingPing(p.id); @@ -156,6 +158,7 @@ function run_test() { PingServer.start(); PingServer.registerPingHandler(pingHandler); do_get_profile(); + loadAddonManager("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); Services.prefs.setBoolPref(PREF_TELEMETRY_ENABLED, true); Services.prefs.setCharPref(TelemetryController.Constants.PREF_SERVER, @@ -168,6 +171,9 @@ function run_test() { * |TelemetryController.testSaveDirectoryToFile| could fail. */ add_task(function* setupEnvironment() { + // The following tests assume this pref to be true by default. + Services.prefs.setBoolPref(PREF_FHR_UPLOAD, true); + yield TelemetryController.setup(); let directory = TelemetryStorage.pingDirectoryPath; @@ -389,7 +395,6 @@ add_task(function* test_overdue_old_format() { add_task(function* test_pendingPingsQuota() { const PING_TYPE = "foo"; - const PREF_FHR_UPLOAD = "datareporting.healthreport.uploadEnabled"; // Disable upload so pings don't get sent and removed from the pending pings directory. Services.prefs.setBoolPref(PREF_FHR_UPLOAD, false); @@ -501,6 +506,48 @@ add_task(function* test_pendingPingsQuota() { yield TelemetryStorage.testPendingQuotaTaskPromise(); yield checkPendingPings(); + const OVERSIZED_PING_ID = "9b21ec8f-f762-4d28-a2c1-44e1c4694f24"; + // Create a pending oversized ping. + const OVERSIZED_PING = { + id: OVERSIZED_PING_ID, + type: PING_TYPE, + creationDate: (new Date()).toISOString(), + // Generate a 2MB string to use as the ping payload. + payload: generateRandomString(2 * 1024 * 1024), + }; + yield TelemetryStorage.savePendingPing(OVERSIZED_PING); + + // Reset the histograms. + Telemetry.getHistogramById("TELEMETRY_PING_SIZE_EXCEEDED_PENDING").clear(); + Telemetry.getHistogramById("TELEMETRY_DISCARDED_PENDING_PINGS_SIZE_MB").clear(); + + // Try to manually load the oversized ping. + yield Assert.rejects(TelemetryStorage.loadPendingPing(OVERSIZED_PING_ID), + "The oversized ping should have been pruned."); + Assert.ok(!(yield OS.File.exists(getSavePathForPingId(OVERSIZED_PING_ID))), + "The ping should not be on the disk anymore."); + + // Make sure we're correctly updating the related histograms. + h = Telemetry.getHistogramById("TELEMETRY_PING_SIZE_EXCEEDED_PENDING").snapshot(); + Assert.equal(h.sum, 1, "Telemetry must report 1 oversized ping in the pending pings directory."); + h = Telemetry.getHistogramById("TELEMETRY_DISCARDED_PENDING_PINGS_SIZE_MB").snapshot(); + Assert.equal(h.counts[2], 1, "Telemetry must report a 2MB, oversized, ping."); + + // Save the ping again to check if it gets pruned when scanning the pings directory. + yield TelemetryStorage.savePendingPing(OVERSIZED_PING); + expectedPrunedPings.push(OVERSIZED_PING_ID); + + // Scan the pending pings directory. + yield TelemetryController.reset(); + yield TelemetryStorage.testPendingQuotaTaskPromise(); + yield checkPendingPings(); + + // Make sure we're correctly updating the related histograms. + h = Telemetry.getHistogramById("TELEMETRY_PING_SIZE_EXCEEDED_PENDING").snapshot(); + Assert.equal(h.sum, 2, "Telemetry must report 1 oversized ping in the pending pings directory."); + h = Telemetry.getHistogramById("TELEMETRY_DISCARDED_PENDING_PINGS_SIZE_MB").snapshot(); + Assert.equal(h.counts[2], 2, "Telemetry must report two 2MB, oversized, pings."); + Services.prefs.setBoolPref(PREF_FHR_UPLOAD, true); }); diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js b/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js index a7c75af65d..b996fa0a90 100644 --- a/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js +++ b/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js @@ -901,6 +901,8 @@ add_task(function* test_dailyDuplication() { return; } + yield TelemetrySend.reset(); + yield clearPendingPings(); PingServer.clearRequests(); let schedulerTickCallback = null; @@ -943,8 +945,8 @@ add_task(function* test_dailyDuplication() { yield schedulerTickCallback(); // Shutdown to cleanup the aborted-session if it gets created. - yield TelemetrySession.shutdown(); PingServer.resetPingHandler(); + yield TelemetrySession.shutdown(); }); add_task(function* test_dailyOverdue() { @@ -979,7 +981,7 @@ add_task(function* test_dailyOverdue() { // Simulate an overdue ping: we're not close to midnight, but the last daily ping // time is too long ago. - let dailyOverdue = new Date(2030, 1, 2, 13, 00, 0); + let dailyOverdue = new Date(2030, 1, 2, 13, 0, 0); fakeNow(dailyOverdue); // Run a scheduler tick: it should trigger the daily ping. @@ -1007,7 +1009,7 @@ add_task(function* test_environmentChange() { let timerCallback = null; let timerDelay = null; - clearPendingPings(); + yield clearPendingPings(); yield TelemetrySend.reset(); PingServer.clearRequests(); @@ -1083,6 +1085,8 @@ add_task(function* test_savedPingsOnShutdown() { const dir = TelemetryStorage.pingDirectoryPath; yield OS.File.removeDir(dir, {ignoreAbsent: true}); yield OS.File.makeDir(dir); + // TODO: Remove the TelemetrySend manual shutdown when bug 1145188 lands. + yield TelemetrySend.shutdown(); yield TelemetrySession.shutdown(); PingServer.clearRequests(); @@ -1157,6 +1161,9 @@ add_task(function* test_savedSessionData() { Assert.equal(payload.info.profileSubsessionCounter, expectedSubsessions); yield TelemetrySession.shutdown(); + // Restore the UUID generator so we don't mess with other tests. + fakeGenerateUUID(generateUUID, generateUUID); + // Load back the serialised session data. let data = yield CommonUtils.readJSON(dataFilePath); Assert.equal(data.profileSubsessionCounter, expectedSubsessions); @@ -1255,6 +1262,9 @@ add_task(function* test_invalidSessionData() { yield TelemetrySession.shutdown(); + // Restore the UUID generator so we don't mess with other tests. + fakeGenerateUUID(generateUUID, generateUUID); + // Load back the serialised session data. let data = yield CommonUtils.readJSON(dataFilePath); Assert.equal(data.profileSubsessionCounter, expectedSubsessions); @@ -1262,61 +1272,6 @@ add_task(function* test_invalidSessionData() { Assert.equal(data.subsessionId, expectedSubsessionUUID); }); -add_task(function* test_pingExtendedStats() { - const EXTENDED_PAYLOAD_FIELDS = [ - "chromeHangs", "threadHangStats", "log", "slowSQL", "fileIOReports", "lateWrites", - "addonHistograms", "addonDetails", "UIMeasurements", - ]; - - // Disable sending extended statistics. - Telemetry.canRecordExtended = false; - - gRequestIterator = Iterator(new Request()); - yield TelemetrySession.reset(); - yield sendPing(); - - let request = yield gRequestIterator.next(); - let ping = decodeRequestPayload(request); - checkPingFormat(ping, PING_TYPE_MAIN, true, true); - - // Check that the payload does not contain extended statistics fields. - for (let f in EXTENDED_PAYLOAD_FIELDS) { - Assert.ok(!(EXTENDED_PAYLOAD_FIELDS[f] in ping.payload), - EXTENDED_PAYLOAD_FIELDS[f] + " must not be in the payload if the extended set is off."); - } - - // We check this one separately so that we can reuse EXTENDED_PAYLOAD_FIELDS below, since - // slowSQLStartup might not be there. - Assert.ok(!("slowSQLStartup" in ping.payload), - "slowSQLStartup must not be sent if the extended set is off"); - - Assert.ok(!("addonManager" in ping.payload.simpleMeasurements), - "addonManager must not be sent if the extended set is off."); - Assert.ok(!("UITelemetry" in ping.payload.simpleMeasurements), - "UITelemetry must not be sent if the extended set is off."); - - // Restore the preference. - Telemetry.canRecordExtended = true; - - // Send a new ping that should contain the extended data. - yield TelemetrySession.reset(); - yield sendPing(); - request = yield gRequestIterator.next(); - ping = decodeRequestPayload(request); - checkPingFormat(ping, PING_TYPE_MAIN, true, true); - - // Check that the payload now contains extended statistics fields. - for (let f in EXTENDED_PAYLOAD_FIELDS) { - Assert.ok(EXTENDED_PAYLOAD_FIELDS[f] in ping.payload, - EXTENDED_PAYLOAD_FIELDS[f] + " must be in the payload if the extended set is on."); - } - - Assert.ok("addonManager" in ping.payload.simpleMeasurements, - "addonManager must be sent if the extended set is on."); - Assert.ok("UITelemetry" in ping.payload.simpleMeasurements, - "UITelemetry must be sent if the extended set is on."); -}); - add_task(function* test_abortedSession() { if (gIsAndroid || gIsGonk) { // We don't have the aborted session ping here. @@ -1370,6 +1325,8 @@ add_task(function* test_abortedSession() { Assert.notEqual(abortedSessionPing.id, updatedAbortedSessionPing.id); Assert.notEqual(abortedSessionPing.creationDate, updatedAbortedSessionPing.creationDate); + // TODO: Remove the TelemetrySend manual shutdown when bug 1145188 lands. + yield TelemetrySend.shutdown(); yield TelemetrySession.shutdown(); Assert.ok(!(yield OS.File.exists(ABORTED_FILE)), "No aborted session ping must be available after a shutdown."); @@ -1380,6 +1337,8 @@ add_task(function* test_abortedSession() { yield clearPendingPings(); PingServer.clearRequests(); + // TODO: Remove the TelemetrySend manual setup when bug 1145188 lands. + yield TelemetrySend.setup(true); yield TelemetrySession.reset(); yield TelemetryController.reset(); @@ -1391,6 +1350,8 @@ add_task(function* test_abortedSession() { Assert.equal(receivedPing.type, PING_TYPE_MAIN, "Should have the correct type"); Assert.equal(receivedPing.payload.info.reason, REASON_ABORTED_SESSION, "Ping should have the correct reason"); + // TODO: Remove the TelemetrySend manual shutdown when bug 1145188 lands. + yield TelemetrySend.shutdown(); yield TelemetrySession.shutdown(); }); @@ -1406,6 +1367,9 @@ add_task(function* test_abortedSession_Shutdown() { let now = fakeNow(2040, 1, 1, 0, 0, 0); // Fake scheduler functions to control aborted-session flow in tests. fakeSchedulerTimer(callback => schedulerTickCallback = callback, () => {}); + // TODO: Remove the TelemetrySend manual setup/reset when bug 1145188 lands. + yield TelemetrySend.setup(true); + yield TelemetrySend.reset(); yield TelemetrySession.reset(); Assert.ok((yield OS.File.exists(DATAREPORTING_PATH)), @@ -1424,6 +1388,8 @@ add_task(function* test_abortedSession_Shutdown() { // not found) do not compromise the shutdown. yield OS.File.remove(ABORTED_FILE); + // TODO: Remove the TelemetrySend manual shutdown when bug 1145188 lands. + yield TelemetrySend.shutdown(); yield TelemetrySession.shutdown(); }); @@ -1441,11 +1407,14 @@ add_task(function* test_abortedDailyCoalescing() { let schedulerTickCallback = null; PingServer.clearRequests(); - let nowDate = new Date(2009, 10, 18, 00, 00, 0); + let nowDate = new Date(2009, 10, 18, 0, 0, 0); fakeNow(nowDate); // Fake scheduler functions to control aborted-session flow in tests. fakeSchedulerTimer(callback => schedulerTickCallback = callback, () => {}); + // TODO: Remove the TelemetrySend manual setup/reset when bug 1145188 lands. + yield TelemetrySend.setup(true); + yield TelemetrySend.reset(); yield TelemetrySession.reset(); Assert.ok((yield OS.File.exists(DATAREPORTING_PATH)), @@ -1475,6 +1444,8 @@ add_task(function* test_abortedDailyCoalescing() { Assert.equal(abortedSessionPing.payload.info.sessionId, dailyPing.payload.info.sessionId); Assert.equal(abortedSessionPing.payload.info.subsessionId, dailyPing.payload.info.subsessionId); + // TODO: Remove the TelemetrySend manual shutdown when bug 1145188 lands. + yield TelemetrySend.shutdown(); yield TelemetrySession.shutdown(); }); @@ -1486,31 +1457,56 @@ add_task(function* test_schedulerComputerSleep() { const ABORTED_FILE = OS.Path.join(DATAREPORTING_PATH, ABORTED_PING_FILE_NAME); + clearPendingPings(); + // TODO: Remove the TelemetrySend manual setup when bug 1145188 lands. + yield TelemetrySend.setup(true); + yield TelemetrySend.reset(); PingServer.clearRequests(); // Remove any aborted-session ping from the previous tests. yield OS.File.removeDir(DATAREPORTING_PATH, { ignoreAbsent: true }); // Set a fake current date and start Telemetry. - let nowDate = new Date(2060, 10, 18, 0, 00, 0); - fakeNow(nowDate); + let nowDate = fakeNow(2009, 10, 18, 0, 0, 0); let schedulerTickCallback = null; fakeSchedulerTimer(callback => schedulerTickCallback = callback, () => {}); yield TelemetrySession.reset(); // Set the current time 3 days in the future at midnight, before running the callback. - let future = futureDate(nowDate, MS_IN_ONE_DAY * 3); - fakeNow(future); + nowDate = fakeNow(futureDate(nowDate, 3 * MS_IN_ONE_DAY)); Assert.ok(!!schedulerTickCallback); // Execute one scheduler tick. yield schedulerTickCallback(); let dailyPing = yield PingServer.promiseNextPing(); - Assert.equal(dailyPing.payload.info.reason, REASON_DAILY); + Assert.equal(dailyPing.payload.info.reason, REASON_DAILY, + "The wake notification should have triggered a daily ping."); + Assert.equal(dailyPing.creationDate, nowDate.toISOString(), + "The daily ping date should be correct."); Assert.ok((yield OS.File.exists(ABORTED_FILE)), "There must be an aborted session ping."); + // Now also test if we are sending a daily ping if we wake up on the next + // day even when the timer doesn't trigger. + // This can happen due to timeouts not running out during sleep times, + // see bug 1262386, bug 1204823 et al. + // Note that we don't get wake notifications on Linux due to bug 758848. + nowDate = fakeNow(futureDate(nowDate, 1 * MS_IN_ONE_DAY)); + + // We emulate the mentioned timeout behavior by sending the wake notification + // instead of triggering the timeout callback. + // This should trigger a daily ping, because we passed midnight. + Services.obs.notifyObservers(null, "wake_notification", null); + + dailyPing = yield PingServer.promiseNextPing(); + Assert.equal(dailyPing.payload.info.reason, REASON_DAILY, + "The wake notification should have triggered a daily ping."); + Assert.equal(dailyPing.creationDate, nowDate.toISOString(), + "The daily ping date should be correct."); + + // TODO: Remove the TelemetrySend manual shutdown when bug 1145188 lands. + yield TelemetrySend.shutdown(); yield TelemetrySession.shutdown(); }); @@ -1529,9 +1525,12 @@ add_task(function* test_schedulerEnvironmentReschedules() { yield clearPendingPings(); PingServer.clearRequests(); + // TODO: Remove the TelemetrySend manual setup/reset when bug 1145188 lands. + yield TelemetrySend.setup(true); + yield TelemetrySend.reset(); // Set a fake current date and start Telemetry. - let nowDate = new Date(2009, 10, 18, 0, 00, 0); + let nowDate = new Date(2060, 10, 18, 0, 0, 0); fakeNow(nowDate); let schedulerTickCallback = null; fakeSchedulerTimer(callback => schedulerTickCallback = callback, () => {}); @@ -1557,6 +1556,8 @@ add_task(function* test_schedulerEnvironmentReschedules() { Assert.ok(!!schedulerTickCallback); yield schedulerTickCallback(); + // TODO: Remove the TelemetrySend manual shutdown when bug 1145188 lands. + yield TelemetrySend.shutdown(); yield TelemetrySession.shutdown(); }); @@ -1571,6 +1572,9 @@ add_task(function* test_schedulerNothingDue() { // Remove any aborted-session ping from the previous tests. yield OS.File.removeDir(DATAREPORTING_PATH, { ignoreAbsent: true }); yield clearPendingPings(); + // TODO: Remove the TelemetrySend manual setup/reset when bug 1145188 lands. + yield TelemetrySend.setup(true); + yield TelemetrySend.reset(); // We don't expect to receive any ping in this test, so assert if we do. PingServer.registerPingHandler((req, res) => { @@ -1595,12 +1599,8 @@ add_task(function* test_schedulerNothingDue() { // Check that no aborted session ping was written to disk. Assert.ok(!(yield OS.File.exists(ABORTED_FILE))); - // We should not miss midnight when going to idle. - now.setHours(23); - now.setMinutes(50); - fakeIdleNotification("idle"); - Assert.equal(schedulerTimeout, 10 * 60 * 1000); - + // TODO: Remove the TelemetrySend manual shutdown when bug 1145188 lands. + yield TelemetrySend.shutdown(); yield TelemetrySession.shutdown(); PingServer.resetPingHandler(); }); @@ -1616,6 +1616,9 @@ add_task(function* test_pingExtendedStats() { yield clearPendingPings(); PingServer.clearRequests(); + // TODO: Remove the TelemetrySend manual setup/reset when bug 1145188 lands. + yield TelemetrySend.setup(true); + yield TelemetrySend.reset(); yield TelemetrySession.reset(); yield sendPing(); @@ -1697,9 +1700,122 @@ add_task(function* test_schedulerUserIdle() { // We should not miss midnight when going to idle. now.setHours(23); now.setMinutes(50); + fakeNow(now); fakeIdleNotification("idle"); Assert.equal(schedulerTimeout, 10 * 60 * 1000); + // TODO: Remove the TelemetrySend manual shutdown when bug 1145188 lands. + yield TelemetrySend.shutdown(); + yield TelemetrySession.shutdown(); +}); + +add_task(function* test_DailyDueAndIdle() { + if (gIsAndroid || gIsGonk) { + // We don't have the aborted session or the daily ping here. + return; + } + + yield TelemetrySession.reset(); + yield clearPendingPings(); + PingServer.clearRequests(); + // TODO: Remove the TelemetrySend setup when bug 1145188 lands. + yield TelemetrySend.setup(true); + yield TelemetrySend.reset(); + + let receivedPingRequest = null; + // Register a ping handler that will assert when receiving multiple daily pings. + PingServer.registerPingHandler(req => { + Assert.ok(!receivedPingRequest, "Telemetry must only send one daily ping."); + receivedPingRequest = req; + }); + + let schedulerTickCallback = null; + let now = new Date(2030, 1, 1, 0, 0, 0); + fakeNow(now); + // Fake scheduler functions to control daily collection flow in tests. + fakeSchedulerTimer(callback => schedulerTickCallback = callback, () => {}); + yield TelemetrySession.setup(); + + // Trigger the daily ping. + let firstDailyDue = new Date(2030, 1, 2, 0, 0, 0); + fakeNow(firstDailyDue); + + // Run a scheduler tick: it should trigger the daily ping. + Assert.ok(!!schedulerTickCallback); + let tickPromise = schedulerTickCallback(); + + // Send an idle and then an active user notification. + fakeIdleNotification("idle"); + fakeIdleNotification("active"); + + // Wait on the tick promise. + yield tickPromise; + + yield TelemetrySend.testWaitOnOutgoingPings(); + + // Decode the ping contained in the request and check that's a daily ping. + Assert.ok(receivedPingRequest, "Telemetry must send one daily ping."); + const receivedPing = decodeRequestPayload(receivedPingRequest); + checkPingFormat(receivedPing, PING_TYPE_MAIN, true, true); + Assert.equal(receivedPing.payload.info.reason, REASON_DAILY); + + // TODO: Remove the TelemetrySend manual shutdown when bug 1145188 lands. + yield TelemetrySend.shutdown(); + yield TelemetrySession.shutdown(); +}); + +add_task(function* test_userIdleAndSchedlerTick() { + if (gIsAndroid || gIsGonk) { + // We don't have the aborted session or the daily ping here. + return; + } + + yield TelemetrySession.reset(); + yield clearPendingPings(); + PingServer.clearRequests(); + // TODO: Remove the TelemetrySend setup when bug 1145188 lands. + yield TelemetrySend.setup(true); + yield TelemetrySend.reset(); + + let receivedPingRequest = null; + // Register a ping handler that will assert when receiving multiple daily pings. + PingServer.registerPingHandler(req => { + Assert.ok(!receivedPingRequest, "Telemetry must only send one daily ping."); + receivedPingRequest = req; + }); + + let schedulerTickCallback = null; + let now = new Date(2030, 1, 1, 0, 0, 0); + fakeNow(now); + // Fake scheduler functions to control daily collection flow in tests. + fakeSchedulerTimer(callback => schedulerTickCallback = callback, () => {}); + yield TelemetrySession.setup(); + + // Move the current date/time to midnight. + let firstDailyDue = new Date(2030, 1, 2, 0, 0, 0); + fakeNow(firstDailyDue); + + // The active notification should trigger a scheduler tick. The latter will send the + // due daily ping. + fakeIdleNotification("active"); + + // Immediately running another tick should not send a daily ping again. + Assert.ok(!!schedulerTickCallback); + yield schedulerTickCallback(); + + // A new "idle" notification should not send a new daily ping. + fakeIdleNotification("idle"); + + yield TelemetrySend.testWaitOnOutgoingPings(); + + // Decode the ping contained in the request and check that's a daily ping. + Assert.ok(receivedPingRequest, "Telemetry must send one daily ping."); + const receivedPing = decodeRequestPayload(receivedPingRequest); + checkPingFormat(receivedPing, PING_TYPE_MAIN, true, true); + Assert.equal(receivedPing.payload.info.reason, REASON_DAILY); + + // TODO: Remove the TelemetrySend manual shutdown when bug 1145188 lands. + yield TelemetrySend.shutdown(); yield TelemetrySession.shutdown(); }); diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetryStopwatch.js b/toolkit/components/telemetry/tests/unit/test_TelemetryStopwatch.js index 07265fecf6..69c8efa0f1 100644 --- a/toolkit/components/telemetry/tests/unit/test_TelemetryStopwatch.js +++ b/toolkit/components/telemetry/tests/unit/test_TelemetryStopwatch.js @@ -1,18 +1,19 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ -let tmpScope = {}; +var tmpScope = {}; Cu.import("resource://gre/modules/TelemetryStopwatch.jsm", tmpScope); -let TelemetryStopwatch = tmpScope.TelemetryStopwatch; +var TelemetryStopwatch = tmpScope.TelemetryStopwatch; // We can't create a histogram here since the ones created with // newHistogram are not seen by getHistogramById that the module uses. const HIST_NAME = "TELEMETRY_PING"; const HIST_NAME2 = "RANGE_CHECKSUM_ERRORS"; +const KEYED_HIST = { id: "TELEMETRY_INVALID_PING_TYPE_SUBMITTED", key: "TEST" }; -let refObj = {}, refObj2 = {}; +var refObj = {}, refObj2 = {}; -let originalCount1, originalCount2; +var originalCount1, originalCount2; function run_test() { let histogram = Telemetry.getHistogramById(HIST_NAME); @@ -23,6 +24,10 @@ function run_test() { snapshot = histogram.snapshot(); originalCount2 = snapshot.counts.reduce((a,b) => a += b); + histogram = Telemetry.getKeyedHistogramById(KEYED_HIST.id); + snapshot = histogram.snapshot(KEYED_HIST.key); + originalCount3 = snapshot.counts.reduce((a,b) => a += b); + do_check_false(TelemetryStopwatch.start(3)); do_check_false(TelemetryStopwatch.start({})); do_check_false(TelemetryStopwatch.start("", 3)); @@ -44,16 +49,10 @@ function run_test() { do_check_false(TelemetryStopwatch.finish("mark1", refObj)); do_check_true(TelemetryStopwatch.start("NON-EXISTENT_HISTOGRAM")); - try { - TelemetryStopwatch.finish("NON-EXISTENT_HISTOGRAM"); - do_throw("Non-existent histogram name should throw an error."); - } catch (e) {} + do_check_false(TelemetryStopwatch.finish("NON-EXISTENT_HISTOGRAM")); do_check_true(TelemetryStopwatch.start("NON-EXISTENT_HISTOGRAM", refObj)); - try { - TelemetryStopwatch.finish("NON-EXISTENT_HISTOGRAM", refObj); - do_throw("Non-existent histogram name should throw an error."); - } catch (e) {} + do_check_false(TelemetryStopwatch.finish("NON-EXISTENT_HISTOGRAM", refObj)); do_check_true(TelemetryStopwatch.start(HIST_NAME)); do_check_true(TelemetryStopwatch.start(HIST_NAME2)); @@ -97,6 +96,44 @@ function run_test() { do_check_false(TelemetryStopwatch.finish(HIST_NAME)); do_check_false(TelemetryStopwatch.finish(HIST_NAME, refObj)); + // Verify that keyed stopwatch reject invalid keys. + for (let key of [3, {}, ""]) { + do_check_false(TelemetryStopwatch.startKeyed(KEYED_HIST.id, key)); + } + + // Verify that keyed histograms can be started. + do_check_true(TelemetryStopwatch.startKeyed("HISTOGRAM", "KEY1")); + do_check_true(TelemetryStopwatch.startKeyed("HISTOGRAM", "KEY2")); + do_check_true(TelemetryStopwatch.startKeyed("HISTOGRAM", "KEY1", refObj)); + do_check_true(TelemetryStopwatch.startKeyed("HISTOGRAM", "KEY2", refObj)); + + // Restarting keyed histograms should fail. + do_check_false(TelemetryStopwatch.startKeyed("HISTOGRAM", "KEY1")); + do_check_false(TelemetryStopwatch.startKeyed("HISTOGRAM", "KEY1", refObj)); + + // Finishing a stopwatch of a non existing histogram should return false. + do_check_false(TelemetryStopwatch.finishKeyed("HISTOGRAM", "KEY2")); + do_check_false(TelemetryStopwatch.finishKeyed("HISTOGRAM", "KEY2", refObj)); + + // Starting & finishing a keyed stopwatch for an existing histogram should work. + do_check_true(TelemetryStopwatch.startKeyed(KEYED_HIST.id, KEYED_HIST.key)); + do_check_true(TelemetryStopwatch.finishKeyed(KEYED_HIST.id, KEYED_HIST.key)); + // Verify that TS.finish deleted the timers + do_check_false(TelemetryStopwatch.finishKeyed(KEYED_HIST.id, KEYED_HIST.key)); + + // Verify that they can be used again + do_check_true(TelemetryStopwatch.startKeyed(KEYED_HIST.id, KEYED_HIST.key)); + do_check_true(TelemetryStopwatch.finishKeyed(KEYED_HIST.id, KEYED_HIST.key)); + + do_check_false(TelemetryStopwatch.finishKeyed("unknown-mark", "unknown-key")); + do_check_false(TelemetryStopwatch.finishKeyed(KEYED_HIST.id, "unknown-key")); + + // Verify that keyed histograms can only be canceled through "keyed" API. + do_check_true(TelemetryStopwatch.startKeyed(KEYED_HIST.id, KEYED_HIST.key)); + do_check_false(TelemetryStopwatch.cancel(KEYED_HIST.id, KEYED_HIST.key)); + do_check_true(TelemetryStopwatch.cancelKeyed(KEYED_HIST.id, KEYED_HIST.key)); + do_check_false(TelemetryStopwatch.cancelKeyed(KEYED_HIST.id, KEYED_HIST.key)); + finishTest(); } @@ -112,4 +149,10 @@ function finishTest() { newCount = snapshot.counts.reduce((a,b) => a += b); do_check_eq(newCount - originalCount2, 3, "The correct number of histograms were added for histogram 2."); + + histogram = Telemetry.getKeyedHistogramById(KEYED_HIST.id); + snapshot = histogram.snapshot(KEYED_HIST.key); + newCount = snapshot.counts.reduce((a,b) => a += b); + + do_check_eq(newCount - originalCount3, 2, "The correct number of histograms were added for histogram 3."); } diff --git a/toolkit/components/telemetry/tests/unit/test_ThirdPartyCookieProbe.js b/toolkit/components/telemetry/tests/unit/test_ThirdPartyCookieProbe.js deleted file mode 100644 index 4b02e612c1..0000000000 --- a/toolkit/components/telemetry/tests/unit/test_ThirdPartyCookieProbe.js +++ /dev/null @@ -1,166 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -Cu.import("resource://gre/modules/Services.jsm", this); -Cu.import("resource://gre/modules/XPCOMUtils.jsm", this); -Cu.import("resource://gre/modules/ThirdPartyCookieProbe.jsm", this); -Cu.import("resource://gre/modules/Promise.jsm", this); -Cu.import("resource://gre/modules/TelemetryPing.jsm", this); - -let TOPIC_ACCEPTED = "third-party-cookie-accepted"; -let TOPIC_REJECTED = "third-party-cookie-rejected"; - -let FLUSH_MILLISECONDS = 1000 * 60 * 60 * 24 / 2; /*Half a day, for testing purposes*/ - -const NUMBER_OF_REJECTS = 30; -const NUMBER_OF_ACCEPTS = 17; -const NUMBER_OF_REPEATS = 5; - -let gCookieService; -let gThirdPartyCookieProbe; - -let gHistograms = { - clear: function() { - this.sitesAccepted.clear(); - this.requestsAccepted.clear(); - this.sitesRejected.clear(); - this.requestsRejected.clear(); - } -}; - -function run_test() { - do_print("Initializing environment"); - do_get_profile(); - createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); - gCookieService = Cc["@mozilla.org/cookieService;1"].getService(Ci.nsICookieService); - - do_print("Initializing ThirdPartyCookieProbe.jsm"); - gThirdPartyCookieProbe = new ThirdPartyCookieProbe(); - gThirdPartyCookieProbe.init(); - - do_print("Acquiring histograms"); - gHistograms.sitesAccepted = - Services.telemetry.getHistogramById("COOKIES_3RDPARTY_NUM_SITES_ACCEPTED"); - gHistograms.sitesRejected = - Services.telemetry.getHistogramById("COOKIES_3RDPARTY_NUM_SITES_BLOCKED"), - gHistograms.requestsAccepted = - Services.telemetry.getHistogramById("COOKIES_3RDPARTY_NUM_ATTEMPTS_ACCEPTED"); - gHistograms.requestsRejected = - Services.telemetry.getHistogramById("COOKIES_3RDPARTY_NUM_ATTEMPTS_BLOCKED"), - - - run_next_test(); -} - -/** - * Utility function: try to set a cookie with the given document uri and referrer uri. - * - * @param obj An object with the following fields - * - {string} request The uri of the request setting the cookie. - * - {string} referrer The uri of the referrer for this request. - */ -function tryToSetCookie(obj) { - let requestURI = Services.io.newURI(obj.request, null, null); - let referrerURI = Services.io.newURI(obj.referrer, null, null); - let requestChannel = Services.io.newChannelFromURI2(requestURI, - null, // aLoadingNode - Services.scriptSecurityManager.getSystemPrincipal(), - null, // aTriggeringPrincipal - Ci.nsILoadInfo.SEC_NORMAL, - Ci.nsIContentPolicy.TYPE_OTHER); - gCookieService.setCookieString(referrerURI, null, "Is there a cookie in my jar?", requestChannel); -} - -function wait(ms) { - let deferred = Promise.defer(); - do_timeout(ms, () => deferred.resolve()); - return deferred.promise; -} - -function oneTest(tld, flushUptime, check) { - gHistograms.clear(); - do_print("Testing with tld " + tld); - - do_print("Adding rejected entries"); - Services.prefs.setIntPref("network.cookie.cookieBehavior", - 1 /*reject third-party cookies*/); - - for (let i = 0; i < NUMBER_OF_REJECTS; ++i) { - for (let j = 0; j < NUMBER_OF_REPEATS; ++j) { - for (let prefix of ["http://", "https://"]) { - // Histogram sitesRejected should only count - // NUMBER_OF_REJECTS entries. - // Histogram requestsRejected should count - // NUMBER_OF_REJECTS * NUMBER_OF_REPEATS * 2 - tryToSetCookie({ - request: prefix + "echelon" + tld, - referrer: prefix + "domain" + i + tld - }); - } - } - } - - do_print("Adding accepted entries"); - Services.prefs.setIntPref("network.cookie.cookieBehavior", - 0 /*accept third-party cookies*/); - - for (let i = 0; i < NUMBER_OF_ACCEPTS; ++i) { - for (let j = 0; j < NUMBER_OF_REPEATS; ++j) { - for (let prefix of ["http://", "https://"]) { - // Histogram sitesAccepted should only count - // NUMBER_OF_ACCEPTS entries. - // Histogram requestsAccepted should count - // NUMBER_OF_ACCEPTS * NUMBER_OF_REPEATS * 2 - tryToSetCookie({ - request: prefix + "prism" + tld, - referrer: prefix + "domain" + i + tld - }); - } - } - } - - do_print("Checking that the histograms have not changed before ping()"); - do_check_eq(gHistograms.sitesAccepted.snapshot().sum, 0); - do_check_eq(gHistograms.sitesRejected.snapshot().sum, 0); - do_check_eq(gHistograms.requestsAccepted.snapshot().sum, 0); - do_check_eq(gHistograms.requestsRejected.snapshot().sum, 0); - - do_print("Checking that the resulting histograms are correct"); - if (flushUptime != null) { - let now = Date.now(); - let before = now - flushUptime; - gThirdPartyCookieProbe._latestFlush = before; - gThirdPartyCookieProbe.flush(now); - } else { - gThirdPartyCookieProbe.flush(); - } - check(); -} - -add_task(function() { - // To ensure that we work correctly with eTLD, test with several suffixes - for (let tld of [".com", ".com.ar", ".co.uk", ".gouv.fr"]) { - oneTest(tld, FLUSH_MILLISECONDS, function() { - do_check_eq(gHistograms.sitesAccepted.snapshot().sum, NUMBER_OF_ACCEPTS * 2); - do_check_eq(gHistograms.sitesRejected.snapshot().sum, NUMBER_OF_REJECTS * 2); - do_check_eq(gHistograms.requestsAccepted.snapshot().sum, NUMBER_OF_ACCEPTS * NUMBER_OF_REPEATS * 2 * 2); - do_check_eq(gHistograms.requestsRejected.snapshot().sum, NUMBER_OF_REJECTS * NUMBER_OF_REPEATS * 2 * 2); - }); - } - - // Check that things still work with default uptime management - for (let tld of [".com", ".com.ar", ".co.uk", ".gouv.fr"]) { - yield wait(1000); // Ensure that uptime is at least one second - oneTest(tld, null, function() { - do_check_true(gHistograms.sitesAccepted.snapshot().sum > 0); - do_check_true(gHistograms.sitesRejected.snapshot().sum > 0); - do_check_true(gHistograms.requestsAccepted.snapshot().sum > 0); - do_check_true(gHistograms.requestsRejected.snapshot().sum > 0); - }); - } -}); - -add_task(function() { - gThirdPartyCookieProbe.dispose(); -}); - diff --git a/toolkit/components/telemetry/tests/unit/test_ThreadHangStats.js b/toolkit/components/telemetry/tests/unit/test_ThreadHangStats.js index 891b119aaa..2826abbf29 100644 --- a/toolkit/components/telemetry/tests/unit/test_ThreadHangStats.js +++ b/toolkit/components/telemetry/tests/unit/test_ThreadHangStats.js @@ -23,24 +23,33 @@ function run_test() { // We use the rt_tgsigqueueinfo syscall on Linux which requires a // certain kernel version. It's not an error if the system running // the test is older than that. - let kernel = Services.sysinfo.kernel_version || Services.sysinfo.version; + let kernel = Services.sysinfo.get('kernel_version') || + Services.sysinfo.get('version'); if (Services.vc.compare(kernel, '2.6.31') < 0) { ok("Hang reporting not supported for old kernel."); return; } } - // Run two events in the event loop: - // the first event causes a hang; - // the second event checks results from the first event. + // Run three events in the event loop: + // the first event causes a transient hang; + // the second event causes a permanent hang; + // the third event checks results from previous events. do_execute_soon(() => { - // Cause a hang lasting 1 second. + // Cause a hang lasting 1 second (transient hang). let startTime = Date.now(); while ((Date.now() - startTime) < 1000) { } }); + do_execute_soon(() => { + // Cause a hang lasting 10 seconds (permanent hang). + let startTime = Date.now(); + while ((Date.now() - startTime) < 10000) { + } + }); + do_execute_soon(() => { do_test_pending(); @@ -77,6 +86,14 @@ function run_test() { notEqual(endHangs.hangs[0].stack.length, 0); equal(typeof endHangs.hangs[0].stack[0], "string"); + // Make sure one of the hangs is a permanent + // hang containing a native stack. + ok(endHangs.hangs.some((hang) => ( + Array.isArray(hang.nativeStack) && + hang.nativeStack.length !== 0 && + typeof hang.nativeStack[0] === "string" + ))); + check_histogram(endHangs.hangs[0].histogram); do_test_finished(); diff --git a/toolkit/components/telemetry/tests/unit/xpcshell.ini b/toolkit/components/telemetry/tests/unit/xpcshell.ini index 5328e779f4..fde3ec7ea5 100644 --- a/toolkit/components/telemetry/tests/unit/xpcshell.ini +++ b/toolkit/components/telemetry/tests/unit/xpcshell.ini @@ -39,25 +39,16 @@ skip-if = os == "android" [test_TelemetryLockCount.js] [test_TelemetryLog.js] [test_TelemetryController.js] -# Bug 676989: test fails consistently on Android -# fail-if = os == "android" -# Bug 1144395: crash on Android 4.3 -skip-if = android_version == "18" tags = addons [test_TelemetryController_idle.js] [test_TelemetryControllerShutdown.js] tags = addons [test_TelemetryStopwatch.js] [test_TelemetryControllerBuildID.js] -# Bug 1144395: crash on Android 4.3 -skip-if = android_version == "18" -[test_ThirdPartyCookieProbe.js] [test_TelemetrySendOldPings.js] skip-if = os == "android" # Disabled due to intermittent orange on Android tags = addons [test_TelemetrySession.js] -# Bug 1144395: crash on Android 4.3 -skip-if = android_version == "18" tags = addons [test_ThreadHangStats.js] run-sequentially = Bug 1046307, test can fail intermittently when CPU load is high diff --git a/toolkit/content/license.html b/toolkit/content/license.html index ddf9bbec57..9e577be170 100644 --- a/toolkit/content/license.html +++ b/toolkit/content/license.html @@ -101,6 +101,9 @@
  • k_exp License
  • Khronos group License
  • Kiss FFT License
  • +#ifdef MOZ_USE_LIBCXX +
  • libc++ License
  • +#endif
  • libcubeb License
  • libevent License
  • libffi License
  • @@ -3038,6 +3041,38 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    +#ifdef MOZ_USE_LIBCXX +

    libc++ License

    + +

    This license applies to the copy of libc++ obtained + from the Android NDK.

    + +
    +Copyright (c) 2009-2014 by the contributors listed in the libc++ CREDITS.TXT
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    +
    + +
    + +#endif +

    libcubeb License

    This license applies to files in the directory diff --git a/toolkit/content/moz.build b/toolkit/content/moz.build index 1b670a1b89..ed1be90af3 100644 --- a/toolkit/content/moz.build +++ b/toolkit/content/moz.build @@ -16,6 +16,8 @@ if CONFIG['OS_TARGET'] == 'Android': if CONFIG['MOZ_ANDROID_CXX_STL'] == 'mozstlport': DEFINES['USE_STLPORT'] = True +if CONFIG['MOZ_ANDROID_CXX_STL'] == 'libc++': + DEFINES['MOZ_USE_LIBCXX'] = True if CONFIG['MOZ_BUILD_APP'] == 'mobile/android': DEFINES['MOZ_FENNEC'] = True diff --git a/toolkit/content/widgets/findbar.xml b/toolkit/content/widgets/findbar.xml index e9e245774c..aebd6d68dc 100644 --- a/toolkit/content/widgets/findbar.xml +++ b/toolkit/content/widgets/findbar.xml @@ -672,7 +672,6 @@ this.browser.finder.focusContent(); this.browser.finder.enableSelection(); - this._findField.blur(); this._cancelTimers(); this.toggleHighlight(false); diff --git a/toolkit/modules/GMPInstallManager.jsm b/toolkit/modules/GMPInstallManager.jsm index 00a5cf4cc1..31333243b4 100644 --- a/toolkit/modules/GMPInstallManager.jsm +++ b/toolkit/modules/GMPInstallManager.jsm @@ -382,21 +382,29 @@ GMPExtractor.prototype = { let entries = this._getZipEntries(zipReader); let extractedPaths = []; + let destDir = Cc["@mozilla.org/file/local;1"]. + createInstance(Ci.nsILocalFile); + destDir.initWithPath(this.installToDirPath); + // Make sure the destination exists + if(!destDir.exists()) { + destDir.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0755", 8)); + } + // Extract each of the entries entries.forEach(entry => { // We don't need these types of files - if (entry.includes("__MACOSX")) { + if (entry.includes("__MACOSX") || + entry == "_metadata/verified_contents.json" || + entry == "imgs/icon-128x128.png") { return; } - let outFile = Cc["@mozilla.org/file/local;1"]. - createInstance(Ci.nsILocalFile); - outFile.initWithPath(this.installToDirPath); - outFile.appendRelativePath(entry); + let outFile = destDir.clone(); + // Do not extract into directories. Extract all files to the same + // directory. DO NOT use |OS.Path.basename()| here, as in Windows it + // does not work properly with forward slashes (which we must use here). + let outBaseName = entry.slice(entry.lastIndexOf("/") + 1); + outFile.appendRelativePath(outBaseName); - // Make sure the directory hierarchy exists - if(!outFile.parent.exists()) { - outFile.parent.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0755", 8)); - } zipReader.extract(entry, outFile); extractedPaths.push(outFile.path); log.info(entry + " was successfully extracted to: " + diff --git a/toolkit/modules/InlineSpellChecker.jsm b/toolkit/modules/InlineSpellChecker.jsm index 3bfec4f704..154534dbfa 100644 --- a/toolkit/modules/InlineSpellChecker.jsm +++ b/toolkit/modules/InlineSpellChecker.jsm @@ -248,8 +248,7 @@ InlineSpellChecker.prototype = { menu.ownerDocument.dispatchEvent(spellcheckChangeEvent); } }; - item.addEventListener - ("command", callback(this, i, sortedList[i].id), true); + item.addEventListener("command", callback(this, i, sortedList[i].id), true); } if (insertBefore) menu.insertBefore(item, insertBefore); diff --git a/toolkit/modules/RemoteFinder.jsm b/toolkit/modules/RemoteFinder.jsm index 86e4d9db87..0d74cc2e8d 100644 --- a/toolkit/modules/RemoteFinder.jsm +++ b/toolkit/modules/RemoteFinder.jsm @@ -119,16 +119,18 @@ RemoteFinder.prototype = { this._browser.messageManager.sendAsyncMessage("Finder:GetInitialSelection", {}); }, - fastFind: function (aSearchString, aLinksOnly) { + fastFind: function (aSearchString, aLinksOnly, aDrawOutline) { this._browser.messageManager.sendAsyncMessage("Finder:FastFind", { searchString: aSearchString, - linksOnly: aLinksOnly }); + linksOnly: aLinksOnly, + drawOutline: aDrawOutline }); }, - findAgain: function (aFindBackwards, aLinksOnly) { + findAgain: function (aFindBackwards, aLinksOnly, aDrawOutline) { this._browser.messageManager.sendAsyncMessage("Finder:FindAgain", { findBackwards: aFindBackwards, - linksOnly: aLinksOnly }); + linksOnly: aLinksOnly, + drawOutline: aDrawOutline }); }, highlight: function (aHighlight, aWord) { @@ -157,12 +159,16 @@ RemoteFinder.prototype = { } } + this._browser.focus(); this._browser.messageManager.sendAsyncMessage("Finder:FocusContent"); }, keyPress: function (aEvent) { this._browser.messageManager.sendAsyncMessage("Finder:KeyPress", { keyCode: aEvent.keyCode, + ctrlKey: aEvent.ctrlKey, + metaKey: aEvent.metaKey, + altKey: aEvent.altKey, shiftKey: aEvent.shiftKey }); }, @@ -239,17 +245,21 @@ RemoteFinderListener.prototype = { } case "Finder:FastFind": - this._finder.fastFind(data.searchString, data.linksOnly); + this._finder.fastFind(data.searchString, data.linksOnly, data.drawOutline); break; case "Finder:FindAgain": - this._finder.findAgain(data.findBackwards, data.linksOnly); + this._finder.findAgain(data.findBackwards, data.linksOnly, data.drawOutline); break; case "Finder:Highlight": this._finder.highlight(data.highlight, data.word); break; + case "Finder:EnableSelection": + this._finder.enableSelection(); + break; + case "Finder:RemoveSelection": this._finder.removeSelection(); break; diff --git a/toolkit/mozapps/extensions/AddonPathService.cpp b/toolkit/mozapps/extensions/AddonPathService.cpp index d171f0aebd..8a405c0ea4 100644 --- a/toolkit/mozapps/extensions/AddonPathService.cpp +++ b/toolkit/mozapps/extensions/AddonPathService.cpp @@ -129,6 +129,16 @@ AddonPathService::InsertPath(const nsAString& path, const nsAString& addonIdStri return NS_OK; } +NS_IMETHODIMP +AddonPathService::MapURIToAddonId(nsIURI* aURI, nsAString& addonIdString) +{ + if (JSAddonId* id = MapURIToAddonID(aURI)) { + JSFlatString* flat = JS_ASSERT_STRING_IS_FLAT(JS::StringOfAddonId(id)); + AssignJSFlatString(addonIdString, flat); + } + return NS_OK; +} + static nsresult ResolveURI(nsIURI* aURI, nsAString& out) { diff --git a/toolkit/mozapps/extensions/amIAddonPathService.idl b/toolkit/mozapps/extensions/amIAddonPathService.idl index 8636898585..9c9197a61c 100644 --- a/toolkit/mozapps/extensions/amIAddonPathService.idl +++ b/toolkit/mozapps/extensions/amIAddonPathService.idl @@ -5,6 +5,8 @@ #include "nsISupports.idl" +interface nsIURI; + /** * This service maps file system paths where add-ons reside to the ID * of the add-on. Paths are added by the add-on manager. They can @@ -26,4 +28,10 @@ interface amIAddonPathService : nsISupports * associated with the given add-on ID. */ void insertPath(in AString path, in AString addonId); + + /** + * Given a URI to a file, return the ID of the add-on that the file belongs + * to. Returns an empty string if there is no add-on there. + */ + AString mapURIToAddonId(in nsIURI aURI); }; diff --git a/toolkit/mozapps/extensions/amWebAPI.js b/toolkit/mozapps/extensions/amWebAPI.js index 2d48c99769..b415a9e4d6 100644 --- a/toolkit/mozapps/extensions/amWebAPI.js +++ b/toolkit/mozapps/extensions/amWebAPI.js @@ -102,9 +102,9 @@ function AddonInstall(window, properties) { * to make sure of that. It also automatically wraps objects when necessary. */ function WebAPITask(generator) { - let task = Task.async(generator); - return function(...args) { + let task = Task.async(generator.bind(this)); + let win = this.window; let wrapForContent = (obj) => { diff --git a/toolkit/profile/test/.eslintrc b/toolkit/profile/test/.eslintrc new file mode 100644 index 0000000000..e617dcd2f8 --- /dev/null +++ b/toolkit/profile/test/.eslintrc @@ -0,0 +1,5 @@ +{ + "extends": [ + "../../../testing/mochitest/chrome.eslintrc" + ] +} diff --git a/tools/leak-gauge/leak-gauge.html b/tools/leak-gauge/leak-gauge.html index 63090f2600..f8c74baf5d 100644 --- a/tools/leak-gauge/leak-gauge.html +++ b/tools/leak-gauge/leak-gauge.html @@ -8,6 +8,7 @@ --> + Leak Gauge