Merge mozilla-central to fx-team

This commit is contained in:
Carsten "Tomcat" Book 2015-11-10 12:49:53 +01:00
commit f5db3fe422
118 changed files with 1682 additions and 39661 deletions

View File

@ -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<RefPtr<Accessible> >* 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<AccReorderEvent> reorderEvent = new AccReorderEvent(owner);
RefPtr<AccMutationEvent> hideEvent =
new AccHideEvent(child, child->GetContent(), false);

View File

@ -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");

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="cf650aa1521151d5e4ac6d04188f911712271ec1"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="c0482775b1526add626b170dd53a72d10bcaf07c"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="cf650aa1521151d5e4ac6d04188f911712271ec1"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="c0482775b1526add626b170dd53a72d10bcaf07c"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="cf650aa1521151d5e4ac6d04188f911712271ec1"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="c0482775b1526add626b170dd53a72d10bcaf07c"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="4ace9aaee0e048dfda11bb787646c59982a3dc80"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="cf650aa1521151d5e4ac6d04188f911712271ec1"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="c0482775b1526add626b170dd53a72d10bcaf07c"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="aa5b7b7f6ed207ea1adc4df11d1d8bdaeabadd85"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="e76ff4b6b6357cf5c54dfafefbef8d1f2692db85"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="cf650aa1521151d5e4ac6d04188f911712271ec1"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="c0482775b1526add626b170dd53a72d10bcaf07c"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="c9d4fe680662ee44a4bdea42ae00366f5df399cf">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="cf650aa1521151d5e4ac6d04188f911712271ec1"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="c0482775b1526add626b170dd53a72d10bcaf07c"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="cf650aa1521151d5e4ac6d04188f911712271ec1"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="c0482775b1526add626b170dd53a72d10bcaf07c"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="4ace9aaee0e048dfda11bb787646c59982a3dc80"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="cf650aa1521151d5e4ac6d04188f911712271ec1"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="c0482775b1526add626b170dd53a72d10bcaf07c"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/>
@ -134,7 +134,7 @@
<project name="platform_external_libnfc-nci" path="external/libnfc-nci" remote="t2m" revision="4186bdecb4dae911b39a8202252cc2310d91b0be"/>
<project name="platform_external_libnfc-pn547" path="external/libnfc-pn547" remote="b2g" revision="5bb999b84b8adc14f6bea004d523ba258dea8188"/>
<project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="5b71e40213f650459e95d35b6f14af7e88d8ab62"/>
<project name="platform/frameworks/av" path="frameworks/av" revision="65f5144987afff35a932262c0c5fad6ecce0c04a"/>
<project name="platform_frameworks_av" path="frameworks/av" remote="b2g" revision="eab4189dd74194e499a081a42f8336d02e2c35bd"/>
<project name="platform/frameworks/base" path="frameworks/base" revision="da8e6bc53c8bc669da0bb627904d08aa293f2497"/>
<project name="platform/frameworks/native" path="frameworks/native" revision="a46a9f1ac0ed5662d614c277cbb14eb3f332f365"/>
<project name="platform/hardware/libhardware" path="hardware/libhardware" revision="7196881a0e9dd7bfbbcf0af64c8064e70f0fa094"/>

View File

@ -1,9 +1,9 @@
{
"git": {
"git_revision": "cf650aa1521151d5e4ac6d04188f911712271ec1",
"git_revision": "c0482775b1526add626b170dd53a72d10bcaf07c",
"remote": "https://git.mozilla.org/releases/gaia.git",
"branch": ""
},
"revision": "a09fd39bc2cefbaef4ee5848c30ba6cd21c41b4b",
"revision": "da79a53a3e7b627176e3a933387f0eaa7ff379fa",
"repo_path": "integration/gaia-central"
}

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="cf650aa1521151d5e4ac6d04188f911712271ec1"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="c0482775b1526add626b170dd53a72d10bcaf07c"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/>

View File

