diff --git a/accessible/generic/DocAccessible.cpp b/accessible/generic/DocAccessible.cpp index 6b90af7585cd..01453a56e007 100644 --- a/accessible/generic/DocAccessible.cpp +++ b/accessible/generic/DocAccessible.cpp @@ -2072,7 +2072,10 @@ DocAccessible::SeizeChild(Accessible* aNewParent, Accessible* aChild, int32_t aIdxInParent) { Accessible* oldParent = aChild->Parent(); - NS_PRECONDITION(oldParent, "No parent?"); + if (!oldParent) { + NS_ERROR("No parent? The tree is broken!"); + return false; + } int32_t oldIdxInParent = aChild->IndexInParent(); @@ -2160,6 +2163,10 @@ DocAccessible::PutChildrenBack(nsTArray >* aChildren, // If the child is in the tree then remove it from the owner. if (child->IsInDocument()) { Accessible* owner = child->Parent(); + if (!owner) { + NS_ERROR("Cannot put the child back. No parent, a broken tree."); + continue; + } RefPtr reorderEvent = new AccReorderEvent(owner); RefPtr hideEvent = new AccHideEvent(child, child->GetContent(), false); diff --git a/b2g/app/b2g.js b/b2g/app/b2g.js index 6601e0afd0db..b6ffeb375af7 100644 --- a/b2g/app/b2g.js +++ b/b2g/app/b2g.js @@ -1028,6 +1028,10 @@ pref("apz.fling_friction", "0.0019"); pref("apz.max_velocity_inches_per_ms", "0.07"); pref("apz.touch_start_tolerance", "0.1"); +#ifdef MOZ_WIDGET_GONK +pref("apz.touch_move_tolerance", "0.03"); +#endif + // Tweak default displayport values to reduce the risk of running out of // memory when zooming in pref("apz.x_skate_size_multiplier", "1.25"); diff --git a/dom/base/nsContentIterator.cpp b/dom/base/nsContentIterator.cpp index 27566762bd52..bf3ba0c62265 100644 --- a/dom/base/nsContentIterator.cpp +++ b/dom/base/nsContentIterator.cpp @@ -30,6 +30,7 @@ NodeToParentOffset(nsINode* aNode, int32_t* aOffset) if (parent) { *aOffset = parent->IndexOf(aNode); + NS_WARN_IF(*aOffset < 0); } return parent; @@ -44,7 +45,7 @@ NodeIsInTraversalRange(nsINode* aNode, bool aIsPreMode, nsINode* aStartNode, int32_t aStartOffset, nsINode* aEndNode, int32_t aEndOffset) { - if (!aStartNode || !aEndNode || !aNode) { + if (NS_WARN_IF(!aStartNode) || NS_WARN_IF(!aEndNode) || NS_WARN_IF(!aNode)) { return false; } @@ -71,6 +72,7 @@ NodeIsInTraversalRange(nsINode* aNode, bool aIsPreMode, } int32_t indx = parent->IndexOf(aNode); + NS_WARN_IF(indx == -1); if (!aIsPreMode) { ++indx; @@ -262,7 +264,7 @@ nsContentIterator::~nsContentIterator() nsresult nsContentIterator::Init(nsINode* aRoot) { - if (!aRoot) { + if (NS_WARN_IF(!aRoot)) { return NS_ERROR_NULL_POINTER; } @@ -272,8 +274,10 @@ nsContentIterator::Init(nsINode* aRoot) if (mPre) { mFirst = aRoot; mLast = GetDeepLastChild(aRoot); + NS_WARN_IF(!mLast); } else { mFirst = GetDeepFirstChild(aRoot); + NS_WARN_IF(!mFirst); mLast = aRoot; } @@ -286,24 +290,34 @@ nsContentIterator::Init(nsINode* aRoot) nsresult nsContentIterator::Init(nsIDOMRange* aDOMRange) { - NS_ENSURE_ARG_POINTER(aDOMRange); + if (NS_WARN_IF(!aDOMRange)) { + return NS_ERROR_INVALID_ARG; + } nsRange* range = static_cast(aDOMRange); mIsDone = false; // get common content parent mCommonParent = range->GetCommonAncestor(); - NS_ENSURE_TRUE(mCommonParent, NS_ERROR_FAILURE); + if (NS_WARN_IF(!mCommonParent)) { + return NS_ERROR_FAILURE; + } // get the start node and offset int32_t startIndx = range->StartOffset(); + NS_WARN_IF(startIndx < 0); nsINode* startNode = range->GetStartParent(); - NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE); + if (NS_WARN_IF(!startNode)) { + return NS_ERROR_FAILURE; + } // get the end node and offset int32_t endIndx = range->EndOffset(); + NS_WARN_IF(endIndx < 0); nsINode* endNode = range->GetEndParent(); - NS_ENSURE_TRUE(endNode, NS_ERROR_FAILURE); + if (NS_WARN_IF(!endNode)) { + return NS_ERROR_FAILURE; + } bool startIsData = startNode->IsNodeOfType(nsINode::eDATA_NODE); @@ -327,7 +341,8 @@ nsContentIterator::Init(nsIDOMRange* aDOMRange) mLast = mFirst; mCurNode = mFirst; - RebuildIndexStack(); + nsresult rv = RebuildIndexStack(); + NS_WARN_IF(NS_FAILED(rv)); return NS_OK; } } @@ -338,6 +353,7 @@ nsContentIterator::Init(nsIDOMRange* aDOMRange) if (!startIsData && startNode->HasChildren()) { cChild = startNode->GetChildAt(startIndx); + NS_WARN_IF(!cChild); } if (!cChild) { @@ -356,11 +372,13 @@ nsContentIterator::Init(nsIDOMRange* aDOMRange) // In other words, if the offset is 1, the node should be ignored. if (!startIsData && startIndx) { mFirst = GetNextSibling(startNode); + NS_WARN_IF(!mFirst); // Does mFirst node really intersect the range? The range could be // 'degenerate', i.e., not collapsed but still contain no content. - if (mFirst && !NodeIsInTraversalRange(mFirst, mPre, startNode, - startIndx, endNode, endIndx)) { + if (mFirst && + NS_WARN_IF(!NodeIsInTraversalRange(mFirst, mPre, startNode, + startIndx, endNode, endIndx))) { mFirst = nullptr; } } else { @@ -368,11 +386,11 @@ nsContentIterator::Init(nsIDOMRange* aDOMRange) } } else { // post-order - if (startNode->IsContent()) { - mFirst = startNode->AsContent(); - } else { + if (NS_WARN_IF(!startNode->IsContent())) { // What else can we do? mFirst = nullptr; + } else { + mFirst = startNode->AsContent(); } } } else { @@ -381,12 +399,14 @@ nsContentIterator::Init(nsIDOMRange* aDOMRange) } else { // post-order mFirst = GetDeepFirstChild(cChild); + NS_WARN_IF(!mFirst); // Does mFirst node really intersect the range? The range could be // 'degenerate', i.e., not collapsed but still contain no content. - if (mFirst && !NodeIsInTraversalRange(mFirst, mPre, startNode, startIndx, - endNode, endIndx)) { + if (mFirst && + NS_WARN_IF(!NodeIsInTraversalRange(mFirst, mPre, startNode, startIndx, + endNode, endIndx))) { mFirst = nullptr; } } @@ -399,22 +419,24 @@ nsContentIterator::Init(nsIDOMRange* aDOMRange) if (endIsData || !endNode->HasChildren() || endIndx == 0) { if (mPre) { - if (endNode->IsContent()) { + if (NS_WARN_IF(!endNode->IsContent())) { + // Not much else to do here... + mLast = nullptr; + } else { // If the end node is an empty element and the end offset is 0, // the last element should be the previous node (i.e., shouldn't // include the end node in the range). if (!endIsData && !endNode->HasChildren() && !endIndx) { mLast = GetPrevSibling(endNode); - if (!NodeIsInTraversalRange(mLast, mPre, startNode, startIndx, - endNode, endIndx)) { + NS_WARN_IF(!mLast); + if (NS_WARN_IF(!NodeIsInTraversalRange(mLast, mPre, + startNode, startIndx, + endNode, endIndx))) { mLast = nullptr; } } else { mLast = endNode->AsContent(); } - } else { - // Not much else to do here... - mLast = nullptr; } } else { // post-order @@ -424,9 +446,11 @@ nsContentIterator::Init(nsIDOMRange* aDOMRange) if (!endIsData) { mLast = GetPrevSibling(endNode); + NS_WARN_IF(!mLast); - if (!NodeIsInTraversalRange(mLast, mPre, startNode, startIndx, - endNode, endIndx)) { + if (NS_WARN_IF(!NodeIsInTraversalRange(mLast, mPre, + startNode, startIndx, + endNode, endIndx))) { mLast = nullptr; } } else { @@ -438,7 +462,7 @@ nsContentIterator::Init(nsIDOMRange* aDOMRange) cChild = endNode->GetChildAt(--indx); - if (!cChild) { + if (NS_WARN_IF(!cChild)) { // No child at offset! NS_NOTREACHED("nsContentIterator::nsContentIterator"); return NS_ERROR_FAILURE; @@ -446,9 +470,11 @@ nsContentIterator::Init(nsIDOMRange* aDOMRange) if (mPre) { mLast = GetDeepLastChild(cChild); + NS_WARN_IF(!mLast); - if (!NodeIsInTraversalRange(mLast, mPre, startNode, startIndx, - endNode, endIndx)) { + if (NS_WARN_IF(!NodeIsInTraversalRange(mLast, mPre, + startNode, startIndx, + endNode, endIndx))) { mLast = nullptr; } } else { @@ -459,7 +485,7 @@ nsContentIterator::Init(nsIDOMRange* aDOMRange) // If either first or last is null, they both have to be null! - if (!mFirst || !mLast) { + if (NS_WARN_IF(!mFirst) || NS_WARN_IF(!mLast)) { mFirst = nullptr; mLast = nullptr; } @@ -470,7 +496,8 @@ nsContentIterator::Init(nsIDOMRange* aDOMRange) if (!mCurNode) { mIndexes.Clear(); } else { - RebuildIndexStack(); + nsresult rv = RebuildIndexStack(); + NS_WARN_IF(NS_FAILED(rv)); } return NS_OK; @@ -499,7 +526,7 @@ nsContentIterator::RebuildIndexStack() while (current != mCommonParent) { parent = current->GetParentNode(); - if (!parent) { + if (NS_WARN_IF(!parent)) { return NS_ERROR_FAILURE; } @@ -526,7 +553,7 @@ nsINode* nsContentIterator::GetDeepFirstChild(nsINode* aRoot, nsTArray* aIndexes) { - if (!aRoot || !aRoot->HasChildren()) { + if (NS_WARN_IF(!aRoot) || !aRoot->HasChildren()) { return aRoot; } // We can't pass aRoot itself to the full GetDeepFirstChild, because that @@ -542,7 +569,7 @@ nsIContent* nsContentIterator::GetDeepFirstChild(nsIContent* aRoot, nsTArray* aIndexes) { - if (!aRoot) { + if (NS_WARN_IF(!aRoot)) { return nullptr; } @@ -565,7 +592,7 @@ nsINode* nsContentIterator::GetDeepLastChild(nsINode* aRoot, nsTArray* aIndexes) { - if (!aRoot || !aRoot->HasChildren()) { + if (NS_WARN_IF(!aRoot) || !aRoot->HasChildren()) { return aRoot; } // We can't pass aRoot itself to the full GetDeepLastChild, because that will @@ -581,7 +608,7 @@ nsIContent* nsContentIterator::GetDeepLastChild(nsIContent* aRoot, nsTArray* aIndexes) { - if (!aRoot) { + if (NS_WARN_IF(!aRoot)) { return nullptr; } @@ -607,12 +634,12 @@ nsIContent* nsContentIterator::GetNextSibling(nsINode* aNode, nsTArray* aIndexes) { - if (!aNode) { + if (NS_WARN_IF(!aNode)) { return nullptr; } nsINode* parent = aNode->GetParentNode(); - if (!parent) { + if (NS_WARN_IF(!parent)) { return nullptr; } @@ -626,6 +653,7 @@ nsContentIterator::GetNextSibling(nsINode* aNode, } else { indx = mCachedIndex; } + NS_WARN_IF(indx < 0); // reverify that the index of the current node hasn't changed. // not super cheap, but a lot cheaper than IndexOf(), and still O(1). @@ -634,6 +662,7 @@ nsContentIterator::GetNextSibling(nsINode* aNode, if (sib != aNode) { // someone changed our index - find the new index the painful way indx = parent->IndexOf(aNode); + NS_WARN_IF(indx < 0); } // indx is now canonically correct @@ -668,12 +697,12 @@ nsIContent* nsContentIterator::GetPrevSibling(nsINode* aNode, nsTArray* aIndexes) { - if (!aNode) { + if (NS_WARN_IF(!aNode)) { return nullptr; } nsINode* parent = aNode->GetParentNode(); - if (!parent) { + if (NS_WARN_IF(!parent)) { return nullptr; } @@ -694,6 +723,7 @@ nsContentIterator::GetPrevSibling(nsINode* aNode, if (sib != aNode) { // someone changed our index - find the new index the painful way indx = parent->IndexOf(aNode); + NS_WARN_IF(indx < 0); } // indx is now canonically correct @@ -725,6 +755,7 @@ nsContentIterator::NextNode(nsINode* aNode, nsTArray* aIndexes) // if it has children then next node is first child if (node->HasChildren()) { nsIContent* firstChild = node->GetFirstChild(); + MOZ_ASSERT(firstChild); // update cache if (aIndexes) { @@ -743,6 +774,7 @@ nsContentIterator::NextNode(nsINode* aNode, nsTArray* aIndexes) // post-order nsINode* parent = node->GetParentNode(); + NS_WARN_IF(!parent); nsIContent* sibling = nullptr; int32_t indx = 0; @@ -765,6 +797,7 @@ nsContentIterator::NextNode(nsINode* aNode, nsTArray* aIndexes) if (sibling != node) { // someone changed our index - find the new index the painful way indx = parent->IndexOf(node); + NS_WARN_IF(indx < 0); } // indx is now canonically correct @@ -806,6 +839,7 @@ nsContentIterator::PrevNode(nsINode* aNode, nsTArray* aIndexes) // if we are a Pre-order iterator, use pre-order if (mPre) { nsINode* parent = node->GetParentNode(); + NS_WARN_IF(!parent); nsIContent* sibling = nullptr; int32_t indx = 0; @@ -824,11 +858,13 @@ nsContentIterator::PrevNode(nsINode* aNode, nsTArray* aIndexes) // this time - the index may now be out of range. if (indx >= 0) { sibling = parent->GetChildAt(indx); + NS_WARN_IF(!sibling); } if (sibling != node) { // someone changed our index - find the new index the painful way indx = parent->IndexOf(node); + NS_WARN_IF(indx < 0); } // indx is now canonically correct @@ -858,10 +894,12 @@ nsContentIterator::PrevNode(nsINode* aNode, nsTArray* aIndexes) // post-order int32_t numChildren = node->GetChildCount(); + NS_WARN_IF(numChildren < 0); // if it has children then prev node is last child if (numChildren) { nsIContent* lastChild = node->GetLastChild(); + NS_WARN_IF(!lastChild); numChildren--; // update cache @@ -887,11 +925,7 @@ void nsContentIterator::First() { if (mFirst) { -#ifdef DEBUG - nsresult rv = -#endif - PositionAt(mFirst); - + DebugOnly rv = PositionAt(mFirst); NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to position iterator!"); } @@ -905,11 +939,7 @@ nsContentIterator::Last() NS_ASSERTION(mLast, "No last node!"); if (mLast) { -#ifdef DEBUG - nsresult rv = -#endif - PositionAt(mLast); - + DebugOnly rv = PositionAt(mLast); NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to position iterator!"); } @@ -920,7 +950,7 @@ nsContentIterator::Last() void nsContentIterator::Next() { - if (mIsDone || !mCurNode) { + if (mIsDone || NS_WARN_IF(!mCurNode)) { return; } @@ -936,7 +966,7 @@ nsContentIterator::Next() void nsContentIterator::Prev() { - if (mIsDone || !mCurNode) { + if (NS_WARN_IF(mIsDone) || NS_WARN_IF(!mCurNode)) { return; } @@ -961,7 +991,7 @@ nsContentIterator::IsDone() nsresult nsContentIterator::PositionAt(nsINode* aCurNode) { - if (!aCurNode) { + if (NS_WARN_IF(!aCurNode)) { return NS_ERROR_NULL_POINTER; } @@ -984,11 +1014,15 @@ nsContentIterator::PositionAt(nsINode* aCurNode) if (firstNode && lastNode) { if (mPre) { firstNode = NodeToParentOffset(mFirst, &firstOffset); + NS_WARN_IF(!firstNode); + NS_WARN_IF(firstOffset < 0); if (lastNode->GetChildCount()) { lastOffset = 0; } else { lastNode = NodeToParentOffset(mLast, &lastOffset); + NS_WARN_IF(!lastNode); + NS_WARN_IF(lastOffset < 0); ++lastOffset; } } else { @@ -996,11 +1030,16 @@ nsContentIterator::PositionAt(nsINode* aCurNode) if (numChildren) { firstOffset = numChildren; + NS_WARN_IF(firstOffset < 0); } else { firstNode = NodeToParentOffset(mFirst, &firstOffset); + NS_WARN_IF(!firstNode); + NS_WARN_IF(firstOffset < 0); } lastNode = NodeToParentOffset(mLast, &lastOffset); + NS_WARN_IF(!lastNode); + NS_WARN_IF(lastOffset < 0); ++lastOffset; } } @@ -1009,9 +1048,10 @@ nsContentIterator::PositionAt(nsINode* aCurNode) // need to allow that or 'iter->Init(root)' would assert in Last() or First() // for example, bug 327694. if (mFirst != mCurNode && mLast != mCurNode && - (!firstNode || !lastNode || - !NodeIsInTraversalRange(mCurNode, mPre, firstNode, firstOffset, - lastNode, lastOffset))) { + (NS_WARN_IF(!firstNode) || NS_WARN_IF(!lastNode) || + NS_WARN_IF(!NodeIsInTraversalRange(mCurNode, mPre, + firstNode, firstOffset, + lastNode, lastOffset)))) { mIsDone = true; return NS_ERROR_FAILURE; } @@ -1040,7 +1080,7 @@ nsContentIterator::PositionAt(nsINode* aCurNode) nsINode* parent = tempNode->GetParentNode(); - if (!parent) { + if (NS_WARN_IF(!parent)) { // this node has no parent, and thus no index break; } @@ -1060,12 +1100,13 @@ nsContentIterator::PositionAt(nsINode* aCurNode) while (newCurNode) { nsINode* parent = newCurNode->GetParentNode(); - if (!parent) { + if (NS_WARN_IF(!parent)) { // this node has no parent, and thus no index break; } int32_t indx = parent->IndexOf(newCurNode); + NS_WARN_IF(indx < 0); // insert at the head! newIndexes.InsertElementAt(0, indx); diff --git a/dom/base/nsDocument.cpp b/dom/base/nsDocument.cpp index 2cc378296cfb..3375629063cf 100644 --- a/dom/base/nsDocument.cpp +++ b/dom/base/nsDocument.cpp @@ -1498,13 +1498,19 @@ nsDocument::nsDocument(const char* aContentType) } } -static PLDHashOperator -ClearAllBoxObjects(nsIContent* aKey, nsPIBoxObject* aBoxObject, void* aUserArg) +void +nsDocument::ClearAllBoxObjects() { - if (aBoxObject) { - aBoxObject->Clear(); + if (mBoxObjectTable) { + for (auto iter = mBoxObjectTable->Iter(); !iter.Done(); iter.Next()) { + nsPIBoxObject* boxObject = iter.UserData(); + if (boxObject) { + boxObject->Clear(); + } + } + delete mBoxObjectTable; + mBoxObjectTable = nullptr; } - return PL_DHASH_NEXT; } nsIDocument::~nsIDocument() @@ -1653,10 +1659,7 @@ nsDocument::~nsDocument() delete mHeaderData; - if (mBoxObjectTable) { - mBoxObjectTable->EnumerateRead(ClearAllBoxObjects, nullptr); - delete mBoxObjectTable; - } + ClearAllBoxObjects(); mPendingTitleChangeEvent.Revoke(); @@ -1746,39 +1749,6 @@ NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsDocument) return Element::CanSkipThis(tmp); NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END -static PLDHashOperator -RadioGroupsTraverser(const nsAString& aKey, nsRadioGroupStruct* aData, - void* aClosure) -{ - nsCycleCollectionTraversalCallback *cb = - static_cast(aClosure); - - NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, - "mRadioGroups entry->mSelectedRadioButton"); - cb->NoteXPCOMChild(ToSupports(aData->mSelectedRadioButton)); - - uint32_t i, count = aData->mRadioButtons.Count(); - for (i = 0; i < count; ++i) { - NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, - "mRadioGroups entry->mRadioButtons[i]"); - cb->NoteXPCOMChild(aData->mRadioButtons[i]); - } - - return PL_DHASH_NEXT; -} - -static PLDHashOperator -BoxObjectTraverser(nsIContent* key, nsPIBoxObject* boxObject, void* userArg) -{ - nsCycleCollectionTraversalCallback *cb = - static_cast(userArg); - - NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, "mBoxObjectTable entry"); - cb->NoteXPCOMChild(boxObject); - - return PL_DHASH_NEXT; -} - static const char* kNSURIs[] = { "([none])", "(xmlns)", @@ -1855,12 +1825,27 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsDocument) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMasterDocument) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImportManager) - tmp->mRadioGroups.EnumerateRead(RadioGroupsTraverser, &cb); + for (auto iter = tmp->mRadioGroups.Iter(); !iter.Done(); iter.Next()) { + nsRadioGroupStruct* radioGroup = iter.UserData(); + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME( + cb, "mRadioGroups entry->mSelectedRadioButton"); + cb.NoteXPCOMChild(ToSupports(radioGroup->mSelectedRadioButton)); + + uint32_t i, count = radioGroup->mRadioButtons.Count(); + for (i = 0; i < count; ++i) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME( + cb, "mRadioGroups entry->mRadioButtons[i]"); + cb.NoteXPCOMChild(radioGroup->mRadioButtons[i]); + } + } // The boxobject for an element will only exist as long as it's in the // document, so we'll traverse the table here instead of from the element. if (tmp->mBoxObjectTable) { - tmp->mBoxObjectTable->EnumerateRead(BoxObjectTraverser, &cb); + for (auto iter = tmp->mBoxObjectTable->Iter(); !iter.Done(); iter.Next()) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mBoxObjectTable entry"); + cb.NoteXPCOMChild(iter.UserData()); + } } NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChannel) @@ -1981,12 +1966,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDocument) NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadingImages) - - if (tmp->mBoxObjectTable) { - tmp->mBoxObjectTable->EnumerateRead(ClearAllBoxObjects, nullptr); - delete tmp->mBoxObjectTable; - tmp->mBoxObjectTable = nullptr; - } + tmp->ClearAllBoxObjects(); if (tmp->mListenerManager) { tmp->mListenerManager->Disconnect(); @@ -3787,14 +3767,6 @@ nsIDocument::ShouldThrottleFrameRequests() return false; } -PLDHashOperator RequestDiscardEnumerator(imgIRequest* aKey, - uint32_t aData, - void* userArg) -{ - aKey->RequestDiscard(); - return PL_DHASH_NEXT; -} - void nsDocument::DeleteShell() { @@ -3809,7 +3781,9 @@ nsDocument::DeleteShell() // When our shell goes away, request that all our images be immediately // discarded, so we don't carry around decoded image data for a document we // no longer intend to paint. - mImageTracker.EnumerateRead(RequestDiscardEnumerator, nullptr); + for (auto iter = mImageTracker.Iter(); !iter.Done(); iter.Next()) { + iter.Key()->RequestDiscard(); + } // Now that we no longer have a shell, we need to forget about any FontFace // objects for @font-face rules that came from the style set. @@ -10567,23 +10541,6 @@ nsDocument::NotifyMediaFeatureValuesChanged() } } -PLDHashOperator LockEnumerator(imgIRequest* aKey, - uint32_t aData, - void* userArg) -{ - aKey->LockImage(); - return PL_DHASH_NEXT; -} - -PLDHashOperator UnlockEnumerator(imgIRequest* aKey, - uint32_t aData, - void* userArg) -{ - aKey->UnlockImage(); - return PL_DHASH_NEXT; -} - - nsresult nsDocument::SetImageLockingState(bool aLocked) { @@ -10597,9 +10554,14 @@ nsDocument::SetImageLockingState(bool aLocked) return NS_OK; // Otherwise, iterate over our images and perform the appropriate action. - mImageTracker.EnumerateRead(aLocked ? LockEnumerator - : UnlockEnumerator, - nullptr); + for (auto iter = mImageTracker.Iter(); !iter.Done(); iter.Next()) { + imgIRequest* image = iter.Key(); + if (aLocked) { + image->LockImage(); + } else { + image->UnlockImage(); + } + } // Update state. mLockingImages = aLocked; @@ -10607,22 +10569,6 @@ nsDocument::SetImageLockingState(bool aLocked) return NS_OK; } -PLDHashOperator IncrementAnimationEnumerator(imgIRequest* aKey, - uint32_t aData, - void* userArg) -{ - aKey->IncrementAnimationConsumers(); - return PL_DHASH_NEXT; -} - -PLDHashOperator DecrementAnimationEnumerator(imgIRequest* aKey, - uint32_t aData, - void* userArg) -{ - aKey->DecrementAnimationConsumers(); - return PL_DHASH_NEXT; -} - void nsDocument::SetImagesNeedAnimating(bool aAnimating) { @@ -10631,9 +10577,14 @@ nsDocument::SetImagesNeedAnimating(bool aAnimating) return; // Otherwise, iterate over our images and perform the appropriate action. - mImageTracker.EnumerateRead(aAnimating ? IncrementAnimationEnumerator - : DecrementAnimationEnumerator, - nullptr); + for (auto iter = mImageTracker.Iter(); !iter.Done(); iter.Next()) { + imgIRequest* image = iter.Key(); + if (aAnimating) { + image->IncrementAnimationConsumers(); + } else { + image->DecrementAnimationConsumers(); + } + } // Update state. mAnimatingImages = aAnimating; diff --git a/dom/base/nsDocument.h b/dom/base/nsDocument.h index 0f20cb6e2a53..9eb1fe177ba7 100644 --- a/dom/base/nsDocument.h +++ b/dom/base/nsDocument.h @@ -1767,6 +1767,8 @@ private: // requestAnimationFrame, if it's OK to do so. void MaybeRescheduleAnimationFrameNotifications(); + void ClearAllBoxObjects(); + // Returns true if the scheme for the url for this document is "about" bool IsAboutPage(); diff --git a/dom/base/nsGkAtomList.h b/dom/base/nsGkAtomList.h index 5ebbdc26ec7c..b87cd98c42ce 100644 --- a/dom/base/nsGkAtomList.h +++ b/dom/base/nsGkAtomList.h @@ -294,6 +294,7 @@ GK_ATOM(destructor, "destructor") GK_ATOM(details, "details") GK_ATOM(deviceAspectRatio, "device-aspect-ratio") GK_ATOM(deviceHeight, "device-height") +GK_ATOM(devicePixelRatio, "device-pixel-ratio") GK_ATOM(deviceWidth, "device-width") GK_ATOM(dfn, "dfn") GK_ATOM(dialog, "dialog") diff --git a/dom/base/test/test_bug976673.html b/dom/base/test/test_bug976673.html index 0bf49900dd13..d028f70d8a4c 100644 --- a/dom/base/test/test_bug976673.html +++ b/dom/base/test/test_bug976673.html @@ -26,6 +26,11 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=976673 SimpleTest.waitForExplicitFinish(); +// In e10s mode, ContentCacheInChild tries to retrieve selected text and +// caret position when IMEContentObserver notifies IME of focus. At this time, +// we hit assertion in nsContentIterator. +SimpleTest.expectAssertions(0, 6); + window.addEventListener("mousedown", function (aEvent) { aEvent.preventDefault(); }, false); function testSetFocus(aEventType, aCallback) diff --git a/dom/bindings/BindingUtils.cpp b/dom/bindings/BindingUtils.cpp index d9424cf60985..8527ffe31ea6 100644 --- a/dom/bindings/BindingUtils.cpp +++ b/dom/bindings/BindingUtils.cpp @@ -135,6 +135,12 @@ ThrowMethodFailed(JSContext* cx, ErrorResult& rv) // uncatchable exception. return false; } + if (rv.IsJSContextException()) { + // Whatever we need to throw is on the JSContext already. We + // can't assert that there is a pending exception on it, though, + // because in the uncatchable exception case there won't be one. + return false; + } if (rv.IsErrorWithMessage()) { rv.ReportErrorWithMessage(cx); return false; diff --git a/dom/bindings/ErrorResult.h b/dom/bindings/ErrorResult.h index ded0b49f80e4..d332398d287b 100644 --- a/dom/bindings/ErrorResult.h +++ b/dom/bindings/ErrorResult.h @@ -167,6 +167,17 @@ public: void ReportDOMException(JSContext* cx); bool IsDOMException() const { return ErrorCode() == NS_ERROR_DOM_DOMEXCEPTION; } + // Flag on the ErrorResult that whatever needs throwing has been + // thrown on the JSContext already and we should not mess with it. + void NoteJSContextException() { + mResult = NS_ERROR_DOM_EXCEPTION_ON_JSCONTEXT; + } + // Check whether the ErrorResult says to just throw whatever is on + // the JSContext already. + bool IsJSContextException() { + return ErrorCode() == NS_ERROR_DOM_EXCEPTION_ON_JSCONTEXT; + } + // Report a generic error. This should only be used if we're not // some more specific exception type. void ReportGenericError(JSContext* cx); @@ -274,6 +285,8 @@ private: MOZ_ASSERT(aRv != NS_ERROR_DOM_DOMEXCEPTION, "Use ThrowDOMException()"); MOZ_ASSERT(!IsDOMException(), "Don't overwrite DOM exceptions"); MOZ_ASSERT(aRv != NS_ERROR_XPC_NOT_ENOUGH_ARGS, "May need to bring back ThrowNotEnoughArgsError"); + MOZ_ASSERT(aRv != NS_ERROR_DOM_EXCEPTION_ON_JSCONTEXT, + "Use NoteJSContextException"); mResult = aRv; } diff --git a/dom/events/EventStateManager.cpp b/dom/events/EventStateManager.cpp index 5544060d22f2..38eff6fb94db 100644 --- a/dom/events/EventStateManager.cpp +++ b/dom/events/EventStateManager.cpp @@ -473,6 +473,14 @@ EventStateManager::OnStopObservingContent( mIMEContentObserver = nullptr; } +void +EventStateManager::TryToFlushPendingNotificationsToIME() +{ + if (mIMEContentObserver) { + mIMEContentObserver->TryToFlushPendingNotifications(); + } +} + nsresult EventStateManager::PreHandleEvent(nsPresContext* aPresContext, WidgetEvent* aEvent, diff --git a/dom/events/EventStateManager.h b/dom/events/EventStateManager.h index 080c9637c668..8fac0ee8218c 100644 --- a/dom/events/EventStateManager.h +++ b/dom/events/EventStateManager.h @@ -146,6 +146,12 @@ public: void OnStartToObserveContent(IMEContentObserver* aIMEContentObserver); void OnStopObservingContent(IMEContentObserver* aIMEContentObserver); + /** + * TryToFlushPendingNotificationsToIME() suggests flushing pending + * notifications to IME to IMEContentObserver. + */ + void TryToFlushPendingNotificationsToIME(); + /** * Register accesskey on the given element. When accesskey is activated then * the element will be notified via nsIContent::PerformAccesskey() method. diff --git a/dom/events/IMEContentObserver.cpp b/dom/events/IMEContentObserver.cpp index 79c5c4e9a93c..570717277025 100644 --- a/dom/events/IMEContentObserver.cpp +++ b/dom/events/IMEContentObserver.cpp @@ -202,7 +202,6 @@ IMEContentObserver::IMEContentObserver() , mNeedsToNotifyIMEOfTextChange(false) , mNeedsToNotifyIMEOfSelectionChange(false) , mNeedsToNotifyIMEOfPositionChange(false) - , mIsFlushingPendingNotifications(false) , mIsHandlingQueryContentEvent(false) { #ifdef DEBUG @@ -251,26 +250,56 @@ IMEContentObserver::Init(nsIWidget* aWidget, } if (firstInitialization) { + // Now, try to send NOTIFY_IME_OF_FOCUS to IME via the widget. MaybeNotifyIMEOfFocusSet(); - - // While Init() notifies IME of focus, pending layout may be flushed - // because the notification may cause querying content. Then, recursive - // call of Init() with the latest content may be occur. In such case, we - // shouldn't keep first initialization. - if (GetState() != eState_Initializing) { - return; - } - - // NOTIFY_IME_OF_FOCUS might cause recreating IMEContentObserver - // instance via IMEStateManager::UpdateIMEState(). So, this - // instance might already have been destroyed, check it. - if (!mRootContent) { - return; - } + // When this is called first time, IME has not received NOTIFY_IME_OF_FOCUS + // yet since NOTIFY_IME_OF_FOCUS will be sent to widget asynchronously. + // So, we need to do nothing here. After NOTIFY_IME_OF_FOCUS has been + // sent, OnIMEReceivedFocus() will be called and content, selection and/or + // position changes will be observed + return; } + // When this is called after editor reframing (i.e., the root editable node + // is also recreated), IME has usually received NOTIFY_IME_OF_FOCUS. In this + // case, we need to restart to observe content, selection and/or position + // changes in new root editable node. ObserveEditableNode(); + if (!NeedsToNotifyIMEOfSomething()) { + return; + } + + // Some change events may wait to notify IME because this was being + // initialized. It is the time to flush them. + FlushMergeableNotifications(); +} + +void +IMEContentObserver::OnIMEReceivedFocus() +{ + // While Init() notifies IME of focus, pending layout may be flushed + // because the notification may cause querying content. Then, recursive + // call of Init() with the latest content may occur. In such case, we + // shouldn't keep first initialization which notified IME of focus. + if (GetState() != eState_Initializing) { + return; + } + + // NOTIFY_IME_OF_FOCUS might cause recreating IMEContentObserver + // instance via IMEStateManager::UpdateIMEState(). So, this + // instance might already have been destroyed, check it. + if (!mRootContent) { + return; + } + + // Start to observe which is needed by IME when IME actually has focus. + ObserveEditableNode(); + + if (!NeedsToNotifyIMEOfSomething()) { + return; + } + // Some change events may wait to notify IME because this was being // initialized. It is the time to flush them. FlushMergeableNotifications(); @@ -404,6 +433,18 @@ IMEContentObserver::ObserveEditableNode() MOZ_RELEASE_ASSERT(mRootContent); MOZ_RELEASE_ASSERT(GetState() != eState_Observing); + // If this is called before sending NOTIFY_IME_OF_FOCUS (it's possible when + // the editor is reframed before sending NOTIFY_IME_OF_FOCUS asynchronously), + // the update preference of mWidget may be different from after the widget + // receives NOTIFY_IME_OF_FOCUS. So, this should be called again by + // OnIMEReceivedFocus() which is called after sending NOTIFY_IME_OF_FOCUS. + if (!mIMEHasFocus) { + MOZ_ASSERT(!mWidget || mNeedsToNotifyIMEOfFocusSet || + mSendingNotification == NOTIFY_IME_OF_FOCUS, + "Wow, OnIMEReceivedFocus() won't be called?"); + return; + } + mIsObserving = true; if (mEditor) { mEditor->AddEditorObserver(this); @@ -668,9 +709,14 @@ nsresult IMEContentObserver::HandleQueryContentEvent(WidgetQueryContentEvent* aEvent) { // If the instance has cache, it should use the cached selection which was - // sent to the widget. + // sent to the widget. However, if this instance has already received new + // selection change notification but hasn't updated the cache yet (i.e., + // not sending selection change notification to IME, don't use the cached + // value. Note that don't update selection cache here since if you update + // selection cache here, IMENotificationSender won't notify IME of selection + // change because it looks like that the selection isn't actually changed. if (aEvent->mMessage == eQuerySelectedText && aEvent->mUseNativeLineBreak && - mSelectionData.IsValid()) { + mSelectionData.IsValid() && !mNeedsToNotifyIMEOfSelectionChange) { aEvent->mReply.mContentsRoot = mRootContent; aEvent->mReply.mHasSelection = !mSelectionData.IsCollapsed(); aEvent->mReply.mOffset = mSelectionData.mOffset; @@ -1314,7 +1360,7 @@ IMEContentObserver::FlushMergeableNotifications() // event. Then, it causes flushing layout which may cause another layout // change notification. - if (mIsFlushingPendingNotifications) { + if (mQueuedSender) { // So, if this is already called, this should do nothing. MOZ_LOG(sIMECOLog, LogLevel::Debug, ("IMECO: 0x%p IMEContentObserver::FlushMergeableNotifications(), " @@ -1336,14 +1382,32 @@ IMEContentObserver::FlushMergeableNotifications() ("IMECO: 0x%p IMEContentObserver::FlushMergeableNotifications(), " "creating IMENotificationSender...", this)); - mIsFlushingPendingNotifications = true; - nsContentUtils::AddScriptRunner(new IMENotificationSender(this)); + // If contents in selection range is modified, the selection range still + // has removed node from the tree. In such case, nsContentIterator won't + // work well. Therefore, we shouldn't use AddScriptRunnder() here since + // it may kick runnable event immediately after DOM tree is changed but + // the selection range isn't modified yet. + mQueuedSender = new IMENotificationSender(this); + NS_DispatchToCurrentThread(mQueuedSender); MOZ_LOG(sIMECOLog, LogLevel::Debug, ("IMECO: 0x%p IMEContentObserver::FlushMergeableNotifications(), " "finished", this)); } +void +IMEContentObserver::TryToFlushPendingNotifications() +{ + if (!mQueuedSender || mSendingNotification != NOTIFY_IME_OF_NOTHING) { + return; + } + + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("IMECO: 0x%p IMEContentObserver::TryToFlushPendingNotifications(), " + "performing queued IMENotificationSender forcibly", this)); + mQueuedSender->Run(); +} + /****************************************************************************** * mozilla::IMEContentObserver::AChangeEvent ******************************************************************************/ @@ -1410,7 +1474,20 @@ IMEContentObserver::AChangeEvent::IsSafeToNotifyIME( NS_IMETHODIMP IMEContentObserver::IMENotificationSender::Run() { - MOZ_ASSERT(mIMEContentObserver->mIsFlushingPendingNotifications); + if (NS_WARN_IF(mIsRunning)) { + MOZ_LOG(sIMECOLog, LogLevel::Error, + ("IMECO: 0x%p IMEContentObserver::IMENotificationSender::Run(), FAILED, " + "called recursively", this)); + return NS_OK; + } + + AutoRestore running(mIsRunning); + mIsRunning = true; + + // This instance was already performed forcibly. + if (mIMEContentObserver->mQueuedSender != this) { + return NS_OK; + } // NOTE: Reset each pending flag because sending notification may cause // another change. @@ -1418,10 +1495,25 @@ IMEContentObserver::IMENotificationSender::Run() if (mIMEContentObserver->mNeedsToNotifyIMEOfFocusSet) { mIMEContentObserver->mNeedsToNotifyIMEOfFocusSet = false; SendFocusSet(); + mIMEContentObserver->mQueuedSender = nullptr; + // If it's not safe to notify IME of focus, SendFocusSet() sets + // mNeedsToNotifyIMEOfFocusSet true again. For guaranteeing to send the + // focus notification later, we should put a new sender into the queue but + // this case must be rare. Note that if mIMEContentObserver is already + // destroyed, mNeedsToNotifyIMEOfFocusSet is never set true again. + if (mIMEContentObserver->mNeedsToNotifyIMEOfFocusSet) { + MOZ_ASSERT(!mIMEContentObserver->mIMEHasFocus); + MOZ_LOG(sIMECOLog, LogLevel::Debug, + ("IMECO: 0x%p IMEContentObserver::IMENotificationSender::Run(), " + "posting IMENotificationSender to current thread", this)); + mIMEContentObserver->mQueuedSender = + new IMENotificationSender(mIMEContentObserver); + NS_DispatchToCurrentThread(mIMEContentObserver->mQueuedSender); + return NS_OK; + } // This is the first notification to IME. So, we don't need to notify // anymore since IME starts to query content after it gets focus. mIMEContentObserver->ClearPendingNotifications(); - mIMEContentObserver->mIsFlushingPendingNotifications = false; return NS_OK; } @@ -1454,16 +1546,16 @@ IMEContentObserver::IMENotificationSender::Run() } } + mIMEContentObserver->mQueuedSender = nullptr; + // If notifications caused some new change, we should notify them now. - mIMEContentObserver->mIsFlushingPendingNotifications = - mIMEContentObserver->NeedsToNotifyIMEOfSomething(); - if (mIMEContentObserver->mIsFlushingPendingNotifications) { + if (mIMEContentObserver->NeedsToNotifyIMEOfSomething()) { MOZ_LOG(sIMECOLog, LogLevel::Debug, ("IMECO: 0x%p IMEContentObserver::IMENotificationSender::Run(), " - "posting AsyncMergeableNotificationsFlusher to current thread", this)); - RefPtr asyncFlusher = - new AsyncMergeableNotificationsFlusher(mIMEContentObserver); - NS_DispatchToCurrentThread(asyncFlusher); + "posting IMENotificationSender to current thread", this)); + mIMEContentObserver->mQueuedSender = + new IMENotificationSender(mIMEContentObserver); + NS_DispatchToCurrentThread(mIMEContentObserver->mQueuedSender); } return NS_OK; } @@ -1505,6 +1597,12 @@ IMEContentObserver::IMENotificationSender::SendFocusSet() mIMEContentObserver->mWidget); mIMEContentObserver->mSendingNotification = NOTIFY_IME_OF_NOTHING; + // nsIMEUpdatePreference referred by ObserveEditableNode() may be different + // before or after widget receives NOTIFY_IME_OF_FOCUS. Therefore, we need + // to guarantee to call ObserveEditableNode() after sending + // NOTIFY_IME_OF_FOCUS. + mIMEContentObserver->OnIMEReceivedFocus(); + MOZ_LOG(sIMECOLog, LogLevel::Debug, ("IMECO: 0x%p IMEContentObserver::IMENotificationSender::" "SendFocusSet(), sent NOTIFY_IME_OF_FOCUS", this)); @@ -1668,31 +1766,4 @@ IMEContentObserver::IMENotificationSender::SendPositionChange() "SendPositionChange(), sent NOTIFY_IME_OF_POSITION_CHANGE", this)); } -/****************************************************************************** - * mozilla::IMEContentObserver::AsyncMergeableNotificationsFlusher - ******************************************************************************/ - -NS_IMETHODIMP -IMEContentObserver::AsyncMergeableNotificationsFlusher::Run() -{ - if (!CanNotifyIME(eChangeEventType_FlushPendingEvents)) { - MOZ_LOG(sIMECOLog, LogLevel::Debug, - ("IMECO: 0x%p IMEContentObserver::AsyncMergeableNotificationsFlusher::" - "Run(), FAILED, due to impossible to flush pending notifications", - this)); - return NS_OK; - } - - MOZ_LOG(sIMECOLog, LogLevel::Info, - ("IMECO: 0x%p IMEContentObserver::AsyncMergeableNotificationsFlusher::" - "Run(), calling FlushMergeableNotifications()...", this)); - - mIMEContentObserver->FlushMergeableNotifications(); - - MOZ_LOG(sIMECOLog, LogLevel::Debug, - ("IMECO: 0x%p IMEContentObserver::AsyncMergeableNotificationsFlusher::" - "Run(), called FlushMergeableNotifications()", this)); - return NS_OK; -} - } // namespace mozilla diff --git a/dom/events/IMEContentObserver.h b/dom/events/IMEContentObserver.h index 785b73d7c1ca..e9bae8b134d1 100644 --- a/dom/events/IMEContentObserver.h +++ b/dom/events/IMEContentObserver.h @@ -103,6 +103,12 @@ public: nsresult GetSelectionAndRoot(nsISelection** aSelection, nsIContent** aRoot) const; + /** + * TryToFlushPendingNotifications() should be called when pending events + * should be flushed. This tries to run the queued IMENotificationSender. + */ + void TryToFlushPendingNotifications(); + private: ~IMEContentObserver() {} @@ -117,6 +123,7 @@ private: nsIEditor* aEditor); bool InitWithPlugin(nsPresContext* aPresContext, nsIContent* aContent); bool IsInitializedWithPlugin() const { return !mEditor; } + void OnIMEReceivedFocus(); void Clear(); bool IsObservingContent(nsPresContext* aPresContext, nsIContent* aContent) const; @@ -182,6 +189,63 @@ private: nsCOMPtr mDocShell; nsCOMPtr mEditor; + /** + * Helper classes to notify IME. + */ + + class AChangeEvent: public nsRunnable + { + protected: + enum ChangeEventType + { + eChangeEventType_Focus, + eChangeEventType_Selection, + eChangeEventType_Text, + eChangeEventType_Position, + eChangeEventType_FlushPendingEvents + }; + + explicit AChangeEvent(IMEContentObserver* aIMEContentObserver) + : mIMEContentObserver(aIMEContentObserver) + { + MOZ_ASSERT(mIMEContentObserver); + } + + RefPtr mIMEContentObserver; + + /** + * CanNotifyIME() checks if mIMEContentObserver can and should notify IME. + */ + bool CanNotifyIME(ChangeEventType aChangeEventType) const; + + /** + * IsSafeToNotifyIME() checks if it's safe to noitify IME. + */ + bool IsSafeToNotifyIME(ChangeEventType aChangeEventType) const; + }; + + class IMENotificationSender: public AChangeEvent + { + public: + explicit IMENotificationSender(IMEContentObserver* aIMEContentObserver) + : AChangeEvent(aIMEContentObserver) + , mIsRunning(false) + { + } + NS_IMETHOD Run() override; + + private: + void SendFocusSet(); + void SendSelectionChange(); + void SendTextChange(); + void SendPositionChange(); + + bool mIsRunning; + }; + + // mQueuedSender is, it was put into the event queue but not run yet. + RefPtr mQueuedSender; + /** * FlatTextCache stores flat text length from start of the content to * mNodeOffset of mContainerNode. @@ -261,76 +325,9 @@ private: bool mNeedsToNotifyIMEOfTextChange; bool mNeedsToNotifyIMEOfSelectionChange; bool mNeedsToNotifyIMEOfPositionChange; - // mIsFlushingPendingNotifications is true between - // FlushMergeableNotifications() creates IMENotificationSender and - // IMENotificationSender sent all pending notifications. - bool mIsFlushingPendingNotifications; // mIsHandlingQueryContentEvent is true when IMEContentObserver is handling // WidgetQueryContentEvent with ContentEventHandler. bool mIsHandlingQueryContentEvent; - - - /** - * Helper classes to notify IME. - */ - - class AChangeEvent: public nsRunnable - { - protected: - enum ChangeEventType - { - eChangeEventType_Focus, - eChangeEventType_Selection, - eChangeEventType_Text, - eChangeEventType_Position, - eChangeEventType_FlushPendingEvents - }; - - explicit AChangeEvent(IMEContentObserver* aIMEContentObserver) - : mIMEContentObserver(aIMEContentObserver) - { - MOZ_ASSERT(mIMEContentObserver); - } - - RefPtr mIMEContentObserver; - - /** - * CanNotifyIME() checks if mIMEContentObserver can and should notify IME. - */ - bool CanNotifyIME(ChangeEventType aChangeEventType) const; - - /** - * IsSafeToNotifyIME() checks if it's safe to noitify IME. - */ - bool IsSafeToNotifyIME(ChangeEventType aChangeEventType) const; - }; - - class IMENotificationSender: public AChangeEvent - { - public: - explicit IMENotificationSender(IMEContentObserver* aIMEContentObserver) - : AChangeEvent(aIMEContentObserver) - { - } - NS_IMETHOD Run() override; - - private: - void SendFocusSet(); - void SendSelectionChange(); - void SendTextChange(); - void SendPositionChange(); - }; - - class AsyncMergeableNotificationsFlusher : public AChangeEvent - { - public: - explicit AsyncMergeableNotificationsFlusher( - IMEContentObserver* aIMEContentObserver) - : AChangeEvent(aIMEContentObserver) - { - } - NS_IMETHOD Run() override; - }; }; } // namespace mozilla diff --git a/dom/events/IMEStateManager.cpp b/dom/events/IMEStateManager.cpp index dda5a2b5c668..8f22b38e9a09 100644 --- a/dom/events/IMEStateManager.cpp +++ b/dom/events/IMEStateManager.cpp @@ -515,6 +515,13 @@ IMEStateManager::OnChangeFocusInternal(nsPresContext* aPresContext, // editor. if (newState.mEnabled == IMEState::PLUGIN) { CreateIMEContentObserver(nullptr); + if (sActiveIMEContentObserver) { + MOZ_LOG(sISMLog, LogLevel::Debug, + ("ISM: IMEStateManager::OnChangeFocusInternal(), an " + "IMEContentObserver instance is created for plugin and trying to " + "flush its pending notifications...")); + sActiveIMEContentObserver->TryToFlushPendingNotifications(); + } } return NS_OK; @@ -684,6 +691,14 @@ IMEStateManager::OnFocusInEditor(nsPresContext* aPresContext, } CreateIMEContentObserver(aEditor); + + // Let's flush the focus notification now. + if (sActiveIMEContentObserver) { + MOZ_LOG(sISMLog, LogLevel::Debug, + ("ISM: IMEStateManager::OnFocusInEditor(), new IMEContentObserver is " + "created, trying to flush pending notifications...")); + sActiveIMEContentObserver->TryToFlushPendingNotifications(); + } } // static @@ -797,6 +812,9 @@ IMEStateManager::UpdateIMEState(const IMEState& aNewIMEState, } if (createTextStateManager) { + // XXX In this case, it might not be enough safe to notify IME of anything. + // So, don't try to flush pending notifications of IMEContentObserver + // here. CreateIMEContentObserver(aEditor); } } diff --git a/dom/media/VideoUtils.h b/dom/media/VideoUtils.h index 1d542b41a9b5..bb358a2da9ab 100644 --- a/dom/media/VideoUtils.h +++ b/dom/media/VideoUtils.h @@ -154,9 +154,9 @@ nsresult SecondsToUsecs(double aSeconds, int64_t& aOutUsecs); // The maximum height and width of the video. Used for // sanitizing the memory allocation of the RGB buffer. // The maximum resolution we anticipate encountering in the -// wild is 2160p - 3840x2160 pixels. -static const int32_t MAX_VIDEO_WIDTH = 4000; -static const int32_t MAX_VIDEO_HEIGHT = 3000; +// wild is 2160p (UHD "4K") or 4320p - 7680x4320 pixels for VR. +static const int32_t MAX_VIDEO_WIDTH = 8192; +static const int32_t MAX_VIDEO_HEIGHT = 4608; // Scales the display rect aDisplay by aspect ratio aAspectRatio. // Note that aDisplay must be validated by IsValidVideoRegion() diff --git a/dom/media/eme/CDMProxy.cpp b/dom/media/eme/CDMProxy.cpp index 8b56a8696a2f..d2a0c42493e6 100644 --- a/dom/media/eme/CDMProxy.cpp +++ b/dom/media/eme/CDMProxy.cpp @@ -570,7 +570,10 @@ CDMProxy::OnExpirationChange(const nsAString& aSessionId, GMPTimestamp aExpiryTime) { MOZ_ASSERT(NS_IsMainThread()); - NS_WARNING("CDMProxy::OnExpirationChange() not implemented"); + RefPtr session(mKeys->GetSession(aSessionId)); + if (session) { + session->SetExpiration(static_cast(aExpiryTime)); + } } void diff --git a/dom/media/eme/MediaKeySession.cpp b/dom/media/eme/MediaKeySession.cpp index 7dfe64751b27..8c31c00c726a 100644 --- a/dom/media/eme/MediaKeySession.cpp +++ b/dom/media/eme/MediaKeySession.cpp @@ -55,6 +55,7 @@ MediaKeySession::MediaKeySession(JSContext* aCx, , mIsClosed(false) , mUninitialized(true) , mKeyStatusMap(new MediaKeyStatusMap(aCx, aParent, aRv)) + , mExpiration(JS::GenericNaN()) { EME_LOG("MediaKeySession[%p,''] session Id set", this); @@ -114,7 +115,7 @@ MediaKeySession::WrapObject(JSContext* aCx, JS::Handle aGivenProto) double MediaKeySession::Expiration() const { - return JS::GenericNaN(); + return mExpiration; } Promise* @@ -263,6 +264,16 @@ MediaKeySession::Update(const ArrayBufferViewOrArrayBuffer& aResponse, ErrorResu if (aRv.Failed()) { return nullptr; } + + if (!IsCallable()) { + // If this object's callable value is false, return a promise rejected + // with a new DOMException whose name is InvalidStateError. + EME_LOG("MediaKeySession[%p,''] Update() called before sessionId set by CDM", this); + promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR, + NS_LITERAL_CSTRING("MediaKeySession.Update() called before sessionId set by CDM")); + return promise.forget(); + } + nsTArray data; if (IsClosed() || !mKeys->GetCDMProxy()) { promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR, @@ -308,6 +319,14 @@ MediaKeySession::Close(ErrorResult& aRv) if (aRv.Failed()) { return nullptr; } + if (!IsCallable()) { + // If this object's callable value is false, return a promise rejected + // with a new DOMException whose name is InvalidStateError. + EME_LOG("MediaKeySession[%p,''] Close() called before sessionId set by CDM", this); + promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR, + NS_LITERAL_CSTRING("MediaKeySession.Close() called before sessionId set by CDM")); + return promise.forget(); + } if (IsClosed() || !mKeys->GetCDMProxy()) { EME_LOG("MediaKeySession[%p,'%s'] Close() already closed", this, NS_ConvertUTF16toUTF8(mSessionId).get()); @@ -351,6 +370,14 @@ MediaKeySession::Remove(ErrorResult& aRv) if (aRv.Failed()) { return nullptr; } + if (!IsCallable()) { + // If this object's callable value is false, return a promise rejected + // with a new DOMException whose name is InvalidStateError. + EME_LOG("MediaKeySession[%p,''] Remove() called before sessionId set by CDM", this); + promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR, + NS_LITERAL_CSTRING("MediaKeySession.Remove() called before sessionId set by CDM")); + return promise.forget(); + } if (mSessionType != SessionType::Persistent) { promise->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR, NS_LITERAL_CSTRING("Calling MediaKeySession.remove() on non-persistent session")); @@ -437,5 +464,15 @@ MediaKeySession::MakePromise(ErrorResult& aRv, const nsACString& aName) return DetailedPromise::Create(global, aRv, aName); } +void +MediaKeySession::SetExpiration(double aExpiration) +{ + EME_LOG("MediaKeySession[%p,'%s'] SetExpiry(%lf)", + this, + NS_ConvertUTF16toUTF8(mSessionId).get(), + aExpiration); + mExpiration = aExpiration; +} + } // namespace dom } // namespace mozilla diff --git a/dom/media/eme/MediaKeySession.h b/dom/media/eme/MediaKeySession.h index 670910a0f8ae..b0b981799b62 100644 --- a/dom/media/eme/MediaKeySession.h +++ b/dom/media/eme/MediaKeySession.h @@ -92,6 +92,8 @@ public: bool IsClosed() const; + void SetExpiration(double aExpiry); + // Process-unique identifier. uint32_t Token() const; @@ -99,6 +101,14 @@ private: ~MediaKeySession(); void UpdateKeyStatusMap(); + + bool IsCallable() const { + // The EME spec sets the "callable value" to true whenever the CDM sets + // the sessionId. When the session is initialized, sessionId is empty and + // callable is thus false. + return !mSessionId.IsEmpty(); + } + already_AddRefed MakePromise(ErrorResult& aRv, const nsACString& aName); @@ -114,6 +124,7 @@ private: bool mIsClosed; bool mUninitialized; RefPtr mKeyStatusMap; + double mExpiration; }; } // namespace dom diff --git a/dom/media/test/mochitest.ini b/dom/media/test/mochitest.ini index c8a314e61904..b72caa903a64 100644 --- a/dom/media/test/mochitest.ini +++ b/dom/media/test/mochitest.ini @@ -616,6 +616,7 @@ skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439 skip-if = buildapp == 'b2g' && toolkit != 'gonk' # bug 1082984 [test_dormant_playback.html] skip-if = (os == 'win' && os_version == '5.1') || (os != 'win' && toolkit != 'gonk') +[test_eme_session_callable_value.html] [test_eme_canvas_blocked.html] skip-if = toolkit == 'android' # bug 1149374 [test_eme_non_mse_fails.html] diff --git a/dom/media/test/test_eme_session_callable_value.html b/dom/media/test/test_eme_session_callable_value.html new file mode 100644 index 000000000000..9ec23c1fe63a --- /dev/null +++ b/dom/media/test/test_eme_session_callable_value.html @@ -0,0 +1,35 @@ + + + + Test Encrypted Media Extensions + + + + + + +
+
+
+ + diff --git a/dom/media/webvtt/update-webvtt.js b/dom/media/webvtt/update-webvtt.js index 2fb2045b68cf..a1c82a1388ce 100644 --- a/dom/media/webvtt/update-webvtt.js +++ b/dom/media/webvtt/update-webvtt.js @@ -9,6 +9,11 @@ var gift = require('gift'), alias: 'dir', describe: 'Path to WebVTT directory.' }) + .options('r', { + alias: 'rev', + describe: 'Revision to update to.', + default: 'master' + }) .options('w', { alias: 'write', describe: 'Path to file to write to.', @@ -22,8 +27,8 @@ repo.status(function(err, status) { console.log("The repository's working directory is not clean. Aborting."); process.exit(1); } - repo.checkout("master", function() { - repo.commits("master", 1, function(err, commits) { + repo.checkout(argv.r, function() { + repo.commits(argv.r, 1, function(err, commits) { var vttjs = fs.readFileSync(argv.d + "/lib/vtt.js", 'utf8'); // Remove settings for VIM and Emacs. diff --git a/dom/media/webvtt/vtt.jsm b/dom/media/webvtt/vtt.jsm index 43fc7bd4de0d..b857afa0a5ec 100644 --- a/dom/media/webvtt/vtt.jsm +++ b/dom/media/webvtt/vtt.jsm @@ -8,7 +8,7 @@ this.EXPORTED_SYMBOLS = ["WebVTT"]; * Code below is vtt.js the JS WebVTT implementation. * Current source code can be found at http://github.com/mozilla/vtt.js * - * Code taken from commit f5a1a60775a615cd9670d6cdaedddf2c6f25fae3 + * Code taken from commit 364c6b951a07306848a706d1d03c2a6ae942517d */ /** * Copyright 2013 vtt.js Contributors @@ -726,38 +726,60 @@ this.EXPORTED_SYMBOLS = ["WebVTT"]; // Constructs the computed display state of the cue (a div). Places the div // into the overlay which should be a block level element (usually a div). function CueStyleBox(window, cue, styleOptions) { + var isIE8 = (/MSIE\s8\.0/).test(navigator.userAgent); + var color = "rgba(255, 255, 255, 1)"; + var backgroundColor = "rgba(0, 0, 0, 0.8)"; + + if (isIE8) { + color = "rgb(255, 255, 255)"; + backgroundColor = "rgb(0, 0, 0)"; + } + StyleBox.call(this); this.cue = cue; // Parse our cue's text into a DOM tree rooted at 'cueDiv'. This div will // have inline positioning and will function as the cue background box. this.cueDiv = parseContent(window, cue.text); - this.applyStyles({ - color: "rgba(255, 255, 255, 1)", - backgroundColor: "rgba(0, 0, 0, 0.8)", + var styles = { + color: color, + backgroundColor: backgroundColor, position: "relative", left: 0, right: 0, top: 0, bottom: 0, display: "inline" - }, this.cueDiv); + }; + + if (!isIE8) { + styles.writingMode = cue.vertical === "" ? "horizontal-tb" + : cue.vertical === "lr" ? "vertical-lr" + : "vertical-rl"; + styles.unicodeBidi = "plaintext"; + } + this.applyStyles(styles, this.cueDiv); // Create an absolutely positioned div that will be used to position the cue // div. Note, all WebVTT cue-setting alignments are equivalent to the CSS // mirrors of them except "middle" which is "center" in CSS. this.div = window.document.createElement("div"); - this.applyStyles({ + styles = { textAlign: cue.align === "middle" ? "center" : cue.align, - direction: determineBidi(this.cueDiv), - writingMode: cue.vertical === "" ? "horizontal-tb" - : cue.vertical === "lr" ? "vertical-lr" - : "vertical-rl", - unicodeBidi: "plaintext", font: styleOptions.font, whiteSpace: "pre-line", position: "absolute" - }); + }; + + if (!isIE8) { + styles.direction = determineBidi(this.cueDiv); + styles.writingMode = cue.vertical === "" ? "horizontal-tb" + : cue.vertical === "lr" ? "vertical-lr" + : "vertical-rl". + stylesunicodeBidi = "plaintext"; + } + + this.applyStyles(styles); this.div.appendChild(this.cueDiv); @@ -783,7 +805,7 @@ this.EXPORTED_SYMBOLS = ["WebVTT"]; if (cue.vertical === "") { this.applyStyles({ left: this.formatStyle(textPos, "%"), - width: this.formatStyle(cue.size, "%"), + width: this.formatStyle(cue.size, "%") }); // Vertical box orientation; textPos is the distance from the top edge of the // area to the top edge of the box and cue.size is the height extending @@ -802,7 +824,7 @@ this.EXPORTED_SYMBOLS = ["WebVTT"]; left: this.formatStyle(box.left, "px"), right: this.formatStyle(box.right, "px"), height: this.formatStyle(box.height, "px"), - width: this.formatStyle(box.width, "px"), + width: this.formatStyle(box.width, "px") }); }; } @@ -813,12 +835,18 @@ this.EXPORTED_SYMBOLS = ["WebVTT"]; // compute things with such as if it overlaps or intersects with another Element. // Can initialize it with either a StyleBox or another BoxPosition. function BoxPosition(obj) { + var isIE8 = (/MSIE\s8\.0/).test(navigator.userAgent); + // Either a BoxPosition was passed in and we need to copy it, or a StyleBox // was passed in and we need to copy the results of 'getBoundingClientRect' // as the object returned is readonly. All co-ordinate values are in reference // to the viewport origin (top left). - var lh; + var lh, height, width, top; if (obj.div) { + height = obj.div.offsetHeight; + width = obj.div.offsetWidth; + top = obj.div.offsetTop; + var rects = (rects = obj.div.childNodes) && (rects = rects[0]) && rects.getClientRects && rects.getClientRects(); obj = obj.div.getBoundingClientRect(); @@ -828,14 +856,19 @@ this.EXPORTED_SYMBOLS = ["WebVTT"]; // result in the desired behaviour. lh = rects ? Math.max((rects[0] && rects[0].height) || 0, obj.height / rects.length) : 0; + } this.left = obj.left; this.right = obj.right; - this.top = obj.top; - this.height = obj.height; - this.bottom = obj.bottom; - this.width = obj.width; + this.top = obj.top || top; + this.height = obj.height || height; + this.bottom = obj.bottom || (top + (obj.height || height)); + this.width = obj.width || width; this.lineHeight = lh !== undefined ? lh : obj.lineHeight; + + if (isIE8 && !this.lineHeight) { + this.lineHeight = 13; + } } // Move the box along a particular axis. Optionally pass in an amount to move @@ -933,16 +966,21 @@ this.EXPORTED_SYMBOLS = ["WebVTT"]; // Get an object that represents the box's position without anything extra. // Can pass a StyleBox, HTMLElement, or another BoxPositon. BoxPosition.getSimpleBoxPosition = function(obj) { + var height = obj.div ? obj.div.offsetHeight : obj.tagName ? obj.offsetHeight : 0; + var width = obj.div ? obj.div.offsetWidth : obj.tagName ? obj.offsetWidth : 0; + var top = obj.div ? obj.div.offsetTop : obj.tagName ? obj.offsetTop : 0; + obj = obj.div ? obj.div.getBoundingClientRect() : obj.tagName ? obj.getBoundingClientRect() : obj; - return { + var ret = { left: obj.left, right: obj.right, - top: obj.top, - height: obj.height, - bottom: obj.bottom, - width: obj.width + top: obj.top || top, + height: obj.height || height, + bottom: obj.bottom || (top + (obj.height || height)), + width: obj.width || width }; + return ret; }; // Move a StyleBox to its specified, or next best, position. The containerBox @@ -1141,9 +1179,9 @@ this.EXPORTED_SYMBOLS = ["WebVTT"]; // We don't need to recompute the cues' display states. Just reuse them. if (!shouldCompute(cues)) { - cues.forEach(function(cue) { - paddedOverlay.appendChild(cue.displayState); - }); + for (var i = 0; i < cues.length; i++) { + paddedOverlay.appendChild(cues[i].displayState); + } return; } @@ -1154,20 +1192,26 @@ this.EXPORTED_SYMBOLS = ["WebVTT"]; font: fontSize + "px " + FONT_STYLE }; - cues.forEach(function(cue) { - // Compute the intial position and styles of the cue div. - var styleBox = new CueStyleBox(window, cue, styleOptions); - paddedOverlay.appendChild(styleBox.div); + (function() { + var styleBox, cue; - // Move the cue div to it's correct line position. - moveBoxToLinePosition(window, styleBox, containerBox, boxPositions); + for (var i = 0; i < cues.length; i++) { + cue = cues[i]; - // Remember the computed div so that we don't have to recompute it later - // if we don't have too. - cue.displayState = styleBox.div; + // Compute the intial position and styles of the cue div. + styleBox = new CueStyleBox(window, cue, styleOptions); + paddedOverlay.appendChild(styleBox.div); - boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox)); - }); + // Move the cue div to it's correct line position. + moveBoxToLinePosition(window, styleBox, containerBox, boxPositions); + + // Remember the computed div so that we don't have to recompute it later + // if we don't have too. + cue.displayState = styleBox.div; + + boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox)); + } + })(); }; WebVTT.Parser = function(window, decoder) { diff --git a/dom/push/Push.js b/dom/push/Push.js index d85611e1805b..8d1b3f625bcb 100644 --- a/dom/push/Push.js +++ b/dom/push/Push.js @@ -4,14 +4,6 @@ "use strict"; -// Don't modify this, instead set dom.push.debug. -var gDebuggingEnabled = false; - -function debug(s) { - if (gDebuggingEnabled) - dump("-*- Push.js: " + s + "\n"); -} - const Cc = Components.classes; const Ci = Components.interfaces; const Cu = Components.utils; @@ -21,6 +13,14 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/DOMRequestHelper.jsm"); +XPCOMUtils.defineLazyGetter(this, "console", () => { + let {ConsoleAPI} = Cu.import("resource://gre/modules/Console.jsm", {}); + return new ConsoleAPI({ + maxLogLevelPref: "dom.push.loglevel", + prefix: "Push", + }); +}); + const PUSH_CID = Components.ID("{cde1d019-fad8-4044-b141-65fb4fb7a245}"); /** @@ -29,7 +29,7 @@ const PUSH_CID = Components.ID("{cde1d019-fad8-4044-b141-65fb4fb7a245}"); * one actually performing all operations. */ function Push() { - debug("Push Constructor"); + console.debug("Push()"); } Push.prototype = { @@ -44,11 +44,7 @@ Push.prototype = { Ci.nsIObserver]), init: function(aWindow) { - // Set debug first so that all debugging actually works. - // NOTE: We don't add an observer here like in PushService. Flipping the - // pref will require a reload of the app/page, which seems acceptable. - gDebuggingEnabled = Services.prefs.getBoolPref("dom.push.debug"); - debug("init()"); + console.debug("init()"); this._window = aWindow; @@ -60,12 +56,12 @@ Push.prototype = { }, setScope: function(scope){ - debug('setScope ' + scope); + console.debug("setScope()", scope); this._scope = scope; }, askPermission: function (aAllowCallback, aCancelCallback) { - debug("askPermission"); + console.debug("askPermission()"); return this.createPromise((resolve, reject) => { let permissionDenied = () => { @@ -94,7 +90,7 @@ Push.prototype = { }, subscribe: function() { - debug("subscribe()"); + console.debug("subscribe()", this._scope); let histogram = Services.telemetry.getHistogramById("PUSH_API_USED"); histogram.add(true); @@ -107,7 +103,7 @@ Push.prototype = { }, getSubscription: function() { - debug("getSubscription()" + this._scope); + console.debug("getSubscription()", this._scope); return this.createPromise((resolve, reject) => { let callback = new PushEndpointCallback(this, resolve, reject); @@ -116,7 +112,7 @@ Push.prototype = { }, permissionState: function() { - debug("permissionState()" + this._scope); + console.debug("permissionState()", this._scope); return this.createPromise((resolve, reject) => { let permission = Ci.nsIPermissionManager.UNKNOWN_ACTION; diff --git a/dom/push/PushClient.js b/dom/push/PushClient.js index e630efcf129e..372704213652 100644 --- a/dom/push/PushClient.js +++ b/dom/push/PushClient.js @@ -4,14 +4,6 @@ "use strict"; -// Don't modify this, instead set dom.push.debug. -var gDebuggingEnabled = false; - -function debug(s) { - if (gDebuggingEnabled) - dump("-*- PushClient.js: " + s + "\n"); -} - const Cc = Components.classes; const Ci = Components.interfaces; const Cu = Components.utils; @@ -20,7 +12,13 @@ const Cr = Components.results; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); -gDebuggingEnabled = Services.prefs.getBoolPref("dom.push.debug"); +XPCOMUtils.defineLazyGetter(this, "console", () => { + let {ConsoleAPI} = Cu.import("resource://gre/modules/Console.jsm", {}); + return new ConsoleAPI({ + maxLogLevelPref: "dom.push.loglevel", + prefix: "PushClient", + }); +}); const kMessages = [ "PushService:Register:OK", @@ -32,7 +30,7 @@ const kMessages = [ ]; this.PushClient = function PushClient() { - debug("PushClient created!"); + console.debug("PushClient()"); this._cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"] .getService(Ci.nsISyncMessageSender); this._requests = {}; @@ -73,7 +71,7 @@ PushClient.prototype = { }, subscribe: function(scope, principal, callback) { - debug("subscribe() " + scope); + console.debug("subscribe()", scope); let requestId = this.addRequest(callback); this._cpmm.sendAsyncMessage("Push:Register", { scope: scope, @@ -82,7 +80,7 @@ PushClient.prototype = { }, unsubscribe: function(scope, principal, callback) { - debug("unsubscribe() " + scope); + console.debug("unsubscribe()", scope); let requestId = this.addRequest(callback); this._cpmm.sendAsyncMessage("Push:Unregister", { scope: scope, @@ -91,9 +89,10 @@ PushClient.prototype = { }, getSubscription: function(scope, principal, callback) { - debug("getSubscription()" + scope); + console.debug("getSubscription()", scope); let requestId = this.addRequest(callback); - debug("Going to send " + scope + " " + principal + " " + requestId); + console.debug("getSubscription: Going to send", scope, principal, + requestId); this._cpmm.sendAsyncMessage("Push:Registration", { scope: scope, requestID: requestId, @@ -101,6 +100,10 @@ PushClient.prototype = { }, _deliverPushEndpoint: function(request, registration) { + if (!registration) { + request.onPushEndpoint(Cr.NS_OK, "", 0, null); + return; + } if (registration.p256dhKey) { let key = new Uint8Array(registration.p256dhKey); request.onPushEndpoint(Cr.NS_OK, @@ -114,38 +117,31 @@ PushClient.prototype = { }, receiveMessage: function(aMessage) { + console.debug("receiveMessage()", aMessage); let json = aMessage.data; let request = this.takeRequest(json.requestID); if (!request) { + console.error("receiveMessage: Unknown request ID", json.requestID); return; } - debug("receiveMessage(): " + JSON.stringify(aMessage)) switch (aMessage.name) { case "PushService:Register:OK": - this._deliverPushEndpoint(request, json); - break; - case "PushService:Register:KO": - request.onPushEndpoint(Cr.NS_ERROR_FAILURE, "", 0, null); - break; case "PushService:Registration:OK": - { - let endpoint = ""; - if (!json.registration) { - request.onPushEndpoint(Cr.NS_OK, "", 0, null); - } else { - this._deliverPushEndpoint(request, json.registration); - } + this._deliverPushEndpoint(request, json.result); break; - } + + case "PushService:Register:KO": case "PushService:Registration:KO": request.onPushEndpoint(Cr.NS_ERROR_FAILURE, "", 0, null); break; + case "PushService:Unregister:OK": if (typeof json.result !== "boolean") { - debug("Expected boolean result from PushService!"); + console.error("receiveMessage: Expected boolean for unregister response", + json.result); request.onUnsubscribe(Cr.NS_ERROR_FAILURE, false); return; } @@ -156,7 +152,7 @@ PushClient.prototype = { request.onUnsubscribe(Cr.NS_ERROR_FAILURE, false); break; default: - debug("NOT IMPLEMENTED! receiveMessage for " + aMessage.name); + console.error("receiveMessage: NOT IMPLEMENTED!", aMessage.name); } }, }; diff --git a/dom/push/PushDB.jsm b/dom/push/PushDB.jsm index 7d241494540c..7139195d6da3 100644 --- a/dom/push/PushDB.jsm +++ b/dom/push/PushDB.jsm @@ -5,26 +5,24 @@ "use strict"; -// Don't modify this, instead set dom.push.debug. -var gDebuggingEnabled = false; - -function debug(s) { - if (gDebuggingEnabled) { - dump("-*- PushDB.jsm: " + s + "\n"); - } -} - const Cu = Components.utils; Cu.import("resource://gre/modules/IndexedDBHelper.jsm"); Cu.import("resource://gre/modules/Preferences.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.importGlobalProperties(["indexedDB"]); -const prefs = new Preferences("dom.push."); - this.EXPORTED_SYMBOLS = ["PushDB"]; +XPCOMUtils.defineLazyGetter(this, "console", () => { + let {ConsoleAPI} = Cu.import("resource://gre/modules/Console.jsm", {}); + return new ConsoleAPI({ + maxLogLevelPref: "dom.push.loglevel", + prefix: "PushDB", + }); +}); + this.PushDB = function PushDB(dbName, dbVersion, dbStoreName, keyPath, model) { - debug("PushDB()"); + console.debug("PushDB()"); this._dbStoreName = dbStoreName; this._keyPath = keyPath; this._model = model; @@ -32,8 +30,6 @@ this.PushDB = function PushDB(dbName, dbVersion, dbStoreName, keyPath, model) { // set the indexeddb database this.initDBHelper(dbName, dbVersion, [dbStoreName]); - gDebuggingEnabled = prefs.get("debug"); - prefs.observe("debug", this); }; this.PushDB.prototype = { @@ -90,7 +86,7 @@ this.PushDB.prototype = { */ put: function(aRecord) { - debug("put()" + JSON.stringify(aRecord)); + console.debug("put()", aRecord); if (!this.isValidRecord(aRecord)) { return Promise.reject(new TypeError( "Scope, originAttributes, and quota are required! " + @@ -107,8 +103,8 @@ this.PushDB.prototype = { aTxn.result = undefined; aStore.put(aRecord).onsuccess = aEvent => { - debug("Request successful. Updated record ID: " + - aEvent.target.result); + console.debug("put: Request successful. Updated record", + aEvent.target.result); aTxn.result = this.toPushRecord(aRecord); }; }, @@ -123,14 +119,14 @@ this.PushDB.prototype = { * The ID of record to be deleted. */ delete: function(aKeyID) { - debug("delete()"); + console.debug("delete()"); return new Promise((resolve, reject) => this.newTxn( "readwrite", this._dbStoreName, function txnCb(aTxn, aStore) { - debug("Going to delete " + aKeyID); + console.debug("delete: Removing record", aKeyID); aStore.delete(aKeyID); }, resolve, @@ -139,25 +135,10 @@ this.PushDB.prototype = { ); }, - clearAll: function clear() { - return new Promise((resolve, reject) => - this.newTxn( - "readwrite", - this._dbStoreName, - function (aTxn, aStore) { - debug("Going to clear all!"); - aStore.clear(); - }, - resolve, - reject - ) - ); - }, - // testFn(record) is called with a database record and should return true if // that record should be deleted. clearIf: function(testFn) { - debug("clearIf()"); + console.debug("clearIf()"); return new Promise((resolve, reject) => this.newTxn( "readwrite", @@ -168,10 +149,12 @@ this.PushDB.prototype = { aStore.openCursor().onsuccess = event => { let cursor = event.target.result; if (cursor) { - if (testFn(this.toPushRecord(cursor.value))) { + let record = this.toPushRecord(cursor.value); + if (testFn(record)) { let deleteRequest = cursor.delete(); deleteRequest.onerror = e => { - debug("Failed to delete entry even when test succeeded!"); + console.error("clearIf: Error removing record", + record.keyID, e); } } cursor.continue(); @@ -185,7 +168,7 @@ this.PushDB.prototype = { }, getByPushEndpoint: function(aPushEndpoint) { - debug("getByPushEndpoint()"); + console.debug("getByPushEndpoint()"); return new Promise((resolve, reject) => this.newTxn( @@ -196,8 +179,9 @@ this.PushDB.prototype = { let index = aStore.index("pushEndpoint"); index.get(aPushEndpoint).onsuccess = aEvent => { - aTxn.result = this.toPushRecord(aEvent.target.result); - debug("Fetch successful " + aEvent.target.result); + let record = this.toPushRecord(aEvent.target.result); + console.debug("getByPushEndpoint: Got record", record); + aTxn.result = record; }; }, resolve, @@ -207,7 +191,7 @@ this.PushDB.prototype = { }, getByKeyID: function(aKeyID) { - debug("getByKeyID()"); + console.debug("getByKeyID()"); return new Promise((resolve, reject) => this.newTxn( @@ -217,8 +201,9 @@ this.PushDB.prototype = { aTxn.result = undefined; aStore.get(aKeyID).onsuccess = aEvent => { - aTxn.result = this.toPushRecord(aEvent.target.result); - debug("Fetch successful " + aEvent.target.result); + let record = this.toPushRecord(aEvent.target.result); + console.debug("getByKeyID: Got record", record); + aTxn.result = record; }; }, resolve, @@ -242,7 +227,7 @@ this.PushDB.prototype = { * updated. */ updateByOrigin: function(origin, originAttributes, updateFunc) { - debug("updateByOrigin()"); + console.debug("updateByOrigin()"); return new Promise((resolve, reject) => this.newTxn( @@ -264,16 +249,16 @@ this.PushDB.prototype = { let record = this.toPushRecord(cursor.value); let newRecord = updateFunc(record); if (newRecord === false) { - debug("updateByOrigin: Removing record for key ID " + + console.debug("updateByOrigin: Removing record for key ID", record.keyID); cursor.delete(); } else if (this.isValidRecord(newRecord)) { - debug("updateByOrigin: Updating record for key ID " + - record.keyID); + console.debug("updateByOrigin: Updating record for key ID", + record.keyID, newRecord); cursor.update(newRecord); } else { - debug("updateByOrigin: Ignoring invalid update for key ID " + - record.keyID + ": " + JSON.stringify(newRecord)); + console.error("updateByOrigin: Ignoring invalid update for record", + record.keyID, newRecord); } cursor.continue(); }; @@ -286,12 +271,11 @@ this.PushDB.prototype = { // Perform a unique match against { scope, originAttributes } getByIdentifiers: function(aPageRecord) { - debug("getByIdentifiers() { " + aPageRecord.scope + ", " + - JSON.stringify(aPageRecord.originAttributes) + " }"); + console.debug("getByIdentifiers()", aPageRecord); if (!aPageRecord.scope || aPageRecord.originAttributes == undefined) { - return Promise.reject( - new TypeError("Scope and originAttributes are required! " + - JSON.stringify(aPageRecord))); + console.error("getByIdentifiers: Scope and originAttributes are required", + aPageRecord); + return Promise.reject(new TypeError("Invalid page record")); } return new Promise((resolve, reject) => @@ -346,7 +330,7 @@ this.PushDB.prototype = { }, getAllKeyIDs: function() { - debug("getAllKeyIDs()"); + console.debug("getAllKeyIDs()"); return new Promise((resolve, reject) => this.newTxn( @@ -366,7 +350,7 @@ this.PushDB.prototype = { }, _getAllByPushQuota: function(range) { - debug("getAllByPushQuota()"); + console.debug("getAllByPushQuota()"); return new Promise((resolve, reject) => this.newTxn( @@ -391,12 +375,12 @@ this.PushDB.prototype = { }, getAllUnexpired: function() { - debug("getAllUnexpired()"); + console.debug("getAllUnexpired()"); return this._getAllByPushQuota(IDBKeyRange.lowerBound(1)); }, getAllExpired: function() { - debug("getAllExpired()"); + console.debug("getAllExpired()"); return this._getAllByPushQuota(IDBKeyRange.only(0)); }, @@ -422,17 +406,17 @@ this.PushDB.prototype = { let record = aEvent.target.result; if (!record) { - debug("update: Key ID " + aKeyID + " does not exist"); + console.error("update: Record does not exist", aKeyID); return; } let newRecord = aUpdateFunc(this.toPushRecord(record)); if (!this.isValidRecord(newRecord)) { - debug("update: Ignoring invalid update for key ID " + aKeyID + - ": " + JSON.stringify(newRecord)); + console.error("update: Ignoring invalid update", + aKeyID, newRecord); return; } aStore.put(newRecord).onsuccess = aEvent => { - debug("update: Update successful for key ID " + aKeyID); + console.debug("update: Update successful", aKeyID, newRecord); aTxn.result = newRecord; }; }; @@ -444,7 +428,7 @@ this.PushDB.prototype = { }, drop: function() { - debug("drop()"); + console.debug("drop()"); return new Promise((resolve, reject) => this.newTxn( @@ -458,9 +442,4 @@ this.PushDB.prototype = { ) ); }, - - observe: function observe(aSubject, aTopic, aData) { - if ((aTopic == "nsPref:changed") && (aData == "dom.push.debug")) - gDebuggingEnabled = prefs.get("debug"); - } }; diff --git a/dom/push/PushNotificationService.js b/dom/push/PushNotificationService.js index 8b0452066a1d..3ab03c88c4ec 100644 --- a/dom/push/PushNotificationService.js +++ b/dom/push/PushNotificationService.js @@ -39,7 +39,7 @@ PushNotificationService.prototype = { Ci.nsIPushNotificationService]), register: function register(scope, originAttributes) { - return PushService._register({ + return PushService.register({ scope: scope, originAttributes: originAttributes, maxQuota: Infinity, @@ -47,11 +47,11 @@ PushNotificationService.prototype = { }, unregister: function unregister(scope, originAttributes) { - return PushService._unregister({scope, originAttributes}); + return PushService.unregister({scope, originAttributes}); }, registration: function registration(scope, originAttributes) { - return PushService._registration({scope, originAttributes}); + return PushService.registration({scope, originAttributes}); }, clearAll: function clearAll() { diff --git a/dom/push/PushService.jsm b/dom/push/PushService.jsm index d7b9fc3aedcd..58be048ced9b 100644 --- a/dom/push/PushService.jsm +++ b/dom/push/PushService.jsm @@ -5,15 +5,6 @@ "use strict"; -// Don't modify this, instead set dom.push.debug. -var gDebuggingEnabled = false; - -function debug(s) { - if (gDebuggingEnabled) { - dump("-*- PushService.jsm: " + s + "\n"); - } -} - const Cc = Components.classes; const Ci = Components.interfaces; const Cu = Components.utils; @@ -38,9 +29,15 @@ XPCOMUtils.defineLazyModuleGetter(this, "AlarmService", this.EXPORTED_SYMBOLS = ["PushService"]; +XPCOMUtils.defineLazyGetter(this, "console", () => { + let {ConsoleAPI} = Cu.import("resource://gre/modules/Console.jsm", {}); + return new ConsoleAPI({ + maxLogLevelPref: "dom.push.loglevel", + prefix: "PushService", + }); +}); + const prefs = new Preferences("dom.push."); -// Set debug first so that all debugging actually works. -gDebuggingEnabled = prefs.get("debug"); const kCHILD_PROCESS_MESSAGES = ["Push:Register", "Push:Unregister", "Push:Registration", "Push:RegisterEventNotificationListener", @@ -120,7 +117,7 @@ this.PushService = { _activated: null, _checkActivated: function() { if (this._state < PUSH_SERVICE_ACTIVATING) { - return Promise.reject({state: 0, error: "Service not active"}); + return Promise.reject(new Error("Push service not active")); } else if (this._state > PUSH_SERVICE_ACTIVATING) { return Promise.resolve(); } else { @@ -152,7 +149,7 @@ this.PushService = { }, _setState: function(aNewState) { - debug("new state: " + aNewState + " old state: " + this._state); + console.debug("setState()", "new state", aNewState, "old state", this._state); if (this._state == aNewState) { return; @@ -164,7 +161,7 @@ this.PushService = { this._state = aNewState; if (this._notifyActivated) { if (aNewState < PUSH_SERVICE_ACTIVATING) { - this._notifyActivated.reject({state: 0, error: "Service not active"}); + this._notifyActivated.reject(new Error("Push service not active")); } else { this._notifyActivated.resolve(); } @@ -176,7 +173,7 @@ this.PushService = { }, _changeStateOfflineEvent: function(offline, calledFromConnEnabledEvent) { - debug("changeStateOfflineEvent: " + offline); + console.debug("changeStateOfflineEvent()", offline); if (this._state < PUSH_SERVICE_ACTIVE_OFFLINE && this._state != PUSH_SERVICE_ACTIVATING && @@ -208,7 +205,7 @@ this.PushService = { }, _changeStateConnectionEnabledEvent: function(enabled) { - debug("changeStateConnectionEnabledEvent: " + enabled); + console.debug("changeStateConnectionEnabledEvent()", enabled); if (this._state < PUSH_SERVICE_CONNECTION_DISABLE && this._state != PUSH_SERVICE_ACTIVATING) { @@ -244,7 +241,7 @@ this.PushService = { case "nsPref:changed": if (aData == "dom.push.serverURL") { - debug("dom.push.serverURL changed! websocket. new value " + + console.debug("observe: dom.push.serverURL changed for websocket", prefs.get("serverURL")); this._stateChangeProcessEnqueue(_ => this._changeServerURL(prefs.get("serverURL"), @@ -255,9 +252,6 @@ this.PushService = { this._stateChangeProcessEnqueue(_ => this._changeStateConnectionEnabledEvent(prefs.get("connection.enabled")) ); - - } else if (aData == "dom.push.debug") { - gDebuggingEnabled = prefs.get("debug"); } break; @@ -267,19 +261,19 @@ this.PushService = { case "perm-changed": this._onPermissionChange(aSubject, aData).catch(error => { - debug("onPermissionChange: Error updating registrations: " + + console.error("onPermissionChange: Error updating registrations:", error); }) break; case "webapps-clear-data": - debug("webapps-clear-data"); + console.debug("webapps-clear-data"); let data = aSubject .QueryInterface(Ci.mozIApplicationClearPrivateDataParams); if (!data) { - debug("webapps-clear-data: Failed to get information about " + - "application"); + console.error("webapps-clear-data: Failed to get information " + + "about application"); return; } @@ -289,12 +283,12 @@ this.PushService = { this._db.getAllByOriginAttributes(originAttributes) .then(records => Promise.all(records.map(record => this._db.delete(record.keyID).then( - _ => this._unregisterIfConnected(record), + _ => this._backgroundUnregister(record), err => { - debug("webapps-clear-data: " + record.scope + - " Could not delete entry " + record.channelID); + console.error("webapps-clear-data: Error removing record", + record, err); - return this._unregisterIfConnected(record); + this._backgroundUnregister(record); }) ) )); @@ -303,14 +297,14 @@ this.PushService = { } }, - _unregisterIfConnected: function(record) { + _backgroundUnregister: function(record) { if (this._service.isConnected()) { // courtesy, but don't establish a connection // just for it - debug("Had a connection, so telling the server"); - return this._sendUnregister({channelID: record.channelID}) + console.debug("backgroundUnregister: Notifying server", record); + return this._sendUnregister(record) .catch(function(e) { - debug("Unregister errored " + e); + console.error("backgroundUnregister: Error notifying server", e); }); } }, @@ -342,7 +336,7 @@ this.PushService = { }, _changeServerURL: function(serverURI, event) { - debug("changeServerURL"); + console.debug("changeServerURL()"); switch(event) { case UNINIT_EVENT: @@ -414,7 +408,7 @@ this.PushService = { * PUSH_SERVICE_CONNECTION_DISABLE. */ init: function(options = {}) { - debug("init()"); + console.debug("init()"); if (this._state > PUSH_SERVICE_UNINIT) { return; @@ -422,9 +416,6 @@ this.PushService = { this._setState(PUSH_SERVICE_ACTIVATING); - // Debugging - prefs.observe("debug", this); - Services.obs.addObserver(this, "xpcom-shutdown", false); if (options.serverURI) { @@ -469,7 +460,7 @@ this.PushService = { }, _startObservers: function() { - debug("startObservers"); + console.debug("startObservers()"); if (this._state != PUSH_SERVICE_ACTIVATING) { return; @@ -510,7 +501,7 @@ this.PushService = { }, _startService: function(service, serverURI, event, options = {}) { - debug("startService"); + console.debug("startService()"); if (this._state != PUSH_SERVICE_ACTIVATING) { return; @@ -549,7 +540,7 @@ this.PushService = { * state is change to PUSH_SERVICE_UNINIT */ _stopService: function(event) { - debug("stopService"); + console.debug("stopService()"); if (this._state < PUSH_SERVICE_ACTIVATING) { return; @@ -593,13 +584,12 @@ this.PushService = { }, _stopObservers: function() { - debug("stopObservers()"); + console.debug("stopObservers()"); if (this._state < PUSH_SERVICE_ACTIVATING) { return; } - prefs.ignore("debug", this); prefs.ignore("connection.enabled", this); Services.obs.removeObserver(this, this._networkStateChangeEventName); @@ -609,7 +599,7 @@ this.PushService = { }, uninit: function() { - debug("uninit()"); + console.debug("uninit()"); this._childListeners = []; @@ -624,7 +614,7 @@ this.PushService = { this._stateChangeProcessEnqueue(_ => this._changeServerURL("", UNINIT_EVENT)); - debug("shutdown complete!"); + console.debug("uninit: shutdown complete!"); }, /** |delay| should be in milliseconds. */ @@ -654,7 +644,8 @@ this.PushService = { } }, (alarmID) => { this._alarmID = alarmID; - debug("Set alarm " + delay + " in the future " + this._alarmID); + console.debug("setAlarm: Set alarm", delay, "in the future", + this._alarmID); this._settingAlarm = false; if (this._waitingForAlarmSet) { @@ -667,7 +658,7 @@ this.PushService = { stopAlarm: function() { if (this._alarmID !== null) { - debug("Stopped existing alarm " + this._alarmID); + console.debug("stopAlarm: Stopped existing alarm", this._alarmID); AlarmService.remove(this._alarmID); this._alarmID = null; } @@ -716,7 +707,7 @@ this.PushService = { // Fires a push-register system message to all applications that have // registration. _notifyAllAppsRegister: function() { - debug("notifyAllAppsRegister()"); + console.debug("notifyAllAppsRegister()"); // records are objects describing the registration as stored in IndexedDB. return this._db.getAllUnexpired().then(records => { records.forEach(record => { @@ -792,7 +783,7 @@ this.PushService = { * versions. */ receivedPushMessage: function(keyID, message, cryptoParams, updateFunc) { - debug("receivedPushMessage()"); + console.debug("receivedPushMessage()"); Services.telemetry.getHistogramById("PUSH_API_NOTIFICATION_RECEIVED").add(); let shouldNotify = false; @@ -820,7 +811,8 @@ this.PushService = { // this, we check if the record has expired before *and* after updating // the quota. if (newRecord.isExpired()) { - debug("receivedPushMessage: Ignoring update for expired key ID " + keyID); + console.error("receivedPushMessage: Ignoring update for expired key ID", + keyID); return null; } newRecord.receivedPush(lastVisit); @@ -852,26 +844,23 @@ this.PushService = { // Drop the registration in the background. If the user returns to the // site, the service worker will be notified on the next `idle-daily` // event. - this._sendUnregister(record).catch(error => { - debug("receivedPushMessage: Unregister error: " + error); - }); + this._backgroundUnregister(record); } return notified; }); }).catch(error => { - debug("receivedPushMessage: Error notifying app: " + error); + console.error("receivedPushMessage: Error notifying app", error); }); }, _notifyApp: function(aPushRecord, message) { if (!aPushRecord || !aPushRecord.scope || aPushRecord.originAttributes === undefined) { - debug("notifyApp() something is undefined. Dropping notification: " + - JSON.stringify(aPushRecord) ); + console.error("notifyApp: Invalid record", aPushRecord); return false; } - debug("notifyApp() " + aPushRecord.scope); + console.debug("notifyApp()", aPushRecord.scope); // Notify XPCOM observers. let notification = Cc["@mozilla.org/push/ObserverNotification;1"] .createInstance(Ci.nsIPushObserverNotification); @@ -898,7 +887,7 @@ this.PushService = { // If permission has been revoked, trash the message. if (!aPushRecord.hasPermission()) { - debug("Does not have permission for push."); + console.warn("notifyApp: Missing push permission", aPushRecord); return false; } @@ -923,12 +912,12 @@ this.PushService = { _sendRequest: function(action, aRecord) { if (this._state == PUSH_SERVICE_CONNECTION_DISABLE) { - return Promise.reject({state: 0, error: "Service not active"}); + return Promise.reject(new Error("Push service disabled")); } else if (this._state == PUSH_SERVICE_ACTIVE_OFFLINE) { if (this._service.serviceType() == "WebSocket" && action == "unregister") { return Promise.resolve(); } - return Promise.reject({state: 0, error: "NetworkError"}); + return Promise.reject(new Error("Push service offline")); } return this._service.request(action, aRecord); }, @@ -938,7 +927,7 @@ this.PushService = { * navigator.push, identifying the sending page and other fields. */ _registerWithServer: function(aPageRecord) { - debug("registerWithServer()" + JSON.stringify(aPageRecord)); + console.debug("registerWithServer()", aPageRecord); Services.telemetry.getHistogramById("PUSH_API_SUBSCRIBE_ATTEMPT").add(); return this._sendRequest("register", aPageRecord) @@ -946,42 +935,13 @@ this.PushService = { err => this._onRegisterError(err)) .then(record => { this._deletePendingRequest(aPageRecord); - return record; + return record.toRegister(); }, err => { this._deletePendingRequest(aPageRecord); throw err; }); }, - _register: function(aPageRecord) { - debug("_register()"); - if (!aPageRecord.scope || aPageRecord.originAttributes === undefined) { - return Promise.reject({state: 0, error: "NotFoundError"}); - } - - return this._checkActivated() - .then(_ => this._db.getByIdentifiers(aPageRecord)) - .then(record => { - if (!record) { - return this._lookupOrPutPendingRequest(aPageRecord); - } - if (record.isExpired()) { - return record.quotaChanged().then(isChanged => { - if (isChanged) { - // If the user revisited the site, drop the expired push - // registration and re-register. - return this._db.delete(record.keyID); - } - throw {state: 0, error: "NotFoundError"}; - }).then(_ => this._lookupOrPutPendingRequest(aPageRecord)); - } - return record; - }, error => { - debug("getByIdentifiers failed"); - throw error; - }); - }, - _sendUnregister: function(aRecord) { Services.telemetry.getHistogramById("PUSH_API_UNSUBSCRIBE_ATTEMPT").add(); return this._sendRequest("unregister", aRecord).then(function(v) { @@ -998,7 +958,7 @@ this.PushService = { * from _service.request, causing the promise to be rejected instead. */ _onRegisterSuccess: function(aRecord) { - debug("_onRegisterSuccess()"); + console.debug("_onRegisterSuccess()"); return this._db.put(aRecord) .then(record => { @@ -1008,10 +968,7 @@ this.PushService = { .catch(error => { Services.telemetry.getHistogramById("PUSH_API_SUBSCRIBE_FAILED").add() // Unable to save. Destroy the subscription in the background. - this._sendUnregister(aRecord).catch(err => { - debug("_onRegisterSuccess: Error unregistering stale subscription" + - err); - }); + this._backgroundUnregister(aRecord); throw error; }); }, @@ -1021,34 +978,36 @@ this.PushService = { * from _service.request, causing the promise to be rejected instead. */ _onRegisterError: function(reply) { - debug("_onRegisterError()"); + console.debug("_onRegisterError()"); Services.telemetry.getHistogramById("PUSH_API_SUBSCRIBE_FAILED").add() if (!reply.error) { - debug("Called without valid error message!"); - throw "Registration error"; + console.warn("onRegisterError: Called without valid error message!", + reply.error); + throw new Error("Registration error"); } throw reply.error; }, receiveMessage: function(aMessage) { - debug("receiveMessage(): " + aMessage.name); + console.debug("receiveMessage()", aMessage.name); if (kCHILD_PROCESS_MESSAGES.indexOf(aMessage.name) == -1) { - debug("Invalid message from child " + aMessage.name); + console.debug("receiveMessage: Invalid message from child", + aMessage.name); return; } if (aMessage.name === "Push:RegisterEventNotificationListener") { - debug("Adding child listener"); + console.debug("receiveMessage: Adding child listener"); this._childListeners.push(aMessage.target); return; } if (aMessage.name === "child-process-shutdown") { - debug("Possibly removing child listener"); + console.debug("receiveMessage: Possibly removing child listener"); for (var i = this._childListeners.length - 1; i >= 0; --i) { if (this._childListeners[i] == aMessage.target) { - debug("Removed child listener"); + console.debug("receiveMessage: Removed child listener"); this._childListeners.splice(i, 1); } } @@ -1056,54 +1015,73 @@ this.PushService = { } if (!aMessage.target.assertPermission("push")) { - debug("Got message from a child process that does not have 'push' permission."); + console.debug("receiveMessage: Got message from a child process that", + "does not have 'push' permission"); return null; } let mm = aMessage.target.QueryInterface(Ci.nsIMessageSender); - let pageRecord = aMessage.data; + let name = aMessage.name.slice("Push:".length); + Promise.resolve().then(_ => { + let pageRecord = this._validatePageRecord(aMessage); + return this[name.toLowerCase()](pageRecord); + }).then(result => { + mm.sendAsyncMessage("PushService:" + name + ":OK", { + requestID: aMessage.data.requestID, + result: result, + }); + }, error => { + console.error("receiveMessage: Error handling message", aMessage, error); + mm.sendAsyncMessage("PushService:" + name + ":KO", { + requestID: aMessage.data.requestID, + }); + }).catch(error => { + console.error("receiveMessage: Error sending reply", error); + }); + }, + + _validatePageRecord: function(aMessage) { let principal = aMessage.principal; if (!principal) { - debug("No principal passed!"); - let message = { - requestID: pageRecord.requestID, - error: "SecurityError" - }; - mm.sendAsyncMessage("PushService:Register:KO", message); - return; + throw new Error("Missing message principal"); + } + + let pageRecord = aMessage.data; + if (!pageRecord.scope) { + throw new Error("Missing page record scope"); } pageRecord.originAttributes = ChromeUtils.originAttributesToSuffix(principal.originAttributes); - if (!pageRecord.scope || pageRecord.originAttributes === undefined) { - debug("Incorrect identifier values set! " + JSON.stringify(pageRecord)); - let message = { - requestID: pageRecord.requestID, - error: "SecurityError" - }; - mm.sendAsyncMessage("PushService:Register:KO", message); - return; - } - - this[aMessage.name.slice("Push:".length).toLowerCase()](pageRecord, mm); + return pageRecord; }, - register: function(aPageRecord, aMessageManager) { - debug("register(): " + JSON.stringify(aPageRecord)); + register: function(aPageRecord) { + console.debug("register()", aPageRecord); - this._register(aPageRecord) + if (!aPageRecord.scope || aPageRecord.originAttributes === undefined) { + return Promise.reject(new Error("Invalid page record")); + } + + return this._checkActivated() + .then(_ => this._db.getByIdentifiers(aPageRecord)) .then(record => { - let message = record.toRegister(); - message.requestID = aPageRecord.requestID; - aMessageManager.sendAsyncMessage("PushService:Register:OK", message); - }, error => { - let message = { - requestID: aPageRecord.requestID, - error - }; - aMessageManager.sendAsyncMessage("PushService:Register:KO", message); + if (!record) { + return this._lookupOrPutPendingRequest(aPageRecord); + } + if (record.isExpired()) { + return record.quotaChanged().then(isChanged => { + if (isChanged) { + // If the user revisited the site, drop the expired push + // registration and re-register. + return this._db.delete(record.keyID); + } + throw new Error("Push subscription expired"); + }).then(_ => this._lookupOrPutPendingRequest(aPageRecord)); + } + return record.toRegister(); }); }, @@ -1132,10 +1110,11 @@ this.PushService = { * client acknowledge. On a server, data is cheap, reliable notification is * not. */ - _unregister: function(aPageRecord) { - debug("_unregister()"); + unregister: function(aPageRecord) { + console.debug("unregister()", aPageRecord); + if (!aPageRecord.scope || aPageRecord.originAttributes === undefined) { - return Promise.reject({state: 0, error: "NotFoundError"}); + return Promise.reject(new Error("Invalid page record")); } return this._checkActivated() @@ -1152,27 +1131,9 @@ this.PushService = { }); }, - unregister: function(aPageRecord, aMessageManager) { - debug("unregister() " + JSON.stringify(aPageRecord)); - - this._unregister(aPageRecord) - .then(result => { - aMessageManager.sendAsyncMessage("PushService:Unregister:OK", { - requestID: aPageRecord.requestID, - result: result, - }) - }, error => { - debug("unregister(): Actual error " + error); - aMessageManager.sendAsyncMessage("PushService:Unregister:KO", { - requestID: aPageRecord.requestID, - }) - } - ); - }, - _clearAll: function _clearAll() { return this._checkActivated() - .then(_ => this._db.clearAll()) + .then(_ => this._db.drop()) .catch(_ => Promise.resolve()); }, @@ -1213,18 +1174,15 @@ this.PushService = { return this._checkActivated() .then(_ => clear(this._db, domain)) .catch(e => { - debug("Error forgetting about domain! " + e); + console.warn("clearForDomain: Error forgetting about domain", e); return Promise.resolve(); }); }, - /** - * Called on message from the child process - */ - _registration: function(aPageRecord) { - debug("_registration()"); + registration: function(aPageRecord) { + console.debug("registration()"); if (!aPageRecord.scope || aPageRecord.originAttributes === undefined) { - return Promise.reject({state: 0, error: "NotFoundError"}); + return Promise.reject(new Error("Invalid page record")); } return this._checkActivated() @@ -1245,24 +1203,8 @@ this.PushService = { }); }, - registration: function(aPageRecord, aMessageManager) { - debug("registration()"); - - return this._registration(aPageRecord) - .then(registration => - aMessageManager.sendAsyncMessage("PushService:Registration:OK", { - requestID: aPageRecord.requestID, - registration - }), error => - aMessageManager.sendAsyncMessage("PushService:Registration:KO", { - requestID: aPageRecord.requestID, - error - }) - ); - }, - _dropExpiredRegistrations: function() { - debug("dropExpiredRegistrations()"); + console.debug("dropExpiredRegistrations()"); return this._db.getAllExpired().then(records => { return Promise.all(records.map(record => @@ -1273,15 +1215,15 @@ this.PushService = { return this.dropRecordAndNotifyApp(record); } }).catch(error => { - debug("dropExpiredRegistrations: Error dropping registration " + - record.keyID + ": " + error); + console.error("dropExpiredRegistrations: Error dropping registration", + record.keyID, error); }) )); }); }, _onPermissionChange: function(subject, data) { - debug("onPermissionChange()"); + console.debug("onPermissionChange()"); if (data == "cleared") { // If the permission list was cleared, drop all registrations @@ -1290,7 +1232,7 @@ this.PushService = { if (record.quotaApplies()) { if (!record.isExpired()) { // Drop the registration in the background. - this._unregisterIfConnected(record); + this._backgroundUnregister(record); } return true; } @@ -1307,7 +1249,7 @@ this.PushService = { }, _updatePermission: function(permission, type) { - debug("updatePermission()"); + console.debug("updatePermission()"); let isAllow = permission.capability == Ci.nsIPermissionManager.ALLOW_ACTION; @@ -1356,7 +1298,7 @@ this.PushService = { return null; } // Drop the registration in the background. - this._unregisterIfConnected(record); + this._backgroundUnregister(record); record.setQuota(0); return record; }, diff --git a/dom/push/PushServiceHttp2.jsm b/dom/push/PushServiceHttp2.jsm index 929400ec60fe..8f167d8cb6cf 100644 --- a/dom/push/PushServiceHttp2.jsm +++ b/dom/push/PushServiceHttp2.jsm @@ -28,18 +28,16 @@ const { this.EXPORTED_SYMBOLS = ["PushServiceHttp2"]; +XPCOMUtils.defineLazyGetter(this, "console", () => { + let {ConsoleAPI} = Cu.import("resource://gre/modules/Console.jsm", {}); + return new ConsoleAPI({ + maxLogLevelPref: "dom.push.loglevel", + prefix: "PushServiceHttp2", + }); +}); + const prefs = new Preferences("dom.push."); -// Don't modify this, instead set dom.push.debug. -// Set debug first so that all debugging actually works. -var gDebuggingEnabled = prefs.get("debug"); - -function debug(s) { - if (gDebuggingEnabled) { - dump("-*- PushServiceHttp2.jsm: " + s + "\n"); - } -} - const kPUSHHTTP2DB_DB_NAME = "pushHttp2"; const kPUSHHTTP2DB_DB_VERSION = 5; // Change this if the IndexedDB format changes const kPUSHHTTP2DB_STORE_NAME = "pushHttp2"; @@ -53,7 +51,7 @@ const kPUSHHTTP2DB_STORE_NAME = "pushHttp2"; * It's easier to stop listening than to have checks at specific points. */ var PushSubscriptionListener = function(pushService, uri) { - debug("Creating a new pushSubscription listener."); + console.debug("PushSubscriptionListener()"); this._pushService = pushService; this.uri = uri; }; @@ -73,12 +71,12 @@ PushSubscriptionListener.prototype = { }, onStartRequest: function(aRequest, aContext) { - debug("PushSubscriptionListener onStartRequest()"); + console.debug("PushSubscriptionListener: onStartRequest()"); // We do not do anything here. }, onDataAvailable: function(aRequest, aContext, aStream, aOffset, aCount) { - debug("PushSubscriptionListener onDataAvailable()"); + console.debug("PushSubscriptionListener: onDataAvailable()"); // Nobody should send data, but just to be sure, otherwise necko will // complain. if (aCount === 0) { @@ -93,7 +91,7 @@ PushSubscriptionListener.prototype = { }, onStopRequest: function(aRequest, aContext, aStatusCode) { - debug("PushSubscriptionListener onStopRequest()"); + console.debug("PushSubscriptionListener: onStopRequest()"); if (!this._pushService) { return; } @@ -104,7 +102,7 @@ PushSubscriptionListener.prototype = { }, onPush: function(associatedChannel, pushChannel) { - debug("PushSubscriptionListener onPush()"); + console.debug("PushSubscriptionListener: onPush()"); var pushChannelListener = new PushChannelListener(this); pushChannel.asyncOpen(pushChannelListener, pushChannel); }, @@ -119,7 +117,7 @@ PushSubscriptionListener.prototype = { * OnDataAvailable and send to the app in OnStopRequest. */ var PushChannelListener = function(pushSubscriptionListener) { - debug("Creating a new push channel listener."); + console.debug("PushChannelListener()"); this._mainListener = pushSubscriptionListener; this._message = []; this._ackUri = null; @@ -132,7 +130,7 @@ PushChannelListener.prototype = { }, onDataAvailable: function(aRequest, aContext, aStream, aOffset, aCount) { - debug("push channel listener onDataAvailable()"); + console.debug("PushChannelListener: onDataAvailable()"); if (aCount === 0) { return; @@ -148,7 +146,8 @@ PushChannelListener.prototype = { }, onStopRequest: function(aRequest, aContext, aStatusCode) { - debug("push channel listener onStopRequest() status code:" + aStatusCode); + console.debug("PushChannelListener: onStopRequest()", "status code", + aStatusCode); if (Components.isSuccessCode(aStatusCode) && this._mainListener && this._mainListener._pushService) { @@ -228,14 +227,14 @@ PushServiceDelete.prototype = { if (Components.isSuccessCode(aStatusCode)) { this._resolve(); } else { - this._reject({status: 0, error: "NetworkError"}); + this._reject(new Error("Error removing subscription: " + aStatusCode)); } } }; var SubscriptionListener = function(aSubInfo, aResolve, aReject, aServerURI, aPushServiceHttp2) { - debug("Creating a new subscription listener."); + console.debug("SubscriptionListener()"); this._subInfo = aSubInfo; this._resolve = aResolve; this._reject = aReject; @@ -250,7 +249,7 @@ SubscriptionListener.prototype = { onStartRequest: function(aRequest, aContext) {}, onDataAvailable: function(aRequest, aContext, aStream, aOffset, aCount) { - debug("subscription listener onDataAvailable()"); + console.debug("SubscriptionListener: onDataAvailable()"); // We do not expect any data, but necko will complain if we do not consume // it. @@ -266,16 +265,16 @@ SubscriptionListener.prototype = { }, onStopRequest: function(aRequest, aContext, aStatus) { - debug("subscription listener onStopRequest()"); + console.debug("SubscriptionListener: onStopRequest()"); // Check if pushService is still active. if (!this._service.hasmainPushService()) { - this._reject({error: "Service deactivated"}); + this._reject(new Error("Push service unavailable")); return; } if (!Components.isSuccessCode(aStatus)) { - this._reject({error: "Error status" + aStatus}); + this._reject(new Error("Error listening for messages: " + aStatus)); return; } @@ -292,11 +291,11 @@ SubscriptionListener.prototype = { }), retryAfter); } else { - this._reject({error: "Error response code: " + statusCode }); + this._reject(new Error("Unexpected server response: " + statusCode)); } return; } else if (statusCode != 201) { - this._reject({error: "Error response code: " + statusCode }); + this._reject(new Error("Unexpected server response: " + statusCode)); return; } @@ -304,37 +303,39 @@ SubscriptionListener.prototype = { try { subscriptionUri = aRequest.getResponseHeader("location"); } catch (err) { - this._reject({error: "Return code 201, but the answer is bogus"}); + this._reject(new Error("Missing Location header")); return; } - debug("subscriptionUri: " + subscriptionUri); + console.debug("onStopRequest: subscriptionUri", subscriptionUri); var linkList; try { linkList = aRequest.getResponseHeader("link"); } catch (err) { - this._reject({error: "Return code 201, but the answer is bogus"}); + this._reject(new Error("Missing Link header")); return; } - var linkParserResult = linkParser(linkList, this._serverURI); - if (linkParserResult.error) { - this._reject(linkParserResult); + var linkParserResult; + try { + linkParserResult = linkParser(linkList, this._serverURI); + } catch (e) { + this._reject(e); return; } if (!subscriptionUri) { - this._reject({error: "Return code 201, but the answer is bogus," + - " missing subscriptionUri"}); + this._reject(new Error("Invalid Location header")); return; } try { let uriTry = Services.io.newURI(subscriptionUri, null, null); } catch (e) { - debug("Invalid URI " + subscriptionUri); - this._reject({error: "Return code 201, but URI is bogus. " + - subscriptionUri}); + console.error("onStopRequest: Invalid subscription URI", + subscriptionUri); + this._reject(new Error("Invalid subscription endpoint: " + + subscriptionUri)); return; } @@ -372,7 +373,7 @@ function linkParser(linkHeader, serverURI) { var linkList = linkHeader.split(','); if ((linkList.length < 1)) { - return {error: "Return code 201, but the answer is bogus"}; + throw new Error("Invalid Link header"); } var pushEndpoint; @@ -393,32 +394,23 @@ function linkParser(linkHeader, serverURI) { } }); - debug("pushEndpoint: " + pushEndpoint); - debug("pushReceiptEndpoint: " + pushReceiptEndpoint); + console.debug("linkParser: pushEndpoint", pushEndpoint); + console.debug("linkParser: pushReceiptEndpoint", pushReceiptEndpoint); // Missing pushReceiptEndpoint is allowed. if (!pushEndpoint) { - return {error: "Return code 201, but the answer is bogus, missing" + - " pushEndpoint"}; + throw new Error("Missing push endpoint"); } - var uri; - var resUri = []; - try { - [pushEndpoint, pushReceiptEndpoint].forEach(u => { - if (u) { - uri = u; - resUri[u] = Services.io.newURI(uri, null, serverURI); - } - }); - } catch (e) { - debug("Invalid URI " + uri); - return {error: "Return code 201, but URI is bogus. " + uri}; + var pushURI = Services.io.newURI(pushEndpoint, null, serverURI); + var pushReceiptURI; + if (pushReceiptEndpoint) { + pushReceiptURI = Services.io.newURI(pushReceiptEndpoint, null, + serverURI); } return { - pushEndpoint: resUri[pushEndpoint].spec, - pushReceiptEndpoint: (pushReceiptEndpoint) ? resUri[pushReceiptEndpoint].spec - : "" + pushEndpoint: pushURI.spec, + pushReceiptEndpoint: (pushReceiptURI) ? pushReceiptURI.spec : "", }; } @@ -451,7 +443,7 @@ this.PushServiceHttp2 = { checkServerURI: function(serverURL) { if (!serverURL) { - debug("No dom.push.serverURL found!"); + console.warn("checkServerURI: No dom.push.serverURL found"); return; } @@ -459,26 +451,18 @@ this.PushServiceHttp2 = { try { uri = Services.io.newURI(serverURL, null, null); } catch(e) { - debug("Error creating valid URI from dom.push.serverURL (" + - serverURL + ")"); + console.warn("checkServerURI: Error creating valid URI from", + "dom.push.serverURL", serverURL); return null; } if (uri.scheme !== "https") { - debug("Unsupported websocket scheme " + uri.scheme); + console.warn("checkServerURI: Unsupported scheme", uri.scheme); return null; } return uri; }, - observe: function(aSubject, aTopic, aData) { - if (aTopic == "nsPref:changed") { - if (aData == "dom.push.debug") { - gDebuggingEnabled = prefs.get("debug"); - } - } - }, - connect: function(subscriptions) { this.startConnections(subscriptions); }, @@ -516,7 +500,7 @@ this.PushServiceHttp2 = { * Subscribe new resource. */ _subscribeResource: function(aRecord) { - debug("subscribeResource()"); + console.debug("subscribeResource()"); return this._subscribeResourceInternal({ record: aRecord, @@ -541,7 +525,7 @@ this.PushServiceHttp2 = { }, _subscribeResourceInternal: function(aSubInfo) { - debug("subscribeResourceInternal()"); + console.debug("subscribeResourceInternal()"); return new Promise((resolve, reject) => { var listener = new SubscriptionListener(aSubInfo, @@ -552,11 +536,7 @@ this.PushServiceHttp2 = { var chan = this._makeChannel(this._serverURI.spec); chan.requestMethod = "POST"; - try { - chan.asyncOpen(listener, null); - } catch(e) { - reject({status: 0, error: "NetworkError"}); - } + chan.asyncOpen(listener, null); }) .catch(err => { if ("retry" in err) { @@ -572,11 +552,7 @@ this.PushServiceHttp2 = { return new Promise((resolve,reject) => { var chan = this._makeChannel(aUri); chan.requestMethod = "DELETE"; - try { - chan.asyncOpen(new PushServiceDelete(resolve, reject), null); - } catch(err) { - reject({status: 0, error: "NetworkError"}); - } + chan.asyncOpen(new PushServiceDelete(resolve, reject), null); }); }, @@ -585,7 +561,7 @@ this.PushServiceHttp2 = { * We can't do anything about it if it fails, so we don't listen for response. */ _unsubscribeResource: function(aSubscriptionUri) { - debug("unsubscribeResource()"); + console.debug("unsubscribeResource()"); return this._deleteResource(aSubscriptionUri); }, @@ -594,9 +570,10 @@ this.PushServiceHttp2 = { * Start listening for messages. */ _listenForMsgs: function(aSubscriptionUri) { - debug("listenForMsgs() " + aSubscriptionUri); + console.debug("listenForMsgs()", aSubscriptionUri); if (!this._conns[aSubscriptionUri]) { - debug("We do not have this subscription " + aSubscriptionUri); + console.warn("listenForMsgs: We do not have this subscription", + aSubscriptionUri); return; } @@ -611,7 +588,8 @@ this.PushServiceHttp2 = { try { chan.asyncOpen(listener, chan); } catch (e) { - debug("Error connecting to push server. asyncOpen failed!"); + console.error("listenForMsgs: Error connecting to push server.", + "asyncOpen failed", e); conn.listener.disconnect(); chan.cancel(Cr.NS_ERROR_ABORT); this._retryAfterBackoff(aSubscriptionUri, -1); @@ -625,22 +603,20 @@ this.PushServiceHttp2 = { }, _ackMsgRecv: function(aAckUri) { - debug("ackMsgRecv() " + aAckUri); + console.debug("ackMsgRecv()", aAckUri); // We can't do anything about it if it fails, // so we don't listen for response. this._deleteResource(aAckUri); }, init: function(aOptions, aMainPushService, aServerURL) { - debug("init()"); + console.debug("init()"); this._mainPushService = aMainPushService; this._serverURI = aServerURL; - gDebuggingEnabled = prefs.get("debug"); - prefs.observe("debug", this); }, _retryAfterBackoff: function(aSubscriptionUri, retryAfter) { - debug("retryAfterBackoff()"); + console.debug("retryAfterBackoff()"); var resetRetryCount = prefs.get("http2.reset_retry_count_after_ms"); // If it was running for some time, reset retry counter. @@ -680,12 +656,13 @@ this.PushServiceHttp2 = { this._conns[aSubscriptionUri].waitingForAlarm = true; this._mainPushService.setAlarm(retryAfter); } - debug("Retry in " + retryAfter); + + console.debug("retryAfterBackoff: Retry in", retryAfter); }, // Close connections. _shutdownConnections: function(deleteInfo) { - debug("shutdownConnections()"); + console.debug("shutdownConnections()"); for (let subscriptionUri in this._conns) { if (this._conns[subscriptionUri]) { @@ -710,20 +687,21 @@ this.PushServiceHttp2 = { // Start listening if subscriptions present. startConnections: function(aSubscriptions) { - debug("startConnections() " + aSubscriptions.length); + console.debug("startConnections()", aSubscriptions.length); for (let i = 0; i < aSubscriptions.length; i++) { let record = aSubscriptions[i]; this._mainPushService.ensureP256dhKey(record).then(record => { this._startSingleConnection(record); }, error => { - debug("startConnections: Error updating record " + record.keyID); + console.error("startConnections: Error updating record", + record.keyID, error); }); } }, _startSingleConnection: function(record) { - debug("_startSingleConnection()"); + console.debug("_startSingleConnection()"); if (typeof this._conns[record.subscriptionUri] != "object") { this._conns[record.subscriptionUri] = {channel: null, listener: null, @@ -738,7 +716,7 @@ this.PushServiceHttp2 = { // Start listening if subscriptions present. _startConnectionsWaitingForAlarm: function() { - debug("startConnectionsWaitingForAlarm()"); + console.debug("startConnectionsWaitingForAlarm()"); for (let subscriptionUri in this._conns) { if ((this._conns[subscriptionUri]) && !this._conns[subscriptionUri].conn && @@ -751,7 +729,7 @@ this.PushServiceHttp2 = { // Close connection and notify apps that subscription are gone. _shutdownSubscription: function(aSubscriptionUri) { - debug("shutdownSubscriptions()"); + console.debug("shutdownSubscriptions()"); if (typeof this._conns[aSubscriptionUri] == "object") { if (this._conns[aSubscriptionUri].listener) { @@ -768,7 +746,7 @@ this.PushServiceHttp2 = { }, uninit: function() { - debug("uninit()"); + console.debug("uninit()"); this._shutdownConnections(true); this._mainPushService = null; }, @@ -777,11 +755,12 @@ this.PushServiceHttp2 = { request: function(action, aRecord) { switch (action) { case "register": - debug("register"); return this._subscribeResource(aRecord); case "unregister": this._shutdownSubscription(aRecord.subscriptionUri); return this._unsubscribeResource(aRecord.subscriptionUri); + default: + return Promise.reject(new Error("Unknown request type: " + action)); } }, @@ -809,7 +788,7 @@ this.PushServiceHttp2 = { connOnStop: function(aRequest, aSuccess, aSubscriptionUri) { - debug("connOnStop() succeeded: " + aSuccess); + console.debug("connOnStop() succeeded", aSuccess); var conn = this._conns[aSubscriptionUri]; if (!conn) { @@ -840,7 +819,7 @@ this.PushServiceHttp2 = { }, _pushChannelOnStop: function(aUri, aAckUri, aMessage, dh, salt, rs) { - debug("pushChannelOnStop() "); + console.debug("pushChannelOnStop()"); let cryptoParams = { dh: dh, @@ -855,7 +834,8 @@ this.PushServiceHttp2 = { ) .then(_ => this._ackMsgRecv(aAckUri)) .catch(err => { - debug("Error receiving message: " + err); + console.error("pushChannelOnStop: Error receiving message", + err); }); }, diff --git a/dom/push/PushServiceWebSocket.jsm b/dom/push/PushServiceWebSocket.jsm index 10e72cfbe5f2..cc1b634819cd 100644 --- a/dom/push/PushServiceWebSocket.jsm +++ b/dom/push/PushServiceWebSocket.jsm @@ -51,8 +51,13 @@ const prefs = new Preferences("dom.push."); this.EXPORTED_SYMBOLS = ["PushServiceWebSocket"]; -// Don't modify this, instead set dom.push.debug. -var gDebuggingEnabled = true; +XPCOMUtils.defineLazyGetter(this, "console", () => { + let {ConsoleAPI} = Cu.import("resource://gre/modules/Console.jsm", {}); + return new ConsoleAPI({ + maxLogLevelPref: "dom.push.loglevel", + prefix: "PushServiceWebSocket", + }); +}); function getCryptoParams(headers) { if (!headers) { @@ -75,15 +80,6 @@ function getCryptoParams(headers) { return {dh, salt, rs}; } -function debug(s) { - if (gDebuggingEnabled) { - dump("-*- PushServiceWebSocket.jsm: " + s + "\n"); - } -} - -// Set debug first so that all debugging actually works. -gDebuggingEnabled = prefs.get("debug"); - /** * A proxy between the PushService and the WebSocket. The listener is used so * that the PushService can silence messages from the WebSocket by setting @@ -169,9 +165,7 @@ this.PushServiceWebSocket = { switch (aTopic) { case "nsPref:changed": - if (aData == "dom.push.debug") { - gDebuggingEnabled = prefs.get("debug"); - } else if (aData == "dom.push.userAgentID") { + if (aData == "dom.push.userAgentID") { this._shutdownWS(); this._reconnectAfterBackoff(); } @@ -190,10 +184,10 @@ this.PushServiceWebSocket = { // also made to fail, since we are going to be disconnecting the // socket. if (requestTimedOut || duration > this._requestTimeout) { - debug("Request timeout: Removing " + channelID); requestTimedOut = true; this._registerRequests[channelID] - .reject({status: 0, error: "TimeoutError"}); + .reject(new Error("Register request timed out for channel ID " + + channelID)); delete this._registerRequests[channelID]; } @@ -211,7 +205,7 @@ this.PushServiceWebSocket = { checkServerURI: function(serverURL) { if (!serverURL) { - debug("No dom.push.serverURL found!"); + console.warn("checkServerURI: No dom.push.serverURL found"); return; } @@ -219,13 +213,13 @@ this.PushServiceWebSocket = { try { uri = Services.io.newURI(serverURL, null, null); } catch(e) { - debug("Error creating valid URI from dom.push.serverURL (" + - serverURL + ")"); + console.warn("checkServerURI: Error creating valid URI from", + "dom.push.serverURL", serverURL); return null; } if (uri.scheme !== "wss") { - debug("Unsupported websocket scheme " + uri.scheme); + console.warn("checkServerURI: Unsupported websocket scheme", uri.scheme); return null; } return uri; @@ -237,11 +231,11 @@ this.PushServiceWebSocket = { set _UAID(newID) { if (typeof(newID) !== "string") { - debug("Got invalid, non-string UAID " + newID + - ". Not updating userAgentID"); + console.warn("Got invalid, non-string UAID", newID, + "Not updating userAgentID"); return; } - debug("New _UAID: " + newID); + console.debug("New _UAID", newID); prefs.set("userAgentID", newID); }, @@ -308,16 +302,17 @@ this.PushServiceWebSocket = { */ _wsSendMessage: function(msg) { if (!this._ws) { - debug("No WebSocket initialized. Cannot send a message."); + console.warn("wsSendMessage: No WebSocket initialized.", + "Cannot send a message"); return; } msg = JSON.stringify(msg); - debug("Sending message: " + msg); + console.debug("wsSendMessage: Sending message", msg); this._ws.sendMsg(msg); }, init: function(options, mainPushService, serverURI) { - debug("init()"); + console.debug("init()"); this._mainPushService = mainPushService; this._serverURI = serverURI; @@ -344,18 +339,16 @@ this.PushServiceWebSocket = { this._requestTimeout = prefs.get("requestTimeout"); this._adaptiveEnabled = prefs.get('adaptive.enabled'); this._upperLimit = prefs.get('adaptive.upperLimit'); - gDebuggingEnabled = prefs.get("debug"); - prefs.observe("debug", this); }, _reconnect: function () { - debug("reconnect()"); + console.debug("reconnect()"); this._shutdownWS(false); this._reconnectAfterBackoff(); }, _shutdownWS: function(shouldCancelPending = true) { - debug("shutdownWS()"); + console.debug("shutdownWS()"); this._currentState = STATE_SHUT_DOWN; this._willBeWokenUpByUDP = false; @@ -373,7 +366,7 @@ this.PushServiceWebSocket = { if (this._mainPushService) { this._mainPushService.stopAlarm(); } else { - dump("This should not happend"); + console.error("shutdownWS: Uninitialized push service"); } if (shouldCancelPending) { @@ -387,8 +380,6 @@ this.PushServiceWebSocket = { }, uninit: function() { - prefs.ignore("debug", this); - if (this._udpServer) { this._udpServer.close(); this._udpServer = null; @@ -426,7 +417,7 @@ this.PushServiceWebSocket = { * cancelled), so the connection won't be reset. */ _reconnectAfterBackoff: function() { - debug("reconnectAfterBackoff()"); + console.debug("reconnectAfterBackoff()"); //Calculate new ping interval this._calculateAdaptivePing(true /* wsWentDown */); @@ -437,11 +428,12 @@ this.PushServiceWebSocket = { this._retryFailCount++; - debug("Retry in " + retryTimeout + " Try number " + this._retryFailCount); + console.debug("reconnectAfterBackoff: Retry in", retryTimeout, + "Try number", this._retryFailCount); if (this._mainPushService) { this._mainPushService.setAlarm(retryTimeout); } else { - dump("This should not happend"); + console.error("reconnectAfterBackoff: Uninitialized push service"); } }, @@ -471,22 +463,22 @@ this.PushServiceWebSocket = { * */ _calculateAdaptivePing: function(wsWentDown) { - debug('_calculateAdaptivePing()'); + console.debug("_calculateAdaptivePing()"); if (!this._adaptiveEnabled) { - debug('Adaptive ping is disabled'); + console.debug("calculateAdaptivePing: Adaptive ping is disabled"); return; } if (this._retryFailCount > 0) { - debug('Push has failed to connect to the Push Server ' + - this._retryFailCount + ' times. ' + - 'Do not calculate a new pingInterval now'); + console.warn("calculateAdaptivePing: Push has failed to connect to the", + "Push Server", this._retryFailCount, "times. Do not calculate a new", + "pingInterval now"); return; } if (!this._recalculatePing && !wsWentDown) { - debug('We do not need to recalculate the ping now, based on previous ' + - 'data'); + console.debug("calculateAdaptivePing: We do not need to recalculate the", + "ping now, based on previous data"); return; } @@ -495,15 +487,15 @@ this.PushServiceWebSocket = { if (ns.ip) { // mobile - debug('mobile'); + console.debug("calculateAdaptivePing: mobile"); let oldNetwork = prefs.get('adaptive.mobile'); let newNetwork = 'mobile-' + ns.mcc + '-' + ns.mnc; // Mobile networks differ, reset all intervals and pings if (oldNetwork !== newNetwork) { // Network differ, reset all values - debug('Mobile networks differ. Old network is ' + oldNetwork + - ' and new is ' + newNetwork); + console.debug("calculateAdaptivePing: Mobile networks differ. Old", + "network is", oldNetwork, "and new is", newNetwork); prefs.set('adaptive.mobile', newNetwork); //We reset the upper bound member this._recalculatePing = true; @@ -522,7 +514,7 @@ this.PushServiceWebSocket = { } else { // wifi - debug('wifi'); + console.debug("calculateAdaptivePing: wifi"); prefs.set('pingInterval', prefs.get('pingInterval.wifi')); this._lastGoodPingInterval = prefs.get('adaptive.lastGoodPingInterval.wifi'); } @@ -531,7 +523,8 @@ this.PushServiceWebSocket = { let lastTriedPingInterval = prefs.get('pingInterval'); if (wsWentDown) { - debug('The WebSocket was disconnected, calculating next ping'); + console.debug("calculateAdaptivePing: The WebSocket was disconnected.", + "Calculating next ping"); // If we have not tried this pingInterval yet, initialize this._pingIntervalRetryTimes[lastTriedPingInterval] = @@ -540,8 +533,9 @@ this.PushServiceWebSocket = { // Try the pingInterval at least 3 times, just to be sure that the // calculated interval is not valid. if (this._pingIntervalRetryTimes[lastTriedPingInterval] < 2) { - debug('pingInterval= ' + lastTriedPingInterval + ' tried only ' + - this._pingIntervalRetryTimes[lastTriedPingInterval] + ' times'); + console.debug("calculateAdaptivePing: pingInterval=", + lastTriedPingInterval, "tried only", + this._pingIntervalRetryTimes[lastTriedPingInterval], "times"); return; } @@ -552,32 +546,33 @@ this.PushServiceWebSocket = { // optimum, so stop calculating. if (nextPingInterval - this._lastGoodPingInterval < prefs.get('adaptive.gap')) { - debug('We have reached the gap, we have finished the calculation'); - debug('nextPingInterval=' + nextPingInterval); - debug('lastGoodPing=' + this._lastGoodPingInterval); + console.debug("calculateAdaptivePing: We have reached the gap, we", + "have finished the calculation. nextPingInterval=", nextPingInterval, + "lastGoodPing=", this._lastGoodPingInterval); nextPingInterval = this._lastGoodPingInterval; this._recalculatePing = false; } else { - debug('We need to calculate next time'); + console.debug("calculateAdaptivePing: We need to calculate next time"); this._recalculatePing = true; } } else { - debug('The WebSocket is still up'); + console.debug("calculateAdaptivePing: The WebSocket is still up"); this._lastGoodPingInterval = lastTriedPingInterval; nextPingInterval = Math.floor(lastTriedPingInterval * 1.5); } // Check if we have reached the upper limit if (this._upperLimit < nextPingInterval) { - debug('Next ping will be bigger than the configured upper limit, ' + - 'capping interval'); + console.debug("calculateAdaptivePing: Next ping will be bigger than the", + "configured upper limit, capping interval"); this._recalculatePing = false; this._lastGoodPingInterval = lastTriedPingInterval; nextPingInterval = lastTriedPingInterval; } - debug('Setting the pingInterval to ' + nextPingInterval); + console.debug("calculateAdaptivePing: Setting the pingInterval to", + nextPingInterval); prefs.set('pingInterval', nextPingInterval); //Save values for our current network @@ -594,11 +589,12 @@ this.PushServiceWebSocket = { _makeWebSocket: function(uri) { if (!prefs.get("connection.enabled")) { - debug("_makeWebSocket: connection.enabled is not set to true. Aborting."); + console.warn("makeWebSocket: connection.enabled is not set to true.", + "Aborting."); return null; } if (Services.io.offline) { - debug("Network is offline."); + console.warn("makeWebSocket: Network is offline."); return null; } let socket = Cc["@mozilla.org/network/protocol;1?name=wss"] @@ -614,10 +610,10 @@ this.PushServiceWebSocket = { }, _beginWSSetup: function() { - debug("beginWSSetup()"); + console.debug("beginWSSetup()"); if (this._currentState != STATE_SHUT_DOWN) { - debug("_beginWSSetup: Not in shutdown state! Current state " + - this._currentState); + console.error("_beginWSSetup: Not in shutdown state! Current state", + this._currentState); return; } @@ -636,7 +632,7 @@ this.PushServiceWebSocket = { } this._ws = socket.QueryInterface(Ci.nsIWebSocketChannel); - debug("serverURL: " + uri.spec); + console.debug("beginWSSetup: Connecting to", uri.spec); this._wsListener = new PushWebSocketListener(this); this._ws.protocol = "push-notification"; @@ -647,13 +643,14 @@ this.PushServiceWebSocket = { this._acquireWakeLock(); this._currentState = STATE_WAITING_FOR_WS_START; } catch(e) { - debug("Error opening websocket. asyncOpen failed!"); + console.error("beginWSSetup: Error opening websocket.", + "asyncOpen failed", e); this._reconnect(); } }, connect: function(records) { - debug("connect"); + console.debug("connect()"); // Check to see if we need to do anything. if (records.length > 0) { this._beginWSSetup(); @@ -694,7 +691,8 @@ this.PushServiceWebSocket = { // Conditions are arranged in decreasing specificity. // i.e. when _waitingForPong is true, other conditions are also true. if (this._waitingForPong) { - debug("Did not receive pong in time. Reconnecting WebSocket."); + console.debug("onAlarmFired: Did not receive pong in time.", + "Reconnecting WebSocket"); this._reconnect(); } else if (this._currentState == STATE_READY) { @@ -713,7 +711,7 @@ this.PushServiceWebSocket = { this._mainPushService.setAlarm(prefs.get("requestTimeout")); } else if (this._mainPushService && this._mainPushService._alarmID !== null) { - debug("reconnect alarm fired."); + console.debug("onAlarmFired: reconnect alarm fired"); // Reconnect after back-off. // The check for a non-null _alarmID prevents a situation where the alarm // fires, but _shutdownWS() is called from another code-path (e.g. @@ -738,16 +736,16 @@ this.PushServiceWebSocket = { // Disable the wake lock on non-B2G platforms to work around bug 1154492. if (!this._socketWakeLock) { - debug("Acquiring Socket Wakelock"); + console.debug("acquireWakeLock: Acquiring Socket Wakelock"); this._socketWakeLock = gPowerManagerService.newWakeLock("cpu"); } if (!this._socketWakeLockTimer) { - debug("Creating Socket WakeLock Timer"); + console.debug("acquireWakeLock: Creating Socket WakeLock Timer"); this._socketWakeLockTimer = Cc["@mozilla.org/timer;1"] .createInstance(Ci.nsITimer); } - debug("Setting Socket WakeLock Timer"); + console.debug("acquireWakeLock: Setting Socket WakeLock Timer"); this._socketWakeLockTimer .initWithCallback(this._releaseWakeLock.bind(this), // Allow the same time for socket setup as we do for @@ -763,7 +761,7 @@ this.PushServiceWebSocket = { return; } - debug("Releasing Socket WakeLock"); + console.debug("releaseWakeLock: Releasing Socket WakeLock"); if (this._socketWakeLockTimer) { this._socketWakeLockTimer.cancel(); } @@ -777,30 +775,30 @@ this.PushServiceWebSocket = { * Protocol handler invoked by server message. */ _handleHelloReply: function(reply) { - debug("handleHelloReply()"); + console.debug("handleHelloReply()"); if (this._currentState != STATE_WAITING_FOR_HELLO) { - debug("Unexpected state " + this._currentState + - "(expected STATE_WAITING_FOR_HELLO)"); + console.error("handleHelloReply: Unexpected state", this._currentState, + "(expected STATE_WAITING_FOR_HELLO)"); this._shutdownWS(); return; } if (typeof reply.uaid !== "string") { - debug("No UAID received or non string UAID received"); + console.error("handleHelloReply: Received invalid UAID", reply.uaid); this._shutdownWS(); return; } if (reply.uaid === "") { - debug("Empty UAID received!"); + console.error("handleHelloReply: Received empty UAID"); this._shutdownWS(); return; } // To avoid sticking extra large values sent by an evil server into prefs. if (reply.uaid.length > 128) { - debug("UAID received from server was too long: " + - reply.uaid); + console.error("handleHelloReply: UAID received from server was too long", + reply.uaid); this._shutdownWS(); return; } @@ -823,7 +821,8 @@ this.PushServiceWebSocket = { this._mainPushService.getAllUnexpired().then(records => Promise.all(records.map(record => this._mainPushService.ensureP256dhKey(record).catch(error => { - debug("finishHandshake: Error updating record " + record.keyID); + console.error("finishHandshake: Error updating record", + record.keyID, error); }) )) ).then(sendRequests); @@ -839,7 +838,7 @@ this.PushServiceWebSocket = { // workers if we receive a new UAID. This ensures we expunge all stale // registrations if the `userAgentID` pref is reset. if (this._UAID != reply.uaid) { - debug("got new UAID: all re-register"); + console.debug("handleHelloReply: Received new UAID"); this._mainPushService.dropRegistrations() .then(finishHandshake.bind(this)); @@ -855,7 +854,7 @@ this.PushServiceWebSocket = { * Protocol handler invoked by server message. */ _handleRegisterReply: function(reply) { - debug("handleRegisterReply()"); + console.debug("handleRegisterReply()"); if (typeof reply.channelID !== "string" || typeof this._registerRequests[reply.channelID] !== "object") { return; @@ -873,9 +872,7 @@ this.PushServiceWebSocket = { Services.io.newURI(reply.pushEndpoint, null, null); } catch (e) { - debug("Invalid pushEndpoint " + reply.pushEndpoint); - tmp.reject({state: 0, error: "Invalid pushEndpoint " + - reply.pushEndpoint}); + tmp.reject(new Error("Invalid push endpoint: " + reply.pushEndpoint)); return; } @@ -888,18 +885,20 @@ this.PushServiceWebSocket = { quota: tmp.record.maxQuota, ctime: Date.now(), }); - dump("PushWebSocket " + JSON.stringify(record)); Services.telemetry.getHistogramById("PUSH_API_SUBSCRIBE_WS_TIME").add(Date.now() - tmp.ctime); tmp.resolve(record); } else { - tmp.reject(reply); + console.error("handleRegisterReply: Unexpected server response", reply); + tmp.reject(new Error("Wrong status code for register reply: " + + reply.status)); } }, _handleDataUpdate: function(update) { let promise; if (typeof update.channelID != "string") { - debug("handleDataUpdate: Discarding message without channel ID"); + console.warn("handleDataUpdate: Discarding update without channel ID", + update); return; } // Unconditionally ack the update. This is important because the Push @@ -921,7 +920,8 @@ this.PushServiceWebSocket = { } else { let params = getCryptoParams(update.headers); if (!params) { - debug("handleDataUpdate: Discarding invalid encrypted message"); + console.warn("handleDataUpdate: Discarding invalid encrypted message", + update); return; } let message = base64UrlDecode(update.data); @@ -933,7 +933,7 @@ this.PushServiceWebSocket = { ); } promise.catch(err => { - debug("handleDataUpdate: Error delivering message: " + err); + console.error("handleDataUpdate: Error delivering message", err); }); }, @@ -941,28 +941,29 @@ this.PushServiceWebSocket = { * Protocol handler invoked by server message. */ _handleNotificationReply: function(reply) { - debug("handleNotificationReply()"); + console.debug("handleNotificationReply()"); if (this._dataEnabled) { this._handleDataUpdate(reply); return; } if (typeof reply.updates !== 'object') { - debug("No 'updates' field in response. Type = " + typeof reply.updates); + console.warn("handleNotificationReply: Missing updates", reply.updates); return; } - debug("Reply updates: " + reply.updates.length); + console.debug("handleNotificationReply: Got updates", reply.updates); for (let i = 0; i < reply.updates.length; i++) { let update = reply.updates[i]; - debug("Update: " + update.channelID + ": " + update.version); + console.debug("handleNotificationReply: Handling update", update); if (typeof update.channelID !== "string") { - debug("Invalid update literal at index " + i); + console.debug("handleNotificationReply: Invalid update at index", + i, update); continue; } if (update.version === undefined) { - debug("update.version does not exist"); + console.debug("handleNotificationReply: Missing version", update); continue; } @@ -983,7 +984,7 @@ this.PushServiceWebSocket = { // FIXME(nsm): batch acks for efficiency reasons. _sendAck: function(channelID, version) { - debug("sendAck()"); + console.debug("sendAck()"); var data = {messageType: 'ack', updates: [{channelID: channelID, version: version}] @@ -999,7 +1000,7 @@ this.PushServiceWebSocket = { }, request: function(action, record) { - debug("request() " + action); + console.debug("request() ", action); if (Object.keys(this._registerRequests).length === 0) { // start the timer since we now have at least one request @@ -1045,7 +1046,7 @@ this.PushServiceWebSocket = { _notifyRequestQueue: null, _queue: null, _enqueue: function(op) { - debug("enqueue"); + console.debug("enqueue()"); if (!this._queue) { this._queue = this._queueStart; } @@ -1101,29 +1102,30 @@ this.PushServiceWebSocket = { }, _receivedUpdate: function(aChannelID, aLatestVersion) { - debug("Updating: " + aChannelID + " -> " + aLatestVersion); + console.debug("receivedUpdate: Updating", aChannelID, "->", aLatestVersion); this._mainPushService.receivedPushMessage(aChannelID, null, null, record => { if (record.version === null || record.version < aLatestVersion) { - debug("Version changed for " + aChannelID + ": " + aLatestVersion); + console.debug("receivedUpdate: Version changed for", aChannelID, + aLatestVersion); record.version = aLatestVersion; return record; } - debug("No significant version change for " + aChannelID + ": " + - aLatestVersion); + console.debug("receivedUpdate: No significant version change for", + aChannelID, aLatestVersion); return null; }); }, // begin Push protocol handshake _wsOnStart: function(context) { - debug("wsOnStart()"); + console.debug("wsOnStart()"); this._releaseWakeLock(); if (this._currentState != STATE_WAITING_FOR_WS_START) { - debug("NOT in STATE_WAITING_FOR_WS_START. Current state " + - this._currentState + ". Skipping"); + console.error("wsOnStart: NOT in STATE_WAITING_FOR_WS_START. Current", + "state", this._currentState, "Skipping"); return; } @@ -1167,12 +1169,12 @@ this.PushServiceWebSocket = { * NS_BASE_STREAM_CLOSED, even on a successful close. */ _wsOnStop: function(context, statusCode) { - debug("wsOnStop()"); + console.debug("wsOnStop()"); this._releaseWakeLock(); if (statusCode != Cr.NS_OK && !(statusCode == Cr.NS_BASE_STREAM_CLOSED && this._willBeWokenUpByUDP)) { - debug("Socket error " + statusCode); + console.debug("wsOnStop: Socket error", statusCode); this._reconnect(); return; } @@ -1181,7 +1183,7 @@ this.PushServiceWebSocket = { }, _wsOnMessageAvailable: function(context, message) { - debug("wsOnMessageAvailable() " + message); + console.debug("wsOnMessageAvailable()", message); this._waitingForPong = false; @@ -1189,7 +1191,7 @@ this.PushServiceWebSocket = { try { reply = JSON.parse(message); } catch(e) { - debug("Parsing JSON failed. text : " + message); + console.warn("wsOnMessageAvailable: Invalid JSON", message, e); return; } @@ -1203,7 +1205,7 @@ this.PushServiceWebSocket = { (reply.messageType === undefined) || (reply.messageType === "ping") || (typeof reply.messageType != "string")) { - debug('Pong received'); + console.debug("wsOnMessageAvailable: Pong received"); this._calculateAdaptivePing(false); doNotHandle = true; } @@ -1227,15 +1229,16 @@ this.PushServiceWebSocket = { reply.messageType.slice(1).toLowerCase(); if (handlers.indexOf(handlerName) == -1) { - debug("No whitelisted handler " + handlerName + ". messageType: " + - reply.messageType); + console.warn("wsOnMessageAvailable: No whitelisted handler", handlerName, + "for message", reply.messageType); return; } let handler = "_handle" + handlerName + "Reply"; if (typeof this[handler] !== "function") { - debug("Handler whitelisted but not implemented! " + handler); + console.warn("wsOnMessageAvailable: Handler", handler, + "whitelisted but not implemented"); return; } @@ -1252,11 +1255,11 @@ this.PushServiceWebSocket = { * and stop reconnecting in _wsOnStop(). */ _wsOnServerClose: function(context, aStatusCode, aReason) { - debug("wsOnServerClose() " + aStatusCode + " " + aReason); + console.debug("wsOnServerClose()", aStatusCode, aReason); // Switch over to UDP. if (aStatusCode == kUDP_WAKEUP_WS_STATUS_CODE) { - debug("Server closed with promise to wake up"); + console.debug("wsOnServerClose: Server closed with promise to wake up"); this._willBeWokenUpByUDP = true; // TODO: there should be no pending requests } @@ -1269,7 +1272,7 @@ this.PushServiceWebSocket = { for (let channelID in this._registerRequests) { let request = this._registerRequests[channelID]; delete this._registerRequests[channelID]; - request.reject({status: 0, error: "AbortError"}); + request.reject(new Error("Register request aborted")); } }, @@ -1282,15 +1285,15 @@ this.PushServiceWebSocket = { * This method should be called only if the device is on a mobile network! */ _listenForUDPWakeup: function() { - debug("listenForUDPWakeup()"); + console.debug("listenForUDPWakeup()"); if (this._udpServer) { - debug("UDP Server already running"); + console.warn("listenForUDPWakeup: UDP Server already running"); return; } if (!prefs.get("udp.wakeupEnabled")) { - debug("UDP support disabled"); + console.debug("listenForUDPWakeup: UDP support disabled"); return; } @@ -1301,7 +1304,7 @@ this.PushServiceWebSocket = { this._udpServer = socket.QueryInterface(Ci.nsIUDPSocket); this._udpServer.init(-1, false, Services.scriptSecurityManager.getSystemPrincipal()); this._udpServer.asyncListen(this); - debug("listenForUDPWakeup listening on " + this._udpServer.port); + console.debug("listenForUDPWakeup: Listening on", this._udpServer.port); return this._udpServer.port; }, @@ -1311,7 +1314,8 @@ this.PushServiceWebSocket = { * reconnect the WebSocket and get the actual data. */ onPacketReceived: function(aServ, aMessage) { - debug("Recv UDP datagram on port: " + this._udpServer.port); + console.debug("onPacketReceived: Recv UDP datagram on port", + this._udpServer.port); this._beginWSSetup(); }, @@ -1322,7 +1326,8 @@ this.PushServiceWebSocket = { * notifications. */ onStopListening: function(aServ, aStatus) { - debug("UDP Server socket was shutdown. Status: " + aStatus); + console.debug("onStopListening: UDP Server socket was shutdown. Status", + aStatus); this._udpServer = undefined; this._beginWSSetup(); }, @@ -1333,11 +1338,12 @@ var PushNetworkInfo = { * Returns information about MCC-MNC and the IP of the current connection. */ getNetworkInformation: function() { - debug("getNetworkInformation()"); + console.debug("PushNetworkInfo: getNetworkInformation()"); try { if (!prefs.get("udp.wakeupEnabled")) { - debug("UDP support disabled, we do not send any carrier info"); + console.debug("getNetworkInformation: UDP support disabled, we do not", + "send any carrier info"); throw new Error("UDP disabled"); } @@ -1356,7 +1362,7 @@ var PushNetworkInfo = { let icc = iccService.getIccByServiceId(clientId); let iccInfo = icc && icc.iccInfo; if (iccInfo) { - debug("Running on mobile data"); + console.debug("getNetworkInformation: Running on mobile data"); let ips = {}; let prefixLengths = {}; @@ -1370,10 +1376,11 @@ var PushNetworkInfo = { } } } catch (e) { - debug("Error recovering mobile network information: " + e); + console.error("getNetworkInformation: Error recovering mobile network", + "information", e); } - debug("Running on wifi"); + console.debug("getNetworkInformation: Running on wifi"); return { mcc: 0, mnc: 0, @@ -1387,7 +1394,7 @@ var PushNetworkInfo = { * with an IP, and optionally a netid). */ getNetworkState: function(callback) { - debug("getNetworkState()"); + console.debug("PushNetworkInfo: getNetworkState()"); if (typeof callback !== 'function') { throw new Error("No callback method. Aborting push agent !"); @@ -1397,7 +1404,7 @@ var PushNetworkInfo = { if (networkInfo.ip) { this._getMobileNetworkId(networkInfo, function(netid) { - debug("Recovered netID = " + netid); + console.debug("getNetworkState: Recovered netID", netid); callback({ mcc: networkInfo.mcc, mnc: networkInfo.mnc, @@ -1419,22 +1426,21 @@ var PushNetworkInfo = { * Callback function to invoke with the netid or null if not found */ _getMobileNetworkId: function(networkInfo, callback) { + console.debug("PushNetworkInfo: getMobileNetworkId()"); if (typeof callback !== 'function') { return; } function queryDNSForDomain(domain) { - debug("[_getMobileNetworkId:queryDNSForDomain] Querying DNS for " + - domain); + console.debug("queryDNSForDomain: Querying DNS for", domain); let netIDDNSListener = { onLookupComplete: function(aRequest, aRecord, aStatus) { if (aRecord) { let netid = aRecord.getNextAddrAsString(); - debug("[_getMobileNetworkId:queryDNSForDomain] NetID found: " + - netid); + console.debug("queryDNSForDomain: NetID found", netid); callback(netid); } else { - debug("[_getMobileNetworkId:queryDNSForDomain] NetID not found"); + console.debug("queryDNSForDomain: NetID not found"); callback(null); } } @@ -1444,7 +1450,7 @@ var PushNetworkInfo = { return []; } - debug("[_getMobileNetworkId:queryDNSForDomain] Getting mobile network ID"); + console.debug("getMobileNetworkId: Getting mobile network ID"); let netidAddress = "wakeup.mnc" + ("00" + networkInfo.mnc).slice(-3) + ".mcc" + ("00" + networkInfo.mcc).slice(-3) + ".3gppnetwork.org"; diff --git a/dom/push/test/test_multiple_register.html b/dom/push/test/test_multiple_register.html index 34df563a755c..1f610ec35616 100644 --- a/dom/push/test/test_multiple_register.html +++ b/dom/push/test/test_multiple_register.html @@ -120,7 +120,6 @@ http://creativecommons.org/licenses/publicdomain/ SpecialPowers.pushPrefEnv({"set": [ ["dom.push.enabled", true], - ["dom.push.debug", true], ["dom.serviceWorkers.exemptFromPerDomainMax", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true] diff --git a/dom/push/test/xpcshell/head.js b/dom/push/test/xpcshell/head.js index 23bb77353a19..6c7d4c5cc87e 100644 --- a/dom/push/test/xpcshell/head.js +++ b/dom/push/test/xpcshell/head.js @@ -194,7 +194,7 @@ function disableServiceWorkerEvents(...scopes) { */ function setPrefs(prefs = {}) { let defaultPrefs = Object.assign({ - debug: true, + loglevel: 'all', serverURL: 'wss://push.example.org', 'connection.enabled': true, userAgentID: '', diff --git a/dom/push/test/xpcshell/test_reconnect_retry.js b/dom/push/test/xpcshell/test_reconnect_retry.js index 86022b6f66d3..aa264fb5b891 100644 --- a/dom/push/test/xpcshell/test_reconnect_retry.js +++ b/dom/push/test/xpcshell/test_reconnect_retry.js @@ -47,7 +47,7 @@ add_task(function* test_reconnect_retry() { this.serverSendMsg(JSON.stringify({ messageType: 'register', channelID: request.channelID, - pushEndpoint: 'https://example.org/push/' + registers, + pushEndpoint: 'https://example.org/push/' + request.channelID, status: 200, })); } @@ -59,13 +59,14 @@ add_task(function* test_reconnect_retry() { 'https://example.com/page/1', ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }) ); - equal(registration.channelID, channelID, 'Wrong channel ID for retried request'); + let retryEndpoint = 'https://example.org/push/' + channelID; + equal(registration.pushEndpoint, retryEndpoint, 'Wrong endpoint for retried request'); registration = yield PushNotificationService.register( 'https://example.com/page/2', ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }) ); - notEqual(registration.channelID, channelID, 'Wrong channel ID for new request'); + notEqual(registration.pushEndpoint, retryEndpoint, 'Wrong endpoint for new request') equal(registers, 3, 'Wrong registration count'); }); diff --git a/dom/push/test/xpcshell/test_register_5xxCode_http2.js b/dom/push/test/xpcshell/test_register_5xxCode_http2.js index e8e5649ca6ba..64bbbc8b49d0 100644 --- a/dom/push/test/xpcshell/test_register_5xxCode_http2.js +++ b/dom/push/test/xpcshell/test_register_5xxCode_http2.js @@ -91,15 +91,11 @@ add_task(function* test1() { var subscriptionUri = serverURL + '/subscription'; var pushEndpoint = serverURL + '/pushEndpoint'; var pushReceiptEndpoint = serverURL + '/receiptPushEndpoint'; - equal(newRecord.subscriptionUri, subscriptionUri, - 'Wrong subscription ID in registration record'); equal(newRecord.pushEndpoint, pushEndpoint, 'Wrong push endpoint in registration record'); equal(newRecord.pushReceiptEndpoint, pushReceiptEndpoint, 'Wrong push endpoint receipt in registration record'); - equal(newRecord.scope, 'https://example.com/retry5xxCode', - 'Wrong scope in registration record'); let record = yield db.getByKeyID(subscriptionUri); equal(record.subscriptionUri, subscriptionUri, diff --git a/dom/push/test/xpcshell/test_register_case.js b/dom/push/test/xpcshell/test_register_case.js index e0ff47b91047..1a922f1487d4 100644 --- a/dom/push/test/xpcshell/test_register_case.js +++ b/dom/push/test/xpcshell/test_register_case.js @@ -54,12 +54,8 @@ add_task(function* test_register_case() { ); equal(newRecord.pushEndpoint, 'https://example.com/update/case', 'Wrong push endpoint in registration record'); - equal(newRecord.scope, 'https://example.net/case', - 'Wrong scope in registration record'); - let record = yield db.getByKeyID(newRecord.channelID); - equal(record.pushEndpoint, 'https://example.com/update/case', - 'Wrong push endpoint in database record'); + let record = yield db.getByPushEndpoint('https://example.com/update/case'); equal(record.scope, 'https://example.net/case', 'Wrong scope in database record'); }); diff --git a/dom/push/test/xpcshell/test_register_error_http2.js b/dom/push/test/xpcshell/test_register_error_http2.js index 0b2296e61763..1dbd364ef82a 100644 --- a/dom/push/test/xpcshell/test_register_error_http2.js +++ b/dom/push/test/xpcshell/test_register_error_http2.js @@ -49,10 +49,7 @@ add_task(function* test_pushSubscriptionNoConnection() { PushNotificationService.register( 'https://example.net/page/invalid-response', ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })), - function(error) { - return error && error.includes("Error"); - }, - 'Wrong error for not being able to establish connecion.' + 'Expected error for not being able to establish connecion.' ); let record = yield db.getAllKeyIDs(); @@ -90,10 +87,7 @@ add_task(function* test_pushSubscriptionMissingLocation() { PushNotificationService.register( 'https://example.net/page/invalid-response', ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })), - function(error) { - return error && error.includes("Return code 201, but the answer is bogus"); - }, - 'Wrong error for the missing location header.' + 'Expected error for the missing location header.' ); let record = yield db.getAllKeyIDs(); @@ -117,10 +111,7 @@ add_task(function* test_pushSubscriptionMissingLink() { PushNotificationService.register( 'https://example.net/page/invalid-response', ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })), - function(error) { - return error && error.includes("Return code 201, but the answer is bogus"); - }, - 'Wrong error for the missing link header.' + 'Expected error for the missing link header.' ); let record = yield db.getAllKeyIDs(); @@ -144,10 +135,7 @@ add_task(function* test_pushSubscriptionMissingLink1() { PushNotificationService.register( 'https://example.net/page/invalid-response', ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })), - function(error) { - return error && error.includes("Return code 201, but the answer is bogus"); - }, - 'Wrong error for the missing push endpoint.' + 'Expected error for the missing push endpoint.' ); let record = yield db.getAllKeyIDs(); @@ -171,10 +159,7 @@ add_task(function* test_pushSubscriptionLocationBogus() { PushNotificationService.register( 'https://example.net/page/invalid-response', ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })), - function(error) { - return error && error.includes("Return code 201, but URI is bogus."); - }, - 'Wrong error for the bogus location' + 'Expected error for the bogus location' ); let record = yield db.getAllKeyIDs(); @@ -198,10 +183,7 @@ add_task(function* test_pushSubscriptionNot2xxCode() { PushNotificationService.register( 'https://example.net/page/invalid-response', ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })), - function(error) { - return error && error.includes("Error"); - }, - 'Wrong error for not 201 responce code.' + 'Expected error for not 201 responce code.' ); let record = yield db.getAllKeyIDs(); diff --git a/dom/push/test/xpcshell/test_register_flush.js b/dom/push/test/xpcshell/test_register_flush.js index b05775dcc6e1..ec6474398a54 100644 --- a/dom/push/test/xpcshell/test_register_flush.js +++ b/dom/push/test/xpcshell/test_register_flush.js @@ -80,8 +80,6 @@ add_task(function* test_register_flush() { 'https://example.com/page/2', ''); equal(newRecord.pushEndpoint, 'https://example.org/update/2', 'Wrong push endpoint in record'); - equal(newRecord.scope, 'https://example.com/page/2', - 'Wrong scope in record'); let {data: scope} = yield waitForPromise(notifyPromise, DEFAULT_TIMEOUT, 'Timed out waiting for notification'); @@ -97,8 +95,6 @@ add_task(function* test_register_flush() { strictEqual(prevRecord.version, 3, 'Should record version updates sent before register responses'); - let registeredRecord = yield db.getByKeyID(newRecord.channelID); - equal(registeredRecord.pushEndpoint, 'https://example.org/update/2', - 'Wrong new push endpoint'); + let registeredRecord = yield db.getByPushEndpoint('https://example.org/update/2'); ok(!registeredRecord.version, 'Should not record premature updates'); }); diff --git a/dom/push/test/xpcshell/test_register_invalid_channel.js b/dom/push/test/xpcshell/test_register_invalid_channel.js index fa1686a643ae..88d27284d377 100644 --- a/dom/push/test/xpcshell/test_register_invalid_channel.js +++ b/dom/push/test/xpcshell/test_register_invalid_channel.js @@ -50,10 +50,7 @@ add_task(function* test_register_invalid_channel() { yield rejects( PushNotificationService.register('https://example.com/invalid-channel', ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })), - function(error) { - return error == 'Invalid channel ID'; - }, - 'Wrong error for invalid channel ID' + 'Expected error for invalid channel ID' ); let record = yield db.getByKeyID(channelID); diff --git a/dom/push/test/xpcshell/test_register_invalid_endpoint.js b/dom/push/test/xpcshell/test_register_invalid_endpoint.js index fc111e8cb08d..85bb46a6e899 100644 --- a/dom/push/test/xpcshell/test_register_invalid_endpoint.js +++ b/dom/push/test/xpcshell/test_register_invalid_endpoint.js @@ -52,10 +52,7 @@ add_task(function* test_register_invalid_endpoint() { PushNotificationService.register( 'https://example.net/page/invalid-endpoint', ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })), - function(error) { - return error && error.includes('Invalid pushEndpoint'); - }, - 'Wrong error for invalid endpoint' + 'Expected error for invalid endpoint' ); let record = yield db.getByKeyID(channelID); diff --git a/dom/push/test/xpcshell/test_register_invalid_json.js b/dom/push/test/xpcshell/test_register_invalid_json.js index 6efaa83e31eb..febf3ea49408 100644 --- a/dom/push/test/xpcshell/test_register_invalid_json.js +++ b/dom/push/test/xpcshell/test_register_invalid_json.js @@ -51,10 +51,7 @@ add_task(function* test_register_invalid_json() { yield rejects( PushNotificationService.register('https://example.net/page/invalid-json', ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })), - function(error) { - return error == 'TimeoutError'; - }, - 'Wrong error for invalid JSON response' + 'Expected error for invalid JSON response' ); yield waitForPromise(helloPromise, DEFAULT_TIMEOUT, diff --git a/dom/push/test/xpcshell/test_register_no_id.js b/dom/push/test/xpcshell/test_register_no_id.js index 7c499df7f094..8ddabbb347e1 100644 --- a/dom/push/test/xpcshell/test_register_no_id.js +++ b/dom/push/test/xpcshell/test_register_no_id.js @@ -55,10 +55,7 @@ add_task(function* test_register_no_id() { yield rejects( PushNotificationService.register('https://example.com/incomplete', ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })), - function(error) { - return error == 'TimeoutError'; - }, - 'Wrong error for incomplete register response' + 'Expected error for incomplete register response' ); yield waitForPromise(helloPromise, DEFAULT_TIMEOUT, diff --git a/dom/push/test/xpcshell/test_register_request_queue.js b/dom/push/test/xpcshell/test_register_request_queue.js index b61ac73c0d62..7cc25729010b 100644 --- a/dom/push/test/xpcshell/test_register_request_queue.js +++ b/dom/push/test/xpcshell/test_register_request_queue.js @@ -55,12 +55,8 @@ add_task(function* test_register_request_queue() { ); yield waitForPromise(Promise.all([ - rejects(firstRegister, function(error) { - return error == 'TimeoutError'; - }, 'Should time out the first request'), - rejects(secondRegister, function(error) { - return error == 'TimeoutError'; - }, 'Should time out the second request') + rejects(firstRegister, 'Should time out the first request'), + rejects(secondRegister, 'Should time out the second request') ]), DEFAULT_TIMEOUT, 'Queued requests did not time out'); yield waitForPromise(helloPromise, DEFAULT_TIMEOUT, diff --git a/dom/push/test/xpcshell/test_register_rollback.js b/dom/push/test/xpcshell/test_register_rollback.js index 567a4c3c7239..ddb1eb2d8e1f 100644 --- a/dom/push/test/xpcshell/test_register_rollback.js +++ b/dom/push/test/xpcshell/test_register_rollback.js @@ -77,10 +77,7 @@ add_task(function* test_register_rollback() { yield rejects( PushNotificationService.register('https://example.com/storage-error', ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })), - function(error) { - return error == 'universe has imploded'; - }, - 'Wrong error for unregister database failure' + 'Expected error for unregister database failure' ); // Should send an out-of-band unregister request. diff --git a/dom/push/test/xpcshell/test_register_success.js b/dom/push/test/xpcshell/test_register_success.js index ce14cffd52c8..82c436518869 100644 --- a/dom/push/test/xpcshell/test_register_success.js +++ b/dom/push/test/xpcshell/test_register_success.js @@ -60,22 +60,14 @@ add_task(function* test_register_success() { 'https://example.org/1', ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }) ); - equal(newRecord.channelID, channelID, - 'Wrong channel ID in registration record'); equal(newRecord.pushEndpoint, 'https://example.com/update/1', 'Wrong push endpoint in registration record'); - equal(newRecord.scope, 'https://example.org/1', - 'Wrong scope in registration record'); - equal(newRecord.quota, Infinity, - 'Wrong quota in registration record'); let record = yield db.getByKeyID(channelID); equal(record.channelID, channelID, 'Wrong channel ID in database record'); equal(record.pushEndpoint, 'https://example.com/update/1', 'Wrong push endpoint in database record'); - equal(record.scope, 'https://example.org/1', - 'Wrong scope in database record'); equal(record.quota, Infinity, 'Wrong quota in database record'); }); diff --git a/dom/push/test/xpcshell/test_register_success_http2.js b/dom/push/test/xpcshell/test_register_success_http2.js index a993e64e6c55..7930ca7d86cc 100644 --- a/dom/push/test/xpcshell/test_register_success_http2.js +++ b/dom/push/test/xpcshell/test_register_success_http2.js @@ -64,15 +64,11 @@ add_task(function* test_pushSubscriptionSuccess() { var subscriptionUri = serverURL + '/pushSubscriptionSuccesss'; var pushEndpoint = serverURL + '/pushEndpointSuccess'; var pushReceiptEndpoint = serverURL + '/receiptPushEndpointSuccess'; - equal(newRecord.subscriptionUri, subscriptionUri, - 'Wrong subscription ID in registration record'); equal(newRecord.pushEndpoint, pushEndpoint, 'Wrong push endpoint in registration record'); equal(newRecord.pushReceiptEndpoint, pushReceiptEndpoint, 'Wrong push endpoint receipt in registration record'); - equal(newRecord.scope, 'https://example.org/1', - 'Wrong scope in registration record'); let record = yield db.getByKeyID(subscriptionUri); equal(record.subscriptionUri, subscriptionUri, @@ -107,15 +103,11 @@ add_task(function* test_pushSubscriptionMissingLink2() { var subscriptionUri = serverURL + '/subscriptionMissingLink2'; var pushEndpoint = serverURL + '/pushEndpointMissingLink2'; var pushReceiptEndpoint = ''; - equal(newRecord.subscriptionUri, subscriptionUri, - 'Wrong subscription ID in registration record'); equal(newRecord.pushEndpoint, pushEndpoint, 'Wrong push endpoint in registration record'); equal(newRecord.pushReceiptEndpoint, pushReceiptEndpoint, 'Wrong push endpoint receipt in registration record'); - equal(newRecord.scope, 'https://example.org/no_receiptEndpoint', - 'Wrong scope in registration record'); let record = yield db.getByKeyID(subscriptionUri); equal(record.subscriptionUri, subscriptionUri, diff --git a/dom/push/test/xpcshell/test_register_timeout.js b/dom/push/test/xpcshell/test_register_timeout.js index 7c558f3cfa27..12e604f6b467 100644 --- a/dom/push/test/xpcshell/test_register_timeout.js +++ b/dom/push/test/xpcshell/test_register_timeout.js @@ -77,10 +77,7 @@ add_task(function* test_register_timeout() { yield rejects( PushNotificationService.register('https://example.net/page/timeout', ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })), - function(error) { - return error == 'TimeoutError'; - }, - 'Wrong error for request timeout' + 'Expected error for request timeout' ); let record = yield db.getByKeyID(channelID); diff --git a/dom/push/test/xpcshell/test_register_wrong_id.js b/dom/push/test/xpcshell/test_register_wrong_id.js index 4ba8003e1501..7fc47cb94843 100644 --- a/dom/push/test/xpcshell/test_register_wrong_id.js +++ b/dom/push/test/xpcshell/test_register_wrong_id.js @@ -61,10 +61,7 @@ add_task(function* test_register_wrong_id() { yield rejects( PushNotificationService.register('https://example.com/mismatched', ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })), - function(error) { - return error == 'TimeoutError'; - }, - 'Wrong error for mismatched register reply' + 'Expected error for mismatched register reply' ); yield waitForPromise(helloPromise, DEFAULT_TIMEOUT, diff --git a/dom/push/test/xpcshell/test_register_wrong_type.js b/dom/push/test/xpcshell/test_register_wrong_type.js index d08996cae527..9793d4f94976 100644 --- a/dom/push/test/xpcshell/test_register_wrong_type.js +++ b/dom/push/test/xpcshell/test_register_wrong_type.js @@ -52,15 +52,10 @@ add_task(function* test_register_wrong_type() { } }); - let promise = - yield rejects( PushNotificationService.register('https://example.com/mistyped', ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })), - function(error) { - return error == 'TimeoutError'; - }, - 'Wrong error for non-string channel ID' + 'Expected error for non-string channel ID' ); yield waitForPromise(helloPromise, DEFAULT_TIMEOUT, diff --git a/dom/push/test/xpcshell/test_registration_missing_scope.js b/dom/push/test/xpcshell/test_registration_missing_scope.js index c32955aead13..b1b4a79a027c 100644 --- a/dom/push/test/xpcshell/test_registration_missing_scope.js +++ b/dom/push/test/xpcshell/test_registration_missing_scope.js @@ -21,9 +21,6 @@ add_task(function* test_registration_missing_scope() { }); yield rejects( PushNotificationService.registration('', ''), - function(error) { - return error.error == 'NotFoundError'; - }, 'Record missing page and manifest URLs' ); }); diff --git a/dom/push/test/xpcshell/test_unregister_empty_scope.js b/dom/push/test/xpcshell/test_unregister_empty_scope.js index 63846cf7a373..c3760be12186 100644 --- a/dom/push/test/xpcshell/test_unregister_empty_scope.js +++ b/dom/push/test/xpcshell/test_unregister_empty_scope.js @@ -31,9 +31,6 @@ add_task(function* test_unregister_empty_scope() { yield rejects( PushNotificationService.unregister('', ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })), - function(error) { - return error.error == 'NotFoundError'; - }, - 'Wrong error for empty endpoint' + 'Expected error for empty endpoint' ); }); diff --git a/dom/push/test/xpcshell/test_updateRecordNoEncryptionKeys_ws.js b/dom/push/test/xpcshell/test_updateRecordNoEncryptionKeys_ws.js index 527de694378e..a3b8bf53806a 100644 --- a/dom/push/test/xpcshell/test_updateRecordNoEncryptionKeys_ws.js +++ b/dom/push/test/xpcshell/test_updateRecordNoEncryptionKeys_ws.js @@ -77,8 +77,7 @@ add_task(function* test_with_data_enabled() { 'https://example.com/page/3', ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }) ); - ok(newRecord.p256dhPublicKey, 'Should generate public keys for new records'); - ok(newRecord.p256dhPrivateKey, 'Should generate private keys for new records'); + ok(newRecord.p256dhKey, 'Should generate public keys for new records'); let record = yield db.getByKeyID('eb18f12a-cc42-4f14-accb-3bfc1227f1aa'); ok(record.p256dhPublicKey, 'Should add public key to partial record'); diff --git a/dom/workers/RuntimeService.cpp b/dom/workers/RuntimeService.cpp index 5f1e4ef3d68e..76487e6be061 100644 --- a/dom/workers/RuntimeService.cpp +++ b/dom/workers/RuntimeService.cpp @@ -1590,7 +1590,6 @@ RuntimeService::RemoveSharedWorker(WorkerDomainInfo* aDomainInfo, SharedWorkerInfo* data = iter.UserData(); if (data->mWorkerPrivate == aWorkerPrivate) { #ifdef DEBUG - fprintf(stderr, "njn: RemoveSharedWorker\n"); nsAutoCString key; GenerateSharedWorkerKey(data->mScriptSpec, data->mName, aWorkerPrivate->IsInPrivateBrowsing(), key); diff --git a/editor/reftests/reftest.list b/editor/reftests/reftest.list index 0bcd5cd6e76f..3c70ecf28609 100644 --- a/editor/reftests/reftest.list +++ b/editor/reftests/reftest.list @@ -82,7 +82,7 @@ needs-focus == spellcheck-non-latin-japanese.html spellcheck-non-latin-japanese- needs-focus == spellcheck-non-latin-korean.html spellcheck-non-latin-korean-ref.html == unneeded_scroll.html unneeded_scroll-ref.html skip-if(B2G||Mulet) == caret_on_presshell_reinit.html caret_on_presshell_reinit-ref.html # Initial mulet triage: parity with B2G/B2G Desktop -skip-if(B2G||Mulet) == caret_on_presshell_reinit-2.html caret_on_presshell_reinit-ref.html # Initial mulet triage: parity with B2G/B2G Desktop +skip-if(B2G||Mulet) fuzzy-if(browserIsRemote,255,3) asserts-if(browserIsRemote,0-1) == caret_on_presshell_reinit-2.html caret_on_presshell_reinit-ref.html # Initial mulet triage: parity with B2G/B2G Desktop skip-if(B2G||Mulet) fuzzy-if(asyncPan&&!layersGPUAccelerated,102,2824) == 642800.html 642800-ref.html # Initial mulet triage: parity with B2G/B2G Desktop == selection_visibility_after_reframe.html selection_visibility_after_reframe-ref.html != selection_visibility_after_reframe-2.html selection_visibility_after_reframe-ref.html diff --git a/gfx/angle/src/commit.h b/gfx/angle/src/commit.h index 290a8f1a5009..08c200311a4e 100644 --- a/gfx/angle/src/commit.h +++ b/gfx/angle/src/commit.h @@ -1,3 +1,3 @@ -#define ANGLE_COMMIT_HASH "ffabe8783f4a" +#define ANGLE_COMMIT_HASH "c7fc1b46df29" #define ANGLE_COMMIT_HASH_SIZE 12 -#define ANGLE_COMMIT_DATE "2015-10-14 14:24:04 -0400" +#define ANGLE_COMMIT_DATE "2015-11-09 16:31:17 -0500" diff --git a/gfx/angle/src/libANGLE/moz.build b/gfx/angle/src/libANGLE/moz.build index d38c3de81d6f..3a87287c62d2 100644 --- a/gfx/angle/src/libANGLE/moz.build +++ b/gfx/angle/src/libANGLE/moz.build @@ -304,6 +304,7 @@ LOCAL_INCLUDES += [ '../../include', '../../src', '../../src/third_party/khronos DEFINES['LIBANGLE_IMPLEMENTATION'] = "1" DEFINES['ANGLE_ENABLE_HLSL'] = "1" DEFINES['ANGLE_ENABLE_KEYEDMUTEX'] = "1" +DEFINES['ANGLE_DEFAULT_D3D11'] = "0" if CONFIG['MOZ_HAS_WINSDK_WITH_D3D']: OS_LIBS += [ 'd3d9', 'dxguid' ] diff --git a/gfx/angle/src/tests/deqp_support/dEQP-EGL-cases.txt.gz b/gfx/angle/src/tests/deqp_support/dEQP-EGL-cases.txt.gz index e69de29bb2d1..56a937c014ec 100644 Binary files a/gfx/angle/src/tests/deqp_support/dEQP-EGL-cases.txt.gz and b/gfx/angle/src/tests/deqp_support/dEQP-EGL-cases.txt.gz differ diff --git a/gfx/angle/src/tests/deqp_support/dEQP-GLES2-cases.txt.gz b/gfx/angle/src/tests/deqp_support/dEQP-GLES2-cases.txt.gz index e69de29bb2d1..0748b7843789 100644 Binary files a/gfx/angle/src/tests/deqp_support/dEQP-GLES2-cases.txt.gz and b/gfx/angle/src/tests/deqp_support/dEQP-GLES2-cases.txt.gz differ diff --git a/gfx/angle/src/tests/deqp_support/dEQP-GLES3-cases.txt.gz b/gfx/angle/src/tests/deqp_support/dEQP-GLES3-cases.txt.gz index e69de29bb2d1..16f7f867f3af 100644 Binary files a/gfx/angle/src/tests/deqp_support/dEQP-GLES3-cases.txt.gz and b/gfx/angle/src/tests/deqp_support/dEQP-GLES3-cases.txt.gz differ diff --git a/gfx/layers/apz/src/AsyncPanZoomController.cpp b/gfx/layers/apz/src/AsyncPanZoomController.cpp index 400f37bb8c56..41fa2968d040 100644 --- a/gfx/layers/apz/src/AsyncPanZoomController.cpp +++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp @@ -290,11 +290,19 @@ using mozilla::gfx::PointTyped; * \li\b apz.test.logging_enabled * Enable logging of APZ test data (see bug 961289). * + * \li\b apz.touch_move_tolerance + * See the description for apz.touch_start_tolerance below. This is a similar + * threshold, except it is used to suppress touchmove events from being delivered + * to content for NON-scrollable frames (or more precisely, for APZCs where + * ArePointerEventsConsumable returns false).\n + * Units: (real-world, i.e. screen) inches + * * \li\b apz.touch_start_tolerance * Constant describing the tolerance in distance we use, multiplied by the * device DPI, before we start panning the screen. This is to prevent us from * accidentally processing taps as touch moves, and from very short/accidental - * touches moving the screen.\n + * touches moving the screen. touchmove events are also not delivered to content + * within this distance on scrollable frames.\n * Units: (real-world, i.e. screen) inches * * \li\b apz.use_paint_duration diff --git a/gfx/layers/apz/src/InputBlockState.cpp b/gfx/layers/apz/src/InputBlockState.cpp index e66609cdcf3b..dacd8780ee4f 100644 --- a/gfx/layers/apz/src/InputBlockState.cpp +++ b/gfx/layers/apz/src/InputBlockState.cpp @@ -831,7 +831,8 @@ TouchBlockState::TouchActionAllowsPanningXY() const } bool -TouchBlockState::UpdateSlopState(const MultiTouchInput& aInput) +TouchBlockState::UpdateSlopState(const MultiTouchInput& aInput, + bool aApzcCanConsumeEvents) { if (aInput.mType == MultiTouchInput::MULTITOUCH_START) { // this is by definition the first event in this block. If it's the first @@ -844,10 +845,12 @@ TouchBlockState::UpdateSlopState(const MultiTouchInput& aInput) return false; } if (mInSlop) { + ScreenCoord threshold = aApzcCanConsumeEvents + ? AsyncPanZoomController::GetTouchStartTolerance() + : ScreenCoord(gfxPrefs::APZTouchMoveTolerance() * APZCTreeManager::GetDPI()); bool stayInSlop = (aInput.mType == MultiTouchInput::MULTITOUCH_MOVE) && (aInput.mTouches.Length() == 1) && - ((aInput.mTouches[0].mScreenPoint - mSlopOrigin).Length() < - AsyncPanZoomController::GetTouchStartTolerance()); + ((aInput.mTouches[0].mScreenPoint - mSlopOrigin).Length() < threshold); if (!stayInSlop) { // we're out of the slop zone, and will stay out for the remainder of // this block diff --git a/gfx/layers/apz/src/InputBlockState.h b/gfx/layers/apz/src/InputBlockState.h index 39eeca1c2fb8..012eefc5f83b 100644 --- a/gfx/layers/apz/src/InputBlockState.h +++ b/gfx/layers/apz/src/InputBlockState.h @@ -434,11 +434,13 @@ public: * Notifies the input block of an incoming touch event so that the block can * update its internal slop state. "Slop" refers to the area around the * initial touchstart where we drop touchmove events so that content doesn't - * see them. + * see them. The |aApzcCanConsumeEvents| parameter is factored into how large + * the slop area is - if this is true the slop area is larger. * @return true iff the provided event is a touchmove in the slop area and * so should not be sent to content. */ - bool UpdateSlopState(const MultiTouchInput& aInput); + bool UpdateSlopState(const MultiTouchInput& aInput, + bool aApzcCanConsumeEvents); bool HasEvents() const override; void DropEvents() override; diff --git a/gfx/layers/apz/src/InputQueue.cpp b/gfx/layers/apz/src/InputQueue.cpp index ee0cc29a06bb..3db4391dd050 100644 --- a/gfx/layers/apz/src/InputQueue.cpp +++ b/gfx/layers/apz/src/InputQueue.cpp @@ -160,12 +160,15 @@ InputQueue::ReceiveTouchInput(const RefPtr& aTarget, INPQ_LOG("dropping event due to block %p being in fast motion\n", block); result = nsEventStatus_eConsumeNoDefault; } else if (target && target->ArePointerEventsConsumable(block, aEvent.AsMultiTouchInput().mTouches.Length())) { - if (block->UpdateSlopState(aEvent.AsMultiTouchInput())) { + if (block->UpdateSlopState(aEvent.AsMultiTouchInput(), true)) { INPQ_LOG("dropping event due to block %p being in slop\n", block); result = nsEventStatus_eConsumeNoDefault; } else { result = nsEventStatus_eConsumeDoDefault; } + } else if (block->UpdateSlopState(aEvent.AsMultiTouchInput(), false)) { + INPQ_LOG("dropping event due to block %p being in mini-slop\n", block); + result = nsEventStatus_eConsumeNoDefault; } if (!MaybeHandleCurrentBlock(block, aEvent)) { block->AddEvent(aEvent.AsMultiTouchInput()); diff --git a/gfx/tests/gtest/TestVsync.cpp b/gfx/tests/gtest/TestVsync.cpp index d50ebbd4f13d..f13afc983fa4 100644 --- a/gfx/tests/gtest/TestVsync.cpp +++ b/gfx/tests/gtest/TestVsync.cpp @@ -195,3 +195,12 @@ TEST_F(VsyncTester, ChildRefreshDriverGetVsyncNotifications) vsyncDispatcher = nullptr; testVsyncObserver = nullptr; } + +// Test that we can read the vsync rate +TEST_F(VsyncTester, VsyncSourceHasVsyncRate) +{ + VsyncSource::Display& globalDisplay = mVsyncSource->GetGlobalDisplay(); + TimeDuration vsyncRate = globalDisplay.GetVsyncRate(); + ASSERT_NE(vsyncRate, TimeDuration::Forever()); + ASSERT_GT(vsyncRate.ToMilliseconds(), 0); +} diff --git a/gfx/thebes/gfxPrefs.h b/gfx/thebes/gfxPrefs.h index 7d10c28030a1..8cc134fbd747 100644 --- a/gfx/thebes/gfxPrefs.h +++ b/gfx/thebes/gfxPrefs.h @@ -181,6 +181,7 @@ private: DECL_GFX_PREF(Live, "apz.printtree", APZPrintTree, bool, false); DECL_GFX_PREF(Live, "apz.smooth_scroll_repaint_interval", APZSmoothScrollRepaintInterval, int32_t, 75); DECL_GFX_PREF(Live, "apz.test.logging_enabled", APZTestLoggingEnabled, bool, false); + DECL_GFX_PREF(Live, "apz.touch_move_tolerance", APZTouchMoveTolerance, float, 0.0); DECL_GFX_PREF(Live, "apz.touch_start_tolerance", APZTouchStartTolerance, float, 1.0f/4.5f); DECL_GFX_PREF(Live, "apz.use_paint_duration", APZUsePaintDuration, bool, true); DECL_GFX_PREF(Live, "apz.velocity_bias", APZVelocityBias, float, 1.0f); diff --git a/js/src/jit/mips-shared/MacroAssembler-mips-shared.cpp b/js/src/jit/mips-shared/MacroAssembler-mips-shared.cpp index b2583c9395e2..e1d0737e0ae7 100644 --- a/js/src/jit/mips-shared/MacroAssembler-mips-shared.cpp +++ b/js/src/jit/mips-shared/MacroAssembler-mips-shared.cpp @@ -1137,12 +1137,16 @@ MacroAssembler::call(Label* label) CodeOffsetLabel MacroAssembler::callWithPatch() { - MOZ_CRASH("NYI"); + addLongJump(nextOffset()); + ma_liPatchable(ScratchRegister, ImmWord(0)); + return call(ScratchRegister); } + void MacroAssembler::patchCall(uint32_t callerOffset, uint32_t calleeOffset) { - MOZ_CRASH("NYI"); + BufferOffset li(callerOffset - 6 * sizeof(uint32_t)); + Assembler::UpdateLoad64Value(editSrc(li), calleeOffset); } void diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index 679eab36a7c9..3b74ee5608e9 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -149,10 +149,10 @@ typedef HashMapStyleDisplay()->mPosition == NS_STYLE_POSITION_FIXED && nsLayoutUtils::IsReallyFixedPos(f)) { - nsIPresShell* ps = f->PresContext()->PresShell(); - // We may want to do this in every document (not just root documents) at - // some point in the future. - if (ps->GetDocument() && ps->GetDocument()->IsRootDisplayDocument()) { - return ps->GetRootScrollFrameAsScrollable(); - } + return f->PresContext()->PresShell()->GetRootScrollFrameAsScrollable(); } } return nullptr; diff --git a/layout/base/nsPresShell.cpp b/layout/base/nsPresShell.cpp index 48ba6e13f1a9..b8f7669fb352 100644 --- a/layout/base/nsPresShell.cpp +++ b/layout/base/nsPresShell.cpp @@ -860,7 +860,7 @@ PresShell::Init(nsIDocument* aDocument, mViewManager = aViewManager; // Create our frame constructor. - mFrameConstructor = new nsCSSFrameConstructor(mDocument, this, aStyleSet); + mFrameConstructor = new nsCSSFrameConstructor(mDocument, this); mFrameManager = mFrameConstructor; @@ -6988,7 +6988,7 @@ PresShell::HandleEvent(nsIFrame* aFrame, aFrame = targetContent->GetPrimaryFrame(); if (!aFrame) { PushCurrentEventInfo(aFrame, targetContent); - nsresult rv = HandleEventInternal(aEvent, aEventStatus); + nsresult rv = HandleEventInternal(aEvent, aEventStatus, true); PopCurrentEventInfo(); return rv; } @@ -7645,7 +7645,7 @@ PresShell::HandleEvent(nsIFrame* aFrame, mCurrentEventFrame = frame; } if (GetCurrentEventFrame()) { - rv = HandleEventInternal(aEvent, aEventStatus); + rv = HandleEventInternal(aEvent, aEventStatus, true); } #ifdef DEBUG @@ -7658,7 +7658,7 @@ PresShell::HandleEvent(nsIFrame* aFrame, if (!NS_EVENT_NEEDS_FRAME(aEvent)) { mCurrentEventFrame = nullptr; - return HandleEventInternal(aEvent, aEventStatus); + return HandleEventInternal(aEvent, aEventStatus, true); } else if (aEvent->HasKeyEventMessage()) { // Keypress events in new blank tabs should not be completely thrown away. @@ -7761,7 +7761,7 @@ PresShell::HandlePositionedEvent(nsIFrame* aTargetFrame, } if (GetCurrentEventFrame()) { - rv = HandleEventInternal(aEvent, aEventStatus); + rv = HandleEventInternal(aEvent, aEventStatus, true); } #ifdef DEBUG @@ -7787,13 +7787,15 @@ PresShell::HandleEventWithTarget(WidgetEvent* aEvent, nsIFrame* aFrame, NS_ENSURE_STATE(!aContent || aContent->GetCrossShadowCurrentDoc() == mDocument); PushCurrentEventInfo(aFrame, aContent); - nsresult rv = HandleEventInternal(aEvent, aStatus); + nsresult rv = HandleEventInternal(aEvent, aStatus, false); PopCurrentEventInfo(); return rv; } nsresult -PresShell::HandleEventInternal(WidgetEvent* aEvent, nsEventStatus* aStatus) +PresShell::HandleEventInternal(WidgetEvent* aEvent, + nsEventStatus* aStatus, + bool aIsHandlingNativeEvent) { RefPtr manager = mPresContext->EventStateManager(); nsresult rv = NS_OK; @@ -7939,6 +7941,14 @@ PresShell::HandleEventInternal(WidgetEvent* aEvent, nsEventStatus* aStatus) } } + if (!mIsDestroying && aIsHandlingNativeEvent) { + // Ensure that notifications to IME should be sent before getting next + // native event from the event queue. + // XXX Should we check the event message or event class instead of + // using aIsHandlingNativeEvent? + manager->TryToFlushPendingNotificationsToIME(); + } + switch (aEvent->mMessage) { case eKeyPress: case eKeyDown: diff --git a/layout/base/nsPresShell.h b/layout/base/nsPresShell.h index 74f95e24d515..1cf936977f8e 100644 --- a/layout/base/nsPresShell.h +++ b/layout/base/nsPresShell.h @@ -580,7 +580,7 @@ protected: mCurrentEventContent = aTarget; nsresult rv = NS_OK; if (GetCurrentEventFrame()) { - rv = HandleEventInternal(aEvent, aStatus); + rv = HandleEventInternal(aEvent, aStatus, true); } PopCurrentEventInfo(); return rv; @@ -669,8 +669,14 @@ protected: nsEventStatus* aEventStatus); void PushCurrentEventInfo(nsIFrame* aFrame, nsIContent* aContent); void PopCurrentEventInfo(); + /** + * @param aIsHandlingNativeEvent true when the caller (perhaps) handles + * an event which is caused by native + * event. Otherwise, false. + */ nsresult HandleEventInternal(mozilla::WidgetEvent* aEvent, - nsEventStatus* aStatus); + nsEventStatus* aStatus, + bool aIsHandlingNativeEvent); nsresult HandlePositionedEvent(nsIFrame* aTargetFrame, mozilla::WidgetGUIEvent* aEvent, nsEventStatus* aEventStatus); diff --git a/layout/style/CSSStyleSheet.cpp b/layout/style/CSSStyleSheet.cpp index d2d32ccf243a..5a6d64148ef7 100644 --- a/layout/style/CSSStyleSheet.cpp +++ b/layout/style/CSSStyleSheet.cpp @@ -471,13 +471,16 @@ nsMediaQuery::AppendToString(nsAString& aString) const aString.Append('('); const nsMediaExpression &expr = mExpressions[i]; + const nsMediaFeature *feature = expr.mFeature; + if (feature->mReqFlags & nsMediaFeature::eHasWebkitPrefix) { + aString.AppendLiteral("-webkit-"); + } if (expr.mRange == nsMediaExpression::eMin) { aString.AppendLiteral("min-"); } else if (expr.mRange == nsMediaExpression::eMax) { aString.AppendLiteral("max-"); } - const nsMediaFeature *feature = expr.mFeature; aString.Append(nsDependentAtomString(*feature->mName)); if (expr.mValue.GetUnit() != eCSSUnit_Null) { diff --git a/layout/style/crashtests/1221902.html b/layout/style/crashtests/1221902.html deleted file mode 100644 index 4186a38b95d1..000000000000 --- a/layout/style/crashtests/1221902.html +++ /dev/null @@ -1,38002 +0,0 @@ - diff --git a/layout/style/crashtests/crashtests.list b/layout/style/crashtests/crashtests.list index c541ed9fdf81..b2ee1748885f 100644 --- a/layout/style/crashtests/crashtests.list +++ b/layout/style/crashtests/crashtests.list @@ -132,4 +132,3 @@ load border-image-visited-link.html load font-face-truncated-src.html load large_border_image_width.html load long-url-list-stack-overflow.html -load 1221902.html diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index f624abd82c59..75d083cf163e 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -3410,22 +3410,35 @@ CSSParserImpl::ParseMediaQueryExpression(nsMediaQuery* aQuery) // case insensitive from CSS - must be lower cased nsContentUtils::ASCIIToLower(mToken.mIdent); - const char16_t *featureString; - if (StringBeginsWith(mToken.mIdent, NS_LITERAL_STRING("min-"))) { + nsDependentString featureString(mToken.mIdent, 0); + uint8_t satisfiedReqFlags = 0; + + // Strip off "-webkit-" prefix from featureString: + if (sWebkitPrefixedAliasesEnabled && + StringBeginsWith(featureString, NS_LITERAL_STRING("-webkit-"))) { + satisfiedReqFlags |= nsMediaFeature::eHasWebkitPrefix; + featureString.Rebind(featureString, 8); + } + + // Strip off "min-"/"max-" prefix from featureString: + if (StringBeginsWith(featureString, NS_LITERAL_STRING("min-"))) { expr->mRange = nsMediaExpression::eMin; - featureString = mToken.mIdent.get() + 4; - } else if (StringBeginsWith(mToken.mIdent, NS_LITERAL_STRING("max-"))) { + featureString.Rebind(featureString, 4); + } else if (StringBeginsWith(featureString, NS_LITERAL_STRING("max-"))) { expr->mRange = nsMediaExpression::eMax; - featureString = mToken.mIdent.get() + 4; + featureString.Rebind(featureString, 4); } else { expr->mRange = nsMediaExpression::eEqual; - featureString = mToken.mIdent.get(); } nsCOMPtr mediaFeatureAtom = do_GetAtom(featureString); const nsMediaFeature *feature = nsMediaFeatures::features; for (; feature->mName; ++feature) { - if (*(feature->mName) == mediaFeatureAtom) { + // See if name matches & all requirement flags are satisfied: + // (We check requirements by turning off all of the flags that have been + // satisfied, and then see if the result is 0.) + if (*(feature->mName) == mediaFeatureAtom && + !(feature->mReqFlags & ~satisfiedReqFlags)) { break; } } diff --git a/layout/style/nsMediaFeatures.cpp b/layout/style/nsMediaFeatures.cpp index 4613daada324..fe08bdd40b89 100644 --- a/layout/style/nsMediaFeatures.cpp +++ b/layout/style/nsMediaFeatures.cpp @@ -420,6 +420,7 @@ nsMediaFeatures::features[] = { &nsGkAtoms::width, nsMediaFeature::eMinMaxAllowed, nsMediaFeature::eLength, + nsMediaFeature::eNoRequirements, { nullptr }, GetWidth }, @@ -427,6 +428,7 @@ nsMediaFeatures::features[] = { &nsGkAtoms::height, nsMediaFeature::eMinMaxAllowed, nsMediaFeature::eLength, + nsMediaFeature::eNoRequirements, { nullptr }, GetHeight }, @@ -434,6 +436,7 @@ nsMediaFeatures::features[] = { &nsGkAtoms::deviceWidth, nsMediaFeature::eMinMaxAllowed, nsMediaFeature::eLength, + nsMediaFeature::eNoRequirements, { nullptr }, GetDeviceWidth }, @@ -441,6 +444,7 @@ nsMediaFeatures::features[] = { &nsGkAtoms::deviceHeight, nsMediaFeature::eMinMaxAllowed, nsMediaFeature::eLength, + nsMediaFeature::eNoRequirements, { nullptr }, GetDeviceHeight }, @@ -448,6 +452,7 @@ nsMediaFeatures::features[] = { &nsGkAtoms::orientation, nsMediaFeature::eMinMaxNotAllowed, nsMediaFeature::eEnumerated, + nsMediaFeature::eNoRequirements, { kOrientationKeywords }, GetOrientation }, @@ -455,6 +460,7 @@ nsMediaFeatures::features[] = { &nsGkAtoms::aspectRatio, nsMediaFeature::eMinMaxAllowed, nsMediaFeature::eIntRatio, + nsMediaFeature::eNoRequirements, { nullptr }, GetAspectRatio }, @@ -462,6 +468,7 @@ nsMediaFeatures::features[] = { &nsGkAtoms::deviceAspectRatio, nsMediaFeature::eMinMaxAllowed, nsMediaFeature::eIntRatio, + nsMediaFeature::eNoRequirements, { nullptr }, GetDeviceAspectRatio }, @@ -469,6 +476,7 @@ nsMediaFeatures::features[] = { &nsGkAtoms::color, nsMediaFeature::eMinMaxAllowed, nsMediaFeature::eInteger, + nsMediaFeature::eNoRequirements, { nullptr }, GetColor }, @@ -476,6 +484,7 @@ nsMediaFeatures::features[] = { &nsGkAtoms::colorIndex, nsMediaFeature::eMinMaxAllowed, nsMediaFeature::eInteger, + nsMediaFeature::eNoRequirements, { nullptr }, GetColorIndex }, @@ -483,6 +492,7 @@ nsMediaFeatures::features[] = { &nsGkAtoms::monochrome, nsMediaFeature::eMinMaxAllowed, nsMediaFeature::eInteger, + nsMediaFeature::eNoRequirements, { nullptr }, GetMonochrome }, @@ -490,6 +500,7 @@ nsMediaFeatures::features[] = { &nsGkAtoms::resolution, nsMediaFeature::eMinMaxAllowed, nsMediaFeature::eResolution, + nsMediaFeature::eNoRequirements, { nullptr }, GetResolution }, @@ -497,6 +508,7 @@ nsMediaFeatures::features[] = { &nsGkAtoms::scan, nsMediaFeature::eMinMaxNotAllowed, nsMediaFeature::eEnumerated, + nsMediaFeature::eNoRequirements, { kScanKeywords }, GetScan }, @@ -504,15 +516,28 @@ nsMediaFeatures::features[] = { &nsGkAtoms::grid, nsMediaFeature::eMinMaxNotAllowed, nsMediaFeature::eBoolInteger, + nsMediaFeature::eNoRequirements, { nullptr }, GetGrid }, + // Webkit extensions that we support for de-facto web compatibility + // -webkit-{min|max}-device-pixel-ratio: + { + &nsGkAtoms::devicePixelRatio, + nsMediaFeature::eMinMaxAllowed, + nsMediaFeature::eFloat, + nsMediaFeature::eHasWebkitPrefix, + { nullptr }, + GetDevicePixelRatio + }, + // Mozilla extensions { &nsGkAtoms::_moz_device_pixel_ratio, nsMediaFeature::eMinMaxAllowed, nsMediaFeature::eFloat, + nsMediaFeature::eNoRequirements, { nullptr }, GetDevicePixelRatio }, @@ -520,6 +545,7 @@ nsMediaFeatures::features[] = { &nsGkAtoms::_moz_device_orientation, nsMediaFeature::eMinMaxNotAllowed, nsMediaFeature::eEnumerated, + nsMediaFeature::eNoRequirements, { kOrientationKeywords }, GetDeviceOrientation }, @@ -527,6 +553,7 @@ nsMediaFeatures::features[] = { &nsGkAtoms::_moz_is_resource_document, nsMediaFeature::eMinMaxNotAllowed, nsMediaFeature::eBoolInteger, + nsMediaFeature::eNoRequirements, { nullptr }, GetIsResourceDocument }, @@ -534,6 +561,7 @@ nsMediaFeatures::features[] = { &nsGkAtoms::_moz_color_picker_available, nsMediaFeature::eMinMaxNotAllowed, nsMediaFeature::eBoolInteger, + nsMediaFeature::eNoRequirements, { &nsGkAtoms::color_picker_available }, GetSystemMetric }, @@ -541,6 +569,7 @@ nsMediaFeatures::features[] = { &nsGkAtoms::_moz_scrollbar_start_backward, nsMediaFeature::eMinMaxNotAllowed, nsMediaFeature::eBoolInteger, + nsMediaFeature::eNoRequirements, { &nsGkAtoms::scrollbar_start_backward }, GetSystemMetric }, @@ -548,6 +577,7 @@ nsMediaFeatures::features[] = { &nsGkAtoms::_moz_scrollbar_start_forward, nsMediaFeature::eMinMaxNotAllowed, nsMediaFeature::eBoolInteger, + nsMediaFeature::eNoRequirements, { &nsGkAtoms::scrollbar_start_forward }, GetSystemMetric }, @@ -555,6 +585,7 @@ nsMediaFeatures::features[] = { &nsGkAtoms::_moz_scrollbar_end_backward, nsMediaFeature::eMinMaxNotAllowed, nsMediaFeature::eBoolInteger, + nsMediaFeature::eNoRequirements, { &nsGkAtoms::scrollbar_end_backward }, GetSystemMetric }, @@ -562,6 +593,7 @@ nsMediaFeatures::features[] = { &nsGkAtoms::_moz_scrollbar_end_forward, nsMediaFeature::eMinMaxNotAllowed, nsMediaFeature::eBoolInteger, + nsMediaFeature::eNoRequirements, { &nsGkAtoms::scrollbar_end_forward }, GetSystemMetric }, @@ -569,6 +601,7 @@ nsMediaFeatures::features[] = { &nsGkAtoms::_moz_scrollbar_thumb_proportional, nsMediaFeature::eMinMaxNotAllowed, nsMediaFeature::eBoolInteger, + nsMediaFeature::eNoRequirements, { &nsGkAtoms::scrollbar_thumb_proportional }, GetSystemMetric }, @@ -576,6 +609,7 @@ nsMediaFeatures::features[] = { &nsGkAtoms::_moz_images_in_menus, nsMediaFeature::eMinMaxNotAllowed, nsMediaFeature::eBoolInteger, + nsMediaFeature::eNoRequirements, { &nsGkAtoms::images_in_menus }, GetSystemMetric }, @@ -583,6 +617,7 @@ nsMediaFeatures::features[] = { &nsGkAtoms::_moz_images_in_buttons, nsMediaFeature::eMinMaxNotAllowed, nsMediaFeature::eBoolInteger, + nsMediaFeature::eNoRequirements, { &nsGkAtoms::images_in_buttons }, GetSystemMetric }, @@ -590,6 +625,7 @@ nsMediaFeatures::features[] = { &nsGkAtoms::_moz_overlay_scrollbars, nsMediaFeature::eMinMaxNotAllowed, nsMediaFeature::eBoolInteger, + nsMediaFeature::eNoRequirements, { &nsGkAtoms::overlay_scrollbars }, GetSystemMetric }, @@ -597,6 +633,7 @@ nsMediaFeatures::features[] = { &nsGkAtoms::_moz_windows_default_theme, nsMediaFeature::eMinMaxNotAllowed, nsMediaFeature::eBoolInteger, + nsMediaFeature::eNoRequirements, { &nsGkAtoms::windows_default_theme }, GetSystemMetric }, @@ -604,6 +641,7 @@ nsMediaFeatures::features[] = { &nsGkAtoms::_moz_mac_graphite_theme, nsMediaFeature::eMinMaxNotAllowed, nsMediaFeature::eBoolInteger, + nsMediaFeature::eNoRequirements, { &nsGkAtoms::mac_graphite_theme }, GetSystemMetric }, @@ -611,6 +649,7 @@ nsMediaFeatures::features[] = { &nsGkAtoms::_moz_mac_lion_theme, nsMediaFeature::eMinMaxNotAllowed, nsMediaFeature::eBoolInteger, + nsMediaFeature::eNoRequirements, { &nsGkAtoms::mac_lion_theme }, GetSystemMetric }, @@ -618,6 +657,7 @@ nsMediaFeatures::features[] = { &nsGkAtoms::_moz_mac_yosemite_theme, nsMediaFeature::eMinMaxNotAllowed, nsMediaFeature::eBoolInteger, + nsMediaFeature::eNoRequirements, { &nsGkAtoms::mac_yosemite_theme }, GetSystemMetric }, @@ -625,6 +665,7 @@ nsMediaFeatures::features[] = { &nsGkAtoms::_moz_windows_compositor, nsMediaFeature::eMinMaxNotAllowed, nsMediaFeature::eBoolInteger, + nsMediaFeature::eNoRequirements, { &nsGkAtoms::windows_compositor }, GetSystemMetric }, @@ -632,6 +673,7 @@ nsMediaFeatures::features[] = { &nsGkAtoms::_moz_windows_classic, nsMediaFeature::eMinMaxNotAllowed, nsMediaFeature::eBoolInteger, + nsMediaFeature::eNoRequirements, { &nsGkAtoms::windows_classic }, GetSystemMetric }, @@ -639,6 +681,7 @@ nsMediaFeatures::features[] = { &nsGkAtoms::_moz_windows_glass, nsMediaFeature::eMinMaxNotAllowed, nsMediaFeature::eBoolInteger, + nsMediaFeature::eNoRequirements, { &nsGkAtoms::windows_glass }, GetSystemMetric }, @@ -646,6 +689,7 @@ nsMediaFeatures::features[] = { &nsGkAtoms::_moz_touch_enabled, nsMediaFeature::eMinMaxNotAllowed, nsMediaFeature::eBoolInteger, + nsMediaFeature::eNoRequirements, { &nsGkAtoms::touch_enabled }, GetSystemMetric }, @@ -653,6 +697,7 @@ nsMediaFeatures::features[] = { &nsGkAtoms::_moz_menubar_drag, nsMediaFeature::eMinMaxNotAllowed, nsMediaFeature::eBoolInteger, + nsMediaFeature::eNoRequirements, { &nsGkAtoms::menubar_drag }, GetSystemMetric }, @@ -660,6 +705,7 @@ nsMediaFeatures::features[] = { &nsGkAtoms::_moz_windows_theme, nsMediaFeature::eMinMaxNotAllowed, nsMediaFeature::eIdent, + nsMediaFeature::eNoRequirements, { nullptr }, GetWindowsTheme }, @@ -667,6 +713,7 @@ nsMediaFeatures::features[] = { &nsGkAtoms::_moz_os_version, nsMediaFeature::eMinMaxNotAllowed, nsMediaFeature::eIdent, + nsMediaFeature::eNoRequirements, { nullptr }, GetOperatinSystemVersion }, @@ -675,6 +722,7 @@ nsMediaFeatures::features[] = { &nsGkAtoms::_moz_swipe_animation_enabled, nsMediaFeature::eMinMaxNotAllowed, nsMediaFeature::eBoolInteger, + nsMediaFeature::eNoRequirements, { &nsGkAtoms::swipe_animation_enabled }, GetSystemMetric }, @@ -683,6 +731,7 @@ nsMediaFeatures::features[] = { &nsGkAtoms::_moz_physical_home_button, nsMediaFeature::eMinMaxNotAllowed, nsMediaFeature::eBoolInteger, + nsMediaFeature::eNoRequirements, { &nsGkAtoms::physical_home_button }, GetSystemMetric }, @@ -694,6 +743,7 @@ nsMediaFeatures::features[] = { &nsGkAtoms::_moz_is_glyph, nsMediaFeature::eMinMaxNotAllowed, nsMediaFeature::eBoolInteger, + nsMediaFeature::eNoRequirements, { nullptr }, GetIsGlyph }, @@ -702,6 +752,7 @@ nsMediaFeatures::features[] = { nullptr, nsMediaFeature::eMinMaxAllowed, nsMediaFeature::eInteger, + nsMediaFeature::eNoRequirements, { nullptr }, nullptr }, diff --git a/layout/style/nsMediaFeatures.h b/layout/style/nsMediaFeatures.h index 0b2a46ee3b3a..8ce7580da65f 100644 --- a/layout/style/nsMediaFeatures.h +++ b/layout/style/nsMediaFeatures.h @@ -46,6 +46,15 @@ struct nsMediaFeature { }; ValueType mValueType; + enum RequirementFlags : uint8_t { + // Bitfield of requirements that must be satisfied in order for this + // media feature to be active. + eNoRequirements = 0, + eHasWebkitPrefix = 1 // Feature name must start w/ "-webkit-", even + // before any "min-"/"max-" qualifier. + }; + uint8_t mReqFlags; + union { // In static arrays, it's the first member that's initialized. We // need that to be void* so we can initialize both other types. diff --git a/layout/style/test/mochitest.ini b/layout/style/test/mochitest.ini index fc057db0ca00..8a48007613b0 100644 --- a/layout/style/test/mochitest.ini +++ b/layout/style/test/mochitest.ini @@ -289,3 +289,4 @@ skip-if = buildapp == 'b2g' || toolkit == 'android' #TIMED_OUT # b2g(bug 870262, skip-if = buildapp == 'b2g' || toolkit == 'android' #TIMED_OUT # b2g(bug 870262, :visited support) b2g-debug(bug 870262, :visited support) b2g-desktop(bug 870262, :visited support) [test_visited_reftests.html] skip-if = buildapp == 'b2g' || toolkit == 'android' #TIMED_OUT # b2g(bug 870262, :visited support) b2g-debug(bug 870262, :visited support) b2g-desktop(bug 870262, :visited support) +[test_webkit_device_pixel_ratio.html] diff --git a/layout/style/test/test_media_queries.html b/layout/style/test/test_media_queries.html index b28fbd2253c1..f3aa229e6e45 100644 --- a/layout/style/test/test_media_queries.html +++ b/layout/style/test/test_media_queries.html @@ -114,6 +114,29 @@ function run() { "expression " + e + " should not be parseable"); } + // Helper to share code between -moz & -webkit device-pixel-ratio versions: + function test_device_pixel_ratio(equal_name, min_name, max_name) { + var real_dpr = 1.0 * getScreenPixelsPerCSSPixel(); + var high_dpr = 1.1 * getScreenPixelsPerCSSPixel(); + var low_dpr = 0.9 * getScreenPixelsPerCSSPixel(); + should_apply("all and (" + max_name + ": " + real_dpr + ")"); + should_apply("all and (" + min_name + ": " + real_dpr + ")"); + should_not_apply("not all and (" + max_name + ": " + real_dpr + ")"); + should_not_apply("not all and (" + min_name + ": " + real_dpr + ")"); + should_apply("all and (" + min_name + ": " + low_dpr + ")"); + should_apply("all and (" + max_name + ": " + high_dpr + ")"); + should_not_apply("all and (" + max_name + ": " + low_dpr + ")"); + should_not_apply("all and (" + min_name + ": " + high_dpr + ")"); + should_apply("not all and (" + max_name + ": " + low_dpr + ")"); + should_apply("not all and (" + min_name + ": " + high_dpr + ")"); + should_apply("(" + equal_name + ": " + real_dpr + ")"); + should_not_apply("(" + equal_name + ": " + high_dpr + ")"); + should_not_apply("(" + equal_name + ": " + low_dpr + ")"); + should_apply("(" + equal_name + ")"); + expression_should_not_be_parseable(min_name); + expression_should_not_be_parseable(max_name); + } + function test_serialization(q, test_application, should_apply) { style.setAttribute("media", q); var ser1 = style.sheet.media.mediaText; @@ -399,25 +422,23 @@ function run() { should_apply("not all and (max-device-aspect-ratio: " + low_dar_2 + ")"); expression_should_not_be_parseable("max-device-aspect-ratio"); - var real_dpr = 1.0 * getScreenPixelsPerCSSPixel(); - var high_dpr = 1.1 * getScreenPixelsPerCSSPixel(); - var low_dpr = 0.9 * getScreenPixelsPerCSSPixel(); - should_apply("all and (max--moz-device-pixel-ratio: " + real_dpr + ")"); - should_apply("all and (min--moz-device-pixel-ratio: " + real_dpr + ")"); - should_not_apply("not all and (max--moz-device-pixel-ratio: " + real_dpr + ")"); - should_not_apply("not all and (min--moz-device-pixel-ratio: " + real_dpr + ")"); - should_apply("all and (min--moz-device-pixel-ratio: " + low_dpr + ")"); - should_apply("all and (max--moz-device-pixel-ratio: " + high_dpr + ")"); - should_not_apply("all and (max--moz-device-pixel-ratio: " + low_dpr + ")"); - should_not_apply("all and (min--moz-device-pixel-ratio: " + high_dpr + ")"); - should_apply("not all and (max--moz-device-pixel-ratio: " + low_dpr + ")"); - should_apply("not all and (min--moz-device-pixel-ratio: " + high_dpr + ")"); - should_apply("(-moz-device-pixel-ratio: " + real_dpr + ")"); - should_not_apply("(-moz-device-pixel-ratio: " + high_dpr + ")"); - should_not_apply("(-moz-device-pixel-ratio: " + low_dpr + ")"); - should_apply("(-moz-device-pixel-ratio)"); - expression_should_not_be_parseable("min--moz-device-pixel-ratio"); - expression_should_not_be_parseable("max--moz-device-pixel-ratio"); + // Tests for -moz- & -webkit versions of "device-pixel-ratio" + // (Note that the vendor prefixes go in different places.) + test_device_pixel_ratio("-moz-device-pixel-ratio", + "min--moz-device-pixel-ratio", + "max--moz-device-pixel-ratio"); + test_device_pixel_ratio("-webkit-device-pixel-ratio", + "-webkit-min-device-pixel-ratio", + "-webkit-max-device-pixel-ratio"); + + // Make sure that we don't accidentally start accepting *unprefixed* + // "device-pixel-ratio" expressions: + expression_should_be_parseable("-webkit-device-pixel-ratio: 1.0"); + expression_should_not_be_parseable("device-pixel-ratio: 1.0"); + expression_should_be_parseable("-webkit-min-device-pixel-ratio: 1.0"); + expression_should_not_be_parseable("min-device-pixel-ratio: 1.0"); + expression_should_be_parseable("-webkit-max-device-pixel-ratio: 1.0"); + expression_should_not_be_parseable("max-device-pixel-ratio: 1.0"); features = [ "max-aspect-ratio", "device-aspect-ratio" ]; for (i in features) { diff --git a/layout/style/test/test_webkit_device_pixel_ratio.html b/layout/style/test/test_webkit_device_pixel_ratio.html new file mode 100644 index 000000000000..ed655bd8a99d --- /dev/null +++ b/layout/style/test/test_webkit_device_pixel_ratio.html @@ -0,0 +1,77 @@ + + + + + Test for Bug 1176968 + + + + + + +Mozilla Bug 1176968 + + +
+
+
+
+ +
+ + diff --git a/media/libstagefright/binding/MP4Metadata.cpp b/media/libstagefright/binding/MP4Metadata.cpp index 8dfbebbdd1db..38e2a52986fc 100644 --- a/media/libstagefright/binding/MP4Metadata.cpp +++ b/media/libstagefright/binding/MP4Metadata.cpp @@ -9,6 +9,7 @@ #include "media/stagefright/MetaData.h" #include "mozilla/Logging.h" #include "mozilla/Monitor.h" +#include "mozilla/Telemetry.h" #include "mp4_demuxer/MoofParser.h" #include "mp4_demuxer/MP4Metadata.h" @@ -142,7 +143,9 @@ MP4Metadata::GetNumberTracks(mozilla::TrackInfo::TrackType aType) const { #ifdef MOZ_RUST_MP4PARSE // Try in rust first. - try_rust(mSource); + bool rust_mp4parse_success = try_rust(mSource); + Telemetry::Accumulate(Telemetry::MEDIA_RUST_MP4PARSE_SUCCESS, + rust_mp4parse_success); #endif size_t tracks = mPrivate->mMetadataExtractor->countTracks(); uint32_t total = 0; diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 9dd540a37370..ed53f787417e 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -588,6 +588,7 @@ pref("apz.printtree", false); pref("apz.test.logging_enabled", false); pref("apz.touch_start_tolerance", "0.2222222"); // 0.2222222 came from 1.0/4.5 +pref("apz.touch_move_tolerance", "0.0"); pref("apz.use_paint_duration", true); pref("apz.velocity_bias", "1.0"); pref("apz.velocity_relevance_time_ms", 150); @@ -4479,7 +4480,7 @@ pref("dom.mozAlarms.enabled", false); pref("dom.push.enabled", false); -pref("dom.push.debug", false); +pref("dom.push.loglevel", "off"); pref("dom.push.serverURL", "wss://push.services.mozilla.com/"); pref("dom.push.userAgentID", ""); diff --git a/netwerk/base/moz.build b/netwerk/base/moz.build index fe14165c6aa1..719bdf7a0691 100644 --- a/netwerk/base/moz.build +++ b/netwerk/base/moz.build @@ -130,7 +130,6 @@ XPIDL_SOURCES += [ 'nsIUploadChannel.idl', 'nsIUploadChannel2.idl', 'nsIURI.idl', - 'nsIURIChecker.idl', 'nsIURIClassifier.idl', 'nsIURIWithPrincipal.idl', 'nsIURL.idl', @@ -244,7 +243,6 @@ UNIFIED_SOURCES += [ 'nsTransportUtils.cpp', 'nsUDPSocket.cpp', 'nsUnicharStreamLoader.cpp', - 'nsURIChecker.cpp', 'nsURLHelper.cpp', 'nsURLParsers.cpp', 'OfflineObserver.cpp', diff --git a/netwerk/base/nsIURIChecker.idl b/netwerk/base/nsIURIChecker.idl deleted file mode 100644 index cd7ba0287b3f..000000000000 --- a/netwerk/base/nsIURIChecker.idl +++ /dev/null @@ -1,62 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "nsIRequest.idl" - -interface nsIURI; -interface nsIChannel; -interface nsIRequestObserver; - -/** - * nsIURIChecker - * - * The URI checker is a component that can be used to verify the existence - * of a resource at the location specified by a given URI. It will use - * protocol specific methods to verify the URI (e.g., use of HEAD request - * for HTTP URIs). - */ -[scriptable, uuid(4660c1a1-be2d-4c78-9baf-c22984176c28)] -interface nsIURIChecker : nsIRequest -{ - /** - * Initializes the URI checker. After this method is called, it is valid - * to further configure the URI checker by calling its nsIRequest methods. - * This method creates the channel that will be used to verify the URI. - * In the case of the HTTP protocol, only a HEAD request will be issued. - * - * @param aURI - * The URI to be checked. - */ - void init(in nsIURI aURI); - - /** - * Returns the base channel that will be used to verify the URI. - */ - readonly attribute nsIChannel baseChannel; - - /** - * Begin asynchronous checking URI for validity. Notification will be - * asynchronous through the nsIRequestObserver callback interface. When - * OnStartRequest is fired, the baseChannel attribute will have been - * updated to reflect the final channel used (corresponding to any redirects - * that may have been followed). - * - * Our interpretations of the nsIRequestObserver status codes: - * NS_BINDING_SUCCEEDED: link is valid - * NS_BINDING_FAILED: link is invalid (gave an error) - * NS_BINDING_ABORTED: timed out, or cancelled - * - * @param aObserver - * The object to notify when the link is verified. We will - * call aObserver.OnStartRequest followed immediately by - * aObserver.OnStopRequest. It is recommended that the caller use - * OnStopRequest to act on the link's status. The underlying request - * will not be cancelled until after OnStopRequest has been called. - * @param aContext - * A closure that will be passed back to the nsIRequestObserver - * methods. - */ - void asyncCheck(in nsIRequestObserver aObserver, in nsISupports aContext); -}; diff --git a/netwerk/base/nsURIChecker.cpp b/netwerk/base/nsURIChecker.cpp deleted file mode 100644 index 0aac395830cf..000000000000 --- a/netwerk/base/nsURIChecker.cpp +++ /dev/null @@ -1,351 +0,0 @@ -/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "nsURIChecker.h" -#include "nsIURI.h" -#include "nsIAuthPrompt.h" -#include "nsIHttpChannel.h" -#include "nsContentUtils.h" -#include "nsNetUtil.h" -#include "nsString.h" -#include "nsIAsyncVerifyRedirectCallback.h" - -//----------------------------------------------------------------------------- - -static bool -ServerIsNES3x(nsIHttpChannel *httpChannel) -{ - nsAutoCString server; - httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Server"), server); - // case sensitive string comparison is OK here. the server string - // is a well-known value, so we should not have to worry about it - // being case-smashed or otherwise case-mutated. - return StringBeginsWith(server, - NS_LITERAL_CSTRING("Netscape-Enterprise/3.")); -} - -//----------------------------------------------------------------------------- - -NS_IMPL_ISUPPORTS(nsURIChecker, - nsIURIChecker, - nsIRequest, - nsIRequestObserver, - nsIStreamListener, - nsIChannelEventSink, - nsIInterfaceRequestor) - -nsURIChecker::nsURIChecker() - : mStatus(NS_OK) - , mIsPending(false) - , mAllowHead(true) -{ -} - -void -nsURIChecker::SetStatusAndCallBack(nsresult aStatus) -{ - mStatus = aStatus; - mIsPending = false; - - if (mObserver) { - mObserver->OnStartRequest(this, mObserverContext); - mObserver->OnStopRequest(this, mObserverContext, mStatus); - mObserver = nullptr; - mObserverContext = nullptr; - } -} - -nsresult -nsURIChecker::CheckStatus() -{ - NS_ASSERTION(mChannel, "no channel"); - - nsresult status; - nsresult rv = mChannel->GetStatus(&status); - // DNS errors and other obvious problems will return failure status - if (NS_FAILED(rv) || NS_FAILED(status)) - return NS_BINDING_FAILED; - - // If status is zero, it might still be an error if it's http: - // http has data even when there's an error like a 404. - nsCOMPtr httpChannel = do_QueryInterface(mChannel); - if (!httpChannel) - return NS_BINDING_SUCCEEDED; - - uint32_t responseStatus; - rv = httpChannel->GetResponseStatus(&responseStatus); - if (NS_FAILED(rv)) - return NS_BINDING_FAILED; - - // If it's between 200-299, it's valid: - if (responseStatus / 100 == 2) - return NS_BINDING_SUCCEEDED; - - // If we got a 404 (not found), we need some extra checking: - // toplevel urls from Netscape Enterprise Server 3.6, like the old AOL- - // hosted http://www.mozilla.org, generate a 404 and will have to be - // retried without the head. - if (responseStatus == 404) { - if (mAllowHead && ServerIsNES3x(httpChannel)) { - mAllowHead = false; - - // save the current value of mChannel in case we can't issue - // the new request for some reason. - nsCOMPtr lastChannel = mChannel; - - nsCOMPtr uri; - uint32_t loadFlags; - - rv = lastChannel->GetOriginalURI(getter_AddRefs(uri)); - nsresult tmp = lastChannel->GetLoadFlags(&loadFlags); - if (NS_FAILED(tmp)) { - rv = tmp; - } - - // XXX we are carrying over the load flags, but what about other - // parameters that may have been set on lastChannel?? - - if (NS_SUCCEEDED(rv)) { - rv = Init(uri); - if (NS_SUCCEEDED(rv)) { - rv = mChannel->SetLoadFlags(loadFlags); - if (NS_SUCCEEDED(rv)) { - rv = AsyncCheck(mObserver, mObserverContext); - // if we succeeded in loading the new channel, then we - // want to return without notifying our observer. - if (NS_SUCCEEDED(rv)) - return NS_BASE_STREAM_WOULD_BLOCK; - } - } - } - // it is important to update this so our observer will be able - // to access our baseChannel attribute if they want. - mChannel = lastChannel; - } - } - - // If we get here, assume the resource does not exist. - return NS_BINDING_FAILED; -} - -//----------------------------------------------------------------------------- -// nsIURIChecker methods: -//----------------------------------------------------------------------------- - -NS_IMETHODIMP -nsURIChecker::Init(nsIURI *aURI) -{ - nsresult rv; - rv = NS_NewChannel(getter_AddRefs(mChannel), - aURI, - nsContentUtils::GetSystemPrincipal(), - nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, - nsIContentPolicy::TYPE_OTHER); - NS_ENSURE_SUCCESS(rv, rv); - - if (mAllowHead) { - mAllowHead = false; - // See if it's an http channel, which needs special treatment: - nsCOMPtr httpChannel = do_QueryInterface(mChannel); - if (httpChannel) { - // We can have an HTTP channel that has a non-HTTP URL if - // we're doing FTP via an HTTP proxy, for example. See for - // example bug 148813 - bool isReallyHTTP = false; - aURI->SchemeIs("http", &isReallyHTTP); - if (!isReallyHTTP) - aURI->SchemeIs("https", &isReallyHTTP); - if (isReallyHTTP) { - httpChannel->SetRequestMethod(NS_LITERAL_CSTRING("HEAD")); - // set back to true so we'll know that this request is issuing - // a HEAD request. this is used down in OnStartRequest to - // handle cases where we need to repeat the request as a normal - // GET to deal with server borkage. - mAllowHead = true; - } - } - } - return NS_OK; -} - -NS_IMETHODIMP -nsURIChecker::AsyncCheck(nsIRequestObserver *aObserver, - nsISupports *aObserverContext) -{ - NS_ENSURE_TRUE(mChannel, NS_ERROR_NOT_INITIALIZED); - - // Hook us up to listen to redirects and the like (this creates a reference - // cycle!) - mChannel->SetNotificationCallbacks(this); - - // and start the request: - nsresult rv = mChannel->AsyncOpen2(this); - if (NS_FAILED(rv)) - mChannel = nullptr; - else { - // ok, wait for OnStartRequest to fire. - mIsPending = true; - mObserver = aObserver; - mObserverContext = aObserverContext; - } - return rv; -} - -NS_IMETHODIMP -nsURIChecker::GetBaseChannel(nsIChannel **aChannel) -{ - NS_ENSURE_TRUE(mChannel, NS_ERROR_NOT_INITIALIZED); - NS_ADDREF(*aChannel = mChannel); - return NS_OK; -} - -//----------------------------------------------------------------------------- -// nsIRequest methods: -//----------------------------------------------------------------------------- - -NS_IMETHODIMP -nsURIChecker::GetName(nsACString &aName) -{ - NS_ENSURE_TRUE(mChannel, NS_ERROR_NOT_INITIALIZED); - return mChannel->GetName(aName); -} - -NS_IMETHODIMP -nsURIChecker::IsPending(bool *aPendingRet) -{ - *aPendingRet = mIsPending; - return NS_OK; -} - -NS_IMETHODIMP -nsURIChecker::GetStatus(nsresult* aStatusRet) -{ - *aStatusRet = mStatus; - return NS_OK; -} - -NS_IMETHODIMP -nsURIChecker::Cancel(nsresult status) -{ - NS_ENSURE_TRUE(mChannel, NS_ERROR_NOT_INITIALIZED); - return mChannel->Cancel(status); -} - -NS_IMETHODIMP -nsURIChecker::Suspend() -{ - NS_ENSURE_TRUE(mChannel, NS_ERROR_NOT_INITIALIZED); - return mChannel->Suspend(); -} - -NS_IMETHODIMP -nsURIChecker::Resume() -{ - NS_ENSURE_TRUE(mChannel, NS_ERROR_NOT_INITIALIZED); - return mChannel->Resume(); -} - -NS_IMETHODIMP -nsURIChecker::GetLoadGroup(nsILoadGroup **aLoadGroup) -{ - NS_ENSURE_TRUE(mChannel, NS_ERROR_NOT_INITIALIZED); - return mChannel->GetLoadGroup(aLoadGroup); -} - -NS_IMETHODIMP -nsURIChecker::SetLoadGroup(nsILoadGroup *aLoadGroup) -{ - NS_ENSURE_TRUE(mChannel, NS_ERROR_NOT_INITIALIZED); - return mChannel->SetLoadGroup(aLoadGroup); -} - -NS_IMETHODIMP -nsURIChecker::GetLoadFlags(nsLoadFlags *aLoadFlags) -{ - NS_ENSURE_TRUE(mChannel, NS_ERROR_NOT_INITIALIZED); - return mChannel->GetLoadFlags(aLoadFlags); -} - -NS_IMETHODIMP -nsURIChecker::SetLoadFlags(nsLoadFlags aLoadFlags) -{ - NS_ENSURE_TRUE(mChannel, NS_ERROR_NOT_INITIALIZED); - return mChannel->SetLoadFlags(aLoadFlags); -} - -//----------------------------------------------------------------------------- -// nsIRequestObserver methods: -//----------------------------------------------------------------------------- - -NS_IMETHODIMP -nsURIChecker::OnStartRequest(nsIRequest *aRequest, nsISupports *aCtxt) -{ - NS_ASSERTION(aRequest == mChannel, "unexpected request"); - - nsresult rv = CheckStatus(); - if (rv != NS_BASE_STREAM_WOULD_BLOCK) - SetStatusAndCallBack(rv); - - // cancel the request (we don't care to look at the data). - return NS_BINDING_ABORTED; -} - -NS_IMETHODIMP -nsURIChecker::OnStopRequest(nsIRequest *request, nsISupports *ctxt, - nsresult statusCode) -{ - // NOTE: we may have kicked off a subsequent request, so we should not do - // any cleanup unless this request matches the one we are currently using. - if (mChannel == request) { - // break reference cycle between us and the channel (see comment in - // AsyncCheckURI) - mChannel = nullptr; - } - return NS_OK; -} - -//----------------------------------------------------------------------------- -// nsIStreamListener methods: -//----------------------------------------------------------------------------- - -NS_IMETHODIMP -nsURIChecker::OnDataAvailable(nsIRequest *aRequest, nsISupports *aCtxt, - nsIInputStream *aInput, uint64_t aOffset, - uint32_t aCount) -{ - NS_NOTREACHED("nsURIChecker::OnDataAvailable"); - return NS_BINDING_ABORTED; -} - -//----------------------------------------------------------------------------- -// nsIInterfaceRequestor methods: -//----------------------------------------------------------------------------- - -NS_IMETHODIMP -nsURIChecker::GetInterface(const nsIID & aIID, void **aResult) -{ - if (mObserver && aIID.Equals(NS_GET_IID(nsIAuthPrompt))) { - nsCOMPtr req = do_QueryInterface(mObserver); - if (req) - return req->GetInterface(aIID, aResult); - } - return QueryInterface(aIID, aResult); -} - -//----------------------------------------------------------------------------- -// nsIChannelEventSink methods: -//----------------------------------------------------------------------------- - -NS_IMETHODIMP -nsURIChecker::AsyncOnChannelRedirect(nsIChannel *aOldChannel, - nsIChannel *aNewChannel, - uint32_t aFlags, - nsIAsyncVerifyRedirectCallback *callback) -{ - // We have a new channel - mChannel = aNewChannel; - callback->OnRedirectVerifyCallback(NS_OK); - return NS_OK; -} diff --git a/netwerk/base/nsURIChecker.h b/netwerk/base/nsURIChecker.h deleted file mode 100644 index b807949dac29..000000000000 --- a/netwerk/base/nsURIChecker.h +++ /dev/null @@ -1,48 +0,0 @@ -/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifndef nsURIChecker_h__ -#define nsURIChecker_h__ - -#include "nsIURIChecker.h" -#include "nsIChannel.h" -#include "nsIStreamListener.h" -#include "nsIChannelEventSink.h" -#include "nsIInterfaceRequestor.h" -#include "nsCOMPtr.h" - -//----------------------------------------------------------------------------- - -class nsURIChecker : public nsIURIChecker, - public nsIStreamListener, - public nsIChannelEventSink, - public nsIInterfaceRequestor -{ - virtual ~nsURIChecker() {} - -public: - nsURIChecker(); - - NS_DECL_ISUPPORTS - NS_DECL_NSIURICHECKER - NS_DECL_NSIREQUEST - NS_DECL_NSIREQUESTOBSERVER - NS_DECL_NSISTREAMLISTENER - NS_DECL_NSICHANNELEVENTSINK - NS_DECL_NSIINTERFACEREQUESTOR - -protected: - nsCOMPtr mChannel; - nsCOMPtr mObserver; - nsCOMPtr mObserverContext; - nsresult mStatus; - bool mIsPending; - bool mAllowHead; - - void SetStatusAndCallBack(nsresult aStatus); - nsresult CheckStatus(); -}; - -#endif // nsURIChecker_h__ diff --git a/netwerk/build/nsNetCID.h b/netwerk/build/nsNetCID.h index c9a27e17c37a..43047ebfde6d 100644 --- a/netwerk/build/nsNetCID.h +++ b/netwerk/build/nsNetCID.h @@ -283,17 +283,6 @@ {0x8b, 0x06, 0x8a, 0xaf, 0xe1, 0x86, 0x9b, 0xd8} \ } -// component implementing nsIURIChecker. -#define NS_URICHECKER_CONTRACT_ID \ - "@mozilla.org/network/urichecker;1" -#define NS_URICHECKER_CID \ -{ /* cf3a0e06-1dd1-11b2-a904-ac1d6da77a02 */ \ - 0xcf3a0e06, \ - 0x1dd1, \ - 0x11b2, \ - {0xa9, 0x04, 0xac, 0x1d, 0x6d, 0xa7, 0x7a, 0x02} \ -} - // component implementing nsIIncrementalDownload. #define NS_INCREMENTALDOWNLOAD_CONTRACTID \ "@mozilla.org/network/incremental-download;1" diff --git a/netwerk/build/nsNetModule.cpp b/netwerk/build/nsNetModule.cpp index 6c6400527292..425b094e7f10 100644 --- a/netwerk/build/nsNetModule.cpp +++ b/netwerk/build/nsNetModule.cpp @@ -368,11 +368,6 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(RtspHandler) /////////////////////////////////////////////////////////////////////////////// -#include "nsURIChecker.h" -NS_GENERIC_FACTORY_CONSTRUCTOR(nsURIChecker) - -/////////////////////////////////////////////////////////////////////////////// - #include "nsURLParsers.h" NS_GENERIC_FACTORY_CONSTRUCTOR(nsNoAuthURLParser) NS_GENERIC_FACTORY_CONSTRUCTOR(nsAuthURLParser) @@ -727,7 +722,6 @@ NS_DEFINE_NAMED_CID(NS_PARTIALLOCALFILEINPUTSTREAM_CID); NS_DEFINE_NAMED_CID(NS_ATOMICLOCALFILEOUTPUTSTREAM_CID); NS_DEFINE_NAMED_CID(NS_SAFELOCALFILEOUTPUTSTREAM_CID); NS_DEFINE_NAMED_CID(NS_LOCALFILESTREAM_CID); -NS_DEFINE_NAMED_CID(NS_URICHECKER_CID); NS_DEFINE_NAMED_CID(NS_INCREMENTALDOWNLOAD_CID); NS_DEFINE_NAMED_CID(NS_STDURLPARSER_CID); NS_DEFINE_NAMED_CID(NS_NOAUTHURLPARSER_CID); @@ -877,7 +871,6 @@ static const mozilla::Module::CIDEntry kNeckoCIDs[] = { { &kNS_ATOMICLOCALFILEOUTPUTSTREAM_CID, false, nullptr, nsAtomicFileOutputStreamConstructor }, { &kNS_SAFELOCALFILEOUTPUTSTREAM_CID, false, nullptr, nsSafeFileOutputStreamConstructor }, { &kNS_LOCALFILESTREAM_CID, false, nullptr, nsFileStreamConstructor }, - { &kNS_URICHECKER_CID, false, nullptr, nsURICheckerConstructor }, { &kNS_INCREMENTALDOWNLOAD_CID, false, nullptr, net_NewIncrementalDownload }, { &kNS_STDURLPARSER_CID, false, nullptr, nsStdURLParserConstructor }, { &kNS_NOAUTHURLPARSER_CID, false, nullptr, nsNoAuthURLParserConstructor }, @@ -1029,7 +1022,6 @@ static const mozilla::Module::ContractIDEntry kNeckoContracts[] = { { NS_ATOMICLOCALFILEOUTPUTSTREAM_CONTRACTID, &kNS_ATOMICLOCALFILEOUTPUTSTREAM_CID }, { NS_SAFELOCALFILEOUTPUTSTREAM_CONTRACTID, &kNS_SAFELOCALFILEOUTPUTSTREAM_CID }, { NS_LOCALFILESTREAM_CONTRACTID, &kNS_LOCALFILESTREAM_CID }, - { NS_URICHECKER_CONTRACT_ID, &kNS_URICHECKER_CID }, { NS_INCREMENTALDOWNLOAD_CONTRACTID, &kNS_INCREMENTALDOWNLOAD_CID }, { NS_STDURLPARSER_CONTRACTID, &kNS_STDURLPARSER_CID }, { NS_NOAUTHURLPARSER_CONTRACTID, &kNS_NOAUTHURLPARSER_CID }, diff --git a/python/mozbuild/mozbuild/backend/recursivemake.py b/python/mozbuild/mozbuild/backend/recursivemake.py index ee8962ef0fd1..48f233876e60 100644 --- a/python/mozbuild/mozbuild/backend/recursivemake.py +++ b/python/mozbuild/mozbuild/backend/recursivemake.py @@ -874,8 +874,8 @@ class RecursiveMakeBackend(CommonBackend): if isinstance(path, SourcePath): if path.full_path.startswith(backend_file.srcdir): return '$(srcdir)' + path.full_path[len(backend_file.srcdir):] - if path.full_path.startswith(self.environment.topsrcdir): - return '$(topsrcdir)' + path.full_path[len(self.environment.topsrcdir):] + if path.full_path.startswith(backend_file.topsrcdir): + return '$(topsrcdir)' + path.full_path[len(backend_file.topsrcdir):] elif isinstance(path, ObjDirPath): if path.full_path.startswith(backend_file.objdir): return path.full_path[len(backend_file.objdir) + 1:] diff --git a/security/certverifier/NSSCertDBTrustDomain.cpp b/security/certverifier/NSSCertDBTrustDomain.cpp index 6b5a8c3759d8..1498ea9713eb 100644 --- a/security/certverifier/NSSCertDBTrustDomain.cpp +++ b/security/certverifier/NSSCertDBTrustDomain.cpp @@ -975,7 +975,7 @@ nss_addEscape(const char* string, char quote) } // unnamed namespace SECStatus -InitializeNSS(const char* dir, bool readOnly) +InitializeNSS(const char* dir, bool readOnly, bool loadPKCS11Modules) { // The NSS_INIT_NOROOTINIT flag turns off the loading of the root certs // module by NSS_Initialize because we will load it in InstallLoadableRoots @@ -986,6 +986,9 @@ InitializeNSS(const char* dir, bool readOnly) if (readOnly) { flags |= NSS_INIT_READONLY; } + if (!loadPKCS11Modules) { + flags |= NSS_INIT_NOMODDB; + } return ::NSS_Initialize(dir, "", "", SECMOD_DB, flags); } diff --git a/security/certverifier/NSSCertDBTrustDomain.h b/security/certverifier/NSSCertDBTrustDomain.h index 7814cb055b92..831ef17cabbe 100644 --- a/security/certverifier/NSSCertDBTrustDomain.h +++ b/security/certverifier/NSSCertDBTrustDomain.h @@ -19,7 +19,7 @@ enum class ValidityCheckingMode { CheckForEV = 1, }; -SECStatus InitializeNSS(const char* dir, bool readOnly); +SECStatus InitializeNSS(const char* dir, bool readOnly, bool loadPKCS11Modules); void DisableMD5(); diff --git a/security/manager/ssl/nsNSSComponent.cpp b/security/manager/ssl/nsNSSComponent.cpp index 71de64c6b8b7..bdb0454f9c26 100644 --- a/security/manager/ssl/nsNSSComponent.cpp +++ b/security/manager/ssl/nsNSSComponent.cpp @@ -29,6 +29,7 @@ #include "nsISiteSecurityService.h" #include "nsITokenPasswordDialogs.h" #include "nsIWindowWatcher.h" +#include "nsIXULRuntime.h" #include "nsNSSHelper.h" #include "nsNSSShutDown.h" #include "nsServiceManagerUtils.h" @@ -989,14 +990,27 @@ nsNSSComponent::InitializeNSS() SECStatus init_rv = SECFailure; bool nocertdb = Preferences::GetBool("security.nocertdb", false); + bool inSafeMode = true; + nsCOMPtr runtime(do_GetService("@mozilla.org/xre/runtime;1")); + // There might not be an nsIXULRuntime in embedded situations. This will + // default to assuming we are in safe mode (as a result, no external PKCS11 + // modules will be loaded). + if (runtime) { + rv = runtime->GetInSafeMode(&inSafeMode); + if (NS_FAILED(rv)) { + return rv; + } + } + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("inSafeMode: %u\n", inSafeMode)); if (!nocertdb && !profileStr.IsEmpty()) { // First try to initialize the NSS DB in read/write mode. - init_rv = ::mozilla::psm::InitializeNSS(profileStr.get(), false); + // Only load PKCS11 modules if we're not in safe mode. + init_rv = ::mozilla::psm::InitializeNSS(profileStr.get(), false, !inSafeMode); // If that fails, attempt read-only mode. if (init_rv != SECSuccess) { MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("could not init NSS r/w in %s\n", profileStr.get())); - init_rv = ::mozilla::psm::InitializeNSS(profileStr.get(), true); + init_rv = ::mozilla::psm::InitializeNSS(profileStr.get(), true, !inSafeMode); } if (init_rv != SECSuccess) { MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("could not init in r/o either\n")); diff --git a/security/manager/ssl/tests/unit/test_pkcs11_safe_mode.js b/security/manager/ssl/tests/unit/test_pkcs11_safe_mode.js new file mode 100644 index 000000000000..64f471f068b4 --- /dev/null +++ b/security/manager/ssl/tests/unit/test_pkcs11_safe_mode.js @@ -0,0 +1,101 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +// This test loads a testing PKCS #11 module. After simulating a shutdown and +// startup, the test verifies that the module is automatically loaded again. +// After simulating a shutdown and restart in safe mode, the test verifies that +// the module is not automatically loaded again. + +var { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {}); + +// Registers an nsIXULRuntime so the test can control whether or not the +// platform thinks it's in safe mode. +function registerXULRuntime() { + let xulRuntime = { + inSafeMode: false, + logConsoleErrors: true, + OS: "XPCShell", + XPCOMABI: "noarch-spidermonkey", + invalidateCachesOnRestart: function invalidateCachesOnRestart() { + // Do nothing + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIXULRuntime]) + }; + + let xulRuntimeFactory = { + createInstance: function (outer, iid) { + if (outer != null) { + throw Cr.NS_ERROR_NO_AGGREGATION; + } + return xulRuntime.QueryInterface(iid); + } + }; + + let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); + const XULRUNTIME_CONTRACTID = "@mozilla.org/xre/runtime;1"; + const XULRUNTIME_CID = Components.ID("{f0f0b230-5525-4127-98dc-7bca39059e70}"); + registrar.registerFactory(XULRUNTIME_CID, "XULRuntime", XULRUNTIME_CONTRACTID, + xulRuntimeFactory); + return xulRuntime; +} + +// Loads the PKCS11 test module. +function loadTestModule() { + let pkcs11 = Cc["@mozilla.org/security/pkcs11;1"].getService(Ci.nsIPKCS11); + let libraryName = ctypes.libraryName("pkcs11testmodule"); + let libraryFile = Services.dirsvc.get("CurWorkD", Ci.nsILocalFile); + libraryFile.append("pkcs11testmodule"); + libraryFile.append(libraryName); + ok(libraryFile.exists(), "The pkcs11testmodule file should exist"); + pkcs11.addModule("PKCS11 Test Module", libraryFile.path, 0, 0); +} + +// Tests finding the PKCS11 test module. 'shouldSucceed' is a boolean +// indicating whether or not the test module is expected to be found. +function testFindTestModule(shouldSucceed) { + let pkcs11ModuleDB = Cc["@mozilla.org/security/pkcs11moduledb;1"] + .getService(Ci.nsIPKCS11ModuleDB); + try { + let module = pkcs11ModuleDB.findModuleByName("PKCS11 Test Module"); + ok(shouldSucceed, "Success expected: findModuleByName should not throw"); + ok(module, "module should be non-null"); + } catch (e) { + ok(!shouldSucceed, "Failure expected: findModuleByName should throw"); + } +} + +function simulateShutdown() { + let psmComponent = Cc["@mozilla.org/psm;1"].getService(Ci.nsIObserver); + psmComponent.observe(null, "profile-change-net-teardown", null); + psmComponent.observe(null, "profile-before-change", null); + psmComponent.observe(null, "xpcom-shutdown", null); +} + +function simulateStartup() { + let psmComponent = Cc["@mozilla.org/psm;1"].getService(Ci.nsIObserver); + psmComponent.observe(null, "profile-do-change", null); +} + +function run_test() { + do_get_profile(); + let xulRuntime = registerXULRuntime(); + loadTestModule(); + + // After having loaded the test module, it should be available. + testFindTestModule(true); + simulateShutdown(); + simulateStartup(); + // After shutting down and starting up, the test module should automatically + // be loaded again. + testFindTestModule(true); + + simulateShutdown(); + // Simulate starting in safe mode. + xulRuntime.inSafeMode = true; + simulateStartup(); + // When starting in safe mode, the test module should not be loaded. + testFindTestModule(false); +} diff --git a/security/manager/ssl/tests/unit/xpcshell-smartcards.ini b/security/manager/ssl/tests/unit/xpcshell-smartcards.ini index a5a420d5c197..349df9e96a2e 100644 --- a/security/manager/ssl/tests/unit/xpcshell-smartcards.ini +++ b/security/manager/ssl/tests/unit/xpcshell-smartcards.ini @@ -11,3 +11,6 @@ skip-if = os == "win" [test_pkcs11_no_events_after_removal.js] # Bug 1049969: this test doesn't work on windows skip-if = os == "win" +[test_pkcs11_safe_mode.js] +# Bug 1049969: this test doesn't work on windows +skip-if = os == "win" diff --git a/testing/mozbase/manifestparser/manifestparser/expression.py b/testing/mozbase/manifestparser/manifestparser/expression.py index 71a54da47d90..1803d3a87f63 100644 --- a/testing/mozbase/manifestparser/manifestparser/expression.py +++ b/testing/mozbase/manifestparser/manifestparser/expression.py @@ -3,6 +3,8 @@ # You can obtain one at http://mozilla.org/MPL/2.0/. import re +import sys +import traceback __all__ = ['parse', 'ParseError', 'ExpressionParser'] @@ -264,7 +266,9 @@ class ExpressionParser(object): self.token = self.iter.next() return self.expression() except: - raise ParseError("could not parse: %s; variables: %s" % (self.text, self.valuemapping)) + extype, ex, tb = sys.exc_info() + formatted = ''.join(traceback.format_exception_only(extype, ex)) + raise ParseError("could not parse: %s\nexception: %svariables: %s" % (self.text, formatted, self.valuemapping)), None, tb __call__ = parse diff --git a/testing/web-platform/mozilla/meta/service-workers/service-worker/oninstall-script-error.https.html.ini b/testing/web-platform/mozilla/meta/service-workers/service-worker/oninstall-script-error.https.html.ini deleted file mode 100644 index cee9f5bb34a2..000000000000 --- a/testing/web-platform/mozilla/meta/service-workers/service-worker/oninstall-script-error.https.html.ini +++ /dev/null @@ -1,8 +0,0 @@ -[oninstall-script-error.https.html] - type: testharness - [install handler throws an error that is cancelled] - expected: FAIL - - [install handler throws an error and prevents default] - expected: FAIL - diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/oninstall-script-error.https.html b/testing/web-platform/mozilla/tests/service-workers/service-worker/oninstall-script-error.https.html index 34ae6522ee3a..a9ca19cab7fd 100644 --- a/testing/web-platform/mozilla/tests/service-workers/service-worker/oninstall-script-error.https.html +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/oninstall-script-error.https.html @@ -45,15 +45,21 @@ function make_test(name, script, expect_install) { script: 'resources/oninstall-throw-error-from-nested-event-worker.js', expect_install: true }, + + // The following two cases test what happens when the ServiceWorkerGlobalScope + // 'error' event handler cancels the resulting error event. Since the + // original 'install' event handler through, the installation should still + // be stopped in this case. See: + // https://github.com/slightlyoff/ServiceWorker/issues/778 { name: 'install handler throws an error that is cancelled', script: 'resources/oninstall-throw-error-then-cancel-worker.js', - expect_install: true + expect_install: false }, { name: 'install handler throws an error and prevents default', script: 'resources/oninstall-throw-error-then-prevent-default-worker.js', - expect_install: true + expect_install: false } ].forEach(function(test_case) { make_test(test_case.name, test_case.script, test_case.expect_install); diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index 89735ded0376..0612ca49ae52 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -6274,6 +6274,12 @@ "n_buckets": "1000", "description": "The time (in milliseconds) that it took a 'reconfigure thread' request to go round trip." }, + "MEDIA_RUST_MP4PARSE_SUCCESS": { + "expires_in_version": "50", + "kind": "boolean", + "description": "(Bug 1220885) Whether the rust mp4 demuxer successfully parsed a stream segment.", + "cpp_guard": "MOZ_RUST_MP4PARSE" + }, "MEDIA_WMF_DECODE_ERROR": { "expires_in_version": "50", "kind": "enumerated", diff --git a/widget/cocoa/nsChildView.mm b/widget/cocoa/nsChildView.mm index 800c7fee0dc8..9269a3fd7f39 100644 --- a/widget/cocoa/nsChildView.mm +++ b/widget/cocoa/nsChildView.mm @@ -3144,7 +3144,7 @@ GLPresenter::EndFrame() // in our native NSView (it is set in |draggingEntered:|). It is unset when the // drag session ends for this view, either with the mouse exiting or when a drop // occurs in this view. -NSPasteboardWrapper* globalDragPboard = nil; +NSPasteboard* globalDragPboard = nil; // gLastDragView and gLastDragMouseDownEvent are used to communicate information // to the drag service during drag invocation (starting a drag in from the view). @@ -5461,7 +5461,11 @@ PanGestureTypeForEvent(NSEvent* aEvent) - (void)insertNewline:(id)sender { - [self insertText:@"\n"]; + if (mTextInputHandler) { + NSAttributedString *attrStr = [[NSAttributedString alloc] initWithString:@"\n"]; + mTextInputHandler->InsertText(attrStr); + [attrStr release]; + } } - (void)flagsChanged:(NSEvent*)theEvent @@ -5745,8 +5749,7 @@ PanGestureTypeForEvent(NSEvent* aEvent) // Set the global drag pasteboard that will be used for this drag session. // This will be set back to nil when the drag session ends (mouse exits // the view or a drop happens within the view). - globalDragPboard = - [[NSPasteboardWrapper alloc] initWithPasteboard:[sender draggingPasteboard]]; + globalDragPboard = [[sender draggingPasteboard] retain]; return [self doDragAction:eDragEnter sender:sender]; diff --git a/widget/cocoa/nsDragService.h b/widget/cocoa/nsDragService.h index 2ad0e4fbefa1..86fab8229d9c 100644 --- a/widget/cocoa/nsDragService.h +++ b/widget/cocoa/nsDragService.h @@ -15,16 +15,6 @@ extern NSString* const kCorePboardType_url; extern NSString* const kCorePboardType_urld; extern NSString* const kCorePboardType_urln; -@interface NSPasteboardWrapper : NSObject -{ - NSPasteboard* mPasteboard; - NSArray* mFilenames; -} -- (id)initWithPasteboard:(NSPasteboard*)aPasteboard; -- (id)propertyListForType:(NSString*)aType; -- (NSPasteboard*)pasteboard; -@end - class nsDragService : public nsBaseDragService { public: @@ -50,6 +40,11 @@ private: NSImage* ConstructDragImage(nsIDOMNode* aDOMNode, nsIntRect* aDragRect, nsIScriptableRegion* aRegion); + bool IsValidType(NSString* availableType, bool allowFileURL); + NSString* GetStringForType(NSPasteboardItem* item, const NSString* type, + bool allowFileURL = false); + NSString* GetTitleForURL(NSPasteboardItem* item); + NSString* GetFilePath(NSPasteboardItem* item); nsCOMPtr mDataItems; // only valid for a drag started within gecko NSView* mNativeDragView; diff --git a/widget/cocoa/nsDragService.mm b/widget/cocoa/nsDragService.mm index fb52074f1f3b..6618ff4abea6 100644 --- a/widget/cocoa/nsDragService.mm +++ b/widget/cocoa/nsDragService.mm @@ -35,7 +35,7 @@ extern PRLogModuleInfo* sCocoaLog; extern void EnsureLogInitialized(); -extern NSPasteboardWrapper* globalDragPboard; +extern NSPasteboard* globalDragPboard; extern NSView* gLastDragView; extern NSEvent* gLastDragMouseDownEvent; extern bool gUserCancelledDrag; @@ -48,46 +48,7 @@ NSString* const kWildcardPboardType = @"MozillaWildcard"; NSString* const kCorePboardType_url = @"CorePasteboardFlavorType 0x75726C20"; // 'url ' url NSString* const kCorePboardType_urld = @"CorePasteboardFlavorType 0x75726C64"; // 'urld' desc NSString* const kCorePboardType_urln = @"CorePasteboardFlavorType 0x75726C6E"; // 'urln' title - -@implementation NSPasteboardWrapper -- (id)initWithPasteboard:(NSPasteboard*)aPasteboard -{ - if ((self = [super init])) { - mPasteboard = [aPasteboard retain]; - mFilenames = nil; - } - return self; -} - -- (id)propertyListForType:(NSString *)aType -{ - if (![aType isEqualToString:NSFilenamesPboardType]) { - return [mPasteboard propertyListForType:aType]; - } - - if (!mFilenames) { - mFilenames = [[mPasteboard propertyListForType:aType] retain]; - } - - return mFilenames; -} - -- (NSPasteboard*) pasteboard -{ - return mPasteboard; -} - -- (void)dealloc -{ - [mPasteboard release]; - mPasteboard = nil; - - [mFilenames release]; - mFilenames = nil; - - [super dealloc]; -} -@end +NSString* const kUTTypeURLName = @"public.url-name"; nsDragService::nsDragService() { @@ -270,6 +231,78 @@ nsDragService::ConstructDragImage(nsIDOMNode* aDOMNode, NS_OBJC_END_TRY_ABORT_BLOCK_NIL; } +bool +nsDragService::IsValidType(NSString* availableType, bool allowFileURL) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + // Prevent exposing fileURL for non-fileURL type. + // We need URL provided by dropped webloc file, but don't need file's URL. + // kUTTypeFileURL is returned by [NSPasteboard availableTypeFromArray:] for + // kUTTypeURL, since it conforms to kUTTypeURL. + if (!allowFileURL && [availableType isEqualToString:(id)kUTTypeFileURL]) { + return false; + } + + return true; + + NS_OBJC_END_TRY_ABORT_BLOCK(false); +} + +NSString* +nsDragService::GetStringForType(NSPasteboardItem* item, const NSString* type, + bool allowFileURL) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + NSString* availableType = [item availableTypeFromArray:[NSArray arrayWithObjects:(id)type, nil]]; + if (availableType && IsValidType(availableType, allowFileURL)) { + return [item stringForType:(id)availableType]; + } + + return nil; + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + +NSString* +nsDragService::GetTitleForURL(NSPasteboardItem* item) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + NSString* name = GetStringForType(item, (const NSString*)kUTTypeURLName); + if (name) { + return name; + } + + NSString* filePath = GetFilePath(item); + if (filePath) { + return [filePath lastPathComponent]; + } + + return nil; + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + +NSString* +nsDragService::GetFilePath(NSPasteboardItem* item) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + NSString* urlString = GetStringForType(item, (const NSString*)kUTTypeFileURL, true); + if (urlString) { + NSURL* url = [NSURL URLWithString:urlString]; + if (url) { + return [url path]; + } + } + + return nil; + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + // We can only invoke NSView's 'dragImage:at:offset:event:pasteboard:source:slideBack:' from // within NSView's 'mouseDown:' or 'mouseDragged:'. Luckily 'mouseDragged' is always on the // stack when InvokeDragSession gets called. @@ -403,12 +436,23 @@ nsDragService::GetData(nsITransferable* aTransferable, uint32_t aItemIndex) MOZ_LOG(sCocoaLog, LogLevel::Info, ("nsDragService::GetData: looking for clipboard data of type %s\n", flavorStr.get())); - if (flavorStr.EqualsLiteral(kFileMime)) { - NSArray* pFiles = [globalDragPboard propertyListForType:NSFilenamesPboardType]; - if (!pFiles || [pFiles count] < (aItemIndex + 1)) - continue; + NSArray* droppedItems = [globalDragPboard pasteboardItems]; + if (!droppedItems) { + continue; + } - NSString* filePath = [pFiles objectAtIndex:aItemIndex]; + uint32_t itemCount = [droppedItems count]; + if (aItemIndex >= itemCount) { + continue; + } + + NSPasteboardItem* item = [droppedItems objectAtIndex:aItemIndex]; + if (!item) { + continue; + } + + if (flavorStr.EqualsLiteral(kFileMime)) { + NSString* filePath = GetFilePath(item); if (!filePath) continue; @@ -431,16 +475,26 @@ nsDragService::GetData(nsITransferable* aTransferable, uint32_t aItemIndex) break; } - NSString *pboardType = NSStringPboardType; - - if (nsClipboard::IsStringType(flavorStr, &pboardType) || - flavorStr.EqualsLiteral(kURLMime) || - flavorStr.EqualsLiteral(kURLDataMime) || - flavorStr.EqualsLiteral(kURLDescriptionMime)) { - NSString* pString = [[globalDragPboard pasteboard] stringForType:pboardType]; - if (!pString) - continue; - + NSString* pString = nil; + if (flavorStr.EqualsLiteral(kUnicodeMime)) { + pString = GetStringForType(item, (const NSString*)kUTTypeUTF8PlainText); + } else if (flavorStr.EqualsLiteral(kHTMLMime)) { + pString = GetStringForType(item, (const NSString*)kUTTypeHTML); + } else if (flavorStr.EqualsLiteral(kURLMime)) { + pString = GetStringForType(item, (const NSString*)kUTTypeURL); + if (pString) { + NSString* title = GetTitleForURL(item); + if (!title) { + title = pString; + } + pString = [NSString stringWithFormat:@"%@\n%@", pString, title]; + } + } else if (flavorStr.EqualsLiteral(kURLDataMime)) { + pString = GetStringForType(item, (const NSString*)kUTTypeURL); + } else if (flavorStr.EqualsLiteral(kURLDescriptionMime)) { + pString = GetTitleForURL(item); + } + if (pString) { NSData* stringData = [pString dataUsingEncoding:NSUnicodeStringEncoding]; unsigned int dataLength = [stringData length]; void* clipboardDataPtr = malloc(dataLength); @@ -533,22 +587,24 @@ nsDragService::IsDataFlavorSupported(const char *aDataFlavor, bool *_retval) } } - NSString *pboardType = nil; - + const NSString* type = nil; + bool allowFileURL = false; if (dataFlavor.EqualsLiteral(kFileMime)) { - NSString* availableType = [[globalDragPboard pasteboard] availableTypeFromArray:[NSArray arrayWithObject:NSFilenamesPboardType]]; - if (availableType && [availableType isEqualToString:NSFilenamesPboardType]) - *_retval = true; + type = (const NSString*)kUTTypeFileURL; + allowFileURL = true; + } else if (dataFlavor.EqualsLiteral(kUnicodeMime)) { + type = (const NSString*)kUTTypeUTF8PlainText; + } else if (dataFlavor.EqualsLiteral(kHTMLMime)) { + type = (const NSString*)kUTTypeHTML; + } else if (dataFlavor.EqualsLiteral(kURLMime) || + dataFlavor.EqualsLiteral(kURLDataMime)) { + type = (const NSString*)kUTTypeURL; + } else if (dataFlavor.EqualsLiteral(kURLDescriptionMime)) { + type = (const NSString*)kUTTypeURLName; } - else if (dataFlavor.EqualsLiteral(kURLMime)) { - NSString* availableType = [[globalDragPboard pasteboard] availableTypeFromArray:[NSArray arrayWithObject:kCorePboardType_url]]; - if (availableType && [availableType isEqualToString:kCorePboardType_url]) - *_retval = true; - } - else if (nsClipboard::IsStringType(dataFlavor, &pboardType)) { - NSString* availableType = [[globalDragPboard pasteboard] availableTypeFromArray:[NSArray arrayWithObject:pboardType]]; - if (availableType && [availableType isEqualToString:pboardType]) - *_retval = true; + NSString* availableType = [globalDragPboard availableTypeFromArray:[NSArray arrayWithObjects:(id)type, nil]]; + if (availableType && IsValidType(availableType, allowFileURL)) { + *_retval = true; } return NS_OK; @@ -569,18 +625,11 @@ nsDragService::GetNumDropItems(uint32_t* aNumItems) return NS_OK; } - // if there is a clipboard and there is something on it, then there is at least 1 item - NSArray* clipboardTypes = [[globalDragPboard pasteboard] types]; - if (globalDragPboard && [clipboardTypes count] > 0) - *aNumItems = 1; - else - return NS_OK; - - // if there is a list of files, send the number of files in that list - NSArray* fileNames = [globalDragPboard propertyListForType:NSFilenamesPboardType]; - if (fileNames) - *aNumItems = [fileNames count]; - + NSArray* droppedItems = [globalDragPboard pasteboardItems]; + if (droppedItems) { + *aNumItems = [droppedItems count]; + } + return NS_OK; NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; diff --git a/widget/tests/test_composition_text_querycontent.xul b/widget/tests/test_composition_text_querycontent.xul index 76c56e83d95f..e48a1b14a9cc 100644 --- a/widget/tests/test_composition_text_querycontent.xul +++ b/widget/tests/test_composition_text_querycontent.xul @@ -24,7 +24,7 @@ // '!(GetStateBits() & NS_FRAME_FIRST_REFLOW) || (GetParent()->GetStateBits() & // NS_FRAME_TOO_DEEP_IN_FRAME_TREE)'" in nsTextFrame.cpp. // Strangely, this doesn't occur with RDP on Windows. -SimpleTest.expectAssertions(0, 2); +SimpleTest.expectAssertions(0, 3); SimpleTest.waitForExplicitFinish(); window.open("window_composition_text_querycontent.xul", "_blank", "chrome,width=600,height=600"); diff --git a/xpcom/base/ErrorList.h b/xpcom/base/ErrorList.h index 9ae973fde0ab..a3b1a8d74bfd 100644 --- a/xpcom/base/ErrorList.h +++ b/xpcom/base/ErrorList.h @@ -530,6 +530,12 @@ a DOMException */ ERROR(NS_ERROR_DOM_DOMEXCEPTION, FAILURE(1017)), + /* An nsresult value to use in ErrorResult to indicate that we + * should just rethrow whatever is on the JSContext (which might be + * nothing if an uncatchable exception was thrown). + */ + ERROR(NS_ERROR_DOM_EXCEPTION_ON_JSCONTEXT, FAILURE(1018)), + /* May be used to indicate when e.g. setting a property value didn't * actually change the value, like for obj.foo = "bar"; obj.foo = "bar"; * the second assignment throws NS_SUCCESS_DOM_NO_OPERATION.