@ -18,7 +18,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="cf650aa1521151d5e4ac6d04188f911712271ec1"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="c0482775b1526add626b170dd53a72d10bcaf07c"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="aa5b7b7f6ed207ea1adc4df11d1d8bdaeabadd85"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="e76ff4b6b6357cf5c54dfafefbef8d1f2692db85"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="c9d4fe680662ee44a4bdea42ae00366f5df399cf">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="cf650aa1521151d5e4ac6d04188f911712271ec1"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="c0482775b1526add626b170dd53a72d10bcaf07c"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/>

View File

@ -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<nsRange*>(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<int32_t>* 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<int32_t>* aIndexes)
{
if (!aRoot) {
if (NS_WARN_IF(!aRoot)) {
return nullptr;
}
@ -565,7 +592,7 @@ nsINode*
nsContentIterator::GetDeepLastChild(nsINode* aRoot,
nsTArray<int32_t>* 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<int32_t>* aIndexes)
{
if (!aRoot) {
if (NS_WARN_IF(!aRoot)) {
return nullptr;
}
@ -607,12 +634,12 @@ nsIContent*
nsContentIterator::GetNextSibling(nsINode* aNode,
nsTArray<int32_t>* 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<int32_t>* 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<int32_t>* 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<int32_t>* 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<int32_t>* 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<int32_t>* 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<int32_t>* 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<int32_t>* 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<nsresult> 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<nsresult> 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);

View File

@ -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<nsCycleCollectionTraversalCallback*>(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<nsCycleCollectionTraversalCallback*>(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;

View File

@ -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();

View File

@ -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")

View File

@ -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)

View File

@ -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;

View File

@ -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;
}

View File

@ -473,6 +473,14 @@ EventStateManager::OnStopObservingContent(
mIMEContentObserver = nullptr;
}
void
EventStateManager::TryToFlushPendingNotificationsToIME()
{
if (mIMEContentObserver) {
mIMEContentObserver->TryToFlushPendingNotifications();
}
}
nsresult
EventStateManager::PreHandleEvent(nsPresContext* aPresContext,
WidgetEvent* aEvent,

View File

@ -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.

View File

@ -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<bool> 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<AsyncMergeableNotificationsFlusher> 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

View File

@ -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<nsIDocShell> mDocShell;
nsCOMPtr<nsIEditor> 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<IMEContentObserver> 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<IMENotificationSender> 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<IMEContentObserver> 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

View File

@ -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);
}
}

View File

@ -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()

View File

@ -570,7 +570,10 @@ CDMProxy::OnExpirationChange(const nsAString& aSessionId,
GMPTimestamp aExpiryTime)
{
MOZ_ASSERT(NS_IsMainThread());
NS_WARNING("CDMProxy::OnExpirationChange() not implemented");
RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
if (session) {
session->SetExpiration(static_cast<double>(aExpiryTime));
}
}
void

View File

@ -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<JSObject*> 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<uint8_t> 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

View File

@ -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<DetailedPromise> MakePromise(ErrorResult& aRv,
const nsACString& aName);
@ -114,6 +124,7 @@ private:
bool mIsClosed;
bool mUninitialized;
RefPtr<MediaKeyStatusMap> mKeyStatusMap;
double mExpiration;
};
} // namespace dom

View File

@ -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]

View File

@ -0,0 +1,35 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test Encrypted Media Extensions</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<script type="text/javascript" src="manifest.js"></script>
<script type="text/javascript" src="eme.js"></script>
</head>
<body>
<pre id="test">
<script class="testbody" type="text/javascript">
function Test() {
navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{ initDataTypes: ['cenc'] }])
.then(access => access.createMediaKeys())
.then(mediaKeys => {
var initData = (new TextEncoder()).encode( 'this is an invalid license, and that is ok');
var s = mediaKeys.createSession("temporary");
s.generateRequest("cenc", initData); // ignore result.
// "update()" call should fail, because MediaKeySession is "not callable"
// yet, since CDM won't have had a chance to set the sessionId on MediaKeySession.
return s.update(initData);
})
.then(()=>{ok(false, "An exception should be thrown; MediaKeySession should be not callable."); SimpleTest.finish();},
()=>{ok(true, "We expect this to fail; MediaKeySession should be not callable."); SimpleTest.finish();});
}
SimpleTest.waitForExplicitFinish();
SetupEMEPref(Test);
</script>
</pre>
</body>
</html>

View File

@ -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.

View File

@ -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) {

View File

@ -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;

View File

@ -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);
}
},
};

View File

@ -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");
}
};

View File

@ -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() {

View File

@ -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;
},

View File

@ -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);
});
},

View File

@ -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";

View File

@ -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]

View File

@ -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: '',

View File

@ -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');
});

View File

@ -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,

View File

@ -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');
});

View File

@ -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();

View File

@ -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');
});

View File

@ -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);

View File

@ -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);

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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.

View File

@ -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');
});

View File

@ -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,

View File

@ -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);

View File

@ -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,

View File

@ -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,

View File

@ -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'
);
});

View File

@ -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'
);
});

View File

@ -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');

View File

@ -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);

View File

@ -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

View File

@ -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"

View File

@ -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' ]

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -160,12 +160,15 @@ InputQueue::ReceiveTouchInput(const RefPtr<AsyncPanZoomController>& 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());

View File

@ -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);
}

View File

@ -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);

View File

@ -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

View File

@ -149,10 +149,10 @@ typedef HashMap<CrossCompartmentKey, ReadBarrieredValue,
// set.
//
// * PendingMetadata: This object has been allocated and is still pending its
// metadata. This should never be the case in an allocation
// path, as a constructor function was supposed to have set
// the metadata of the previous object *before* allocating
// another object.
// metadata. This should never be the case when we begin an
// allocation, as a constructor function was supposed to have
// set the metadata of the previous object *before*
// allocating another object.
//
// The js::AutoSetNewObjectMetadata RAII class provides an ergonomic way for
// constructor functions to navigate state transitions, and its instances

View File

@ -1453,10 +1453,9 @@ MoveChildrenTo(nsPresContext* aPresContext,
//----------------------------------------------------------------------
nsCSSFrameConstructor::nsCSSFrameConstructor(nsIDocument *aDocument,
nsIPresShell *aPresShell,
nsStyleSet* aStyleSet)
: nsFrameManager(aPresShell, aStyleSet)
nsCSSFrameConstructor::nsCSSFrameConstructor(nsIDocument* aDocument,
nsIPresShell* aPresShell)
: nsFrameManager(aPresShell)
, mDocument(aDocument)
, mRootElementFrame(nullptr)
, mRootElementStyleFrame(nullptr)

View File

@ -55,8 +55,7 @@ public:
friend class mozilla::RestyleManager;
nsCSSFrameConstructor(nsIDocument *aDocument, nsIPresShell* aPresShell,
nsStyleSet* aStyleSet);
nsCSSFrameConstructor(nsIDocument* aDocument, nsIPresShell* aPresShell);
~nsCSSFrameConstructor(void) {
NS_ASSERTION(mUpdateCount == 0, "Dying in the middle of our own update?");
}

View File

@ -78,7 +78,6 @@ static const PLDHashTableOps PlaceholderMapOps = {
nsFrameManagerBase::nsFrameManagerBase()
: mPresShell(nullptr)
, mStyleSet(nullptr)
, mRootFrame(nullptr)
, mPlaceholderMap(&PlaceholderMapOps, sizeof(PlaceholderMapEntry))
, mUndisplayedMap(nullptr)

View File

@ -82,11 +82,9 @@ class nsFrameManager : public nsFrameManagerBase
typedef mozilla::layout::FrameChildListID ChildListID;
public:
nsFrameManager(nsIPresShell *aPresShell, nsStyleSet* aStyleSet) {
explicit nsFrameManager(nsIPresShell* aPresShell) {
mPresShell = aPresShell;
mStyleSet = aStyleSet;
MOZ_ASSERT(mPresShell, "need a pres shell");
MOZ_ASSERT(mStyleSet, "need a style set");
}
~nsFrameManager();

View File

@ -53,8 +53,6 @@ protected:
// weak link, because the pres shell owns us
nsIPresShell* MOZ_NON_OWNING_REF mPresShell;
// the pres shell owns the style set
nsStyleSet* mStyleSet;
nsIFrame* mRootFrame;
PLDHashTable mPlaceholderMap;
UndisplayedMap* mUndisplayedMap;

View File

@ -1959,12 +1959,7 @@ nsLayoutUtils::GetNearestScrollableFrame(nsIFrame* aFrame, uint32_t aFlags)
if ((aFlags & SCROLLABLE_FIXEDPOS_FINDS_ROOT) &&
f->StyleDisplay()->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;

View File

@ -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<EventStateManager> 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:

View File

@ -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);

View File

@ -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) {

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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<nsIAtom> 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;
}
}

View File

@ -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
},

View File

@ -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.

View File

@ -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]

View File

@ -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) {

View File

@ -0,0 +1,77 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1176968
-->
<head>
<title>Test for Bug 1176968</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<style>.zoom-test { visibility: hidden; }</style>
<style><!-- placeholder for dynamic additions --></style>
</head>
<body onload="run()">
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1176968">Mozilla Bug 1176968</a>
<div id="content" style="display: none">
</div>
<script type="text/javascript">
</script>
<pre id="test">
<div id="zoom1" class="zoom-test"></div>
<div id="zoom2" class="zoom-test"></div>
<div id="zoom3" class="zoom-test"></div>
<script class="testbody" type="application/javascript">
/** Test for Bug 1176968 **/
SimpleTest.waitForExplicitFinish();
function run() {
function zoom(factor) {
var previous = SpecialPowers.getFullZoom(window);
SpecialPowers.setFullZoom(window, factor);
return previous;
}
function isVisible(divName) {
return window.getComputedStyle(document.getElementById(divName), null).visibility == "visible";
}
function getScreenPixelsPerCSSPixel() {
return SpecialPowers.DOMWindowUtils.screenPixelsPerCSSPixel;
}
var screenPixelsPerCSSPixel = getScreenPixelsPerCSSPixel();
var baseRatio = 1.0 * screenPixelsPerCSSPixel;
var doubleRatio = 2.0 * screenPixelsPerCSSPixel;
var halfRatio = 0.5 * screenPixelsPerCSSPixel;
var styleElem = document.getElementsByTagName("style")[1];
styleElem.textContent =
["@media all and (-webkit-device-pixel-ratio: " + baseRatio + ") {",
"#zoom1 { visibility: visible; }",
"}",
"@media all and (-webkit-device-pixel-ratio: " + doubleRatio + ") {",
"#zoom2 { visibility: visible; }",
"}",
"@media all and (-webkit-device-pixel-ratio: " + halfRatio + ") {",
"#zoom3 { visibility: visible; }",
"}"
].join("\n");
ok(isVisible("zoom1"), "Base ratio rule should apply at base zoom level");
ok(!isVisible("zoom2") && !isVisible("zoom3"), "no other rules should apply");
var origZoom = zoom(2);
ok(isVisible("zoom2"), "Double ratio rule should apply at double zoom level");
ok(!isVisible("zoom1") && !isVisible("zoom3"), "no other rules should apply");
zoom(0.5);
ok(isVisible("zoom3"), "Half ratio rule should apply at half zoom level");
ok(!isVisible("zoom1") && !isVisible("zoom2"), "no other rules should apply");
zoom(origZoom);
SimpleTest.finish();
}
</script>
</pre>
</body>
</html>

View File

@ -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;

View File

@ -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", "");

View File

@ -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',

View File

@ -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);
};

View File

@ -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<nsIHttpChannel> 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<nsIChannel> lastChannel = mChannel;
nsCOMPtr<nsIURI> 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<nsIHttpChannel> 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<nsIInterfaceRequestor> 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;
}

View File

@ -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<nsIChannel> mChannel;
nsCOMPtr<nsIRequestObserver> mObserver;
nsCOMPtr<nsISupports> mObserverContext;
nsresult mStatus;
bool mIsPending;
bool mAllowHead;
void SetStatusAndCallBack(nsresult aStatus);
nsresult CheckStatus();
};
#endif // nsURIChecker_h__

Some files were not shown because too many files have changed in this diff Show More