Update On Sun Feb 27 19:29:20 CET 2022
This commit is contained in:
parent
e8d802fee6
commit
44b99fd55d
108 changed files with 2412 additions and 1182 deletions
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include "InterfaceInitFuncs.h"
|
||||
#include "mozilla/a11y/PDocAccessible.h"
|
||||
#include "mozilla/StaticPrefs_accessibility.h"
|
||||
#include "LocalAccessible-inl.h"
|
||||
#include "HyperTextAccessible-inl.h"
|
||||
#include "nsMai.h"
|
||||
|
@ -444,49 +445,49 @@ static gint getOffsetAtPointCB(AtkText* aText, gint aX, gint aY,
|
|||
}
|
||||
|
||||
static gint getTextSelectionCountCB(AtkText* aText) {
|
||||
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
|
||||
if (accWrap) {
|
||||
HyperTextAccessible* text = accWrap->AsHyperText();
|
||||
if (!text || !text->IsTextRole()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return text->SelectionCount();
|
||||
Accessible* acc = GetInternalObj(ATK_OBJECT(aText));
|
||||
if (!acc) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (RemoteAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
|
||||
return proxy->SelectionCount();
|
||||
HyperTextAccessibleBase* text = acc->AsHyperTextBase();
|
||||
if (!text || !acc->IsTextRole()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return text->SelectionCount();
|
||||
}
|
||||
|
||||
static gchar* getTextSelectionCB(AtkText* aText, gint aSelectionNum,
|
||||
gint* aStartOffset, gint* aEndOffset) {
|
||||
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
|
||||
int32_t startOffset = 0, endOffset = 0;
|
||||
if (accWrap) {
|
||||
HyperTextAccessible* text = accWrap->AsHyperText();
|
||||
if (!text || !text->IsTextRole()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
text->SelectionBoundsAt(aSelectionNum, &startOffset, &endOffset);
|
||||
*aStartOffset = startOffset;
|
||||
*aEndOffset = endOffset;
|
||||
|
||||
return getTextCB(aText, *aStartOffset, *aEndOffset);
|
||||
Accessible* acc = GetInternalObj(ATK_OBJECT(aText));
|
||||
if (!acc) {
|
||||
return nullptr;
|
||||
}
|
||||
if (RemoteAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
|
||||
|
||||
int32_t startOffset = 0, endOffset = 0;
|
||||
if (acc->IsRemote() &&
|
||||
!StaticPrefs::accessibility_cache_enabled_AtStartup()) {
|
||||
RemoteAccessible* remote = acc->AsRemote();
|
||||
nsString data;
|
||||
proxy->SelectionBoundsAt(aSelectionNum, data, &startOffset, &endOffset);
|
||||
remote->SelectionBoundsAt(aSelectionNum, data, &startOffset, &endOffset);
|
||||
*aStartOffset = startOffset;
|
||||
*aEndOffset = endOffset;
|
||||
|
||||
NS_ConvertUTF16toUTF8 dataAsUTF8(data);
|
||||
return (dataAsUTF8.get()) ? g_strdup(dataAsUTF8.get()) : nullptr;
|
||||
}
|
||||
return nullptr;
|
||||
|
||||
HyperTextAccessibleBase* text = acc->AsHyperTextBase();
|
||||
if (!text || !acc->IsTextRole()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
text->SelectionBoundsAt(aSelectionNum, &startOffset, &endOffset);
|
||||
*aStartOffset = startOffset;
|
||||
*aEndOffset = endOffset;
|
||||
|
||||
return getTextCB(aText, *aStartOffset, *aEndOffset);
|
||||
}
|
||||
|
||||
// set methods
|
||||
|
|
|
@ -259,8 +259,7 @@ already_AddRefed<nsIAccessibleEvent> a11y::MakeXPCEvent(AccEvent* aEvent) {
|
|||
do_CreateInstance(NS_ARRAY_CONTRACTID);
|
||||
uint32_t len = ranges.Length();
|
||||
for (uint32_t idx = 0; idx < len; idx++) {
|
||||
xpcRanges->AppendElement(
|
||||
new xpcAccessibleTextRange(std::move(ranges[idx])));
|
||||
xpcRanges->AppendElement(new xpcAccessibleTextRange(ranges[idx]));
|
||||
}
|
||||
|
||||
xpEvent = new xpcAccTextSelectionChangeEvent(
|
||||
|
|
|
@ -13,9 +13,9 @@
|
|||
namespace mozilla {
|
||||
namespace a11y {
|
||||
|
||||
inline LocalAccessible* TextRange::Container() const {
|
||||
inline Accessible* TextRange::Container() const {
|
||||
uint32_t pos1 = 0, pos2 = 0;
|
||||
AutoTArray<LocalAccessible*, 30> parents1, parents2;
|
||||
AutoTArray<Accessible*, 30> parents1, parents2;
|
||||
return CommonParent(mStartContainer, mEndContainer, &parents1, &pos1,
|
||||
&parents2, &pos2);
|
||||
}
|
||||
|
|
|
@ -15,6 +15,34 @@
|
|||
namespace mozilla {
|
||||
namespace a11y {
|
||||
|
||||
/**
|
||||
* Returns a text point for aAcc within aContainer.
|
||||
*/
|
||||
static void ToTextPoint(Accessible* aAcc, Accessible** aContainer,
|
||||
int32_t* aOffset, bool aIsBefore = true) {
|
||||
if (aAcc->IsHyperText()) {
|
||||
*aContainer = aAcc;
|
||||
*aOffset =
|
||||
aIsBefore
|
||||
? 0
|
||||
: static_cast<int32_t>(aAcc->AsHyperTextBase()->CharacterCount());
|
||||
return;
|
||||
}
|
||||
|
||||
Accessible* child = nullptr;
|
||||
Accessible* parent = aAcc;
|
||||
do {
|
||||
child = parent;
|
||||
parent = parent->Parent();
|
||||
} while (parent && !parent->IsHyperText());
|
||||
|
||||
if (parent) {
|
||||
*aContainer = parent;
|
||||
*aOffset = parent->AsHyperTextBase()->GetChildOffset(
|
||||
child->IndexInParent() + static_cast<int32_t>(!aIsBefore));
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// TextPoint
|
||||
|
||||
|
@ -22,23 +50,23 @@ bool TextPoint::operator<(const TextPoint& aPoint) const {
|
|||
if (mContainer == aPoint.mContainer) return mOffset < aPoint.mOffset;
|
||||
|
||||
// Build the chain of parents
|
||||
LocalAccessible* p1 = mContainer;
|
||||
LocalAccessible* p2 = aPoint.mContainer;
|
||||
AutoTArray<LocalAccessible*, 30> parents1, parents2;
|
||||
Accessible* p1 = mContainer;
|
||||
Accessible* p2 = aPoint.mContainer;
|
||||
AutoTArray<Accessible*, 30> parents1, parents2;
|
||||
do {
|
||||
parents1.AppendElement(p1);
|
||||
p1 = p1->LocalParent();
|
||||
p1 = p1->Parent();
|
||||
} while (p1);
|
||||
do {
|
||||
parents2.AppendElement(p2);
|
||||
p2 = p2->LocalParent();
|
||||
p2 = p2->Parent();
|
||||
} while (p2);
|
||||
|
||||
// Find where the parent chain differs
|
||||
uint32_t pos1 = parents1.Length(), pos2 = parents2.Length();
|
||||
for (uint32_t len = std::min(pos1, pos2); len > 0; --len) {
|
||||
LocalAccessible* child1 = parents1.ElementAt(--pos1);
|
||||
LocalAccessible* child2 = parents2.ElementAt(--pos2);
|
||||
Accessible* child1 = parents1.ElementAt(--pos1);
|
||||
Accessible* child2 = parents2.ElementAt(--pos2);
|
||||
if (child1 != child2) {
|
||||
return child1->IndexInParent() < child2->IndexInParent();
|
||||
}
|
||||
|
@ -49,8 +77,8 @@ bool TextPoint::operator<(const TextPoint& aPoint) const {
|
|||
// descendant of aPoint.mContainer. The next element down in parents1
|
||||
// is mContainer's ancestor that is the child of aPoint.mContainer.
|
||||
// We compare its end offset in aPoint.mContainer with aPoint.mOffset.
|
||||
LocalAccessible* child = parents1.ElementAt(pos1 - 1);
|
||||
MOZ_ASSERT(child->LocalParent() == aPoint.mContainer);
|
||||
Accessible* child = parents1.ElementAt(pos1 - 1);
|
||||
MOZ_ASSERT(child->Parent() == aPoint.mContainer);
|
||||
return child->EndOffset() < static_cast<uint32_t>(aPoint.mOffset);
|
||||
}
|
||||
|
||||
|
@ -59,8 +87,8 @@ bool TextPoint::operator<(const TextPoint& aPoint) const {
|
|||
// descendant of mContainer. The next element down in parents2
|
||||
// is aPoint.mContainer's ancestor that is the child of mContainer.
|
||||
// We compare its start offset in mContainer with mOffset.
|
||||
LocalAccessible* child = parents2.ElementAt(pos2 - 1);
|
||||
MOZ_ASSERT(child->LocalParent() == mContainer);
|
||||
Accessible* child = parents2.ElementAt(pos2 - 1);
|
||||
MOZ_ASSERT(child->Parent() == mContainer);
|
||||
return static_cast<uint32_t>(mOffset) < child->StartOffset();
|
||||
}
|
||||
|
||||
|
@ -71,21 +99,22 @@ bool TextPoint::operator<(const TextPoint& aPoint) const {
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
// TextRange
|
||||
|
||||
TextRange::TextRange(HyperTextAccessible* aRoot,
|
||||
HyperTextAccessible* aStartContainer, int32_t aStartOffset,
|
||||
HyperTextAccessible* aEndContainer, int32_t aEndOffset)
|
||||
TextRange::TextRange(Accessible* aRoot, Accessible* aStartContainer,
|
||||
int32_t aStartOffset, Accessible* aEndContainer,
|
||||
int32_t aEndOffset)
|
||||
: mRoot(aRoot),
|
||||
mStartContainer(aStartContainer),
|
||||
mEndContainer(aEndContainer),
|
||||
mStartOffset(aStartOffset),
|
||||
mEndOffset(aEndOffset) {}
|
||||
|
||||
void TextRange::EmbeddedChildren(nsTArray<LocalAccessible*>* aChildren) const {
|
||||
void TextRange::EmbeddedChildren(nsTArray<Accessible*>* aChildren) const {
|
||||
HyperTextAccessibleBase* startHyper = mStartContainer->AsHyperTextBase();
|
||||
if (mStartContainer == mEndContainer) {
|
||||
int32_t startIdx = mStartContainer->GetChildIndexAtOffset(mStartOffset);
|
||||
int32_t endIdx = mStartContainer->GetChildIndexAtOffset(mEndOffset);
|
||||
int32_t startIdx = startHyper->GetChildIndexAtOffset(mStartOffset);
|
||||
int32_t endIdx = startHyper->GetChildIndexAtOffset(mEndOffset);
|
||||
for (int32_t idx = startIdx; idx <= endIdx; idx++) {
|
||||
LocalAccessible* child = mStartContainer->LocalChildAt(idx);
|
||||
Accessible* child = mStartContainer->ChildAt(idx);
|
||||
if (!child->IsText()) {
|
||||
aChildren->AppendElement(child);
|
||||
}
|
||||
|
@ -93,22 +122,23 @@ void TextRange::EmbeddedChildren(nsTArray<LocalAccessible*>* aChildren) const {
|
|||
return;
|
||||
}
|
||||
|
||||
LocalAccessible* p1 = mStartContainer->GetChildAtOffset(mStartOffset);
|
||||
LocalAccessible* p2 = mEndContainer->GetChildAtOffset(mEndOffset);
|
||||
Accessible* p1 = startHyper->GetChildAtOffset(mStartOffset);
|
||||
HyperTextAccessibleBase* endHyper = mEndContainer->AsHyperTextBase();
|
||||
Accessible* p2 = endHyper->GetChildAtOffset(mEndOffset);
|
||||
|
||||
uint32_t pos1 = 0, pos2 = 0;
|
||||
AutoTArray<LocalAccessible*, 30> parents1, parents2;
|
||||
LocalAccessible* container =
|
||||
AutoTArray<Accessible*, 30> parents1, parents2;
|
||||
Accessible* container =
|
||||
CommonParent(p1, p2, &parents1, &pos1, &parents2, &pos2);
|
||||
|
||||
// Traverse the tree up to the container and collect embedded objects.
|
||||
for (uint32_t idx = 0; idx < pos1 - 1; idx++) {
|
||||
LocalAccessible* parent = parents1[idx + 1];
|
||||
LocalAccessible* child = parents1[idx];
|
||||
Accessible* parent = parents1[idx + 1];
|
||||
Accessible* child = parents1[idx];
|
||||
uint32_t childCount = parent->ChildCount();
|
||||
for (uint32_t childIdx = child->IndexInParent(); childIdx < childCount;
|
||||
childIdx++) {
|
||||
LocalAccessible* next = parent->LocalChildAt(childIdx);
|
||||
Accessible* next = parent->ChildAt(childIdx);
|
||||
if (!next->IsText()) {
|
||||
aChildren->AppendElement(next);
|
||||
}
|
||||
|
@ -119,7 +149,7 @@ void TextRange::EmbeddedChildren(nsTArray<LocalAccessible*>* aChildren) const {
|
|||
int32_t endIdx = parents2[pos2 - 1]->IndexInParent();
|
||||
int32_t childIdx = parents1[pos1 - 1]->IndexInParent() + 1;
|
||||
for (; childIdx < endIdx; childIdx++) {
|
||||
LocalAccessible* next = container->LocalChildAt(childIdx);
|
||||
Accessible* next = container->ChildAt(childIdx);
|
||||
if (!next->IsText()) {
|
||||
aChildren->AppendElement(next);
|
||||
}
|
||||
|
@ -127,11 +157,11 @@ void TextRange::EmbeddedChildren(nsTArray<LocalAccessible*>* aChildren) const {
|
|||
|
||||
// Traverse down from the container to end point.
|
||||
for (int32_t idx = pos2 - 2; idx > 0; idx--) {
|
||||
LocalAccessible* parent = parents2[idx];
|
||||
LocalAccessible* child = parents2[idx - 1];
|
||||
Accessible* parent = parents2[idx];
|
||||
Accessible* child = parents2[idx - 1];
|
||||
int32_t endIdx = child->IndexInParent();
|
||||
for (int32_t childIdx = 0; childIdx < endIdx; childIdx++) {
|
||||
LocalAccessible* next = parent->LocalChildAt(childIdx);
|
||||
Accessible* next = parent->ChildAt(childIdx);
|
||||
if (!next->IsText()) {
|
||||
aChildren->AppendElement(next);
|
||||
}
|
||||
|
@ -140,29 +170,26 @@ void TextRange::EmbeddedChildren(nsTArray<LocalAccessible*>* aChildren) const {
|
|||
}
|
||||
|
||||
void TextRange::Text(nsAString& aText) const {
|
||||
LocalAccessible* current = mStartContainer->GetChildAtOffset(mStartOffset);
|
||||
uint32_t startIntlOffset =
|
||||
mStartOffset - mStartContainer->GetChildOffset(current);
|
||||
HyperTextAccessibleBase* startHyper = mStartContainer->AsHyperTextBase();
|
||||
Accessible* current = startHyper->GetChildAtOffset(mStartOffset);
|
||||
uint32_t startIntlOffset = mStartOffset - startHyper->GetChildOffset(current);
|
||||
|
||||
while (current && TextInternal(aText, current, startIntlOffset)) {
|
||||
current = current->LocalParent();
|
||||
current = current->Parent();
|
||||
if (!current) break;
|
||||
|
||||
current = current->LocalNextSibling();
|
||||
current = current->NextSibling();
|
||||
}
|
||||
}
|
||||
|
||||
void TextRange::Bounds(nsTArray<nsIntRect> aRects) const {}
|
||||
|
||||
void TextRange::Normalize(ETextUnit aUnit) {}
|
||||
|
||||
bool TextRange::Crop(LocalAccessible* aContainer) {
|
||||
bool TextRange::Crop(Accessible* aContainer) {
|
||||
uint32_t boundaryPos = 0, containerPos = 0;
|
||||
AutoTArray<LocalAccessible*, 30> boundaryParents, containerParents;
|
||||
AutoTArray<Accessible*, 30> boundaryParents, containerParents;
|
||||
|
||||
// Crop the start boundary.
|
||||
LocalAccessible* container = nullptr;
|
||||
LocalAccessible* boundary = mStartContainer->GetChildAtOffset(mStartOffset);
|
||||
Accessible* container = nullptr;
|
||||
HyperTextAccessibleBase* startHyper = mStartContainer->AsHyperTextBase();
|
||||
Accessible* boundary = startHyper->GetChildAtOffset(mStartOffset);
|
||||
if (boundary != aContainer) {
|
||||
CommonParent(boundary, aContainer, &boundaryParents, &boundaryPos,
|
||||
&containerParents, &containerPos);
|
||||
|
@ -171,14 +198,12 @@ bool TextRange::Crop(LocalAccessible* aContainer) {
|
|||
if (containerPos != 0) {
|
||||
// The container is contained by the start boundary, reduce the range to
|
||||
// the point starting at the container.
|
||||
aContainer->ToTextPoint(mStartContainer.StartAssignment(),
|
||||
&mStartOffset);
|
||||
static_cast<LocalAccessible*>(mStartContainer)->AddRef();
|
||||
ToTextPoint(aContainer, &mStartContainer, &mStartOffset);
|
||||
} else {
|
||||
// The start boundary and the container are siblings.
|
||||
container = aContainer;
|
||||
}
|
||||
} else if (containerPos != 0) {
|
||||
} else {
|
||||
// The container does not contain the start boundary.
|
||||
boundary = boundaryParents[boundaryPos];
|
||||
container = containerParents[containerPos];
|
||||
|
@ -193,9 +218,7 @@ bool TextRange::Crop(LocalAccessible* aContainer) {
|
|||
// If the range starts before the container, then reduce the range to
|
||||
// the point starting at the container.
|
||||
if (boundary->IndexInParent() < container->IndexInParent()) {
|
||||
container->ToTextPoint(mStartContainer.StartAssignment(),
|
||||
&mStartOffset);
|
||||
mStartContainer.get()->AddRef();
|
||||
ToTextPoint(container, &mStartContainer, &mStartOffset);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -203,7 +226,8 @@ bool TextRange::Crop(LocalAccessible* aContainer) {
|
|||
containerParents.SetLengthAndRetainStorage(0);
|
||||
}
|
||||
|
||||
boundary = mEndContainer->GetChildAtOffset(mEndOffset);
|
||||
HyperTextAccessibleBase* endHyper = mEndContainer->AsHyperTextBase();
|
||||
boundary = endHyper->GetChildAtOffset(mEndOffset);
|
||||
if (boundary == aContainer) {
|
||||
return true;
|
||||
}
|
||||
|
@ -215,13 +239,11 @@ bool TextRange::Crop(LocalAccessible* aContainer) {
|
|||
|
||||
if (boundaryPos == 0) {
|
||||
if (containerPos != 0) {
|
||||
aContainer->ToTextPoint(mEndContainer.StartAssignment(), &mEndOffset,
|
||||
false);
|
||||
static_cast<LocalAccessible*>(mEndContainer)->AddRef();
|
||||
ToTextPoint(aContainer, &mEndContainer, &mEndOffset, false);
|
||||
} else {
|
||||
container = aContainer;
|
||||
}
|
||||
} else if (containerPos != 0) {
|
||||
} else {
|
||||
boundary = boundaryParents[boundaryPos];
|
||||
container = containerParents[containerPos];
|
||||
}
|
||||
|
@ -235,34 +257,27 @@ bool TextRange::Crop(LocalAccessible* aContainer) {
|
|||
}
|
||||
|
||||
if (boundary->IndexInParent() > container->IndexInParent()) {
|
||||
container->ToTextPoint(mEndContainer.StartAssignment(), &mEndOffset, false);
|
||||
static_cast<LocalAccessible*>(mEndContainer)->AddRef();
|
||||
ToTextPoint(container, &mEndContainer, &mEndOffset, false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void TextRange::FindText(const nsAString& aText, EDirection aDirection,
|
||||
nsCaseTreatment aCaseSensitive,
|
||||
TextRange* aFoundRange) const {}
|
||||
|
||||
void TextRange::FindAttr(EAttr aAttr, nsIVariant* aValue, EDirection aDirection,
|
||||
TextRange* aFoundRange) const {}
|
||||
|
||||
void TextRange::AddToSelection() const {}
|
||||
|
||||
void TextRange::RemoveFromSelection() const {}
|
||||
|
||||
bool TextRange::SetSelectionAt(int32_t aSelectionNum) const {
|
||||
RefPtr<dom::Selection> domSel = mRoot->DOMSelection();
|
||||
HyperTextAccessible* root = mRoot->AsLocal()->AsHyperText();
|
||||
if (!root) {
|
||||
MOZ_ASSERT_UNREACHABLE("Not supported for RemoteAccessible");
|
||||
return false;
|
||||
}
|
||||
RefPtr<dom::Selection> domSel = root->DOMSelection();
|
||||
if (!domSel) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RefPtr<nsRange> range = nsRange::Create(mRoot->GetContent());
|
||||
RefPtr<nsRange> range = nsRange::Create(root->GetContent());
|
||||
uint32_t rangeCount = domSel->RangeCount();
|
||||
if (aSelectionNum == static_cast<int32_t>(rangeCount)) {
|
||||
range = nsRange::Create(mRoot->GetContent());
|
||||
range = nsRange::Create(root->GetContent());
|
||||
} else {
|
||||
range = domSel->GetRangeAt(AssertedCast<uint32_t>(aSelectionNum));
|
||||
}
|
||||
|
@ -294,10 +309,15 @@ bool TextRange::SetSelectionAt(int32_t aSelectionNum) const {
|
|||
}
|
||||
|
||||
void TextRange::ScrollIntoView(uint32_t aScrollType) const {
|
||||
RefPtr<nsRange> range = nsRange::Create(mRoot->GetContent());
|
||||
LocalAccessible* root = mRoot->AsLocal();
|
||||
if (!root) {
|
||||
MOZ_ASSERT_UNREACHABLE("Not supported for RemoteAccessible");
|
||||
return;
|
||||
}
|
||||
RefPtr<nsRange> range = nsRange::Create(root->GetContent());
|
||||
if (AssignDOMRange(range)) {
|
||||
nsCoreUtils::ScrollSubstringTo(mStartContainer->GetFrame(), range,
|
||||
aScrollType);
|
||||
nsCoreUtils::ScrollSubstringTo(mStartContainer->AsLocal()->GetFrame(),
|
||||
range, aScrollType);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -351,14 +371,16 @@ static nsIContent* GetElementAsContentOf(nsINode* aNode) {
|
|||
}
|
||||
|
||||
bool TextRange::AssignDOMRange(nsRange* aRange, bool* aReversed) const {
|
||||
MOZ_ASSERT(mRoot->IsLocal(), "Not supported for RemoteAccessible");
|
||||
bool reversed = EndPoint() < StartPoint();
|
||||
if (aReversed) {
|
||||
*aReversed = reversed;
|
||||
}
|
||||
|
||||
DOMPoint startPoint = reversed
|
||||
? mEndContainer->OffsetToDOMPoint(mEndOffset)
|
||||
: mStartContainer->OffsetToDOMPoint(mStartOffset);
|
||||
HyperTextAccessible* startHyper = mStartContainer->AsLocal()->AsHyperText();
|
||||
HyperTextAccessible* endHyper = mEndContainer->AsLocal()->AsHyperText();
|
||||
DOMPoint startPoint = reversed ? endHyper->OffsetToDOMPoint(mEndOffset)
|
||||
: startHyper->OffsetToDOMPoint(mStartOffset);
|
||||
if (!startPoint.node) {
|
||||
return false;
|
||||
}
|
||||
|
@ -379,8 +401,8 @@ bool TextRange::AssignDOMRange(nsRange* aRange, bool* aReversed) const {
|
|||
return true;
|
||||
}
|
||||
|
||||
DOMPoint endPoint = reversed ? mStartContainer->OffsetToDOMPoint(mStartOffset)
|
||||
: mEndContainer->OffsetToDOMPoint(mEndOffset);
|
||||
DOMPoint endPoint = reversed ? startHyper->OffsetToDOMPoint(mStartOffset)
|
||||
: endHyper->OffsetToDOMPoint(mEndOffset);
|
||||
if (!endPoint.node) {
|
||||
return false;
|
||||
}
|
||||
|
@ -432,9 +454,9 @@ void TextRange::TextRangesFromSelection(dom::Selection* aSelection,
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
// pivate
|
||||
|
||||
void TextRange::Set(HyperTextAccessible* aRoot,
|
||||
HyperTextAccessible* aStartContainer, int32_t aStartOffset,
|
||||
HyperTextAccessible* aEndContainer, int32_t aEndOffset) {
|
||||
void TextRange::Set(Accessible* aRoot, Accessible* aStartContainer,
|
||||
int32_t aStartOffset, Accessible* aEndContainer,
|
||||
int32_t aEndOffset) {
|
||||
mRoot = aRoot;
|
||||
mStartContainer = aStartContainer;
|
||||
mEndContainer = aEndContainer;
|
||||
|
@ -442,13 +464,14 @@ void TextRange::Set(HyperTextAccessible* aRoot,
|
|||
mEndOffset = aEndOffset;
|
||||
}
|
||||
|
||||
bool TextRange::TextInternal(nsAString& aText, LocalAccessible* aCurrent,
|
||||
bool TextRange::TextInternal(nsAString& aText, Accessible* aCurrent,
|
||||
uint32_t aStartIntlOffset) const {
|
||||
bool moveNext = true;
|
||||
int32_t endIntlOffset = -1;
|
||||
if (aCurrent->LocalParent() == mEndContainer &&
|
||||
mEndContainer->GetChildAtOffset(mEndOffset) == aCurrent) {
|
||||
uint32_t currentStartOffset = mEndContainer->GetChildOffset(aCurrent);
|
||||
HyperTextAccessibleBase* endHyper = mEndContainer->AsHyperTextBase();
|
||||
if (aCurrent->Parent() == mEndContainer &&
|
||||
endHyper->GetChildAtOffset(mEndOffset) == aCurrent) {
|
||||
uint32_t currentStartOffset = endHyper->GetChildOffset(aCurrent);
|
||||
endIntlOffset = mEndOffset - currentStartOffset;
|
||||
if (endIntlOffset == 0) return false;
|
||||
|
||||
|
@ -461,12 +484,12 @@ bool TextRange::TextInternal(nsAString& aText, LocalAccessible* aCurrent,
|
|||
if (!moveNext) return false;
|
||||
}
|
||||
|
||||
LocalAccessible* next = aCurrent->LocalFirstChild();
|
||||
Accessible* next = aCurrent->FirstChild();
|
||||
if (next) {
|
||||
if (!TextInternal(aText, next, 0)) return false;
|
||||
}
|
||||
|
||||
next = aCurrent->LocalNextSibling();
|
||||
next = aCurrent->NextSibling();
|
||||
if (next) {
|
||||
if (!TextInternal(aText, next, 0)) return false;
|
||||
}
|
||||
|
@ -474,17 +497,11 @@ bool TextRange::TextInternal(nsAString& aText, LocalAccessible* aCurrent,
|
|||
return moveNext;
|
||||
}
|
||||
|
||||
void TextRange::MoveInternal(ETextUnit aUnit, int32_t aCount,
|
||||
HyperTextAccessible& aContainer, int32_t aOffset,
|
||||
HyperTextAccessible* aStopContainer,
|
||||
int32_t aStopOffset) {}
|
||||
|
||||
LocalAccessible* TextRange::CommonParent(LocalAccessible* aAcc1,
|
||||
LocalAccessible* aAcc2,
|
||||
nsTArray<LocalAccessible*>* aParents1,
|
||||
uint32_t* aPos1,
|
||||
nsTArray<LocalAccessible*>* aParents2,
|
||||
uint32_t* aPos2) const {
|
||||
Accessible* TextRange::CommonParent(Accessible* aAcc1, Accessible* aAcc2,
|
||||
nsTArray<Accessible*>* aParents1,
|
||||
uint32_t* aPos1,
|
||||
nsTArray<Accessible*>* aParents2,
|
||||
uint32_t* aPos2) const {
|
||||
if (aAcc1 == aAcc2) {
|
||||
return aAcc1;
|
||||
}
|
||||
|
@ -493,25 +510,25 @@ LocalAccessible* TextRange::CommonParent(LocalAccessible* aAcc1,
|
|||
"Wrong arguments");
|
||||
|
||||
// Build the chain of parents.
|
||||
LocalAccessible* p1 = aAcc1;
|
||||
LocalAccessible* p2 = aAcc2;
|
||||
Accessible* p1 = aAcc1;
|
||||
Accessible* p2 = aAcc2;
|
||||
do {
|
||||
aParents1->AppendElement(p1);
|
||||
p1 = p1->LocalParent();
|
||||
p1 = p1->Parent();
|
||||
} while (p1);
|
||||
do {
|
||||
aParents2->AppendElement(p2);
|
||||
p2 = p2->LocalParent();
|
||||
p2 = p2->Parent();
|
||||
} while (p2);
|
||||
|
||||
// Find where the parent chain differs
|
||||
*aPos1 = aParents1->Length();
|
||||
*aPos2 = aParents2->Length();
|
||||
LocalAccessible* parent = nullptr;
|
||||
Accessible* parent = nullptr;
|
||||
uint32_t len = 0;
|
||||
for (len = std::min(*aPos1, *aPos2); len > 0; --len) {
|
||||
LocalAccessible* child1 = aParents1->ElementAt(--(*aPos1));
|
||||
LocalAccessible* child2 = aParents2->ElementAt(--(*aPos2));
|
||||
Accessible* child1 = aParents1->ElementAt(--(*aPos1));
|
||||
Accessible* child2 = aParents2->ElementAt(--(*aPos2));
|
||||
if (child1 != child2) break;
|
||||
|
||||
parent = child1;
|
||||
|
|
|
@ -9,11 +9,8 @@
|
|||
|
||||
#include <utility>
|
||||
|
||||
#include "nsCaseTreatment.h"
|
||||
#include "nsRect.h"
|
||||
#include "nsTArray.h"
|
||||
|
||||
class nsIVariant;
|
||||
class nsRange;
|
||||
|
||||
namespace mozilla {
|
||||
|
@ -22,19 +19,22 @@ class Selection;
|
|||
} // namespace dom
|
||||
namespace a11y {
|
||||
|
||||
class Accessible;
|
||||
class LocalAccessible;
|
||||
class HyperTextAccessible;
|
||||
|
||||
/**
|
||||
* A text point (hyper text + offset), represents a boundary of text range.
|
||||
* A text point (HyperText + offset), represents a boundary of text range.
|
||||
* In new code, This should only be used when you explicitly need to deal with
|
||||
* HyperText containers and offsets, including embedded objects; e.g. for
|
||||
* IAccessible2 and ATK. Otherwise, use TextLeafPoint instead.
|
||||
*/
|
||||
struct TextPoint final {
|
||||
TextPoint(HyperTextAccessible* aContainer, int32_t aOffset)
|
||||
TextPoint(Accessible* aContainer, int32_t aOffset)
|
||||
: mContainer(aContainer), mOffset(aOffset) {}
|
||||
TextPoint(const TextPoint& aPoint)
|
||||
: mContainer(aPoint.mContainer), mOffset(aPoint.mOffset) {}
|
||||
|
||||
HyperTextAccessible* mContainer;
|
||||
Accessible* mContainer;
|
||||
int32_t mOffset;
|
||||
|
||||
bool operator==(const TextPoint& aPoint) const {
|
||||
|
@ -44,12 +44,15 @@ struct TextPoint final {
|
|||
};
|
||||
|
||||
/**
|
||||
* Represents a text range within the text control or document.
|
||||
* Represents a HyperText range within the text control or document.
|
||||
* In new code, This should only be used when you explicitly need to deal with
|
||||
* HyperText containers and offsets, including embedded objects; e.g. for
|
||||
* IAccessible2 and ATK. Otherwise, use TextLeafRange instead.
|
||||
*/
|
||||
class TextRange final {
|
||||
public:
|
||||
TextRange(HyperTextAccessible* aRoot, HyperTextAccessible* aStartContainer,
|
||||
int32_t aStartOffset, HyperTextAccessible* aEndContainer,
|
||||
TextRange(Accessible* aRoot, Accessible* aStartContainer,
|
||||
int32_t aStartOffset, Accessible* aEndContainer,
|
||||
int32_t aEndOffset);
|
||||
TextRange() : mStartOffset{0}, mEndOffset{0} {}
|
||||
TextRange(TextRange&& aRange)
|
||||
|
@ -68,9 +71,10 @@ class TextRange final {
|
|||
return *this;
|
||||
}
|
||||
|
||||
HyperTextAccessible* StartContainer() const { return mStartContainer; }
|
||||
Accessible* Root() { return mRoot; }
|
||||
Accessible* StartContainer() const { return mStartContainer; }
|
||||
int32_t StartOffset() const { return mStartOffset; }
|
||||
HyperTextAccessible* EndContainer() const { return mEndContainer; }
|
||||
Accessible* EndContainer() const { return mEndContainer; }
|
||||
int32_t EndOffset() const { return mEndOffset; }
|
||||
|
||||
bool operator==(const TextRange& aRange) const {
|
||||
|
@ -88,114 +92,25 @@ class TextRange final {
|
|||
/**
|
||||
* Return a container containing both start and end points.
|
||||
*/
|
||||
LocalAccessible* Container() const;
|
||||
Accessible* Container() const;
|
||||
|
||||
/**
|
||||
* Return a list of embedded objects enclosed by the text range (includes
|
||||
* partially overlapped objects).
|
||||
*/
|
||||
void EmbeddedChildren(nsTArray<LocalAccessible*>* aChildren) const;
|
||||
void EmbeddedChildren(nsTArray<Accessible*>* aChildren) const;
|
||||
|
||||
/**
|
||||
* Return text enclosed by the range.
|
||||
*/
|
||||
void Text(nsAString& aText) const;
|
||||
|
||||
/**
|
||||
* Return list of bounding rects of the text range by lines.
|
||||
*/
|
||||
void Bounds(nsTArray<nsIntRect> aRects) const;
|
||||
|
||||
enum ETextUnit { eFormat, eWord, eLine, eParagraph, ePage, eDocument };
|
||||
|
||||
/**
|
||||
* Move the range or its points on specified amount of given units.
|
||||
*/
|
||||
void Move(ETextUnit aUnit, int32_t aCount) {
|
||||
MoveEnd(aUnit, aCount);
|
||||
MoveStart(aUnit, aCount);
|
||||
}
|
||||
void MoveStart(ETextUnit aUnit, int32_t aCount) {
|
||||
MoveInternal(aUnit, aCount, *mStartContainer, mStartOffset, mEndContainer,
|
||||
mEndOffset);
|
||||
}
|
||||
void MoveEnd(ETextUnit aUnit, int32_t aCount) {
|
||||
MoveInternal(aUnit, aCount, *mEndContainer, mEndOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the range points to the closest unit boundaries.
|
||||
*/
|
||||
void Normalize(ETextUnit aUnit);
|
||||
|
||||
/**
|
||||
* Crops the range if it overlaps the given accessible element boundaries,
|
||||
* returns true if the range was cropped successfully.
|
||||
*/
|
||||
bool Crop(LocalAccessible* aContainer);
|
||||
bool Crop(Accessible* aContainer);
|
||||
|
||||
enum EDirection { eBackward, eForward };
|
||||
|
||||
/**
|
||||
* Return range enclosing the found text.
|
||||
*/
|
||||
void FindText(const nsAString& aText, EDirection aDirection,
|
||||
nsCaseTreatment aCaseSensitive, TextRange* aFoundRange) const;
|
||||
|
||||
enum EAttr {
|
||||
eAnimationStyleAttr,
|
||||
eAnnotationObjectsAttr,
|
||||
eAnnotationTypesAttr,
|
||||
eBackgroundColorAttr,
|
||||
eBulletStyleAttr,
|
||||
eCapStyleAttr,
|
||||
eCaretBidiModeAttr,
|
||||
eCaretPositionAttr,
|
||||
eCultureAttr,
|
||||
eFontNameAttr,
|
||||
eFontSizeAttr,
|
||||
eFontWeightAttr,
|
||||
eForegroundColorAttr,
|
||||
eHorizontalTextAlignmentAttr,
|
||||
eIndentationFirstLineAttr,
|
||||
eIndentationLeadingAttr,
|
||||
eIndentationTrailingAttr,
|
||||
eIsActiveAttr,
|
||||
eIsHiddenAttr,
|
||||
eIsItalicAttr,
|
||||
eIsReadOnlyAttr,
|
||||
eIsSubscriptAttr,
|
||||
eIsSuperscriptAttr,
|
||||
eLinkAttr,
|
||||
eMarginBottomAttr,
|
||||
eMarginLeadingAttr,
|
||||
eMarginTopAttr,
|
||||
eMarginTrailingAttr,
|
||||
eOutlineStylesAttr,
|
||||
eOverlineColorAttr,
|
||||
eOverlineStyleAttr,
|
||||
eSelectionActiveEndAttr,
|
||||
eStrikethroughColorAttr,
|
||||
eStrikethroughStyleAttr,
|
||||
eStyleIdAttr,
|
||||
eStyleNameAttr,
|
||||
eTabsAttr,
|
||||
eTextFlowDirectionsAttr,
|
||||
eUnderlineColorAttr,
|
||||
eUnderlineStyleAttr
|
||||
};
|
||||
|
||||
/**
|
||||
* Return range enclosing text having requested attribute.
|
||||
*/
|
||||
void FindAttr(EAttr aAttr, nsIVariant* aValue, EDirection aDirection,
|
||||
TextRange* aFoundRange) const;
|
||||
|
||||
/**
|
||||
* Add/remove the text range from selection.
|
||||
*/
|
||||
void AddToSelection() const;
|
||||
void RemoveFromSelection() const;
|
||||
MOZ_CAN_RUN_SCRIPT bool SetSelectionAt(int32_t aSelectionNum) const;
|
||||
|
||||
/**
|
||||
|
@ -223,11 +138,11 @@ class TextRange final {
|
|||
*/
|
||||
bool IsValid() const { return mRoot; }
|
||||
|
||||
void SetStartPoint(HyperTextAccessible* aContainer, int32_t aOffset) {
|
||||
void SetStartPoint(Accessible* aContainer, int32_t aOffset) {
|
||||
mStartContainer = aContainer;
|
||||
mStartOffset = aOffset;
|
||||
}
|
||||
void SetEndPoint(HyperTextAccessible* aContainer, int32_t aOffset) {
|
||||
void SetEndPoint(Accessible* aContainer, int32_t aOffset) {
|
||||
mStartContainer = aContainer;
|
||||
mStartOffset = aOffset;
|
||||
}
|
||||
|
@ -242,9 +157,8 @@ class TextRange final {
|
|||
friend class HyperTextAccessible;
|
||||
friend class xpcAccessibleTextRange;
|
||||
|
||||
void Set(HyperTextAccessible* aRoot, HyperTextAccessible* aStartContainer,
|
||||
int32_t aStartOffset, HyperTextAccessible* aEndContainer,
|
||||
int32_t aEndOffset);
|
||||
void Set(Accessible* aRoot, Accessible* aStartContainer, int32_t aStartOffset,
|
||||
Accessible* aEndContainer, int32_t aEndOffset);
|
||||
|
||||
/**
|
||||
* Text() method helper.
|
||||
|
@ -253,27 +167,21 @@ class TextRange final {
|
|||
* @param aStartIntlOffset [in] start offset if current node is a text node
|
||||
* @return true if calculation is not finished yet
|
||||
*/
|
||||
bool TextInternal(nsAString& aText, LocalAccessible* aCurrent,
|
||||
bool TextInternal(nsAString& aText, Accessible* aCurrent,
|
||||
uint32_t aStartIntlOffset) const;
|
||||
|
||||
void MoveInternal(ETextUnit aUnit, int32_t aCount,
|
||||
HyperTextAccessible& aContainer, int32_t aOffset,
|
||||
HyperTextAccessible* aStopContainer = nullptr,
|
||||
int32_t aStopOffset = 0);
|
||||
|
||||
/**
|
||||
* A helper method returning a common parent for two given accessible
|
||||
* elements.
|
||||
*/
|
||||
LocalAccessible* CommonParent(LocalAccessible* aAcc1, LocalAccessible* aAcc2,
|
||||
nsTArray<LocalAccessible*>* aParents1,
|
||||
uint32_t* aPos1,
|
||||
nsTArray<LocalAccessible*>* aParents2,
|
||||
uint32_t* aPos2) const;
|
||||
Accessible* CommonParent(Accessible* aAcc1, Accessible* aAcc2,
|
||||
nsTArray<Accessible*>* aParents1, uint32_t* aPos1,
|
||||
nsTArray<Accessible*>* aParents2,
|
||||
uint32_t* aPos2) const;
|
||||
|
||||
RefPtr<HyperTextAccessible> mRoot;
|
||||
RefPtr<HyperTextAccessible> mStartContainer;
|
||||
RefPtr<HyperTextAccessible> mEndContainer;
|
||||
Accessible* mRoot;
|
||||
Accessible* mStartContainer;
|
||||
Accessible* mEndContainer;
|
||||
int32_t mStartOffset;
|
||||
int32_t mEndOffset;
|
||||
};
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "mozilla/StaticPrefs_accessibility.h"
|
||||
#include "nsAccUtils.h"
|
||||
#include "TextLeafRange.h"
|
||||
#include "TextRange.h"
|
||||
|
||||
namespace mozilla::a11y {
|
||||
|
||||
|
@ -541,4 +542,54 @@ already_AddRefed<AccAttributes> HyperTextAccessibleBase::TextAttributes(
|
|||
return attributes.forget();
|
||||
}
|
||||
|
||||
void HyperTextAccessibleBase::CroppedSelectionRanges(
|
||||
nsTArray<TextRange>& aRanges) const {
|
||||
SelectionRanges(&aRanges);
|
||||
const Accessible* acc = Acc();
|
||||
if (!acc->IsDoc()) {
|
||||
aRanges.RemoveElementsBy([acc](auto& range) {
|
||||
return range.StartPoint() == range.EndPoint() ||
|
||||
!range.Crop(const_cast<Accessible*>(acc));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
int32_t HyperTextAccessibleBase::SelectionCount() {
|
||||
nsTArray<TextRange> ranges;
|
||||
CroppedSelectionRanges(ranges);
|
||||
return static_cast<int32_t>(ranges.Length());
|
||||
}
|
||||
|
||||
bool HyperTextAccessibleBase::SelectionBoundsAt(int32_t aSelectionNum,
|
||||
int32_t* aStartOffset,
|
||||
int32_t* aEndOffset) {
|
||||
nsTArray<TextRange> ranges;
|
||||
CroppedSelectionRanges(ranges);
|
||||
if (aSelectionNum >= static_cast<int32_t>(ranges.Length())) {
|
||||
return false;
|
||||
}
|
||||
TextRange& range = ranges[aSelectionNum];
|
||||
Accessible* thisAcc = Acc();
|
||||
if (range.StartContainer() == thisAcc) {
|
||||
*aStartOffset = range.StartOffset();
|
||||
} else {
|
||||
bool ok;
|
||||
// range.StartContainer() isn't a text leaf, so don't use its offset.
|
||||
std::tie(ok, *aStartOffset) =
|
||||
TransformOffset(range.StartContainer(), 0, /* aDescendToEnd */ false);
|
||||
}
|
||||
if (range.EndContainer() == thisAcc) {
|
||||
*aEndOffset = range.EndOffset();
|
||||
} else {
|
||||
bool ok;
|
||||
// range.EndContainer() isn't a text leaf, so don't use its offset. If
|
||||
// range.EndOffset() is > 0, we want to include this container, so pas
|
||||
// offset 1.
|
||||
std::tie(ok, *aEndOffset) =
|
||||
TransformOffset(range.EndContainer(), range.EndOffset() == 0 ? 0 : 1,
|
||||
/* aDescendToEnd */ true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace mozilla::a11y
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
namespace mozilla::a11y {
|
||||
class Accessible;
|
||||
class TextLeafPoint;
|
||||
class TextRange;
|
||||
|
||||
// This character marks where in the text returned via Text interface,
|
||||
// that embedded object characters exist
|
||||
|
@ -159,6 +160,23 @@ class HyperTextAccessibleBase {
|
|||
*/
|
||||
virtual already_AddRefed<AccAttributes> DefaultTextAttributes() = 0;
|
||||
|
||||
/**
|
||||
* Return an array of disjoint ranges for selected text within the text
|
||||
* control or the document this accessible belongs to.
|
||||
*/
|
||||
virtual void SelectionRanges(nsTArray<TextRange>* aRanges) const = 0;
|
||||
|
||||
/**
|
||||
* Return selected regions count within the accessible.
|
||||
*/
|
||||
virtual int32_t SelectionCount();
|
||||
|
||||
/**
|
||||
* Return the start and end offset of the specified selection.
|
||||
*/
|
||||
virtual bool SelectionBoundsAt(int32_t aSelectionNum, int32_t* aStartOffset,
|
||||
int32_t* aEndOffset);
|
||||
|
||||
protected:
|
||||
virtual const Accessible* Acc() const = 0;
|
||||
Accessible* Acc() {
|
||||
|
@ -189,6 +207,12 @@ class HyperTextAccessibleBase {
|
|||
void AdjustOriginIfEndBoundary(TextLeafPoint& aOrigin,
|
||||
AccessibleTextBoundary aBoundaryType,
|
||||
bool aAtOffset = false) const;
|
||||
|
||||
/**
|
||||
* Return text selection ranges cropped to this Accessible (rather than for
|
||||
* the entire text control or document). This also excludes collapsed ranges.
|
||||
*/
|
||||
virtual void CroppedSelectionRanges(nsTArray<TextRange>& aRanges) const;
|
||||
};
|
||||
|
||||
} // namespace mozilla::a11y
|
||||
|
|
|
@ -226,16 +226,10 @@ class HyperTextAccessible : public AccessibleWrap,
|
|||
*/
|
||||
bool IsCaretAtEndOfLine() const;
|
||||
|
||||
/**
|
||||
* Return selected regions count within the accessible.
|
||||
*/
|
||||
int32_t SelectionCount();
|
||||
virtual int32_t SelectionCount() override;
|
||||
|
||||
/**
|
||||
* Return the start and end offset of the specified selection.
|
||||
*/
|
||||
bool SelectionBoundsAt(int32_t aSelectionNum, int32_t* aStartOffset,
|
||||
int32_t* aEndOffset);
|
||||
virtual bool SelectionBoundsAt(int32_t aSelectionNum, int32_t* aStartOffset,
|
||||
int32_t* aEndOffset) override;
|
||||
|
||||
/*
|
||||
* Changes the start and end offset of the specified selection.
|
||||
|
@ -277,11 +271,7 @@ class HyperTextAccessible : public AccessibleWrap,
|
|||
*/
|
||||
void EnclosingRange(TextRange& aRange) const;
|
||||
|
||||
/**
|
||||
* Return an array of disjoint ranges for selected text within the text
|
||||
* control or the document this accessible belongs to.
|
||||
*/
|
||||
void SelectionRanges(nsTArray<TextRange>* aRanges) const;
|
||||
virtual void SelectionRanges(nsTArray<TextRange>* aRanges) const override;
|
||||
|
||||
/**
|
||||
* Return an array of disjoint ranges of visible text within the text control
|
||||
|
|
|
@ -1009,15 +1009,27 @@ nsresult LocalAccessible::HandleAccEvent(AccEvent* aEvent) {
|
|||
announcementEvent->Priority());
|
||||
break;
|
||||
}
|
||||
#endif // !defined(XP_WIN)
|
||||
case nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED: {
|
||||
#if defined(XP_WIN)
|
||||
if (!StaticPrefs::accessibility_cache_enabled_AtStartup()) {
|
||||
// On Windows, when the cache is disabled, we have to defer events
|
||||
// until we are notified that the DocAccessibleParent has been
|
||||
// constructed, which needs specific code for each event payload.
|
||||
// Since we don't need a special event payload for text selection in
|
||||
// this case anyway, just send it as a generic event.
|
||||
ipcDoc->SendEvent(id, aEvent->GetEventType());
|
||||
break;
|
||||
}
|
||||
#endif // defined(XP_WIN)
|
||||
AccTextSelChangeEvent* textSelChangeEvent = downcast_accEvent(aEvent);
|
||||
AutoTArray<TextRange, 1> ranges;
|
||||
textSelChangeEvent->SelectionRanges(&ranges);
|
||||
nsTArray<TextRangeData> textRangeData(ranges.Length());
|
||||
for (size_t i = 0; i < ranges.Length(); i++) {
|
||||
const TextRange& range = ranges.ElementAt(i);
|
||||
LocalAccessible* start = range.StartContainer();
|
||||
LocalAccessible* end = range.EndContainer();
|
||||
LocalAccessible* start = range.StartContainer()->AsLocal();
|
||||
LocalAccessible* end = range.EndContainer()->AsLocal();
|
||||
textRangeData.AppendElement(TextRangeData(
|
||||
start->IsDoc() && start->AsDoc()->IPCDoc()
|
||||
? 0
|
||||
|
@ -1030,7 +1042,6 @@ nsresult LocalAccessible::HandleAccEvent(AccEvent* aEvent) {
|
|||
ipcDoc->SendTextSelectionChangeEvent(id, textRangeData);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
case nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE:
|
||||
case nsIAccessibleEvent::EVENT_NAME_CHANGE: {
|
||||
SendCache(CacheDomain::NameAndDescription, CacheUpdateType::Update);
|
||||
|
@ -2745,29 +2756,6 @@ already_AddRefed<nsIURI> LocalAccessible::AnchorURIAt(
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
void LocalAccessible::ToTextPoint(HyperTextAccessible** aContainer,
|
||||
int32_t* aOffset, bool aIsBefore) const {
|
||||
if (IsHyperText()) {
|
||||
*aContainer = const_cast<LocalAccessible*>(this)->AsHyperText();
|
||||
*aOffset = aIsBefore ? 0 : (*aContainer)->CharacterCount();
|
||||
return;
|
||||
}
|
||||
|
||||
const LocalAccessible* child = nullptr;
|
||||
const LocalAccessible* parent = this;
|
||||
do {
|
||||
child = parent;
|
||||
parent = parent->LocalParent();
|
||||
} while (parent && !parent->IsHyperText());
|
||||
|
||||
if (parent) {
|
||||
*aContainer = const_cast<LocalAccessible*>(parent)->AsHyperText();
|
||||
*aOffset = (*aContainer)
|
||||
->GetChildOffset(child->IndexInParent() +
|
||||
static_cast<int32_t>(!aIsBefore));
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// SelectAccessible
|
||||
|
||||
|
|
|
@ -554,12 +554,6 @@ class LocalAccessible : public nsISupports, public Accessible {
|
|||
*/
|
||||
virtual already_AddRefed<nsIURI> AnchorURIAt(uint32_t aAnchorIndex) const;
|
||||
|
||||
/**
|
||||
* Returns a text point for the accessible element.
|
||||
*/
|
||||
void ToTextPoint(HyperTextAccessible** aContainer, int32_t* aOffset,
|
||||
bool aIsBefore = true) const;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// SelectAccessible
|
||||
|
||||
|
|
|
@ -57,98 +57,11 @@ interface nsIAccessibleTextRange : nsISupports
|
|||
*/
|
||||
readonly attribute AString text;
|
||||
|
||||
/**
|
||||
* Return list of rects of the range.
|
||||
*/
|
||||
readonly attribute nsIArray bounds;
|
||||
|
||||
const unsigned long FormatUnit = 0;
|
||||
const unsigned long WordUnit = 1;
|
||||
const unsigned long LineUnit = 2;
|
||||
const unsigned long ParagraphUnit = 3;
|
||||
const unsigned long PageUnit = 4;
|
||||
const unsigned long DocumentUnit = 5;
|
||||
|
||||
/**
|
||||
* Move the boundary(ies) by the given number of the unit.
|
||||
*/
|
||||
void move(in unsigned long aUnit, in long aCount);
|
||||
void moveStart(in unsigned long aUnit, in long aCount);
|
||||
void moveEnd(in unsigned long aUnit, in long aCount);
|
||||
|
||||
/**
|
||||
* Normalize the range to the closest unit of the given type.
|
||||
*/
|
||||
void normalize(in unsigned long aUnit);
|
||||
|
||||
/**
|
||||
* Crops the range by the given accessible element.
|
||||
*/
|
||||
boolean crop(in nsIAccessible aContainer);
|
||||
|
||||
/**
|
||||
* Return range enclosing the found text.
|
||||
*/
|
||||
nsIAccessibleTextRange findText(in AString aText, in boolean aIsBackward,
|
||||
in boolean aIsIgnoreCase);
|
||||
|
||||
/**
|
||||
* Text attributes. Used in conjunction with findAttrs().
|
||||
*/
|
||||
const unsigned long AnimationStyleAttr = 0;
|
||||
const unsigned long AnnotationObjectsAttr = 1;
|
||||
const unsigned long AnnotationTypesAttr = 2;
|
||||
const unsigned long BackgroundColorAttr = 3;
|
||||
const unsigned long BulletStyleAttr = 4;
|
||||
const unsigned long CapStyleAttr = 5;
|
||||
const unsigned long CaretBidiModeAttr = 6;
|
||||
const unsigned long CaretPositionAttr = 7;
|
||||
const unsigned long CultureAttr = 8;
|
||||
const unsigned long FontNameAttr = 9;
|
||||
const unsigned long FontSizeAttr = 10;
|
||||
const unsigned long FontWeightAttr = 11;
|
||||
const unsigned long ForegroundColorAttr = 12;
|
||||
const unsigned long HorizontalTextAlignmentAttr = 13;
|
||||
const unsigned long IndentationFirstLineAttr = 14;
|
||||
const unsigned long IndentationLeadingAttr = 15;
|
||||
const unsigned long IndentationTrailingAttr = 16;
|
||||
const unsigned long IsActiveAttr = 17;
|
||||
const unsigned long IsHiddenAttr = 18;
|
||||
const unsigned long IsItalicAttr = 19;
|
||||
const unsigned long IsReadOnlyAttr = 20;
|
||||
const unsigned long IsSubscriptAttr = 21;
|
||||
const unsigned long IsSuperscriptAttr = 22;
|
||||
const unsigned long LinkAttr = 23;
|
||||
const unsigned long MarginBottomAttr = 24;
|
||||
const unsigned long MarginLeadingAttr = 25;
|
||||
const unsigned long MarginTopAttr = 26;
|
||||
const unsigned long MarginTrailingAttr = 27;
|
||||
const unsigned long OutlineStylesAttr = 28;
|
||||
const unsigned long OverlineColorAttr = 29;
|
||||
const unsigned long OverlineStyleAttr = 30;
|
||||
const unsigned long SelectionActiveEndAttr = 31;
|
||||
const unsigned long StrikethroughColorAttr = 32;
|
||||
const unsigned long StrikethroughStyleAttr = 33;
|
||||
const unsigned long StyleIdAttr = 34;
|
||||
const unsigned long StyleNameAttr = 35;
|
||||
const unsigned long TabsAttr = 36;
|
||||
const unsigned long TextFlowDirectionsAttr = 37;
|
||||
const unsigned long UnderlineColorAttr = 38;
|
||||
const unsigned long UnderlineStyleAttr = 39;
|
||||
|
||||
/**
|
||||
* Return range enslosing the text having requested attribute.
|
||||
*/
|
||||
nsIAccessibleTextRange findAttr(in unsigned long aAttr, in nsIVariant aValue,
|
||||
in boolean aIsBackward);
|
||||
|
||||
/**
|
||||
* Add/remove the text range from selection.
|
||||
*/
|
||||
void addToSelection();
|
||||
void removeFromSelection();
|
||||
void select();
|
||||
|
||||
const unsigned long AlignToTop = 0;
|
||||
const unsigned long AlignToBottom = 1;
|
||||
|
||||
|
|
|
@ -355,6 +355,13 @@ mozilla::ipc::IPCResult DocAccessibleParent::RecvCaretMoveEvent(
|
|||
mCaretId = aID;
|
||||
mCaretOffset = aOffset;
|
||||
mIsCaretAtEndOfLine = aIsAtEndOfLine;
|
||||
if (aIsSelectionCollapsed) {
|
||||
// We don't fire selection events for collapsed selections, but we need to
|
||||
// ensure we don't have a stale cached selection; e.g. when selecting
|
||||
// forward and then unselecting backward.
|
||||
mTextSelections.ClearAndRetainStorage();
|
||||
mTextSelections.AppendElement(TextRangeData(aID, aID, aOffset, aOffset));
|
||||
}
|
||||
|
||||
#if defined(XP_WIN)
|
||||
ProxyCaretMoveEvent(proxy, aCaretRect);
|
||||
|
@ -589,10 +596,10 @@ mozilla::ipc::IPCResult DocAccessibleParent::RecvAnnouncementEvent(
|
|||
|
||||
return IPC_OK();
|
||||
}
|
||||
#endif // !defined(XP_WIN)
|
||||
|
||||
mozilla::ipc::IPCResult DocAccessibleParent::RecvTextSelectionChangeEvent(
|
||||
const uint64_t& aID, nsTArray<TextRangeData>&& aSelection) {
|
||||
# ifdef MOZ_WIDGET_COCOA
|
||||
if (mShutdown) {
|
||||
return IPC_OK();
|
||||
}
|
||||
|
@ -603,16 +610,32 @@ mozilla::ipc::IPCResult DocAccessibleParent::RecvTextSelectionChangeEvent(
|
|||
return IPC_OK();
|
||||
}
|
||||
|
||||
if (StaticPrefs::accessibility_cache_enabled_AtStartup()) {
|
||||
mTextSelections.ClearAndRetainStorage();
|
||||
mTextSelections.AppendElements(aSelection);
|
||||
}
|
||||
|
||||
#ifdef MOZ_WIDGET_COCOA
|
||||
ProxyTextSelectionChangeEvent(target, aSelection);
|
||||
#else
|
||||
ProxyEvent(target, nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED);
|
||||
#endif
|
||||
|
||||
if (!nsCoreUtils::AccEventObserversExist()) {
|
||||
return IPC_OK();
|
||||
}
|
||||
xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(target);
|
||||
xpcAccessibleDocument* doc = nsAccessibilityService::GetXPCDocument(this);
|
||||
nsINode* node = nullptr;
|
||||
bool fromUser = true; // XXX fix me
|
||||
RefPtr<xpcAccEvent> event =
|
||||
new xpcAccEvent(nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED, xpcAcc,
|
||||
doc, node, fromUser);
|
||||
nsCoreUtils::DispatchAccEvent(std::move(event));
|
||||
|
||||
return IPC_OK();
|
||||
# else
|
||||
return RecvEvent(aID, nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED);
|
||||
# endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
mozilla::ipc::IPCResult DocAccessibleParent::RecvRoleChangedEvent(
|
||||
const a11y::role& aRole) {
|
||||
if (mShutdown) {
|
||||
|
@ -1085,5 +1108,16 @@ DocAccessiblePlatformExtParent* DocAccessibleParent::GetPlatformExtension() {
|
|||
|
||||
#endif // !defined(XP_WIN)
|
||||
|
||||
void DocAccessibleParent::SelectionRanges(nsTArray<TextRange>* aRanges) const {
|
||||
for (const auto& data : mTextSelections) {
|
||||
aRanges->AppendElement(
|
||||
TextRange(const_cast<DocAccessibleParent*>(this),
|
||||
const_cast<RemoteAccessible*>(GetAccessible(data.StartID())),
|
||||
data.StartOffset(),
|
||||
const_cast<RemoteAccessible*>(GetAccessible(data.EndID())),
|
||||
data.EndOffset()));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace a11y
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
namespace mozilla {
|
||||
namespace a11y {
|
||||
|
||||
class TextRange;
|
||||
class xpcAccessibleGeneric;
|
||||
|
||||
#if !defined(XP_WIN)
|
||||
|
@ -152,10 +153,10 @@ class DocAccessibleParent : public RemoteAccessible,
|
|||
virtual mozilla::ipc::IPCResult RecvAnnouncementEvent(
|
||||
const uint64_t& aID, const nsString& aAnnouncement,
|
||||
const uint16_t& aPriority) override;
|
||||
#endif
|
||||
|
||||
virtual mozilla::ipc::IPCResult RecvTextSelectionChangeEvent(
|
||||
const uint64_t& aID, nsTArray<TextRangeData>&& aSelection) override;
|
||||
#endif
|
||||
|
||||
mozilla::ipc::IPCResult RecvRoleChangedEvent(const a11y::role& aRole) final;
|
||||
|
||||
|
@ -314,6 +315,8 @@ class DocAccessibleParent : public RemoteAccessible,
|
|||
|
||||
bool IsCaretAtEndOfLine() const { return mIsCaretAtEndOfLine; }
|
||||
|
||||
virtual void SelectionRanges(nsTArray<TextRange>* aRanges) const override;
|
||||
|
||||
private:
|
||||
~DocAccessibleParent() {
|
||||
LiveDocs().Remove(mActorID);
|
||||
|
@ -388,6 +391,7 @@ class DocAccessibleParent : public RemoteAccessible,
|
|||
uint64_t mCaretId;
|
||||
int32_t mCaretOffset;
|
||||
bool mIsCaretAtEndOfLine;
|
||||
nsTArray<TextRangeData> mTextSelections;
|
||||
|
||||
static uint64_t sMaxDocID;
|
||||
static nsTHashMap<nsUint64HashKey, DocAccessibleParent*>& LiveDocs() {
|
||||
|
|
|
@ -686,6 +686,12 @@ bool RemoteAccessibleBase<Derived>::DoAction(uint8_t aIndex) const {
|
|||
return true;
|
||||
}
|
||||
|
||||
template <class Derived>
|
||||
void RemoteAccessibleBase<Derived>::SelectionRanges(
|
||||
nsTArray<TextRange>* aRanges) const {
|
||||
Document()->SelectionRanges(aRanges);
|
||||
}
|
||||
|
||||
template <class Derived>
|
||||
void RemoteAccessibleBase<Derived>::ARIAGroupPosition(
|
||||
int32_t* aLevel, int32_t* aSetSize, int32_t* aPosInSet) const {
|
||||
|
|
|
@ -189,6 +189,8 @@ class RemoteAccessibleBase : public Accessible, public HyperTextAccessibleBase {
|
|||
|
||||
virtual bool DoAction(uint8_t aIndex) const override;
|
||||
|
||||
virtual void SelectionRanges(nsTArray<TextRange>* aRanges) const override;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// SelectAccessible
|
||||
|
||||
|
|
|
@ -73,8 +73,6 @@ int32_t CaretLineNumber();
|
|||
virtual int32_t CaretOffset() const override;
|
||||
void SetCaretOffset(int32_t aOffset);
|
||||
|
||||
int32_t SelectionCount();
|
||||
|
||||
virtual void TextSubstring(int32_t aStartOffset, int32_t aEndOfset,
|
||||
nsAString& aText) const override;
|
||||
|
||||
|
@ -103,9 +101,6 @@ LayoutDeviceIntRect CharBounds(int32_t aOffset, uint32_t aCoordType);
|
|||
|
||||
int32_t OffsetAtPoint(int32_t aX, int32_t aY, uint32_t aCoordType);
|
||||
|
||||
bool SelectionBoundsAt(int32_t aSelectionNum, nsString& aData,
|
||||
int32_t* aStartOffset, int32_t* aEndOffset);
|
||||
|
||||
bool SetSelectionBoundsAt(int32_t aSelectionNum, int32_t aStartOffset,
|
||||
int32_t aEndOffset);
|
||||
|
||||
|
|
|
@ -195,6 +195,9 @@ uint32_t RemoteAccessible::CharacterCount() const {
|
|||
}
|
||||
|
||||
int32_t RemoteAccessible::SelectionCount() {
|
||||
if (StaticPrefs::accessibility_cache_enabled_AtStartup()) {
|
||||
return RemoteAccessibleBase<RemoteAccessible>::SelectionCount();
|
||||
}
|
||||
int32_t count = 0;
|
||||
Unused << mDoc->SendSelectionCount(mID, &count);
|
||||
return count;
|
||||
|
|
|
@ -61,6 +61,12 @@ class RemoteAccessible : public RemoteAccessibleBase<RemoteAccessible> {
|
|||
|
||||
virtual nsAtom* LandmarkRole() const override;
|
||||
|
||||
virtual int32_t SelectionCount() override;
|
||||
|
||||
using RemoteAccessibleBase<RemoteAccessible>::SelectionBoundsAt;
|
||||
bool SelectionBoundsAt(int32_t aSelectionNum, nsString& aData,
|
||||
int32_t* aStartOffset, int32_t* aEndOffset);
|
||||
|
||||
protected:
|
||||
explicit RemoteAccessible(DocAccessibleParent* aThisAsDoc)
|
||||
: RemoteAccessibleBase(aThisAsDoc) {
|
||||
|
|
|
@ -44,6 +44,14 @@ struct ShowEventData
|
|||
bool EventSuppressed;
|
||||
};
|
||||
|
||||
struct TextRangeData
|
||||
{
|
||||
uint64_t StartID;
|
||||
uint64_t EndID;
|
||||
int32_t StartOffset;
|
||||
int32_t EndOffset;
|
||||
};
|
||||
|
||||
[ManualDealloc, ChildImpl=virtual, ParentImpl=virtual]
|
||||
sync protocol PDocAccessible
|
||||
{
|
||||
|
@ -80,6 +88,7 @@ parent:
|
|||
async ScrollingEvent(uint64_t aID, uint64_t aType,
|
||||
uint32_t aScrollX, uint32_t aScrollY,
|
||||
uint32_t aMaxScrollX, uint32_t aMaxScrollY);
|
||||
async TextSelectionChangeEvent(uint64_t aID, TextRangeData[] aSelection);
|
||||
|
||||
/*
|
||||
* Tell the parent document to bind the existing document as a new child
|
||||
|
|
|
@ -403,25 +403,26 @@ void HyperTextAccessibleWrap::LeftWordAt(int32_t aOffset,
|
|||
return;
|
||||
}
|
||||
|
||||
auto* startContainer = static_cast<HyperTextAccessibleWrap*>(
|
||||
start.mContainer->AsLocal()->AsHyperText());
|
||||
if ((NativeState() & states::EDITABLE) &&
|
||||
!(start.mContainer->NativeState() & states::EDITABLE)) {
|
||||
!(startContainer->NativeState() & states::EDITABLE)) {
|
||||
// The word search crossed an editable boundary. Return the first word of
|
||||
// the editable root.
|
||||
return EditableRoot()->RightWordAt(0, aStartContainer, aStartOffset,
|
||||
aEndContainer, aEndOffset);
|
||||
}
|
||||
|
||||
TextPoint end =
|
||||
static_cast<HyperTextAccessibleWrap*>(start.mContainer)
|
||||
->FindTextPoint(start.mOffset, eDirNext, eSelectWord, eEndWord);
|
||||
TextPoint end = startContainer->FindTextPoint(start.mOffset, eDirNext,
|
||||
eSelectWord, eEndWord);
|
||||
if (end < here) {
|
||||
*aStartContainer = end.mContainer;
|
||||
*aEndContainer = here.mContainer;
|
||||
*aStartContainer = end.mContainer->AsLocal()->AsHyperText();
|
||||
*aEndContainer = here.mContainer->AsLocal()->AsHyperText();
|
||||
*aStartOffset = end.mOffset;
|
||||
*aEndOffset = here.mOffset;
|
||||
} else {
|
||||
*aStartContainer = start.mContainer;
|
||||
*aEndContainer = end.mContainer;
|
||||
*aStartContainer = startContainer;
|
||||
*aEndContainer = end.mContainer->AsLocal()->AsHyperText();
|
||||
*aStartOffset = start.mOffset;
|
||||
*aEndOffset = end.mOffset;
|
||||
}
|
||||
|
@ -440,24 +441,25 @@ void HyperTextAccessibleWrap::RightWordAt(int32_t aOffset,
|
|||
return;
|
||||
}
|
||||
|
||||
auto* endContainer = static_cast<HyperTextAccessibleWrap*>(
|
||||
end.mContainer->AsLocal()->AsHyperText());
|
||||
if ((NativeState() & states::EDITABLE) &&
|
||||
!(end.mContainer->NativeState() & states::EDITABLE)) {
|
||||
!(endContainer->NativeState() & states::EDITABLE)) {
|
||||
// The word search crossed an editable boundary. Return with no result.
|
||||
return;
|
||||
}
|
||||
|
||||
TextPoint start =
|
||||
static_cast<HyperTextAccessibleWrap*>(end.mContainer)
|
||||
->FindTextPoint(end.mOffset, eDirPrevious, eSelectWord, eStartWord);
|
||||
TextPoint start = endContainer->FindTextPoint(end.mOffset, eDirPrevious,
|
||||
eSelectWord, eStartWord);
|
||||
|
||||
if (here < start) {
|
||||
*aStartContainer = here.mContainer;
|
||||
*aEndContainer = start.mContainer;
|
||||
*aStartContainer = here.mContainer->AsLocal()->AsHyperText();
|
||||
*aEndContainer = start.mContainer->AsLocal()->AsHyperText();
|
||||
*aStartOffset = here.mOffset;
|
||||
*aEndOffset = start.mOffset;
|
||||
} else {
|
||||
*aStartContainer = start.mContainer;
|
||||
*aEndContainer = end.mContainer;
|
||||
*aStartContainer = start.mContainer->AsLocal()->AsHyperText();
|
||||
*aEndContainer = endContainer;
|
||||
*aStartOffset = start.mOffset;
|
||||
*aEndOffset = end.mOffset;
|
||||
}
|
||||
|
@ -477,9 +479,10 @@ void HyperTextAccessibleWrap::LineAt(int32_t aOffset, bool aNextLine,
|
|||
return;
|
||||
}
|
||||
|
||||
TextPoint start = static_cast<HyperTextAccessibleWrap*>(end.mContainer)
|
||||
->FindTextPoint(end.mOffset, eDirPrevious,
|
||||
eSelectBeginLine, eDefaultBehavior);
|
||||
auto* endContainer = static_cast<HyperTextAccessibleWrap*>(
|
||||
end.mContainer->AsLocal()->AsHyperText());
|
||||
TextPoint start = endContainer->FindTextPoint(
|
||||
end.mOffset, eDirPrevious, eSelectBeginLine, eDefaultBehavior);
|
||||
|
||||
if (!aNextLine && here < start) {
|
||||
start = FindTextPoint(aOffset, eDirPrevious, eSelectBeginLine,
|
||||
|
@ -488,13 +491,14 @@ void HyperTextAccessibleWrap::LineAt(int32_t aOffset, bool aNextLine,
|
|||
return;
|
||||
}
|
||||
|
||||
end = static_cast<HyperTextAccessibleWrap*>(start.mContainer)
|
||||
->FindTextPoint(start.mOffset, eDirNext, eSelectEndLine,
|
||||
eDefaultBehavior);
|
||||
auto* startContainer = static_cast<HyperTextAccessibleWrap*>(
|
||||
start.mContainer->AsLocal()->AsHyperText());
|
||||
end = startContainer->FindTextPoint(start.mOffset, eDirNext, eSelectEndLine,
|
||||
eDefaultBehavior);
|
||||
}
|
||||
|
||||
*aStartContainer = start.mContainer;
|
||||
*aEndContainer = end.mContainer;
|
||||
*aStartContainer = start.mContainer->AsLocal()->AsHyperText();
|
||||
*aEndContainer = end.mContainer->AsLocal()->AsHyperText();
|
||||
*aStartOffset = start.mOffset;
|
||||
*aEndOffset = end.mOffset;
|
||||
}
|
||||
|
@ -524,12 +528,14 @@ void HyperTextAccessibleWrap::ParagraphAt(int32_t aOffset,
|
|||
return;
|
||||
}
|
||||
|
||||
TextPoint start = static_cast<HyperTextAccessibleWrap*>(end.mContainer)
|
||||
auto* endContainer = static_cast<HyperTextAccessibleWrap*>(
|
||||
end.mContainer->AsLocal()->AsHyperText());
|
||||
TextPoint start = static_cast<HyperTextAccessibleWrap*>(endContainer)
|
||||
->FindTextPoint(end.mOffset, eDirPrevious,
|
||||
eSelectParagraph, eDefaultBehavior);
|
||||
|
||||
*aStartContainer = start.mContainer;
|
||||
*aEndContainer = end.mContainer;
|
||||
*aStartContainer = start.mContainer->AsLocal()->AsHyperText();
|
||||
*aEndContainer = endContainer;
|
||||
*aStartOffset = start.mOffset;
|
||||
*aEndOffset = end.mOffset;
|
||||
}
|
||||
|
@ -572,7 +578,7 @@ void HyperTextAccessibleWrap::NextClusterAt(
|
|||
*aNextContainer = this;
|
||||
*aNextOffset = aOffset;
|
||||
} else {
|
||||
*aNextContainer = next.mContainer;
|
||||
*aNextContainer = next.mContainer->AsLocal()->AsHyperText();
|
||||
*aNextOffset = next.mOffset;
|
||||
}
|
||||
}
|
||||
|
@ -582,7 +588,7 @@ void HyperTextAccessibleWrap::PreviousClusterAt(
|
|||
int32_t* aPrevOffset) {
|
||||
TextPoint prev =
|
||||
FindTextPoint(aOffset, eDirPrevious, eSelectCluster, eDefaultBehavior);
|
||||
*aPrevContainer = prev.mContainer;
|
||||
*aPrevContainer = prev.mContainer->AsLocal()->AsHyperText();
|
||||
*aPrevOffset = prev.mOffset;
|
||||
}
|
||||
|
||||
|
|
|
@ -677,3 +677,252 @@ addAccessibleTask(
|
|||
},
|
||||
{ chrome: true, topLevel: true, iframe: true, remoteIframe: true }
|
||||
);
|
||||
|
||||
function waitForSelectionChange(selectionAcc, caretAcc) {
|
||||
if (!caretAcc) {
|
||||
caretAcc = selectionAcc;
|
||||
}
|
||||
return waitForEvents(
|
||||
[
|
||||
[EVENT_TEXT_SELECTION_CHANGED, selectionAcc],
|
||||
// We must swallow the caret events as well to avoid confusion with later,
|
||||
// unrelated caret events.
|
||||
[EVENT_TEXT_CARET_MOVED, caretAcc],
|
||||
],
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
function changeDomSelection(
|
||||
browser,
|
||||
anchorId,
|
||||
anchorOffset,
|
||||
focusId,
|
||||
focusOffset
|
||||
) {
|
||||
return invokeContentTask(
|
||||
browser,
|
||||
[anchorId, anchorOffset, focusId, focusOffset],
|
||||
(
|
||||
contentAnchorId,
|
||||
contentAnchorOffset,
|
||||
contentFocusId,
|
||||
contentFocusOffset
|
||||
) => {
|
||||
// We want the text node, so we use firstChild.
|
||||
content.window
|
||||
.getSelection()
|
||||
.setBaseAndExtent(
|
||||
content.document.getElementById(contentAnchorId).firstChild,
|
||||
contentAnchorOffset,
|
||||
content.document.getElementById(contentFocusId).firstChild,
|
||||
contentFocusOffset
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function testSelectionRange(
|
||||
browser,
|
||||
root,
|
||||
startContainer,
|
||||
startOffset,
|
||||
endContainer,
|
||||
endOffset
|
||||
) {
|
||||
if (browser.isRemoteBrowser && !isCacheEnabled) {
|
||||
todo(
|
||||
false,
|
||||
"selectionRanges not implemented for non-cached RemoteAccessible"
|
||||
);
|
||||
return;
|
||||
}
|
||||
let selRange = root.selectionRanges.queryElementAt(0, nsIAccessibleTextRange);
|
||||
testTextRange(
|
||||
selRange,
|
||||
getAccessibleDOMNodeID(root),
|
||||
startContainer,
|
||||
startOffset,
|
||||
endContainer,
|
||||
endOffset
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test text selection.
|
||||
*/
|
||||
addAccessibleTask(
|
||||
`
|
||||
<textarea id="textarea">ab</textarea>
|
||||
<div id="editable" contenteditable>
|
||||
<p id="p1">a</p>
|
||||
<p id="p2">bc</p>
|
||||
<p id="pWithLink">d<a id="link" href="https://example.com/">e</a><span id="textAfterLink">f</span></p>
|
||||
</div>
|
||||
`,
|
||||
async function(browser, docAcc) {
|
||||
const textarea = findAccessibleChildByID(docAcc, "textarea", [
|
||||
nsIAccessibleText,
|
||||
]);
|
||||
info("Focusing textarea");
|
||||
let caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
|
||||
textarea.takeFocus();
|
||||
await caretMoved;
|
||||
testSelectionRange(browser, textarea, textarea, 0, textarea, 0);
|
||||
is(textarea.selectionCount, 0, "textarea selectionCount is 0");
|
||||
|
||||
info("Selecting a in textarea");
|
||||
let selChanged = waitForSelectionChange(textarea);
|
||||
EventUtils.synthesizeKey("KEY_ArrowRight", { shiftKey: true });
|
||||
await selChanged;
|
||||
testSelectionRange(browser, textarea, textarea, 0, textarea, 1);
|
||||
testTextGetSelection(textarea, 0, 1, 0);
|
||||
|
||||
info("Selecting b in textarea");
|
||||
selChanged = waitForSelectionChange(textarea);
|
||||
EventUtils.synthesizeKey("KEY_ArrowRight", { shiftKey: true });
|
||||
await selChanged;
|
||||
testSelectionRange(browser, textarea, textarea, 0, textarea, 2);
|
||||
testTextGetSelection(textarea, 0, 2, 0);
|
||||
|
||||
info("Unselecting b in textarea");
|
||||
selChanged = waitForSelectionChange(textarea);
|
||||
EventUtils.synthesizeKey("KEY_ArrowLeft", { shiftKey: true });
|
||||
await selChanged;
|
||||
testSelectionRange(browser, textarea, textarea, 0, textarea, 1);
|
||||
testTextGetSelection(textarea, 0, 1, 0);
|
||||
|
||||
info("Unselecting a in textarea");
|
||||
// We don't fire selection changed when the selection collapses.
|
||||
caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
|
||||
EventUtils.synthesizeKey("KEY_ArrowLeft", { shiftKey: true });
|
||||
await caretMoved;
|
||||
testSelectionRange(browser, textarea, textarea, 0, textarea, 0);
|
||||
is(textarea.selectionCount, 0, "textarea selectionCount is 0");
|
||||
|
||||
const editable = findAccessibleChildByID(docAcc, "editable", [
|
||||
nsIAccessibleText,
|
||||
]);
|
||||
const p1 = findAccessibleChildByID(docAcc, "p1", [nsIAccessibleText]);
|
||||
info("Focusing editable, caret to start");
|
||||
caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, p1);
|
||||
await changeDomSelection(browser, "p1", 0, "p1", 0);
|
||||
await caretMoved;
|
||||
testSelectionRange(browser, editable, p1, 0, p1, 0);
|
||||
is(editable.selectionCount, 0, "editable selectionCount is 0");
|
||||
is(p1.selectionCount, 0, "p1 selectionCount is 0");
|
||||
|
||||
info("Selecting a in editable");
|
||||
selChanged = waitForSelectionChange(p1);
|
||||
await changeDomSelection(browser, "p1", 0, "p1", 1);
|
||||
await selChanged;
|
||||
testSelectionRange(browser, editable, p1, 0, p1, 1);
|
||||
testTextGetSelection(editable, 0, 1, 0);
|
||||
testTextGetSelection(p1, 0, 1, 0);
|
||||
const p2 = findAccessibleChildByID(docAcc, "p2", [nsIAccessibleText]);
|
||||
if (isCacheEnabled && browser.isRemoteBrowser) {
|
||||
is(p2.selectionCount, 0, "p2 selectionCount is 0");
|
||||
} else {
|
||||
todo(
|
||||
false,
|
||||
"Siblings report wrong selection in non-cache implementation"
|
||||
);
|
||||
}
|
||||
|
||||
// Selecting across two Accessibles with only a partial selection in the
|
||||
// second.
|
||||
info("Selecting ab in editable");
|
||||
selChanged = waitForSelectionChange(editable, p2);
|
||||
await changeDomSelection(browser, "p1", 0, "p2", 1);
|
||||
await selChanged;
|
||||
testSelectionRange(browser, editable, p1, 0, p2, 1);
|
||||
testTextGetSelection(editable, 0, 2, 0);
|
||||
testTextGetSelection(p1, 0, 1, 0);
|
||||
testTextGetSelection(p2, 0, 1, 0);
|
||||
|
||||
const pWithLink = findAccessibleChildByID(docAcc, "pWithLink", [
|
||||
nsIAccessibleText,
|
||||
]);
|
||||
const link = findAccessibleChildByID(docAcc, "link", [nsIAccessibleText]);
|
||||
// Selecting both text and a link.
|
||||
info("Selecting de in editable");
|
||||
selChanged = waitForSelectionChange(pWithLink, link);
|
||||
await changeDomSelection(browser, "pWithLink", 0, "link", 1);
|
||||
await selChanged;
|
||||
testSelectionRange(browser, editable, pWithLink, 0, link, 1);
|
||||
testTextGetSelection(editable, 2, 3, 0);
|
||||
testTextGetSelection(pWithLink, 0, 2, 0);
|
||||
testTextGetSelection(link, 0, 1, 0);
|
||||
|
||||
// Selecting a link and text on either side.
|
||||
info("Selecting def in editable");
|
||||
selChanged = waitForSelectionChange(pWithLink, pWithLink);
|
||||
await changeDomSelection(browser, "pWithLink", 0, "textAfterLink", 1);
|
||||
await selChanged;
|
||||
testSelectionRange(browser, editable, pWithLink, 0, pWithLink, 3);
|
||||
testTextGetSelection(editable, 2, 3, 0);
|
||||
testTextGetSelection(pWithLink, 0, 3, 0);
|
||||
testTextGetSelection(link, 0, 1, 0);
|
||||
|
||||
// Noncontiguous selection.
|
||||
info("Selecting a in editable");
|
||||
selChanged = waitForSelectionChange(p1);
|
||||
await changeDomSelection(browser, "p1", 0, "p1", 1);
|
||||
await selChanged;
|
||||
info("Adding c to selection in editable");
|
||||
selChanged = waitForSelectionChange(p2);
|
||||
await invokeContentTask(browser, [], () => {
|
||||
const r = content.document.createRange();
|
||||
const p2text = content.document.getElementById("p2").firstChild;
|
||||
r.setStart(p2text, 0);
|
||||
r.setEnd(p2text, 1);
|
||||
content.window.getSelection().addRange(r);
|
||||
});
|
||||
await selChanged;
|
||||
if (browser.isRemoteBrowser && !isCacheEnabled) {
|
||||
todo(
|
||||
false,
|
||||
"selectionRanges not implemented for non-cached RemoteAccessible"
|
||||
);
|
||||
} else {
|
||||
let selRanges = editable.selectionRanges;
|
||||
is(selRanges.length, 2, "2 selection ranges");
|
||||
testTextRange(
|
||||
selRanges.queryElementAt(0, nsIAccessibleTextRange),
|
||||
"range 0",
|
||||
p1,
|
||||
0,
|
||||
p1,
|
||||
1
|
||||
);
|
||||
testTextRange(
|
||||
selRanges.queryElementAt(1, nsIAccessibleTextRange),
|
||||
"range 1",
|
||||
p2,
|
||||
0,
|
||||
p2,
|
||||
1
|
||||
);
|
||||
}
|
||||
is(editable.selectionCount, 2, "editable selectionCount is 2");
|
||||
testTextGetSelection(editable, 0, 1, 0);
|
||||
testTextGetSelection(editable, 1, 2, 1);
|
||||
if (isCacheEnabled && browser.isRemoteBrowser) {
|
||||
is(p1.selectionCount, 1, "p1 selectionCount is 1");
|
||||
testTextGetSelection(p1, 0, 1, 0);
|
||||
is(p2.selectionCount, 1, "p2 selectionCount is 1");
|
||||
testTextGetSelection(p2, 0, 1, 0);
|
||||
} else {
|
||||
todo(
|
||||
false,
|
||||
"Siblings report wrong selection in non-cache implementation"
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
chrome: true,
|
||||
topLevel: !isWinNoCache,
|
||||
iframe: !isWinNoCache,
|
||||
remoteIframe: !isWinNoCache,
|
||||
}
|
||||
);
|
||||
|
|
|
@ -105,6 +105,19 @@
|
|||
res = a11yrange.compareEndPoints(EndPoint_End, a11yrange, EndPoint_Start);
|
||||
is(res, 1, "end must be greater than start");
|
||||
|
||||
// Crop a range to its next sibling.
|
||||
range.selectNode(getNode("c3p1").firstChild);
|
||||
a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges;
|
||||
a11yrange = a11yranges.queryElementAt(0, nsIAccessibleTextRange);
|
||||
testTextRange(a11yrange, "selection range #8", "c3p1", 0, "c3p1", 1);
|
||||
ok(!a11yrange.crop(getAccessible("c3p2")), "Crop #8 succeeded but shouldn't have.");
|
||||
// Crop a range to its previous sibling.
|
||||
range.selectNode(getNode("c3p2").firstChild);
|
||||
a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges;
|
||||
a11yrange = a11yranges.queryElementAt(0, nsIAccessibleTextRange);
|
||||
testTextRange(a11yrange, "selection range #9", "c3p2", 0, "c3p2", 1);
|
||||
ok(!a11yrange.crop(getAccessible("c3p1")), "Crop #9 succeeded but shouldn't have.");
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
|
@ -125,5 +138,7 @@
|
|||
<p id="p1">text <a id="p2_a" href="www">link<img id="p2_img", src="../moz.png"></a> text</p>
|
||||
|
||||
<div id="c2">start<table id="table"><tr><td>cell</td></tr></table>end</div>
|
||||
|
||||
<div id="c3"><p id="c3p1">a</p><p id="c3p2">b</p></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -576,14 +576,14 @@ ia2Accessible::get_selectionRanges(IA2Range** aRanges, long* aNRanges) {
|
|||
if (!*aRanges) return E_OUTOFMEMORY;
|
||||
|
||||
for (uint32_t idx = 0; idx < static_cast<uint32_t>(*aNRanges); idx++) {
|
||||
RefPtr<IAccessible2> anchor;
|
||||
ranges[idx].StartContainer()->GetNativeInterface(getter_AddRefs(anchor));
|
||||
RefPtr<IAccessible2> anchor =
|
||||
MsaaAccessible::GetFrom(ranges[idx].StartContainer());
|
||||
anchor.forget(&(*aRanges)[idx].anchor);
|
||||
|
||||
(*aRanges)[idx].anchorOffset = ranges[idx].StartOffset();
|
||||
|
||||
RefPtr<IAccessible2> active;
|
||||
ranges[idx].EndContainer()->GetNativeInterface(getter_AddRefs(active));
|
||||
RefPtr<IAccessible2> active =
|
||||
MsaaAccessible::GetFrom(ranges[idx].EndContainer());
|
||||
active.forget(&(*aRanges)[idx].active);
|
||||
|
||||
(*aRanges)[idx].activeOffset = ranges[idx].EndOffset();
|
||||
|
|
|
@ -132,9 +132,9 @@ ia2AccessibleText::get_nSelections(long* aNSelections) {
|
|||
if (!aNSelections) return E_INVALIDARG;
|
||||
*aNSelections = 0;
|
||||
|
||||
auto [textAcc, hr] = LocalTextAcc();
|
||||
HyperTextAccessibleBase* textAcc = TextAcc();
|
||||
if (!textAcc) {
|
||||
return hr;
|
||||
return CO_E_OBJNOTCONNECTED;
|
||||
}
|
||||
|
||||
*aNSelections = textAcc->SelectionCount();
|
||||
|
@ -171,9 +171,9 @@ ia2AccessibleText::get_selection(long aSelectionIndex, long* aStartOffset,
|
|||
*aStartOffset = *aEndOffset = 0;
|
||||
|
||||
int32_t startOffset = 0, endOffset = 0;
|
||||
auto [textAcc, hr] = LocalTextAcc();
|
||||
HyperTextAccessibleBase* textAcc = TextAcc();
|
||||
if (!textAcc) {
|
||||
return hr;
|
||||
return CO_E_OBJNOTCONNECTED;
|
||||
}
|
||||
|
||||
if (!textAcc->SelectionBoundsAt(aSelectionIndex, &startOffset, &endOffset)) {
|
||||
|
|
|
@ -110,13 +110,18 @@ inline xpcAccessibleGeneric* ToXPC(Accessible* aAccessible) {
|
|||
return xpcDoc ? xpcDoc->GetAccessible(aAccessible) : nullptr;
|
||||
}
|
||||
|
||||
inline xpcAccessibleHyperText* ToXPCText(HyperTextAccessible* aAccessible) {
|
||||
if (!aAccessible) return nullptr;
|
||||
inline xpcAccessibleHyperText* ToXPCText(Accessible* aAccessible) {
|
||||
if (!aAccessible || !aAccessible->IsHyperText()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
xpcAccessibleDocument* xpcDoc =
|
||||
GetAccService()->GetXPCDocument(aAccessible->Document());
|
||||
aAccessible->IsLocal()
|
||||
? GetAccService()->GetXPCDocument(aAccessible->AsLocal()->Document())
|
||||
: nsAccessibilityService::GetXPCDocument(
|
||||
aAccessible->AsRemote()->Document());
|
||||
return static_cast<xpcAccessibleHyperText*>(
|
||||
xpcDoc->GetAccessible(aAccessible));
|
||||
xpcDoc ? xpcDoc->GetAccessible(aAccessible) : nullptr);
|
||||
}
|
||||
|
||||
inline xpcAccessibleDocument* ToXPCDocument(DocAccessible* aAccessible) {
|
||||
|
|
|
@ -307,15 +307,14 @@ xpcAccessibleHyperText::GetSelectionCount(int32_t* aSelectionCount) {
|
|||
|
||||
if (!mIntl) return NS_ERROR_FAILURE;
|
||||
|
||||
if (mIntl->IsLocal()) {
|
||||
*aSelectionCount = IntlLocal()->SelectionCount();
|
||||
} else {
|
||||
#if defined(XP_WIN)
|
||||
if (mIntl->IsRemote() &&
|
||||
!StaticPrefs::accessibility_cache_enabled_AtStartup()) {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
#else
|
||||
*aSelectionCount = mIntl->AsRemote()->SelectionCount();
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
*aSelectionCount = Intl()->SelectionCount();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -331,12 +330,13 @@ xpcAccessibleHyperText::GetSelectionBounds(int32_t aSelectionNum,
|
|||
|
||||
if (aSelectionNum < 0) return NS_ERROR_INVALID_ARG;
|
||||
|
||||
if (mIntl->IsLocal()) {
|
||||
if (aSelectionNum >= IntlLocal()->SelectionCount()) {
|
||||
if (mIntl->IsLocal() ||
|
||||
StaticPrefs::accessibility_cache_enabled_AtStartup()) {
|
||||
if (aSelectionNum >= Intl()->SelectionCount()) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
IntlLocal()->SelectionBoundsAt(aSelectionNum, aStartOffset, aEndOffset);
|
||||
Intl()->SelectionBoundsAt(aSelectionNum, aStartOffset, aEndOffset);
|
||||
} else {
|
||||
#if defined(XP_WIN)
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
|
@ -437,12 +437,12 @@ xpcAccessibleHyperText::GetEnclosingRange(nsIAccessibleTextRange** aRange) {
|
|||
|
||||
if (!IntlLocal()) return NS_ERROR_FAILURE;
|
||||
|
||||
RefPtr<xpcAccessibleTextRange> range = new xpcAccessibleTextRange;
|
||||
IntlLocal()->EnclosingRange(range->mRange);
|
||||
NS_ASSERTION(range->mRange.IsValid(),
|
||||
"Should always have an enclosing range!");
|
||||
TextRange range;
|
||||
IntlLocal()->EnclosingRange(range);
|
||||
NS_ASSERTION(range.IsValid(), "Should always have an enclosing range!");
|
||||
RefPtr<xpcAccessibleTextRange> xpcRange = new xpcAccessibleTextRange(range);
|
||||
|
||||
range.forget(aRange);
|
||||
xpcRange.forget(aRange);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -452,7 +452,9 @@ xpcAccessibleHyperText::GetSelectionRanges(nsIArray** aRanges) {
|
|||
NS_ENSURE_ARG_POINTER(aRanges);
|
||||
*aRanges = nullptr;
|
||||
|
||||
if (!IntlLocal()) return NS_ERROR_FAILURE;
|
||||
if (!IntlLocal() && !StaticPrefs::accessibility_cache_enabled_AtStartup()) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsresult rv = NS_OK;
|
||||
nsCOMPtr<nsIMutableArray> xpcRanges =
|
||||
|
@ -460,11 +462,10 @@ xpcAccessibleHyperText::GetSelectionRanges(nsIArray** aRanges) {
|
|||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
AutoTArray<TextRange, 1> ranges;
|
||||
IntlLocal()->SelectionRanges(&ranges);
|
||||
Intl()->SelectionRanges(&ranges);
|
||||
uint32_t len = ranges.Length();
|
||||
for (uint32_t idx = 0; idx < len; idx++) {
|
||||
xpcRanges->AppendElement(
|
||||
new xpcAccessibleTextRange(std::move(ranges[idx])));
|
||||
xpcRanges->AppendElement(new xpcAccessibleTextRange(ranges[idx]));
|
||||
}
|
||||
|
||||
xpcRanges.forget(aRanges);
|
||||
|
@ -487,8 +488,7 @@ xpcAccessibleHyperText::GetVisibleRanges(nsIArray** aRanges) {
|
|||
IntlLocal()->VisibleRanges(&ranges);
|
||||
uint32_t len = ranges.Length();
|
||||
for (uint32_t idx = 0; idx < len; idx++) {
|
||||
xpcRanges->AppendElement(
|
||||
new xpcAccessibleTextRange(std::move(ranges[idx])));
|
||||
xpcRanges->AppendElement(new xpcAccessibleTextRange(ranges[idx]));
|
||||
}
|
||||
|
||||
xpcRanges.forget(aRanges);
|
||||
|
@ -505,9 +505,13 @@ xpcAccessibleHyperText::GetRangeByChild(nsIAccessible* aChild,
|
|||
|
||||
LocalAccessible* child = aChild->ToInternalAccessible();
|
||||
if (child) {
|
||||
RefPtr<xpcAccessibleTextRange> range = new xpcAccessibleTextRange;
|
||||
IntlLocal()->RangeByChild(child, range->mRange);
|
||||
if (range->mRange.IsValid()) range.forget(aRange);
|
||||
TextRange range;
|
||||
IntlLocal()->RangeByChild(child, range);
|
||||
if (range.IsValid()) {
|
||||
RefPtr<xpcAccessibleTextRange> xpcRange =
|
||||
new xpcAccessibleTextRange(range);
|
||||
xpcRange.forget(aRange);
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
|
@ -521,9 +525,12 @@ xpcAccessibleHyperText::GetRangeAtPoint(int32_t aX, int32_t aY,
|
|||
|
||||
if (!IntlLocal()) return NS_ERROR_FAILURE;
|
||||
|
||||
RefPtr<xpcAccessibleTextRange> range = new xpcAccessibleTextRange;
|
||||
IntlLocal()->RangeAtPoint(aX, aY, range->mRange);
|
||||
if (range->mRange.IsValid()) range.forget(aRange);
|
||||
TextRange range;
|
||||
IntlLocal()->RangeAtPoint(aX, aY, range);
|
||||
if (range.IsValid()) {
|
||||
RefPtr<xpcAccessibleTextRange> xpcRange = new xpcAccessibleTextRange(range);
|
||||
xpcRange.forget(aRange);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
|
|
@ -18,52 +18,63 @@ using namespace mozilla::a11y;
|
|||
|
||||
// nsISupports and cycle collection
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION(xpcAccessibleTextRange, mRange.mRoot,
|
||||
mRange.mStartContainer, mRange.mEndContainer)
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(xpcAccessibleTextRange)
|
||||
NS_INTERFACE_MAP_BEGIN(xpcAccessibleTextRange)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIAccessibleTextRange)
|
||||
NS_INTERFACE_MAP_ENTRY(xpcAccessibleTextRange)
|
||||
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAccessibleTextRange)
|
||||
NS_INTERFACE_MAP_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(xpcAccessibleTextRange)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(xpcAccessibleTextRange)
|
||||
NS_IMPL_ADDREF(xpcAccessibleTextRange)
|
||||
NS_IMPL_RELEASE(xpcAccessibleTextRange)
|
||||
|
||||
a11y::TextRange xpcAccessibleTextRange::Range() {
|
||||
return a11y::TextRange(mRoot->ToInternalGeneric(),
|
||||
mStartContainer->ToInternalGeneric(), mStartOffset,
|
||||
mEndContainer->ToInternalGeneric(), mEndOffset);
|
||||
}
|
||||
|
||||
void xpcAccessibleTextRange::SetRange(TextRange& aRange) {
|
||||
mRoot = ToXPCText(aRange.Root());
|
||||
mStartContainer = ToXPCText(aRange.StartContainer());
|
||||
mStartOffset = aRange.StartOffset();
|
||||
mEndContainer = ToXPCText(aRange.EndContainer());
|
||||
mEndOffset = aRange.EndOffset();
|
||||
}
|
||||
|
||||
// nsIAccessibleTextRange
|
||||
|
||||
NS_IMETHODIMP
|
||||
xpcAccessibleTextRange::GetStartContainer(nsIAccessibleText** aAnchor) {
|
||||
NS_ENSURE_ARG_POINTER(aAnchor);
|
||||
NS_IF_ADDREF(*aAnchor = ToXPCText(mRange.StartContainer()));
|
||||
NS_IF_ADDREF(*aAnchor = mStartContainer);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
xpcAccessibleTextRange::GetStartOffset(int32_t* aOffset) {
|
||||
NS_ENSURE_ARG_POINTER(aOffset);
|
||||
*aOffset = mRange.StartOffset();
|
||||
*aOffset = mStartOffset;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
xpcAccessibleTextRange::GetEndContainer(nsIAccessibleText** aAnchor) {
|
||||
NS_ENSURE_ARG_POINTER(aAnchor);
|
||||
NS_IF_ADDREF(*aAnchor = ToXPCText(mRange.EndContainer()));
|
||||
NS_IF_ADDREF(*aAnchor = mEndContainer);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
xpcAccessibleTextRange::GetEndOffset(int32_t* aOffset) {
|
||||
NS_ENSURE_ARG_POINTER(aOffset);
|
||||
*aOffset = mRange.EndOffset();
|
||||
*aOffset = mEndOffset;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
xpcAccessibleTextRange::GetContainer(nsIAccessible** aContainer) {
|
||||
NS_ENSURE_ARG_POINTER(aContainer);
|
||||
NS_IF_ADDREF(*aContainer = ToXPC(mRange.Container()));
|
||||
NS_IF_ADDREF(*aContainer = ToXPC(Range().Container()));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -74,8 +85,8 @@ xpcAccessibleTextRange::GetEmbeddedChildren(nsIArray** aList) {
|
|||
do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsTArray<LocalAccessible*> objects;
|
||||
mRange.EmbeddedChildren(&objects);
|
||||
nsTArray<Accessible*> objects;
|
||||
Range().EmbeddedChildren(&objects);
|
||||
|
||||
uint32_t len = objects.Length();
|
||||
for (uint32_t idx = 0; idx < len; idx++) {
|
||||
|
@ -93,7 +104,7 @@ xpcAccessibleTextRange::Compare(nsIAccessibleTextRange* aOtherRange,
|
|||
RefPtr<xpcAccessibleTextRange> xpcRange(do_QueryObject(aOtherRange));
|
||||
if (!xpcRange || !aResult) return NS_ERROR_INVALID_ARG;
|
||||
|
||||
*aResult = (mRange == xpcRange->mRange);
|
||||
*aResult = (Range() == xpcRange->Range());
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -105,11 +116,13 @@ xpcAccessibleTextRange::CompareEndPoints(uint32_t aEndPoint,
|
|||
RefPtr<xpcAccessibleTextRange> xpcRange(do_QueryObject(aOtherRange));
|
||||
if (!xpcRange || !aResult) return NS_ERROR_INVALID_ARG;
|
||||
|
||||
TextPoint p =
|
||||
(aEndPoint == EndPoint_Start) ? mRange.StartPoint() : mRange.EndPoint();
|
||||
TextRange thisRange = Range();
|
||||
TextRange otherRange = xpcRange->Range();
|
||||
TextPoint p = (aEndPoint == EndPoint_Start) ? thisRange.StartPoint()
|
||||
: thisRange.EndPoint();
|
||||
TextPoint otherPoint = (aOtherRangeEndPoint == EndPoint_Start)
|
||||
? xpcRange->mRange.StartPoint()
|
||||
: xpcRange->mRange.EndPoint();
|
||||
? otherRange.StartPoint()
|
||||
: otherRange.EndPoint();
|
||||
|
||||
if (p == otherPoint) {
|
||||
*aResult = 0;
|
||||
|
@ -123,62 +136,24 @@ xpcAccessibleTextRange::CompareEndPoints(uint32_t aEndPoint,
|
|||
NS_IMETHODIMP
|
||||
xpcAccessibleTextRange::GetText(nsAString& aText) {
|
||||
nsAutoString text;
|
||||
mRange.Text(text);
|
||||
Range().Text(text);
|
||||
aText.Assign(text);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
xpcAccessibleTextRange::GetBounds(nsIArray** aRectList) { return NS_OK; }
|
||||
|
||||
NS_IMETHODIMP
|
||||
xpcAccessibleTextRange::Move(uint32_t aUnit, int32_t aCount) { return NS_OK; }
|
||||
|
||||
NS_IMETHODIMP
|
||||
xpcAccessibleTextRange::MoveStart(uint32_t aUnit, int32_t aCount) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
xpcAccessibleTextRange::MoveEnd(uint32_t aUnit, int32_t aCount) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
xpcAccessibleTextRange::Normalize(uint32_t aUnit) { return NS_OK; }
|
||||
|
||||
NS_IMETHODIMP
|
||||
xpcAccessibleTextRange::Crop(nsIAccessible* aContainer, bool* aSuccess) {
|
||||
LocalAccessible* container = aContainer->ToInternalAccessible();
|
||||
Accessible* container = aContainer->ToInternalGeneric();
|
||||
NS_ENSURE_TRUE(container, NS_ERROR_INVALID_ARG);
|
||||
|
||||
*aSuccess = mRange.Crop(container);
|
||||
TextRange range = Range();
|
||||
*aSuccess = range.Crop(container);
|
||||
if (*aSuccess) {
|
||||
SetRange(range);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
xpcAccessibleTextRange::FindText(const nsAString& aText, bool aIsBackward,
|
||||
bool aIsIgnoreCase,
|
||||
nsIAccessibleTextRange** aRange) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
xpcAccessibleTextRange::FindAttr(uint32_t aAttr, nsIVariant* aVal,
|
||||
bool aIsBackward,
|
||||
nsIAccessibleTextRange** aRange) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
xpcAccessibleTextRange::AddToSelection() { return NS_OK; }
|
||||
|
||||
NS_IMETHODIMP
|
||||
xpcAccessibleTextRange::RemoveFromSelection() { return NS_OK; }
|
||||
|
||||
NS_IMETHODIMP
|
||||
xpcAccessibleTextRange::Select() { return NS_OK; }
|
||||
|
||||
NS_IMETHODIMP
|
||||
xpcAccessibleTextRange::ScrollIntoView(uint32_t aHow) { return NS_OK; }
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
#include <utility>
|
||||
|
||||
#include "TextRange.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "nsIAccessibleTextRange.h"
|
||||
#include "xpcAccessibleHyperText.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace a11y {
|
||||
|
@ -27,11 +27,9 @@ class TextRange;
|
|||
|
||||
class xpcAccessibleTextRange final : public nsIAccessibleTextRange {
|
||||
public:
|
||||
explicit xpcAccessibleTextRange(TextRange&& aRange)
|
||||
: mRange(std::forward<TextRange>(aRange)) {}
|
||||
explicit xpcAccessibleTextRange(TextRange& aRange) { SetRange(aRange); }
|
||||
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS(xpcAccessibleTextRange)
|
||||
NS_DECL_ISUPPORTS
|
||||
|
||||
NS_IMETHOD GetStartContainer(nsIAccessibleText** aAnchor) final;
|
||||
NS_IMETHOD GetStartOffset(int32_t* aOffset) final;
|
||||
|
@ -45,20 +43,7 @@ class xpcAccessibleTextRange final : public nsIAccessibleTextRange {
|
|||
uint32_t aOtherRangeEndPoint,
|
||||
int32_t* aResult) final;
|
||||
NS_IMETHOD GetText(nsAString& aText) final;
|
||||
NS_IMETHOD GetBounds(nsIArray** aRectList) final;
|
||||
NS_IMETHOD Move(uint32_t aUnit, int32_t aCount) final;
|
||||
NS_IMETHOD MoveStart(uint32_t aUnit, int32_t aCount) final;
|
||||
NS_IMETHOD MoveEnd(uint32_t aUnit, int32_t aCount) final;
|
||||
NS_IMETHOD Normalize(uint32_t aUnit) final;
|
||||
NS_IMETHOD Crop(nsIAccessible* aContainer, bool* aSuccess) final;
|
||||
NS_IMETHOD FindText(const nsAString& aText, bool aIsBackward,
|
||||
bool aIsIgnoreCase,
|
||||
nsIAccessibleTextRange** aRange) final;
|
||||
NS_IMETHOD FindAttr(uint32_t aAttr, nsIVariant* aVal, bool aIsBackward,
|
||||
nsIAccessibleTextRange** aRange) final;
|
||||
NS_IMETHOD AddToSelection() final;
|
||||
NS_IMETHOD RemoveFromSelection() final;
|
||||
NS_IMETHOD Select() final;
|
||||
NS_IMETHOD ScrollIntoView(uint32_t aHow) final;
|
||||
|
||||
NS_DECLARE_STATIC_IID_ACCESSOR(NS_ACCESSIBLETEXTRANGE_IMPL_IID)
|
||||
|
@ -70,10 +55,21 @@ class xpcAccessibleTextRange final : public nsIAccessibleTextRange {
|
|||
|
||||
friend class xpcAccessibleHyperText;
|
||||
|
||||
xpcAccessibleTextRange(const xpcAccessibleTextRange&) = delete;
|
||||
xpcAccessibleTextRange& operator=(const xpcAccessibleTextRange&) = delete;
|
||||
|
||||
TextRange mRange;
|
||||
void SetRange(TextRange& aRange);
|
||||
|
||||
TextRange Range();
|
||||
|
||||
// We can't hold a strong reference to an Accessible, but XPCOM needs strong
|
||||
// references. Thus, instead of holding a TextRange here, we hold
|
||||
// xpcAccessibleHyperText references and create the TextRange for each call
|
||||
// using Range().
|
||||
RefPtr<xpcAccessibleHyperText> mRoot;
|
||||
RefPtr<xpcAccessibleHyperText> mStartContainer;
|
||||
int32_t mStartOffset;
|
||||
RefPtr<xpcAccessibleHyperText> mEndContainer;
|
||||
int32_t mEndOffset;
|
||||
};
|
||||
|
||||
NS_DEFINE_STATIC_IID_ACCESSOR(xpcAccessibleTextRange,
|
||||
|
|
|
@ -26,11 +26,18 @@ XPCOMUtils.defineLazyModuleGetters(this, {
|
|||
ShellService: "resource:///modules/ShellService.jsm",
|
||||
UpdatePing: "resource://gre/modules/UpdatePing.jsm",
|
||||
});
|
||||
XPCOMUtils.defineLazyServiceGetters(this, {
|
||||
UpdateManager: ["@mozilla.org/updates/update-manager;1", "nsIUpdateManager"],
|
||||
WinTaskbar: ["@mozilla.org/windows-taskbar;1", "nsIWinTaskbar"],
|
||||
WindowsUIUtils: ["@mozilla.org/windows-ui-utils;1", "nsIWindowsUIUtils"],
|
||||
});
|
||||
XPCOMUtils.defineLazyServiceGetter(
|
||||
this,
|
||||
"WindowsUIUtils",
|
||||
"@mozilla.org/windows-ui-utils;1",
|
||||
"nsIWindowsUIUtils"
|
||||
);
|
||||
XPCOMUtils.defineLazyServiceGetter(
|
||||
this,
|
||||
"UpdateManager",
|
||||
"@mozilla.org/updates/update-manager;1",
|
||||
"nsIUpdateManager"
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "gSystemPrincipal", () =>
|
||||
Services.scriptSecurityManager.getSystemPrincipal()
|
||||
|
@ -41,11 +48,6 @@ XPCOMUtils.defineLazyGlobalGetters(this, [URL]);
|
|||
const ONCE_DOMAINS = ["mozilla.org", "firefox.com"];
|
||||
const ONCE_PREF = "browser.startup.homepage_override.once";
|
||||
|
||||
// Index of Private Browsing icon in firefox.exe
|
||||
// Must line up with the one in nsNativeAppSupportWin.h.
|
||||
const PRIVATE_BROWSING_ICON_INDEX = 5;
|
||||
const PRIVACY_SEGMENTATION_PREF = "browser.privacySegmentation.enabled";
|
||||
|
||||
function shouldLoadURI(aURI) {
|
||||
if (aURI && !aURI.schemeIs("chrome")) {
|
||||
return true;
|
||||
|
@ -274,20 +276,6 @@ function openBrowserWindow(
|
|||
win.docShell.QueryInterface(
|
||||
Ci.nsILoadContext
|
||||
).usePrivateBrowsing = true;
|
||||
if (Services.prefs.getBoolPref(PRIVACY_SEGMENTATION_PREF)) {
|
||||
// TODO: Changing this after the Window has been painted causes it to
|
||||
// change Taskbar icons if the original one had a different AUMID.
|
||||
// This must stay pref'ed off until this is resolved.
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1751010
|
||||
WinTaskbar.setGroupIdForWindow(win, WinTaskbar.defaultPrivateGroupId);
|
||||
WindowsUIUtils.setWindowIconFromExe(
|
||||
win,
|
||||
Services.dirsvc.get("XREExeF", Ci.nsIFile).path,
|
||||
// This corresponds to the definitions in
|
||||
// nsNativeAppSupportWin.h
|
||||
PRIVATE_BROWSING_ICON_INDEX
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let openTime = win.openTime;
|
||||
|
|
|
@ -411,7 +411,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "ad164e99fa1130ed382c5d58376fb7fc560ea82b"
|
||||
"revision": "36278602ab4250b31a91431fe3a30ba16ebb119e"
|
||||
},
|
||||
"en-CA": {
|
||||
"pin": false,
|
||||
|
@ -993,7 +993,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "300bd68426f1a75e754e0aceee03b0cf257524a3"
|
||||
"revision": "cf38241abced5a8b4b2f74f4ce209756355274b9"
|
||||
},
|
||||
"kab": {
|
||||
"pin": false,
|
||||
|
@ -1749,7 +1749,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "e32e3ef4f2705ddb6f4dd4c8ce0e07c557d56250"
|
||||
"revision": "daf9cd936499807652dce019010a7bb074efa985"
|
||||
},
|
||||
"th": {
|
||||
"pin": false,
|
||||
|
|
|
@ -141,34 +141,52 @@ let privateWindowTask = {
|
|||
|
||||
// Implementation
|
||||
|
||||
var Builder = class {
|
||||
constructor(builder) {
|
||||
this._builder = builder;
|
||||
this._tasks = null;
|
||||
this._pendingStatements = {};
|
||||
this._shuttingDown = false;
|
||||
// These are ultimately controlled by prefs, so we disable
|
||||
// everything until is read from there
|
||||
this._showTasks = false;
|
||||
this._showFrequent = false;
|
||||
this._showRecent = false;
|
||||
this._maxItemCount = 0;
|
||||
}
|
||||
var WinTaskbarJumpList = {
|
||||
_builder: null,
|
||||
_tasks: null,
|
||||
_shuttingDown: false,
|
||||
|
||||
refreshPrefs(showTasks, showFrequent, showRecent, maxItemCount) {
|
||||
this._showTasks = showTasks;
|
||||
this._showFrequent = showFrequent;
|
||||
this._showRecent = showRecent;
|
||||
this._maxItemCount = maxItemCount;
|
||||
}
|
||||
/**
|
||||
* Startup, shutdown, and update
|
||||
*/
|
||||
|
||||
updateShutdownState(shuttingDown) {
|
||||
this._shuttingDown = shuttingDown;
|
||||
}
|
||||
startup: function WTBJL_startup() {
|
||||
// exit if this isn't win7 or higher.
|
||||
if (!this._initTaskbar()) {
|
||||
return;
|
||||
}
|
||||
|
||||
delete() {
|
||||
delete this._builder;
|
||||
}
|
||||
// Store our task list config data
|
||||
this._tasks = tasksCfg;
|
||||
|
||||
if (PrivateBrowsingUtils.enabled) {
|
||||
tasksCfg.push(privateWindowTask);
|
||||
}
|
||||
|
||||
// retrieve taskbar related prefs.
|
||||
this._refreshPrefs();
|
||||
|
||||
// observer for private browsing and our prefs branch
|
||||
this._initObs();
|
||||
|
||||
// jump list refresh timer
|
||||
this._updateTimer();
|
||||
},
|
||||
|
||||
update: function WTBJL_update() {
|
||||
// are we disabled via prefs? don't do anything!
|
||||
if (!this._enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// do what we came here to do, update the taskbar jumplist
|
||||
this._buildList();
|
||||
},
|
||||
|
||||
_shutdown: function WTBJL__shutdown() {
|
||||
this._shuttingDown = true;
|
||||
this._free();
|
||||
},
|
||||
|
||||
/**
|
||||
* List building
|
||||
|
@ -180,15 +198,13 @@ var Builder = class {
|
|||
* commitBuild() will commit for real.
|
||||
*/
|
||||
|
||||
_hasPendingStatements() {
|
||||
_pendingStatements: {},
|
||||
_hasPendingStatements: function WTBJL__hasPendingStatements() {
|
||||
return !!Object.keys(this._pendingStatements).length;
|
||||
}
|
||||
},
|
||||
|
||||
async buildList() {
|
||||
if (
|
||||
(this._showFrequent || this._showRecent) &&
|
||||
this._hasPendingStatements()
|
||||
) {
|
||||
async _buildList() {
|
||||
if (this._hasPendingStatements()) {
|
||||
// We were requested to update the list while another update was in
|
||||
// progress, this could happen at shutdown, idle or privatebrowsing.
|
||||
// Abort the current list building.
|
||||
|
@ -222,7 +238,7 @@ var Builder = class {
|
|||
}
|
||||
|
||||
this._commitBuild();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Taskbar api wrappers
|
||||
|
@ -235,13 +251,10 @@ var Builder = class {
|
|||
// Prior to building, delete removed items from history.
|
||||
this._clearHistory(URIsToRemove);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_commitBuild() {
|
||||
if (
|
||||
(this._showFrequent || this._showRecent) &&
|
||||
this._hasPendingStatements()
|
||||
) {
|
||||
_commitBuild: function WTBJL__commitBuild() {
|
||||
if (this._hasPendingStatements()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -250,9 +263,9 @@ var Builder = class {
|
|||
this._builder.abortListBuild();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_buildTasks() {
|
||||
_buildTasks: function WTBJL__buildTasks() {
|
||||
var items = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
|
||||
this._tasks.forEach(function(task) {
|
||||
if (
|
||||
|
@ -277,9 +290,9 @@ var Builder = class {
|
|||
items
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_buildCustom(title, items) {
|
||||
_buildCustom: function WTBJL__buildCustom(title, items) {
|
||||
if (items.length) {
|
||||
this._builder.addListToBuild(
|
||||
this._builder.JUMPLIST_CATEGORY_CUSTOMLIST,
|
||||
|
@ -287,9 +300,9 @@ var Builder = class {
|
|||
title
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_buildFrequent() {
|
||||
_buildFrequent: function WTBJL__buildFrequent() {
|
||||
// Windows supports default frequent and recent lists,
|
||||
// but those depend on internal windows visit tracking
|
||||
// which we don't populate. So we build our own custom
|
||||
|
@ -326,9 +339,9 @@ var Builder = class {
|
|||
},
|
||||
this
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
_buildRecent() {
|
||||
_buildRecent: function WTBJL__buildRecent() {
|
||||
var items = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
|
||||
// Frequent items will be skipped, so we select a double amount of
|
||||
// entries and stop fetching results at _maxItemCount.
|
||||
|
@ -372,17 +385,23 @@ var Builder = class {
|
|||
},
|
||||
this
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
_deleteActiveJumpList() {
|
||||
_deleteActiveJumpList: function WTBJL__deleteAJL() {
|
||||
this._builder.deleteActiveList();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Jump list item creation helpers
|
||||
*/
|
||||
|
||||
_getHandlerAppItem(name, description, args, iconIndex, faviconPageUri) {
|
||||
_getHandlerAppItem: function WTBJL__getHandlerAppItem(
|
||||
name,
|
||||
description,
|
||||
args,
|
||||
iconIndex,
|
||||
faviconPageUri
|
||||
) {
|
||||
var file = Services.dirsvc.get("XREExeF", Ci.nsIFile);
|
||||
|
||||
var handlerApp = Cc[
|
||||
|
@ -403,13 +422,25 @@ var Builder = class {
|
|||
item.iconIndex = iconIndex;
|
||||
item.faviconPageUri = faviconPageUri;
|
||||
return item;
|
||||
}
|
||||
},
|
||||
|
||||
_getSeparatorItem: function WTBJL__getSeparatorItem() {
|
||||
var item = Cc["@mozilla.org/windows-jumplistseparator;1"].createInstance(
|
||||
Ci.nsIJumpListSeparator
|
||||
);
|
||||
return item;
|
||||
},
|
||||
|
||||
/**
|
||||
* Nav history helpers
|
||||
*/
|
||||
|
||||
_getHistoryResults(aSortingMode, aLimit, aCallback, aScope) {
|
||||
_getHistoryResults: function WTBLJL__getHistoryResults(
|
||||
aSortingMode,
|
||||
aLimit,
|
||||
aCallback,
|
||||
aScope
|
||||
) {
|
||||
var options = PlacesUtils.history.getNewQueryOptions();
|
||||
options.maxResults = aLimit;
|
||||
options.sortingMode = aSortingMode;
|
||||
|
@ -433,12 +464,12 @@ var Builder = class {
|
|||
);
|
||||
},
|
||||
handleCompletion(aReason) {
|
||||
aCallback.call(aScope, null);
|
||||
aCallback.call(WinTaskbarJumpList, null);
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_clearHistory(uriSpecsToRemove) {
|
||||
_clearHistory: function WTBJL__clearHistory(uriSpecsToRemove) {
|
||||
let URIsToRemove = uriSpecsToRemove
|
||||
.map(spec => {
|
||||
try {
|
||||
|
@ -453,61 +484,6 @@ var Builder = class {
|
|||
if (URIsToRemove.length) {
|
||||
PlacesUtils.history.remove(URIsToRemove).catch(Cu.reportError);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var WinTaskbarJumpList = {
|
||||
// We build two separate jump lists -- one for the regular Firefox icon
|
||||
// and one for the Private Browsing icon
|
||||
_builder: null,
|
||||
_pbBuilder: null,
|
||||
_shuttingDown: false,
|
||||
|
||||
/**
|
||||
* Startup, shutdown, and update
|
||||
*/
|
||||
|
||||
startup: function WTBJL_startup() {
|
||||
// exit if this isn't win7 or higher.
|
||||
if (!this._initTaskbar()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (PrivateBrowsingUtils.enabled) {
|
||||
tasksCfg.push(privateWindowTask);
|
||||
}
|
||||
// Store our task list config data
|
||||
this._builder._tasks = tasksCfg;
|
||||
this._pbBuilder._tasks = tasksCfg;
|
||||
|
||||
// retrieve taskbar related prefs.
|
||||
this._refreshPrefs();
|
||||
|
||||
// observer for private browsing and our prefs branch
|
||||
this._initObs();
|
||||
|
||||
// jump list refresh timer
|
||||
this._updateTimer();
|
||||
|
||||
// We only build the Private Browsing Jump List once
|
||||
this._pbBuilder.buildList();
|
||||
},
|
||||
|
||||
update: function WTBJL_update() {
|
||||
// are we disabled via prefs? don't do anything!
|
||||
if (!this._enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// do what we came here to do, update the taskbar jumplist
|
||||
this._builder.buildList();
|
||||
},
|
||||
|
||||
_shutdown: function WTBJL__shutdown() {
|
||||
this._builder.updateShutdownState(true);
|
||||
this._pbBuilder.updateShutdownState(true);
|
||||
this._shuttingDown = true;
|
||||
this._free();
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -516,17 +492,10 @@ var WinTaskbarJumpList = {
|
|||
|
||||
_refreshPrefs: function WTBJL__refreshPrefs() {
|
||||
this._enabled = _prefs.getBoolPref(PREF_TASKBAR_ENABLED);
|
||||
var showTasks = _prefs.getBoolPref(PREF_TASKBAR_TASKS);
|
||||
this._builder.refreshPrefs(
|
||||
showTasks,
|
||||
_prefs.getBoolPref(PREF_TASKBAR_FREQUENT),
|
||||
_prefs.getBoolPref(PREF_TASKBAR_RECENT),
|
||||
_prefs.getIntPref(PREF_TASKBAR_ITEMCOUNT)
|
||||
);
|
||||
// showTasks is the only relevant pref for the Private Browsing Jump List
|
||||
// the others are are related to frequent/recent entries, which are
|
||||
// explicitly disabled for it
|
||||
this._pbBuilder.refreshPrefs(showTasks, false, false, 0);
|
||||
this._showFrequent = _prefs.getBoolPref(PREF_TASKBAR_FREQUENT);
|
||||
this._showRecent = _prefs.getBoolPref(PREF_TASKBAR_RECENT);
|
||||
this._showTasks = _prefs.getBoolPref(PREF_TASKBAR_TASKS);
|
||||
this._maxItemCount = _prefs.getIntPref(PREF_TASKBAR_ITEMCOUNT);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -534,15 +503,11 @@ var WinTaskbarJumpList = {
|
|||
*/
|
||||
|
||||
_initTaskbar: function WTBJL__initTaskbar() {
|
||||
var builder = _taskbarService.createJumpListBuilder(false);
|
||||
var pbBuilder = _taskbarService.createJumpListBuilder(true);
|
||||
if (!builder || !builder.available || !pbBuilder || !pbBuilder.available) {
|
||||
this._builder = _taskbarService.createJumpListBuilder();
|
||||
if (!this._builder || !this._builder.available) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._builder = new Builder(builder, true, true, true);
|
||||
this._pbBuilder = new Builder(pbBuilder, true, false, false);
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
|
@ -606,8 +571,7 @@ var WinTaskbarJumpList = {
|
|||
this._freeObs();
|
||||
this._updateTimer();
|
||||
this._updateIdleObserver();
|
||||
this._builder.delete();
|
||||
this._pbBuilder.delete();
|
||||
delete this._builder;
|
||||
},
|
||||
|
||||
notify: function WTBJL_notify(aTimer) {
|
||||
|
|
|
@ -170,6 +170,15 @@ static void Register(BrowsingContext* aBrowsingContext) {
|
|||
aBrowsingContext->Group()->Register(aBrowsingContext);
|
||||
}
|
||||
|
||||
// static
|
||||
void BrowsingContext::UpdateCurrentTopByBrowserId(
|
||||
BrowsingContext* aNewBrowsingContext) {
|
||||
if (aNewBrowsingContext->IsTopContent()) {
|
||||
sCurrentTopByBrowserId->InsertOrUpdate(aNewBrowsingContext->BrowserId(),
|
||||
aNewBrowsingContext);
|
||||
}
|
||||
}
|
||||
|
||||
BrowsingContext* BrowsingContext::GetParent() const {
|
||||
return mParentWindow ? mParentWindow->GetBrowsingContext() : nullptr;
|
||||
}
|
||||
|
@ -2910,6 +2919,7 @@ void BrowsingContext::DidSet(FieldIndex<IDX_IsInBFCache>) {
|
|||
|
||||
const bool isInBFCache = GetIsInBFCache();
|
||||
if (!isInBFCache) {
|
||||
UpdateCurrentTopByBrowserId(this);
|
||||
PreOrderWalk([&](BrowsingContext* aContext) {
|
||||
aContext->mIsInBFCache = false;
|
||||
nsCOMPtr<nsIDocShell> shell = aContext->GetDocShell();
|
||||
|
|
|
@ -268,6 +268,8 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
|
|||
return GetCurrentTopByBrowserId(aId);
|
||||
}
|
||||
|
||||
static void UpdateCurrentTopByBrowserId(BrowsingContext* aNewBrowsingContext);
|
||||
|
||||
static already_AddRefed<BrowsingContext> GetFromWindow(
|
||||
WindowProxyHolder& aProxy);
|
||||
static already_AddRefed<BrowsingContext> GetFromWindow(
|
||||
|
|
|
@ -117,6 +117,7 @@ support-files =
|
|||
https_first_disabled = true
|
||||
skip-if = !fission || !crashreporter # On a crash we only keep history when fission is enabled.
|
||||
[browser_bug1719178.js]
|
||||
[browser_bug1757005.js]
|
||||
[browser_bug234628-1.js]
|
||||
[browser_bug234628-10.js]
|
||||
[browser_bug234628-11.js]
|
||||
|
|
73
docshell/test/browser/browser_bug1757005.js
Normal file
73
docshell/test/browser/browser_bug1757005.js
Normal file
|
@ -0,0 +1,73 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
add_task(async function() {
|
||||
// (1) Load one page with bfcache disabled and another one with bfcache enabled.
|
||||
// (2) Check that BrowsingContext.getCurrentTopByBrowserId(browserId) returns
|
||||
// the expected browsing context both in the parent process and in the child process.
|
||||
// (3) Go back and then forward
|
||||
// (4) Run the same checks as in step 2 again.
|
||||
|
||||
let url1 = "data:text/html,<body onunload='/* disable bfcache */'>";
|
||||
let url2 = "data:text/html,page2";
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: url1,
|
||||
},
|
||||
async function(browser) {
|
||||
info("Initial load");
|
||||
|
||||
let loaded = BrowserTestUtils.browserLoaded(browser);
|
||||
BrowserTestUtils.loadURI(browser, url2);
|
||||
await loaded;
|
||||
info("Second page loaded");
|
||||
|
||||
let browserId = browser.browserId;
|
||||
ok(!!browser.browsingContext, "Should have a BrowsingContext. (1)");
|
||||
is(
|
||||
BrowsingContext.getCurrentTopByBrowserId(browserId),
|
||||
browser.browsingContext,
|
||||
"Should get the correct browsingContext(1)"
|
||||
);
|
||||
|
||||
await ContentTask.spawn(browser, browserId, async function(browserId) {
|
||||
Assert.ok(
|
||||
BrowsingContext.getCurrentTopByBrowserId(browserId) ==
|
||||
docShell.browsingContext
|
||||
);
|
||||
Assert.ok(docShell.browsingContext.browserId == browserId);
|
||||
});
|
||||
|
||||
let awaitPageShow = BrowserTestUtils.waitForContentEvent(
|
||||
browser,
|
||||
"pageshow"
|
||||
);
|
||||
browser.goBack();
|
||||
await awaitPageShow;
|
||||
info("Back");
|
||||
|
||||
awaitPageShow = BrowserTestUtils.waitForContentEvent(browser, "pageshow");
|
||||
browser.goForward();
|
||||
await awaitPageShow;
|
||||
info("Forward");
|
||||
|
||||
ok(!!browser.browsingContext, "Should have a BrowsingContext. (2)");
|
||||
is(
|
||||
BrowsingContext.getCurrentTopByBrowserId(browserId),
|
||||
browser.browsingContext,
|
||||
"Should get the correct BrowsingContext. (2)"
|
||||
);
|
||||
|
||||
await ContentTask.spawn(browser, browserId, async function(browserId) {
|
||||
Assert.ok(
|
||||
BrowsingContext.getCurrentTopByBrowserId(browserId) ==
|
||||
docShell.browsingContext
|
||||
);
|
||||
Assert.ok(docShell.browsingContext.browserId == browserId);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
|
@ -1172,11 +1172,7 @@ already_AddRefed<Promise> ChromeUtils::RequestProcInfo(GlobalObject& aGlobal,
|
|||
|
||||
/* static */
|
||||
bool ChromeUtils::VsyncEnabled(GlobalObject& aGlobal) {
|
||||
mozilla::gfx::VsyncSource* vsyncSource =
|
||||
gfxPlatform::GetPlatform()->GetHardwareVsync();
|
||||
MOZ_ASSERT(vsyncSource != nullptr);
|
||||
|
||||
return vsyncSource->GetGlobalDisplay().IsVsyncEnabled();
|
||||
return mozilla::gfx::VsyncSource::GetFastestVsyncRate().isSome();
|
||||
}
|
||||
|
||||
/* static */
|
||||
|
|
|
@ -49,8 +49,16 @@ class ShadowIncludingTreeIterator {
|
|||
|
||||
bool operator!=(std::nullptr_t) const { return !!mCurrent; }
|
||||
|
||||
explicit operator bool() const { return !!mCurrent; }
|
||||
|
||||
void operator++() { Next(); }
|
||||
|
||||
void SkipChildren() {
|
||||
MOZ_ASSERT(mCurrent, "Shouldn't be at end");
|
||||
mCurrent = mCurrent->GetNextNonChildNode(mRoots.LastElement());
|
||||
WalkOutOfShadowRootsIfNeeded();
|
||||
}
|
||||
|
||||
nsINode* operator*() { return mCurrent; }
|
||||
|
||||
private:
|
||||
|
@ -67,6 +75,10 @@ class ShadowIncludingTreeIterator {
|
|||
}
|
||||
|
||||
mCurrent = mCurrent->GetNextNode(mRoots.LastElement());
|
||||
WalkOutOfShadowRootsIfNeeded();
|
||||
}
|
||||
|
||||
void WalkOutOfShadowRootsIfNeeded() {
|
||||
while (!mCurrent) {
|
||||
// Nothing left under this root. Keep trying to pop the stack until we
|
||||
// find a node or run out of stack.
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
#include "mozilla/dom/BlobURLProtocolHandler.h"
|
||||
#include "mozilla/dom/File.h"
|
||||
#include "mozilla/dom/PerformanceStorage.h"
|
||||
#include "mozilla/dom/PerformanceTiming.h"
|
||||
#include "mozilla/dom/UserActivation.h"
|
||||
#include "mozilla/dom/WorkerCommon.h"
|
||||
#include "mozilla/PreloaderBase.h"
|
||||
|
@ -1545,6 +1546,23 @@ void FetchDriver::SetController(
|
|||
mController = aController;
|
||||
}
|
||||
|
||||
PerformanceTimingData* FetchDriver::GetPerformanceTimingData(
|
||||
nsAString& aInitiatorType, nsAString& aEntryName) {
|
||||
MOZ_ASSERT(XRE_IsParentProcess());
|
||||
MOZ_ASSERT(mChannel);
|
||||
|
||||
nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(mChannel);
|
||||
if (!timedChannel) {
|
||||
return nullptr;
|
||||
}
|
||||
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
|
||||
if (!httpChannel) {
|
||||
return nullptr;
|
||||
}
|
||||
return dom::PerformanceTimingData::Create(timedChannel, httpChannel, 0,
|
||||
aInitiatorType, aEntryName);
|
||||
}
|
||||
|
||||
void FetchDriver::SetRequestHeaders(nsIHttpChannel* aChannel,
|
||||
bool aStripRequestBodyHeader) const {
|
||||
MOZ_ASSERT(aChannel);
|
||||
|
|
|
@ -38,6 +38,7 @@ class Document;
|
|||
class InternalRequest;
|
||||
class InternalResponse;
|
||||
class PerformanceStorage;
|
||||
class PerformanceTimingData;
|
||||
|
||||
/**
|
||||
* Provides callbacks to be called when response is available or on error.
|
||||
|
@ -124,6 +125,9 @@ class FetchDriver final : public nsIStreamListener,
|
|||
mOriginStack = std::move(aOriginStack);
|
||||
}
|
||||
|
||||
PerformanceTimingData* GetPerformanceTimingData(nsAString& aInitiatorType,
|
||||
nsAString& aEntryName);
|
||||
|
||||
// AbortFollower
|
||||
void RunAbortAlgorithm() override;
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
@ -17,16 +15,24 @@
|
|||
#include "mozilla/BasePrincipal.h"
|
||||
#include "mozilla/ClearOnShutdown.h"
|
||||
#include "mozilla/ScopeExit.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/dom/FetchService.h"
|
||||
#include "mozilla/dom/InternalRequest.h"
|
||||
#include "mozilla/dom/InternalResponse.h"
|
||||
#include "mozilla/dom/PerformanceStorage.h"
|
||||
#include "mozilla/dom/PerformanceTiming.h"
|
||||
#include "mozilla/ipc/BackgroundUtils.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
mozilla::LazyLogModule gFetchLog("Fetch");
|
||||
|
||||
FetchServiceResponse CreateErrorResponse(nsresult aRv) {
|
||||
IPCPerformanceTimingData ipcTimingData;
|
||||
return MakeTuple(InternalResponse::NetworkError(aRv), ipcTimingData,
|
||||
EmptyString(), EmptyString());
|
||||
}
|
||||
|
||||
// FetchInstance
|
||||
|
||||
FetchService::FetchInstance::FetchInstance(SafeRefPtr<InternalRequest> aRequest)
|
||||
|
@ -129,8 +135,7 @@ RefPtr<FetchServiceResponsePromise> FetchService::FetchInstance::Fetch() {
|
|||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
FETCH_LOG(
|
||||
("FetchInstance::Fetch FetchDriver::Fetch failed(0x%X)", (uint32_t)rv));
|
||||
return FetchServiceResponsePromise::CreateAndResolve(
|
||||
InternalResponse::NetworkError(rv), __func__);
|
||||
return FetchService::NetworkErrorResponse(rv);
|
||||
}
|
||||
|
||||
return mResponsePromiseHolder.Ensure(__func__);
|
||||
|
@ -147,20 +152,18 @@ void FetchService::FetchInstance::Cancel() {
|
|||
}
|
||||
|
||||
mResponsePromiseHolder.ResolveIfExists(
|
||||
InternalResponse::NetworkError(NS_ERROR_DOM_ABORT_ERR), __func__);
|
||||
CreateErrorResponse(NS_ERROR_DOM_ABORT_ERR), __func__);
|
||||
}
|
||||
|
||||
void FetchService::FetchInstance::OnResponseEnd(
|
||||
FetchDriverObserver::EndReason aReason) {
|
||||
FETCH_LOG(("FetchInstance::OnResponseEnd [%p]", this));
|
||||
if (aReason == eAborted) {
|
||||
FETCH_LOG(("FetchInstance::OnResponseEnd end with eAborted"));
|
||||
mResponsePromiseHolder.ResolveIfExists(
|
||||
InternalResponse::NetworkError(NS_ERROR_DOM_ABORT_ERR), __func__);
|
||||
CreateErrorResponse(NS_ERROR_DOM_ABORT_ERR), __func__);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void FetchService::FetchInstance::OnResponseAvailableInternal(
|
||||
SafeRefPtr<InternalResponse> aResponse) {
|
||||
FETCH_LOG(("FetchInstance::OnResponseAvailableInternal [%p]", this));
|
||||
if (!mResponsePromiseHolder.IsEmpty()) {
|
||||
// Remove the FetchInstance from FetchInstanceTable
|
||||
RefPtr<FetchServiceResponsePromise> responsePromise =
|
||||
|
@ -171,12 +174,31 @@ void FetchService::FetchInstance::OnResponseAvailableInternal(
|
|||
MOZ_ASSERT(entry);
|
||||
entry.Remove();
|
||||
FETCH_LOG(
|
||||
("FetchInstance::OnResponseAvailableInternal entry of "
|
||||
"responsePromise[%p] is removed",
|
||||
("FetchInstance::OnResponseEnd entry of responsePromise[%p] is removed",
|
||||
responsePromise.get()));
|
||||
}
|
||||
|
||||
// Get PerformanceTimingData from FetchDriver.
|
||||
nsString initiatorType;
|
||||
nsString entryName;
|
||||
UniquePtr<PerformanceTimingData> performanceTiming(
|
||||
mFetchDriver->GetPerformanceTimingData(initiatorType, entryName));
|
||||
MOZ_ASSERT(performanceTiming);
|
||||
|
||||
initiatorType = u"navigation"_ns;
|
||||
|
||||
FetchServiceResponse response =
|
||||
MakeTuple(std::move(mResponse), performanceTiming->ToIPC(), initiatorType,
|
||||
entryName);
|
||||
|
||||
// Resolve the FetchServiceResponsePromise
|
||||
mResponsePromiseHolder.ResolveIfExists(std::move(aResponse), __func__);
|
||||
mResponsePromiseHolder.ResolveIfExists(std::move(response), __func__);
|
||||
}
|
||||
|
||||
void FetchService::FetchInstance::OnResponseAvailableInternal(
|
||||
SafeRefPtr<InternalResponse> aResponse) {
|
||||
FETCH_LOG(("FetchInstance::OnResponseAvailableInternal [%p]", this));
|
||||
mResponse = std::move(aResponse);
|
||||
}
|
||||
|
||||
// TODO:
|
||||
|
@ -206,8 +228,8 @@ already_AddRefed<FetchService> FetchService::GetInstance() {
|
|||
/*static*/
|
||||
RefPtr<FetchServiceResponsePromise> FetchService::NetworkErrorResponse(
|
||||
nsresult aRv) {
|
||||
return FetchServiceResponsePromise::CreateAndResolve(
|
||||
InternalResponse::NetworkError(aRv), __func__);
|
||||
return FetchServiceResponsePromise::CreateAndResolve(CreateErrorResponse(aRv),
|
||||
__func__);
|
||||
}
|
||||
|
||||
FetchService::FetchService() {
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
@ -12,6 +10,7 @@
|
|||
#include "mozilla/MozPromise.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/dom/FetchDriver.h"
|
||||
#include "mozilla/dom/PerformanceTimingTypes.h"
|
||||
#include "mozilla/dom/SafeRefPtr.h"
|
||||
|
||||
class nsILoadGroup;
|
||||
|
@ -24,8 +23,12 @@ namespace mozilla::dom {
|
|||
class InternalRequest;
|
||||
class InternalResponse;
|
||||
|
||||
using FetchServiceResponse =
|
||||
Tuple<SafeRefPtr<InternalResponse>, IPCPerformanceTimingData, nsString,
|
||||
nsString>;
|
||||
|
||||
using FetchServiceResponsePromise =
|
||||
MozPromise<SafeRefPtr<InternalResponse>, CopyableErrorResult, true>;
|
||||
MozPromise<FetchServiceResponse, CopyableErrorResult, true>;
|
||||
|
||||
/**
|
||||
* FetchService is a singleton object which designed to be used in parent
|
||||
|
@ -102,6 +105,7 @@ class FetchService final {
|
|||
nsCOMPtr<nsICookieJarSettings> mCookieJarSettings;
|
||||
RefPtr<PerformanceStorage> mPerformanceStorage;
|
||||
RefPtr<FetchDriver> mFetchDriver;
|
||||
SafeRefPtr<InternalResponse> mResponse;
|
||||
|
||||
MozPromiseHolder<FetchServiceResponsePromise> mResponsePromiseHolder;
|
||||
};
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
include IPCStream;
|
||||
include ChannelInfo;
|
||||
include PBackgroundSharedTypes;
|
||||
include PerformanceTimingTypes;
|
||||
|
||||
include protocol PRemoteLazyInputStream;
|
||||
|
||||
|
@ -100,5 +101,19 @@ struct ChildToParentInternalResponse {
|
|||
ChildToParentStream? alternativeBody;
|
||||
};
|
||||
|
||||
struct ParentToParentResponseWithTiming {
|
||||
ParentToParentInternalResponse response;
|
||||
IPCPerformanceTimingData timingData;
|
||||
nsString initiatorType;
|
||||
nsString entryName;
|
||||
};
|
||||
|
||||
struct ParentToChildResponseWithTiming {
|
||||
ParentToChildInternalResponse response;
|
||||
IPCPerformanceTimingData timingData;
|
||||
nsString initiatorType;
|
||||
nsString entryName;
|
||||
};
|
||||
|
||||
} // namespace ipc
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -120,6 +120,20 @@ void PerformanceStorageWorker::AddEntry(nsIHttpChannel* aChannel,
|
|||
Unused << NS_WARN_IF(!r->Dispatch());
|
||||
}
|
||||
|
||||
void PerformanceStorageWorker::AddEntry(
|
||||
const nsString& aEntryName, const nsString& aInitiatorType,
|
||||
UniquePtr<PerformanceTimingData>&& aData) {
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
if (!aData) {
|
||||
return;
|
||||
}
|
||||
|
||||
UniquePtr<PerformanceProxyData> data = MakeUnique<PerformanceProxyData>(
|
||||
std::move(aData), aInitiatorType, aEntryName);
|
||||
|
||||
AddEntryOnWorker(std::move(data));
|
||||
}
|
||||
|
||||
void PerformanceStorageWorker::ShutdownOnWorker() {
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
|
|
|
@ -29,9 +29,8 @@ class PerformanceStorageWorker final : public PerformanceStorage {
|
|||
|
||||
void AddEntry(nsIHttpChannel* aChannel,
|
||||
nsITimedChannel* aTimedChannel) override;
|
||||
virtual void AddEntry(const nsString& entryName,
|
||||
const nsString& initiatorType,
|
||||
UniquePtr<PerformanceTimingData>&& aData) override {}
|
||||
void AddEntry(const nsString& aEntryName, const nsString& aInitiatorType,
|
||||
UniquePtr<PerformanceTimingData>&& aData) override;
|
||||
void AddEntryOnWorker(UniquePtr<PerformanceProxyData>&& aData);
|
||||
|
||||
private:
|
||||
|
|
|
@ -205,6 +205,67 @@ PerformanceTimingData::PerformanceTimingData(nsITimedChannel* aChannel,
|
|||
}
|
||||
}
|
||||
|
||||
PerformanceTimingData::PerformanceTimingData(
|
||||
const IPCPerformanceTimingData& aIPCData)
|
||||
: mNextHopProtocol(aIPCData.nextHopProtocol()),
|
||||
mAsyncOpen(aIPCData.asyncOpen()),
|
||||
mRedirectStart(aIPCData.redirectStart()),
|
||||
mRedirectEnd(aIPCData.redirectEnd()),
|
||||
mDomainLookupStart(aIPCData.domainLookupStart()),
|
||||
mDomainLookupEnd(aIPCData.domainLookupEnd()),
|
||||
mConnectStart(aIPCData.connectStart()),
|
||||
mSecureConnectionStart(aIPCData.secureConnectionStart()),
|
||||
mConnectEnd(aIPCData.connectEnd()),
|
||||
mRequestStart(aIPCData.requestStart()),
|
||||
mResponseStart(aIPCData.responseStart()),
|
||||
mCacheReadStart(aIPCData.cacheReadStart()),
|
||||
mResponseEnd(aIPCData.responseEnd()),
|
||||
mCacheReadEnd(aIPCData.cacheReadEnd()),
|
||||
mWorkerStart(aIPCData.workerStart()),
|
||||
mWorkerRequestStart(aIPCData.workerRequestStart()),
|
||||
mWorkerResponseEnd(aIPCData.workerResponseEnd()),
|
||||
mZeroTime(aIPCData.zeroTime()),
|
||||
mFetchStart(aIPCData.fetchStart()),
|
||||
mEncodedBodySize(aIPCData.encodedBodySize()),
|
||||
mTransferSize(aIPCData.transferSize()),
|
||||
mDecodedBodySize(aIPCData.decodedBodySize()),
|
||||
mRedirectCount(aIPCData.redirectCount()),
|
||||
mAllRedirectsSameOrigin(aIPCData.allRedirectsSameOrigin()),
|
||||
mAllRedirectsPassTAO(aIPCData.allRedirectsPassTAO()),
|
||||
mSecureConnection(aIPCData.secureConnection()),
|
||||
mTimingAllowed(aIPCData.timingAllowed()),
|
||||
mInitialized(aIPCData.initialized()) {
|
||||
for (const auto& serverTimingData : aIPCData.serverTiming()) {
|
||||
RefPtr<nsServerTiming> timing = new nsServerTiming();
|
||||
timing->SetName(serverTimingData.name());
|
||||
timing->SetDuration(serverTimingData.duration());
|
||||
timing->SetDescription(serverTimingData.description());
|
||||
mServerTiming.AppendElement(timing);
|
||||
}
|
||||
}
|
||||
|
||||
IPCPerformanceTimingData PerformanceTimingData::ToIPC() {
|
||||
nsTArray<IPCServerTiming> ipcServerTiming;
|
||||
for (auto& serverTimingData : mServerTiming) {
|
||||
nsAutoCString name;
|
||||
Unused << serverTimingData->GetName(name);
|
||||
double duration = 0;
|
||||
Unused << serverTimingData->GetDuration(&duration);
|
||||
nsAutoCString description;
|
||||
Unused << serverTimingData->GetDescription(description);
|
||||
ipcServerTiming.AppendElement(IPCServerTiming(name, duration, description));
|
||||
}
|
||||
return IPCPerformanceTimingData(
|
||||
ipcServerTiming, mNextHopProtocol, mAsyncOpen, mRedirectStart,
|
||||
mRedirectEnd, mDomainLookupStart, mDomainLookupEnd, mConnectStart,
|
||||
mSecureConnectionStart, mConnectEnd, mRequestStart, mResponseStart,
|
||||
mCacheReadStart, mResponseEnd, mCacheReadEnd, mWorkerStart,
|
||||
mWorkerRequestStart, mWorkerResponseEnd, mZeroTime, mFetchStart,
|
||||
mEncodedBodySize, mTransferSize, mDecodedBodySize, mRedirectCount,
|
||||
mAllRedirectsSameOrigin, mAllRedirectsPassTAO, mSecureConnection,
|
||||
mTimingAllowed, mInitialized);
|
||||
}
|
||||
|
||||
void PerformanceTimingData::SetPropertiesFromHttpChannel(
|
||||
nsIHttpChannel* aHttpChannel, nsITimedChannel* aChannel) {
|
||||
MOZ_ASSERT(aHttpChannel);
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "nsWrapperCache.h"
|
||||
#include "Performance.h"
|
||||
#include "nsITimedChannel.h"
|
||||
#include "mozilla/dom/PerformanceTimingTypes.h"
|
||||
#include "mozilla/ipc/IPDLParamTraits.h"
|
||||
#include "ipc/IPCMessageUtils.h"
|
||||
#include "ipc/IPCMessageUtilsSpecializations.h"
|
||||
|
@ -45,6 +46,10 @@ class PerformanceTimingData final {
|
|||
PerformanceTimingData(nsITimedChannel* aChannel, nsIHttpChannel* aHttpChannel,
|
||||
DOMHighResTimeStamp aZeroTime);
|
||||
|
||||
explicit PerformanceTimingData(const IPCPerformanceTimingData& aIPCData);
|
||||
|
||||
IPCPerformanceTimingData ToIPC();
|
||||
|
||||
void SetPropertiesFromHttpChannel(nsIHttpChannel* aHttpChannel,
|
||||
nsITimedChannel* aChannel);
|
||||
|
||||
|
|
50
dom/performance/PerformanceTimingTypes.ipdlh
Normal file
50
dom/performance/PerformanceTimingTypes.ipdlh
Normal file
|
@ -0,0 +1,50 @@
|
|||
/* 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/. */
|
||||
|
||||
using class mozilla::TimeStamp from "mozilla/TimeStamp.h";
|
||||
using DOMHighResTimeStamp from "nsDOMNavigationTiming.h";
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
struct IPCServerTiming {
|
||||
nsCString name;
|
||||
double duration;
|
||||
nsCString description;
|
||||
};
|
||||
|
||||
struct IPCPerformanceTimingData {
|
||||
IPCServerTiming[] serverTiming;
|
||||
nsString nextHopProtocol;
|
||||
TimeStamp asyncOpen;
|
||||
TimeStamp redirectStart;
|
||||
TimeStamp redirectEnd;
|
||||
TimeStamp domainLookupStart;
|
||||
TimeStamp domainLookupEnd;
|
||||
TimeStamp connectStart;
|
||||
TimeStamp secureConnectionStart;
|
||||
TimeStamp connectEnd;
|
||||
TimeStamp requestStart;
|
||||
TimeStamp responseStart;
|
||||
TimeStamp cacheReadStart;
|
||||
TimeStamp responseEnd;
|
||||
TimeStamp cacheReadEnd;
|
||||
TimeStamp workerStart;
|
||||
TimeStamp workerRequestStart;
|
||||
TimeStamp workerResponseEnd;
|
||||
DOMHighResTimeStamp zeroTime;
|
||||
DOMHighResTimeStamp fetchStart;
|
||||
uint64_t encodedBodySize;
|
||||
uint64_t transferSize;
|
||||
uint64_t decodedBodySize;
|
||||
uint8_t redirectCount;
|
||||
bool allRedirectsSameOrigin;
|
||||
bool allRedirectsPassTAO;
|
||||
bool secureConnection;
|
||||
bool timingAllowed;
|
||||
bool initialized;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
|
@ -49,6 +49,10 @@ UNIFIED_SOURCES += [
|
|||
"PerformanceWorker.cpp",
|
||||
]
|
||||
|
||||
IPDL_SOURCES += [
|
||||
"PerformanceTimingTypes.ipdlh",
|
||||
]
|
||||
|
||||
include("/ipc/chromium/chromium-config.mozbuild")
|
||||
|
||||
MOCHITEST_MANIFESTS += ["tests/mochitest.ini"]
|
||||
|
|
|
@ -230,9 +230,19 @@ FetchEventOpChild::FetchEventOpChild(
|
|||
mPreloadResponseReadyPromise
|
||||
->Then(
|
||||
GetCurrentSerialEventTarget(), __func__,
|
||||
[this](SafeRefPtr<InternalResponse> aInternalResponse) {
|
||||
auto response =
|
||||
aInternalResponse->ToParentToParentInternalResponse();
|
||||
[this](FetchServiceResponse&& aResponse) {
|
||||
SafeRefPtr<InternalResponse> preloadResponse;
|
||||
IPCPerformanceTimingData timingData;
|
||||
nsString initiatorType;
|
||||
nsString entryName;
|
||||
Tie(preloadResponse, timingData, initiatorType, entryName) =
|
||||
std::move(aResponse);
|
||||
ParentToParentResponseWithTiming response;
|
||||
response.response() =
|
||||
preloadResponse->ToParentToParentInternalResponse();
|
||||
response.timingData() = timingData;
|
||||
response.initiatorType() = initiatorType;
|
||||
response.entryName() = entryName;
|
||||
if (!mWasSent) {
|
||||
// The actor wasn't sent yet, we can still send the preload
|
||||
// response with it.
|
||||
|
|
|
@ -25,9 +25,9 @@ using namespace ipc;
|
|||
|
||||
namespace dom {
|
||||
|
||||
Maybe<ParentToParentInternalResponse> FetchEventOpParent::OnStart(
|
||||
Maybe<ParentToParentResponseWithTiming> FetchEventOpParent::OnStart(
|
||||
MovingNotNull<RefPtr<FetchEventOpProxyParent>> aFetchEventOpProxyParent) {
|
||||
Maybe<ParentToParentInternalResponse> preloadResponse =
|
||||
Maybe<ParentToParentResponseWithTiming> preloadResponse =
|
||||
std::move(mState.as<Pending>().mPreloadResponse);
|
||||
mState = AsVariant(Started{std::move(aFetchEventOpProxyParent)});
|
||||
return preloadResponse;
|
||||
|
@ -39,7 +39,7 @@ void FetchEventOpParent::OnFinish() {
|
|||
}
|
||||
|
||||
mozilla::ipc::IPCResult FetchEventOpParent::RecvPreloadResponse(
|
||||
ParentToParentInternalResponse&& aResponse) {
|
||||
ParentToParentResponseWithTiming&& aResponse) {
|
||||
AssertIsOnBackgroundThread();
|
||||
|
||||
mState.match(
|
||||
|
@ -48,11 +48,17 @@ mozilla::ipc::IPCResult FetchEventOpParent::RecvPreloadResponse(
|
|||
aPending.mPreloadResponse = Some(std::move(aResponse));
|
||||
},
|
||||
[&aResponse](Started& aStarted) {
|
||||
ParentToChildResponseWithTiming response;
|
||||
auto backgroundParent = WrapNotNull(
|
||||
WrapNotNull(aStarted.mFetchEventOpProxyParent->Manager())
|
||||
->Manager());
|
||||
response.response() =
|
||||
ToParentToChild(aResponse.response(), backgroundParent);
|
||||
response.timingData() = aResponse.timingData();
|
||||
response.initiatorType() = aResponse.initiatorType();
|
||||
response.entryName() = aResponse.entryName();
|
||||
Unused << aStarted.mFetchEventOpProxyParent->SendPreloadResponse(
|
||||
ToParentToChild(aResponse, backgroundParent));
|
||||
response);
|
||||
},
|
||||
[](const Finished&) {});
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ class FetchEventOpParent final : public PFetchEventOpParent {
|
|||
|
||||
// Transition from the Pending state to the Started state. Returns the preload
|
||||
// response, if it has already arrived.
|
||||
Maybe<ParentToParentInternalResponse> OnStart(
|
||||
Maybe<ParentToParentResponseWithTiming> OnStart(
|
||||
MovingNotNull<RefPtr<FetchEventOpProxyParent>> aFetchEventOpProxyParent);
|
||||
|
||||
// Transition from the Started state to the Finished state.
|
||||
|
@ -37,12 +37,12 @@ class FetchEventOpParent final : public PFetchEventOpParent {
|
|||
// IPDL methods
|
||||
|
||||
mozilla::ipc::IPCResult RecvPreloadResponse(
|
||||
ParentToParentInternalResponse&& aResponse);
|
||||
ParentToParentResponseWithTiming&& aResponse);
|
||||
|
||||
void ActorDestroy(ActorDestroyReason) override;
|
||||
|
||||
struct Pending {
|
||||
Maybe<ParentToParentInternalResponse> mPreloadResponse;
|
||||
Maybe<ParentToParentResponseWithTiming> mPreloadResponse;
|
||||
};
|
||||
|
||||
struct Started {
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/Unused.h"
|
||||
#include "mozilla/dom/InternalRequest.h"
|
||||
#include "mozilla/dom/InternalResponse.h"
|
||||
#include "mozilla/dom/RemoteWorkerChild.h"
|
||||
#include "mozilla/dom/RemoteWorkerService.h"
|
||||
#include "mozilla/dom/ServiceWorkerOp.h"
|
||||
|
@ -77,8 +79,13 @@ void FetchEventOpProxyChild::Initialize(
|
|||
MakeRefPtr<FetchEventPreloadResponsePromise::Private>(__func__);
|
||||
mPreloadResponsePromise->UseSynchronousTaskDispatch(__func__);
|
||||
if (aArgs.preloadResponse().isSome()) {
|
||||
mPreloadResponsePromise->Resolve(
|
||||
InternalResponse::FromIPC(aArgs.preloadResponse().ref()), __func__);
|
||||
FetchEventPreloadResponseArgs response = MakeTuple(
|
||||
InternalResponse::FromIPC(aArgs.preloadResponse().ref().response()),
|
||||
aArgs.preloadResponse().ref().timingData(),
|
||||
aArgs.preloadResponse().ref().initiatorType(),
|
||||
aArgs.preloadResponse().ref().entryName());
|
||||
|
||||
mPreloadResponsePromise->Resolve(std::move(response), __func__);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -177,13 +184,16 @@ FetchEventOpProxyChild::GetPreloadResponsePromise() {
|
|||
}
|
||||
|
||||
mozilla::ipc::IPCResult FetchEventOpProxyChild::RecvPreloadResponse(
|
||||
ParentToChildInternalResponse&& aResponse) {
|
||||
ParentToChildResponseWithTiming&& aResponse) {
|
||||
// Receiving this message implies that navigation preload is enabled, so
|
||||
// Initialize() should have created this promise.
|
||||
MOZ_ASSERT(mPreloadResponsePromise);
|
||||
|
||||
mPreloadResponsePromise->Resolve(InternalResponse::FromIPC(aResponse),
|
||||
__func__);
|
||||
FetchEventPreloadResponseArgs response = MakeTuple(
|
||||
InternalResponse::FromIPC(aResponse.response()), aResponse.timingData(),
|
||||
aResponse.initiatorType(), aResponse.entryName());
|
||||
|
||||
mPreloadResponsePromise->Resolve(std::move(response), __func__);
|
||||
|
||||
return IPC_OK();
|
||||
}
|
||||
|
@ -196,8 +206,12 @@ void FetchEventOpProxyChild::ActorDestroy(ActorDestroyReason) {
|
|||
// be valid anymore since it is too late to respond to the FetchEvent.
|
||||
// Resolve the preload response promise with NS_ERROR_DOM_ABORT_ERR.
|
||||
if (mPreloadResponsePromise) {
|
||||
mPreloadResponsePromise->Resolve(
|
||||
InternalResponse::NetworkError(NS_ERROR_DOM_ABORT_ERR), __func__);
|
||||
IPCPerformanceTimingData timingData;
|
||||
FetchEventPreloadResponseArgs response =
|
||||
MakeTuple(InternalResponse::NetworkError(NS_ERROR_DOM_ABORT_ERR),
|
||||
timingData, EmptyString(), EmptyString());
|
||||
|
||||
mPreloadResponsePromise->Resolve(std::move(response), __func__);
|
||||
}
|
||||
|
||||
mOp->RevokeActor(this);
|
||||
|
|
|
@ -42,7 +42,7 @@ class FetchEventOpProxyChild final : public PFetchEventOpProxyChild {
|
|||
~FetchEventOpProxyChild() = default;
|
||||
|
||||
mozilla::ipc::IPCResult RecvPreloadResponse(
|
||||
ParentToChildInternalResponse&& aResponse);
|
||||
ParentToChildResponseWithTiming&& aResponse);
|
||||
|
||||
void ActorDestroy(ActorDestroyReason) override;
|
||||
|
||||
|
|
|
@ -121,9 +121,15 @@ ParentToParentFetchEventRespondWithResult ToParentToParent(
|
|||
ParentToChildServiceWorkerFetchEventOpArgs copyArgs(aArgs.common(),
|
||||
Nothing());
|
||||
if (aArgs.preloadResponse().isSome()) {
|
||||
// Convert the preload response to ParentToChildInternalResponse.
|
||||
copyArgs.preloadResponse() = Some(ToParentToChild(
|
||||
aArgs.preloadResponse().ref(), WrapNotNull(aManager->Manager())));
|
||||
// Convert the preload response to ParentToChildResponseWithTiming.
|
||||
ParentToChildResponseWithTiming response;
|
||||
response.response() =
|
||||
ToParentToChild(aArgs.preloadResponse().ref().response(),
|
||||
WrapNotNull(aManager->Manager()));
|
||||
response.timingData() = aArgs.preloadResponse().ref().timingData();
|
||||
response.initiatorType() = aArgs.preloadResponse().ref().initiatorType();
|
||||
response.entryName() = aArgs.preloadResponse().ref().entryName();
|
||||
copyArgs.preloadResponse() = Some(response);
|
||||
}
|
||||
|
||||
FetchEventOpProxyParent* actor =
|
||||
|
@ -135,11 +141,16 @@ ParentToParentFetchEventRespondWithResult ToParentToParent(
|
|||
// need to add it to the arguments. Note that we have to make sure that the
|
||||
// arguments don't contain the preload response already, otherwise we'll end
|
||||
// up overwriting it with a Nothing.
|
||||
Maybe<ParentToParentInternalResponse> preloadResponse =
|
||||
Maybe<ParentToParentResponseWithTiming> preloadResponse =
|
||||
actor->mReal->OnStart(WrapNotNull(actor));
|
||||
if (copyArgs.preloadResponse().isNothing() && preloadResponse.isSome()) {
|
||||
copyArgs.preloadResponse() = Some(ToParentToChild(
|
||||
preloadResponse.ref(), WrapNotNull(aManager->Manager())));
|
||||
ParentToChildResponseWithTiming response;
|
||||
response.response() = ToParentToChild(preloadResponse.ref().response(),
|
||||
WrapNotNull(aManager->Manager()));
|
||||
response.timingData() = preloadResponse.ref().timingData();
|
||||
response.initiatorType() = preloadResponse.ref().initiatorType();
|
||||
response.entryName() = preloadResponse.ref().entryName();
|
||||
copyArgs.preloadResponse() = Some(response);
|
||||
}
|
||||
|
||||
IPCInternalRequest& copyRequest = copyArgs.common().internalRequest();
|
||||
|
|
|
@ -15,7 +15,7 @@ protocol PFetchEventOp {
|
|||
manager PRemoteWorkerController;
|
||||
|
||||
parent:
|
||||
async PreloadResponse(ParentToParentInternalResponse aResponse);
|
||||
async PreloadResponse(ParentToParentResponseWithTiming aResponse);
|
||||
|
||||
child:
|
||||
async AsyncLog(nsCString aScriptSpec, uint32_t aLineNumber,
|
||||
|
|
|
@ -23,7 +23,7 @@ protocol PFetchEventOpProxy {
|
|||
async __delete__(ServiceWorkerFetchEventOpResult aResult);
|
||||
|
||||
child:
|
||||
async PreloadResponse(ParentToChildInternalResponse aResponse);
|
||||
async PreloadResponse(ParentToChildResponseWithTiming aResponse);
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
|
|
|
@ -47,6 +47,8 @@
|
|||
#include "mozilla/dom/Notification.h"
|
||||
#include "mozilla/dom/NotificationEvent.h"
|
||||
#include "mozilla/dom/NotificationEventBinding.h"
|
||||
#include "mozilla/dom/PerformanceTiming.h"
|
||||
#include "mozilla/dom/PerformanceStorage.h"
|
||||
#include "mozilla/dom/PushEventBinding.h"
|
||||
#include "mozilla/dom/RemoteWorkerChild.h"
|
||||
#include "mozilla/dom/RemoteWorkerService.h"
|
||||
|
@ -1678,14 +1680,29 @@ nsresult FetchEventOp::DispatchFetchEvent(JSContext* aCx,
|
|||
// If preloadResponsePromise has already settled then this callback will get
|
||||
// run synchronously here.
|
||||
RefPtr<FetchEventOp> self = this;
|
||||
RefPtr<PerformanceStorage> performanceStorage =
|
||||
aWorkerPrivate->GetPerformanceStorage();
|
||||
preloadResponsePromise
|
||||
->Then(
|
||||
GetCurrentSerialEventTarget(), __func__,
|
||||
[self, globalObjectAsSupports = std::move(globalObjectAsSupports)](
|
||||
SafeRefPtr<InternalResponse> aInternalResponse) {
|
||||
self->mPreloadResponse->MaybeResolve(
|
||||
MakeRefPtr<Response>(globalObjectAsSupports,
|
||||
std::move(aInternalResponse), nullptr));
|
||||
[self, performanceStorage,
|
||||
globalObjectAsSupports = std::move(globalObjectAsSupports)](
|
||||
FetchEventPreloadResponseArgs&& aArgs) {
|
||||
SafeRefPtr<InternalResponse> preloadResponse;
|
||||
IPCPerformanceTimingData timingData;
|
||||
nsString initiatorType;
|
||||
nsString entryName;
|
||||
Tie(preloadResponse, timingData, initiatorType, entryName) =
|
||||
std::move(aArgs);
|
||||
if (performanceStorage &&
|
||||
NS_SUCCEEDED(preloadResponse->GetErrorCode())) {
|
||||
performanceStorage->AddEntry(
|
||||
entryName, initiatorType,
|
||||
MakeUnique<PerformanceTimingData>(timingData));
|
||||
}
|
||||
|
||||
self->mPreloadResponse->MaybeResolve(MakeRefPtr<Response>(
|
||||
globalObjectAsSupports, std::move(preloadResponse), nullptr));
|
||||
self->mPreloadResponsePromiseRequestHolder.Complete();
|
||||
},
|
||||
[self](int) {
|
||||
|
|
|
@ -93,12 +93,12 @@ struct ServiceWorkerFetchEventOpArgsCommon {
|
|||
|
||||
struct ParentToParentServiceWorkerFetchEventOpArgs {
|
||||
ServiceWorkerFetchEventOpArgsCommon common;
|
||||
ParentToParentInternalResponse? preloadResponse;
|
||||
ParentToParentResponseWithTiming? preloadResponse;
|
||||
};
|
||||
|
||||
struct ParentToChildServiceWorkerFetchEventOpArgs {
|
||||
ServiceWorkerFetchEventOpArgsCommon common;
|
||||
ParentToChildInternalResponse? preloadResponse;
|
||||
ParentToChildResponseWithTiming? preloadResponse;
|
||||
};
|
||||
|
||||
union ServiceWorkerOpArgs {
|
||||
|
|
|
@ -30,10 +30,14 @@ using FetchEventRespondWithResult =
|
|||
using FetchEventRespondWithPromise =
|
||||
MozPromise<FetchEventRespondWithResult, CancelInterceptionArgs, true>;
|
||||
|
||||
using FetchEventPreloadResponseArgs =
|
||||
Tuple<SafeRefPtr<InternalResponse>, IPCPerformanceTimingData, nsString,
|
||||
nsString>;
|
||||
|
||||
// The reject type int is arbitrary, since this promise will never get rejected.
|
||||
// Unfortunately void is not supported as a reject type.
|
||||
using FetchEventPreloadResponsePromise =
|
||||
MozPromise<SafeRefPtr<InternalResponse>, int, true>;
|
||||
MozPromise<FetchEventPreloadResponseArgs, int, true>;
|
||||
|
||||
using ServiceWorkerOpPromise =
|
||||
MozPromise<ServiceWorkerOpResult, nsresult, true>;
|
||||
|
|
|
@ -120,6 +120,7 @@ class SVGElement : public SVGElementBase // nsIContent
|
|||
int32_t aModType) const override;
|
||||
|
||||
virtual bool IsNodeOfType(uint32_t aFlags) const override;
|
||||
virtual bool IsSVGGraphicsElement() const { return false; }
|
||||
|
||||
/**
|
||||
* We override the default to unschedule computation of Servo declaration
|
||||
|
|
|
@ -10,8 +10,7 @@
|
|||
#include "mozilla/dom/SVGTests.h"
|
||||
#include "mozilla/dom/SVGTransformableElement.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
namespace mozilla::dom {
|
||||
|
||||
using SVGGraphicsElementBase = SVGTransformableElement;
|
||||
|
||||
|
@ -32,7 +31,10 @@ class SVGGraphicsElement : public SVGGraphicsElementBase, public SVGTests {
|
|||
}
|
||||
|
||||
bool IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse) override;
|
||||
bool IsSVGGraphicsElement() const final { return true; }
|
||||
nsresult BindToTree(BindContext&, nsINode& aParent) override;
|
||||
|
||||
// Overrides SVGTests.
|
||||
SVGElement* AsSVGElement() final { return this; }
|
||||
|
||||
protected:
|
||||
|
@ -51,7 +53,6 @@ class SVGGraphicsElement : public SVGGraphicsElementBase, public SVGTests {
|
|||
}
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
} // namespace mozilla::dom
|
||||
|
||||
#endif // DOM_SVG_SVGGRAPHICSELEMENT_H_
|
||||
|
|
|
@ -9,11 +9,13 @@
|
|||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "mozilla/ScopeExit.h"
|
||||
#include "mozilla/StaticPrefs_svg.h"
|
||||
#include "mozilla/SVGObserverUtils.h"
|
||||
#include "mozilla/SVGUseFrame.h"
|
||||
#include "mozilla/URLExtraData.h"
|
||||
#include "mozilla/dom/Document.h"
|
||||
#include "mozilla/dom/Element.h"
|
||||
#include "mozilla/dom/ShadowIncludingTreeIterator.h"
|
||||
#include "mozilla/dom/SVGLengthBinding.h"
|
||||
#include "mozilla/dom/SVGSVGElement.h"
|
||||
#include "mozilla/dom/SVGUseElementBinding.h"
|
||||
|
@ -24,8 +26,7 @@
|
|||
|
||||
NS_IMPL_NS_NEW_SVG_ELEMENT(Use)
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
namespace mozilla::dom {
|
||||
|
||||
JSObject* SVGUseElement::WrapNode(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto) {
|
||||
|
@ -285,6 +286,70 @@ auto SVGUseElement::ScanAncestors(const Element& aTarget) const -> ScanResult {
|
|||
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
static bool IsForbiddenUseNode(const nsINode& aNode) {
|
||||
if (!aNode.IsElement()) {
|
||||
return false;
|
||||
}
|
||||
const auto* svg = SVGElement::FromNode(aNode);
|
||||
return !svg || !svg->IsSVGGraphicsElement();
|
||||
}
|
||||
|
||||
static void CollectForbiddenNodes(Element& aRoot,
|
||||
nsTArray<RefPtr<nsINode>>& aNodes) {
|
||||
auto iter = dom::ShadowIncludingTreeIterator(aRoot);
|
||||
while (iter) {
|
||||
nsINode* node = *iter;
|
||||
if (IsForbiddenUseNode(*node)) {
|
||||
aNodes.AppendElement(node);
|
||||
iter.SkipChildren();
|
||||
continue;
|
||||
}
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
|
||||
// SVG1 restricted <use> trees to SVGGraphicsElements.
|
||||
// https://www.w3.org/TR/SVG11/struct.html#UseElement:
|
||||
//
|
||||
// Any ‘svg’, ‘symbol’, ‘g’, graphics element or other ‘use’ is potentially a
|
||||
// template object that can be re-used (i.e., "instanced") in the SVG
|
||||
// document via a ‘use’ element. The ‘use’ element references another element
|
||||
// and indicates that the graphical contents of that element is
|
||||
// included/drawn at that given point in the document.
|
||||
//
|
||||
// SVG2 doesn't have that same restriction.
|
||||
// https://www.w3.org/TR/SVG2/struct.html#UseShadowTree:
|
||||
//
|
||||
// Previous versions of SVG restricted the contents of the shadow tree to SVG
|
||||
// graphics elements. This specification allows any valid SVG document
|
||||
// subtree to be cloned. Cloning non-graphical content, however, will not
|
||||
// usually have any visible effect.
|
||||
//
|
||||
// But it's pretty ambiguous as to what the behavior should be for some
|
||||
// elements, because <script> is inert, but <iframe> is not, see:
|
||||
// https://github.com/w3c/svgwg/issues/876
|
||||
//
|
||||
// So, fairly confusing, all-in-all.
|
||||
static void RemoveForbiddenNodes(Element& aRoot, bool aIsCrossDocument) {
|
||||
switch (StaticPrefs::svg_use_element_graphics_element_restrictions()) {
|
||||
case 0:
|
||||
return;
|
||||
case 1:
|
||||
if (!aIsCrossDocument) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
AutoTArray<RefPtr<nsINode>, 10> unsafeNodes;
|
||||
CollectForbiddenNodes(aRoot, unsafeNodes);
|
||||
for (auto& unsafeNode : unsafeNodes) {
|
||||
unsafeNode->Remove();
|
||||
}
|
||||
}
|
||||
|
||||
void SVGUseElement::UpdateShadowTree() {
|
||||
MOZ_ASSERT(IsInComposedDoc());
|
||||
|
||||
|
@ -330,9 +395,10 @@ void SVGUseElement::UpdateShadowTree() {
|
|||
}
|
||||
|
||||
{
|
||||
nsNodeInfoManager* nodeInfoManager = targetElement->OwnerDoc() == OwnerDoc()
|
||||
? nullptr
|
||||
: OwnerDoc()->NodeInfoManager();
|
||||
const bool isCrossDocument = targetElement->OwnerDoc() != OwnerDoc();
|
||||
|
||||
nsNodeInfoManager* nodeInfoManager =
|
||||
isCrossDocument ? OwnerDoc()->NodeInfoManager() : nullptr;
|
||||
|
||||
nsCOMPtr<nsINode> newNode =
|
||||
targetElement->Clone(true, nodeInfoManager, IgnoreErrors());
|
||||
|
@ -342,6 +408,7 @@ void SVGUseElement::UpdateShadowTree() {
|
|||
|
||||
MOZ_ASSERT(newNode->IsElement());
|
||||
newElement = newNode.forget().downcast<Element>();
|
||||
RemoveForbiddenNodes(*newElement, isCrossDocument);
|
||||
}
|
||||
|
||||
if (newElement->IsAnyOfSVGElements(nsGkAtoms::svg, nsGkAtoms::symbol)) {
|
||||
|
@ -568,5 +635,4 @@ nsCSSPropertyID SVGUseElement::GetCSSPropertyIdForAttrEnum(uint8_t aAttrEnum) {
|
|||
}
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
} // namespace mozilla::dom
|
||||
|
|
|
@ -5375,7 +5375,6 @@ void WorkerPrivate::DumpCrashInformation(nsACString& aString) {
|
|||
}
|
||||
|
||||
PerformanceStorage* WorkerPrivate::GetPerformanceStorage() {
|
||||
AssertIsOnMainThread();
|
||||
MOZ_ASSERT(mPerformanceStorage);
|
||||
return mPerformanceStorage;
|
||||
}
|
||||
|
|
|
@ -123,16 +123,6 @@ Compositing for the final result
|
|||
Graphics API
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Moz2D
|
||||
~~~~~
|
||||
|
||||
- The Moz2D graphics API, part of the Azure project, is a
|
||||
cross-platform interface onto the various graphics backends that
|
||||
Gecko uses for rendering such as Direct2D (1.0 and 1.1), Skia, Cairo,
|
||||
Quartz, and NV Path. Adding a new graphics platform to Gecko is
|
||||
accomplished by adding a backend to Moz2D.
|
||||
See `Moz2D documentation on wiki <https://wiki.mozilla.org/Platform/GFX/Moz2D>`__.
|
||||
|
||||
Compositing
|
||||
~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -12,3 +12,5 @@ Gecko but we've slipped from this because C++/Gecko don't have a good
|
|||
mechanism for modularization/dependencies. That being said, we still try
|
||||
to keep the coupling with the rest of Gecko low for hygiene, simplicity
|
||||
and perhaps a more modular future.
|
||||
|
||||
See also `Moz2D documentation on wiki <https://wiki.mozilla.org/Platform/GFX/Moz2D>`.
|
||||
|
|
|
@ -8,6 +8,11 @@
|
|||
#include "nsXULAppAPI.h"
|
||||
#include "mozilla/VsyncDispatcher.h"
|
||||
#include "MainThreadUtils.h"
|
||||
#include "gfxPlatform.h"
|
||||
|
||||
#ifdef MOZ_WAYLAND
|
||||
# include "WaylandVsyncSource.h"
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
namespace gfx {
|
||||
|
@ -271,6 +276,33 @@ VsyncSource::Display::GetRefreshTimerVsyncDispatcher() {
|
|||
return mRefreshTimerVsyncDispatcher;
|
||||
}
|
||||
|
||||
// static
|
||||
Maybe<TimeDuration> VsyncSource::GetFastestVsyncRate() {
|
||||
Maybe<TimeDuration> retVal;
|
||||
if (!gfxPlatform::Initialized()) {
|
||||
return retVal;
|
||||
}
|
||||
|
||||
mozilla::gfx::VsyncSource* vsyncSource =
|
||||
gfxPlatform::GetPlatform()->GetHardwareVsync();
|
||||
if (vsyncSource && vsyncSource->GetGlobalDisplay().IsVsyncEnabled()) {
|
||||
retVal.emplace(vsyncSource->GetGlobalDisplay().GetVsyncRate());
|
||||
}
|
||||
|
||||
#ifdef MOZ_WAYLAND
|
||||
Maybe<TimeDuration> waylandRate = WaylandVsyncSource::GetFastestVsyncRate();
|
||||
if (waylandRate) {
|
||||
if (!retVal) {
|
||||
retVal.emplace(*waylandRate);
|
||||
} else if (*waylandRate < *retVal) {
|
||||
retVal = waylandRate;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
void VsyncSource::Shutdown() { GetGlobalDisplay().Shutdown(); }
|
||||
|
||||
} // namespace gfx
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include "nsTArray.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "nsISupportsImpl.h"
|
||||
|
@ -125,6 +126,9 @@ class VsyncSource {
|
|||
virtual Display& GetGlobalDisplay() = 0; // Works across all displays
|
||||
void Shutdown();
|
||||
|
||||
// Returns the rate of the fastest enabled VsyncSource::Display or Nothing().
|
||||
static Maybe<TimeDuration> GetFastestVsyncRate();
|
||||
|
||||
protected:
|
||||
virtual ~VsyncSource() = default;
|
||||
};
|
||||
|
|
|
@ -260,6 +260,7 @@ LOCAL_INCLUDES += [
|
|||
"/dom/media/platforms/apple",
|
||||
"/dom/xml",
|
||||
"/gfx/cairo/cairo/src",
|
||||
"/widget/gtk",
|
||||
]
|
||||
|
||||
if CONFIG["MOZ_WIDGET_TOOLKIT"] in ("android", "gtk"):
|
||||
|
|
|
@ -73,16 +73,17 @@ wr::WrExternalImage RenderBufferTextureHost::Lock(
|
|||
}
|
||||
} else {
|
||||
const layers::YCbCrDescriptor& desc = mDescriptor.get_YCbCrDescriptor();
|
||||
auto cbcrSize = layers::ImageDataSerializer::GetCroppedCbCrSize(desc);
|
||||
|
||||
mYSurface = gfx::Factory::CreateWrappingDataSourceSurface(
|
||||
layers::ImageDataSerializer::GetYChannel(GetBuffer(), desc),
|
||||
desc.yStride(), desc.ySize(), gfx::SurfaceFormat::A8);
|
||||
desc.yStride(), desc.display().Size(), gfx::SurfaceFormat::A8);
|
||||
mCbSurface = gfx::Factory::CreateWrappingDataSourceSurface(
|
||||
layers::ImageDataSerializer::GetCbChannel(GetBuffer(), desc),
|
||||
desc.cbCrStride(), desc.cbCrSize(), gfx::SurfaceFormat::A8);
|
||||
desc.cbCrStride(), cbcrSize, gfx::SurfaceFormat::A8);
|
||||
mCrSurface = gfx::Factory::CreateWrappingDataSourceSurface(
|
||||
layers::ImageDataSerializer::GetCrChannel(GetBuffer(), desc),
|
||||
desc.cbCrStride(), desc.cbCrSize(), gfx::SurfaceFormat::A8);
|
||||
desc.cbCrStride(), cbcrSize, gfx::SurfaceFormat::A8);
|
||||
if (NS_WARN_IF(!mYSurface || !mCbSurface || !mCrSurface)) {
|
||||
mYSurface = mCbSurface = mCrSurface = nullptr;
|
||||
gfxCriticalNote << "YCbCr Surface is null";
|
||||
|
|
|
@ -25,7 +25,7 @@ RenderExternalTextureHost::RenderExternalTextureHost(
|
|||
switch (mDescriptor.type()) {
|
||||
case layers::BufferDescriptor::TYCbCrDescriptor: {
|
||||
const layers::YCbCrDescriptor& ycbcr = mDescriptor.get_YCbCrDescriptor();
|
||||
mSize = ycbcr.ySize();
|
||||
mSize = ycbcr.display().Size();
|
||||
mFormat = gfx::SurfaceFormat::YUV;
|
||||
break;
|
||||
}
|
||||
|
@ -63,16 +63,17 @@ bool RenderExternalTextureHost::CreateSurfaces() {
|
|||
const layers::YCbCrDescriptor& desc = mDescriptor.get_YCbCrDescriptor();
|
||||
const gfx::SurfaceFormat surfaceFormat =
|
||||
SurfaceFormatForColorDepth(desc.colorDepth());
|
||||
auto cbcrSize = layers::ImageDataSerializer::GetCroppedCbCrSize(desc);
|
||||
|
||||
mSurfaces[0] = gfx::Factory::CreateWrappingDataSourceSurface(
|
||||
layers::ImageDataSerializer::GetYChannel(GetBuffer(), desc),
|
||||
desc.yStride(), desc.ySize(), surfaceFormat);
|
||||
desc.yStride(), desc.display().Size(), surfaceFormat);
|
||||
mSurfaces[1] = gfx::Factory::CreateWrappingDataSourceSurface(
|
||||
layers::ImageDataSerializer::GetCbChannel(GetBuffer(), desc),
|
||||
desc.cbCrStride(), desc.cbCrSize(), surfaceFormat);
|
||||
desc.cbCrStride(), cbcrSize, surfaceFormat);
|
||||
mSurfaces[2] = gfx::Factory::CreateWrappingDataSourceSurface(
|
||||
layers::ImageDataSerializer::GetCrChannel(GetBuffer(), desc),
|
||||
desc.cbCrStride(), desc.cbCrSize(), surfaceFormat);
|
||||
desc.cbCrStride(), cbcrSize, surfaceFormat);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < PlaneCount(); ++i) {
|
||||
|
@ -239,19 +240,21 @@ bool RenderExternalTextureHost::MapPlane(RenderCompositor* aCompositor,
|
|||
aPlaneInfo.mData =
|
||||
layers::ImageDataSerializer::GetYChannel(mBuffer, desc);
|
||||
aPlaneInfo.mStride = desc.yStride();
|
||||
aPlaneInfo.mSize = desc.ySize();
|
||||
aPlaneInfo.mSize = desc.display().Size();
|
||||
break;
|
||||
case 1:
|
||||
aPlaneInfo.mData =
|
||||
layers::ImageDataSerializer::GetCbChannel(mBuffer, desc);
|
||||
aPlaneInfo.mStride = desc.cbCrStride();
|
||||
aPlaneInfo.mSize = desc.cbCrSize();
|
||||
aPlaneInfo.mSize =
|
||||
layers::ImageDataSerializer::GetCroppedCbCrSize(desc);
|
||||
break;
|
||||
case 2:
|
||||
aPlaneInfo.mData =
|
||||
layers::ImageDataSerializer::GetCrChannel(mBuffer, desc);
|
||||
aPlaneInfo.mStride = desc.cbCrStride();
|
||||
aPlaneInfo.mSize = desc.cbCrSize();
|
||||
aPlaneInfo.mSize =
|
||||
layers::ImageDataSerializer::GetCroppedCbCrSize(desc);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -103,7 +103,7 @@ static void NotifyImageLoading(nsIURI* aURI) {
|
|||
already_AddRefed<Image> ImageFactory::CreateImage(
|
||||
nsIRequest* aRequest, ProgressTracker* aProgressTracker,
|
||||
const nsCString& aMimeType, nsIURI* aURI, bool aIsMultiPart,
|
||||
uint32_t aInnerWindowId) {
|
||||
uint64_t aInnerWindowId) {
|
||||
// Compute the image's initialization flags.
|
||||
uint32_t imageFlags = ComputeImageFlags(aURI, aMimeType, aIsMultiPart);
|
||||
|
||||
|
@ -225,7 +225,7 @@ uint32_t GetContentSize(nsIRequest* aRequest) {
|
|||
already_AddRefed<Image> ImageFactory::CreateRasterImage(
|
||||
nsIRequest* aRequest, ProgressTracker* aProgressTracker,
|
||||
const nsCString& aMimeType, nsIURI* aURI, uint32_t aImageFlags,
|
||||
uint32_t aInnerWindowId) {
|
||||
uint64_t aInnerWindowId) {
|
||||
MOZ_ASSERT(aProgressTracker);
|
||||
|
||||
nsresult rv;
|
||||
|
@ -253,7 +253,7 @@ already_AddRefed<Image> ImageFactory::CreateRasterImage(
|
|||
already_AddRefed<Image> ImageFactory::CreateVectorImage(
|
||||
nsIRequest* aRequest, ProgressTracker* aProgressTracker,
|
||||
const nsCString& aMimeType, nsIURI* aURI, uint32_t aImageFlags,
|
||||
uint32_t aInnerWindowId) {
|
||||
uint64_t aInnerWindowId) {
|
||||
MOZ_ASSERT(aProgressTracker);
|
||||
|
||||
nsresult rv;
|
||||
|
|
|
@ -43,7 +43,7 @@ class ImageFactory {
|
|||
ProgressTracker* aProgressTracker,
|
||||
const nsCString& aMimeType,
|
||||
nsIURI* aURI, bool aIsMultiPart,
|
||||
uint32_t aInnerWindowId);
|
||||
uint64_t aInnerWindowId);
|
||||
/**
|
||||
* Creates a new image which isn't associated with a URI or loaded through
|
||||
* the usual image loading mechanism.
|
||||
|
@ -71,12 +71,12 @@ class ImageFactory {
|
|||
static already_AddRefed<Image> CreateRasterImage(
|
||||
nsIRequest* aRequest, ProgressTracker* aProgressTracker,
|
||||
const nsCString& aMimeType, nsIURI* aURI, uint32_t aImageFlags,
|
||||
uint32_t aInnerWindowId);
|
||||
uint64_t aInnerWindowId);
|
||||
|
||||
static already_AddRefed<Image> CreateVectorImage(
|
||||
nsIRequest* aRequest, ProgressTracker* aProgressTracker,
|
||||
const nsCString& aMimeType, nsIURI* aURI, uint32_t aImageFlags,
|
||||
uint32_t aInnerWindowId);
|
||||
uint64_t aInnerWindowId);
|
||||
|
||||
// This is a static factory class, so disallow instantiation.
|
||||
virtual ~ImageFactory() = 0;
|
||||
|
|
|
@ -2927,7 +2927,8 @@ void nsRefreshDriver::CancelPendingAnimationEvents(
|
|||
}
|
||||
|
||||
/* static */
|
||||
TimeStamp nsRefreshDriver::GetIdleDeadlineHint(TimeStamp aDefault) {
|
||||
TimeStamp nsRefreshDriver::GetIdleDeadlineHint(TimeStamp aDefault,
|
||||
IdleCheck aCheckType) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(!aDefault.IsNull());
|
||||
|
||||
|
@ -2938,7 +2939,10 @@ TimeStamp nsRefreshDriver::GetIdleDeadlineHint(TimeStamp aDefault) {
|
|||
// busy but tasks resulting from a tick on sThrottledRateTimer
|
||||
// counts as being idle.
|
||||
if (sRegularRateTimer) {
|
||||
return sRegularRateTimer->GetIdleDeadlineHint(aDefault);
|
||||
TimeStamp retVal = sRegularRateTimer->GetIdleDeadlineHint(aDefault);
|
||||
if (retVal != aDefault) {
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
|
||||
// The following calculation is only used on platform using per-BrowserChild
|
||||
|
@ -2950,6 +2954,8 @@ TimeStamp nsRefreshDriver::GetIdleDeadlineHint(TimeStamp aDefault) {
|
|||
// For now we use a somewhat simplistic approach that in many situations
|
||||
// gives us similar behaviour to what we would get using sRegularRateTimer:
|
||||
// use the highest result that is still lower than the aDefault fallback.
|
||||
// XXXsmaug None of this makes much sense. We should always return the
|
||||
// lowest result, not highest.
|
||||
TimeStamp hint = TimeStamp();
|
||||
if (sRegularRateTimerList) {
|
||||
for (RefreshDriverTimer* timer : *sRegularRateTimerList) {
|
||||
|
@ -2960,7 +2966,23 @@ TimeStamp nsRefreshDriver::GetIdleDeadlineHint(TimeStamp aDefault) {
|
|||
}
|
||||
}
|
||||
|
||||
return hint.IsNull() ? aDefault : hint;
|
||||
if (!hint.IsNull()) {
|
||||
return hint;
|
||||
}
|
||||
|
||||
if (aCheckType == IdleCheck::AllVsyncListeners && XRE_IsParentProcess()) {
|
||||
Maybe<TimeDuration> rate = mozilla::gfx::VsyncSource::GetFastestVsyncRate();
|
||||
if (rate.isSome()) {
|
||||
TimeStamp newHint = TimeStamp::Now() + *rate -
|
||||
TimeDuration::FromMilliseconds(
|
||||
StaticPrefs::layout_idle_period_time_limit());
|
||||
if (newHint < aDefault) {
|
||||
return newHint;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return aDefault;
|
||||
}
|
||||
|
||||
/* static */
|
||||
|
|
|
@ -359,8 +359,19 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
|
|||
*
|
||||
* If we're animating and we have skipped paints a time in the past
|
||||
* is returned.
|
||||
*
|
||||
* If aCheckType is AllVsyncListeners and we're in the parent process,
|
||||
* which doesn't have a RefreshDriver ticking, but some other process does
|
||||
* have, the return value is
|
||||
* (now + refreshrate - layout.idle_period.time_limit) as an approximation
|
||||
* when something will happen.
|
||||
* This can be useful check when parent process tries to avoid having too
|
||||
* long idle periods for example when it is sending input events to an
|
||||
* active child process.
|
||||
*/
|
||||
static mozilla::TimeStamp GetIdleDeadlineHint(mozilla::TimeStamp aDefault);
|
||||
enum IdleCheck { OnlyThisProcessRefreshDriver, AllVsyncListeners };
|
||||
static mozilla::TimeStamp GetIdleDeadlineHint(mozilla::TimeStamp aDefault,
|
||||
IdleCheck aCheckType);
|
||||
|
||||
/**
|
||||
* It returns the expected timestamp of the next tick or nothing if the next
|
||||
|
|
|
@ -559,6 +559,10 @@ pref(layout.css.devPixelsPerPx,"1.0") == svg-blurry-with-subpixel-position.html
|
|||
== use-extref-dataURI-01.svg pass.svg
|
||||
== use-children.svg pass.svg
|
||||
|
||||
test-pref(svg.use-element.graphics-element-restrictions,0) == use-restrictions.svg use-restrictions-not-restricted-ref.svg
|
||||
test-pref(svg.use-element.graphics-element-restrictions,1) != use-restrictions.svg use-restrictions-not-restricted-ref.svg
|
||||
test-pref(svg.use-element.graphics-element-restrictions,1) ref-pref(svg.use-element.graphics-element-restrictions,2) != use-restrictions.svg use-restrictions.svg
|
||||
|
||||
== use-element-shadow-tree-rule-matching.html pass.svg
|
||||
== use-image-01.svg pass.svg
|
||||
|
||||
|
|
|
@ -3,23 +3,28 @@
|
|||
<g color="ghostwhite">
|
||||
<defs>
|
||||
<linearGradient id="linearGrad1" gradientUnits="objectBoundingBox" y1="0" x1="0" y2="1" x2="1" >
|
||||
<stop offset="000%" stop-color="orange" />
|
||||
<stop offset="033%" stop-color="red" />
|
||||
<stop offset="050%" stop-color="gold" />
|
||||
<stop offset="066%" stop-color="red" />
|
||||
<stop offset="100%" stop-color="orange" />
|
||||
<stop offset="000%" stop-color="orange" />
|
||||
<stop offset="033%" stop-color="red" />
|
||||
<stop offset="050%" stop-color="gold" />
|
||||
<stop offset="066%" stop-color="red" />
|
||||
<stop offset="100%" stop-color="orange" />
|
||||
</linearGradient>
|
||||
|
||||
<radialGradient id="radialGrad1" gradientUnits="objectBoundingBox" cx="0.5" cy="0.5" r="0.5" fx="0.15" fy="0.15" >
|
||||
<stop offset="000%" stop-color="orange" />
|
||||
<stop offset="033%" stop-color="red" />
|
||||
<stop offset="050%" stop-color="gold" />
|
||||
<stop offset="066%" stop-color="red" />
|
||||
<stop offset="100%" stop-color="orange" />
|
||||
<stop offset="000%" stop-color="orange" />
|
||||
<stop offset="033%" stop-color="red" />
|
||||
<stop offset="050%" stop-color="gold" />
|
||||
<stop offset="066%" stop-color="red" />
|
||||
<stop offset="100%" stop-color="orange" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
<g>
|
||||
<g id="foreign">
|
||||
<foreignObject x="0" y="0" width="100" height="100">
|
||||
<p xmlns="http://www.w3.org/1999/xhtml">Foo</p>
|
||||
</foreignObject>
|
||||
</g>
|
||||
<rect id="rect1" x="0" y="0" width="200" height="125" stroke="none" />
|
||||
<rect id="rect2" x="200" y="0" width="200" height="125" stroke="none" />
|
||||
<rect id="rect3" x="0" y="125" width="200" height="125" stroke="none" fill="currentColor"/>
|
||||
|
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.4 KiB |
11
layout/reftests/svg/use-restrictions-not-restricted-ref.svg
Normal file
11
layout/reftests/svg/use-restrictions-not-restricted-ref.svg
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<g color="green">
|
||||
<foreignObject x="0" y="0" width="100" height="100">
|
||||
<p xmlns="http://www.w3.org/1999/xhtml">Foo</p>
|
||||
</foreignObject>
|
||||
<foreignObject x="100" y="0" width="100" height="100">
|
||||
<p xmlns="http://www.w3.org/1999/xhtml">Foo</p>
|
||||
</foreignObject>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 363 B |
14
layout/reftests/svg/use-restrictions.svg
Normal file
14
layout/reftests/svg/use-restrictions.svg
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<g id="foreign">
|
||||
<foreignObject x="100" y="0" width="100" height="100">
|
||||
<p xmlns="http://www.w3.org/1999/xhtml">Foo</p>
|
||||
</foreignObject>
|
||||
</g>
|
||||
</defs>
|
||||
<g color="green">
|
||||
<use href="use-02-extref-resource.svg#foreign" />
|
||||
<use href="#foreign" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 368 B |
|
@ -11843,6 +11843,25 @@
|
|||
value: false
|
||||
mirror: always
|
||||
|
||||
# Tweak which elements are allowed in <svg:use> subtrees, and in which
|
||||
# circumstances. See RemoveForbiddenNodes in SVGUseElement.cpp for the spec
|
||||
# text.
|
||||
#
|
||||
# - 0: Don't restrict ever.
|
||||
# - 1: Restrict only cross-document.
|
||||
# - 2/other: restrict always.
|
||||
#
|
||||
# We allow the behavior to be configurable via this pref. Our chosen default
|
||||
# value forbids non-graphical content in <svg:use> clones of cross-document
|
||||
# elements. This is a compromise between our more-permissive pre-existing
|
||||
# behavior (which SVG 2 seems to call for, and maps to pref value 0) and the
|
||||
# behavior of other UAs (which SVG 1.1 seems to call for, and maps to pref
|
||||
# value 2).
|
||||
- name: svg.use-element.graphics-element-restrictions
|
||||
type: int32_t
|
||||
value: 1
|
||||
mirror: always
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Prefs starting with "telemetry."
|
||||
#---------------------------------------------------------------------------
|
||||
|
|
|
@ -601,6 +601,7 @@ bool CachePerfStats::IsCacheSlow() {
|
|||
|
||||
// static
|
||||
void CachePerfStats::GetSlowStats(uint32_t* aSlow, uint32_t* aNotSlow) {
|
||||
StaticMutexAutoLock lock(sLock);
|
||||
*aSlow = sCacheSlowCnt;
|
||||
*aNotSlow = sCacheNotSlowCnt;
|
||||
}
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
[resource-timing.https.html]
|
||||
expected:
|
||||
if os == "win": TIMEOUT
|
||||
if os == "android": TIMEOUT
|
||||
[TIMEOUT, OK]
|
||||
[Navigation Preload Resource Timing.]
|
||||
expected:
|
||||
if os == "mac": [TIMEOUT, FAIL]
|
||||
if os == "linux": [TIMEOUT, FAIL]
|
||||
TIMEOUT
|
|
@ -883,12 +883,13 @@ class FirefoxWdSpecBrowser(WebDriverBrowser):
|
|||
super().start(group_metadata, **kwargs)
|
||||
|
||||
def stop(self, force=False):
|
||||
# Initially wait for any WebDriver session to cleanly shutdown
|
||||
# When this is called the executor is usually sending a end session
|
||||
# Initially wait for any WebDriver session to cleanly shutdown if the
|
||||
# process doesn't have to be force stopped.
|
||||
# When this is called the executor is usually sending an end session
|
||||
# command to the browser. We don't have a synchronisation mechanism
|
||||
# that allows us to know that process is ongoing, so poll the status
|
||||
# endpoint until there isn't a session, before killing the driver.
|
||||
if self.is_alive():
|
||||
if self.is_alive() and not force:
|
||||
end_time = time.time() + BrowserInstance.shutdown_timeout
|
||||
while time.time() < end_time:
|
||||
self.logger.debug("Waiting for WebDriver session to end")
|
||||
|
|
|
@ -246,9 +246,9 @@ class _RunnerManagerState(object):
|
|||
initializing = namedtuple("initializing",
|
||||
["test", "test_group", "group_metadata", "failure_count"])
|
||||
running = namedtuple("running", ["test", "test_group", "group_metadata"])
|
||||
restarting = namedtuple("restarting", ["test", "test_group", "group_metadata"])
|
||||
restarting = namedtuple("restarting", ["test", "test_group", "group_metadata", "force_stop"])
|
||||
error = namedtuple("error", [])
|
||||
stop = namedtuple("stop", [])
|
||||
stop = namedtuple("stop", ["force_stop"])
|
||||
|
||||
|
||||
RunnerManagerState = _RunnerManagerState()
|
||||
|
@ -350,7 +350,7 @@ class TestRunnerManager(threading.Thread):
|
|||
RunnerManagerState.before_init: self.start_init,
|
||||
RunnerManagerState.initializing: self.init,
|
||||
RunnerManagerState.running: self.run_test,
|
||||
RunnerManagerState.restarting: self.restart_runner
|
||||
RunnerManagerState.restarting: self.restart_runner,
|
||||
}
|
||||
|
||||
self.state = RunnerManagerState.before_init()
|
||||
|
@ -385,8 +385,8 @@ class TestRunnerManager(threading.Thread):
|
|||
raise
|
||||
finally:
|
||||
self.logger.debug("TestRunnerManager main loop terminating, starting cleanup")
|
||||
clean = isinstance(self.state, RunnerManagerState.stop)
|
||||
self.stop_runner(force=not clean)
|
||||
force_stop = not isinstance(self.state, RunnerManagerState.stop) or self.state.force_stop
|
||||
self.stop_runner(force=force_stop)
|
||||
self.teardown()
|
||||
self.logger.debug("TestRunnerManager main loop terminated")
|
||||
|
||||
|
@ -417,12 +417,15 @@ class TestRunnerManager(threading.Thread):
|
|||
self.logger.debug("Got command: %r" % command)
|
||||
except IOError:
|
||||
self.logger.error("Got IOError from poll")
|
||||
return RunnerManagerState.restarting(0)
|
||||
return RunnerManagerState.restarting(self.state.test,
|
||||
self.state.test_group,
|
||||
self.state.group_metadata,
|
||||
False)
|
||||
except Empty:
|
||||
if (self.debug_info and self.debug_info.interactive and
|
||||
self.browser.started and not self.browser.is_alive()):
|
||||
self.logger.debug("Debugger exited")
|
||||
return RunnerManagerState.stop()
|
||||
return RunnerManagerState.stop(False)
|
||||
|
||||
if (isinstance(self.state, RunnerManagerState.running) and
|
||||
not self.test_runner_proc.is_alive()):
|
||||
|
@ -442,7 +445,10 @@ class TestRunnerManager(threading.Thread):
|
|||
self.logger.critical("Last test did not complete")
|
||||
return RunnerManagerState.error()
|
||||
self.logger.warning("More tests found, but runner process died, restarting")
|
||||
return RunnerManagerState.restarting(0)
|
||||
return RunnerManagerState.restarting(self.state.test,
|
||||
self.state.test_group,
|
||||
self.state.group_metadata,
|
||||
False)
|
||||
else:
|
||||
f = (dispatch.get(self.state.__class__, {}).get(command) or
|
||||
dispatch.get(None, {}).get(command))
|
||||
|
@ -459,7 +465,7 @@ class TestRunnerManager(threading.Thread):
|
|||
test, test_group, group_metadata = self.get_next_test()
|
||||
self.recording.set(["testrunner", "init"])
|
||||
if test is None:
|
||||
return RunnerManagerState.stop()
|
||||
return RunnerManagerState.stop(True)
|
||||
else:
|
||||
return RunnerManagerState.initializing(test, test_group, group_metadata, 0)
|
||||
|
||||
|
@ -549,7 +555,8 @@ class TestRunnerManager(threading.Thread):
|
|||
self.logger.info("Restarting browser for new test environment")
|
||||
return RunnerManagerState.restarting(self.state.test,
|
||||
self.state.test_group,
|
||||
self.state.group_metadata)
|
||||
self.state.group_metadata,
|
||||
False)
|
||||
|
||||
self.recording.set(["testrunner", "test"] + self.state.test.id.split("/")[1:])
|
||||
self.logger.test_start(self.state.test.id)
|
||||
|
@ -683,6 +690,7 @@ class TestRunnerManager(threading.Thread):
|
|||
file_result.status in ("CRASH", "EXTERNAL-TIMEOUT", "INTERNAL-ERROR") or
|
||||
((subtest_unexpected or is_unexpected) and
|
||||
self.restart_on_unexpected))
|
||||
force_stop = test.test_type == "wdspec" and file_result.status == "EXTERNAL-TIMEOUT"
|
||||
|
||||
self.recording.set(["testrunner", "after-test"])
|
||||
if (not file_result.status == "CRASH" and
|
||||
|
@ -691,7 +699,7 @@ class TestRunnerManager(threading.Thread):
|
|||
self.logger.info("Pausing until the browser exits")
|
||||
self.send_message("wait")
|
||||
else:
|
||||
return self.after_test_end(test, restart_before_next)
|
||||
return self.after_test_end(test, restart_before_next, force_stop=force_stop)
|
||||
|
||||
def wait_finished(self, rerun=False):
|
||||
assert isinstance(self.state, RunnerManagerState.running)
|
||||
|
@ -701,7 +709,7 @@ class TestRunnerManager(threading.Thread):
|
|||
# post-stop processing
|
||||
return self.after_test_end(self.state.test, not rerun, force_rerun=rerun)
|
||||
|
||||
def after_test_end(self, test, restart, force_rerun=False):
|
||||
def after_test_end(self, test, restart, force_rerun=False, force_stop=False):
|
||||
assert isinstance(self.state, RunnerManagerState.running)
|
||||
# Mixing manual reruns and automatic reruns is confusing; we currently assume
|
||||
# that as long as we've done at least the automatic run count in total we can
|
||||
|
@ -709,7 +717,7 @@ class TestRunnerManager(threading.Thread):
|
|||
if not force_rerun and self.run_count >= self.rerun:
|
||||
test, test_group, group_metadata = self.get_next_test()
|
||||
if test is None:
|
||||
return RunnerManagerState.stop()
|
||||
return RunnerManagerState.stop(force_stop)
|
||||
if test_group is not self.state.test_group:
|
||||
# We are starting a new group of tests, so force a restart
|
||||
self.logger.info("Restarting browser for new test group")
|
||||
|
@ -718,14 +726,14 @@ class TestRunnerManager(threading.Thread):
|
|||
test_group = self.state.test_group
|
||||
group_metadata = self.state.group_metadata
|
||||
if restart:
|
||||
return RunnerManagerState.restarting(test, test_group, group_metadata)
|
||||
return RunnerManagerState.restarting(test, test_group, group_metadata, force_stop)
|
||||
else:
|
||||
return RunnerManagerState.running(test, test_group, group_metadata)
|
||||
|
||||
def restart_runner(self):
|
||||
"""Stop and restart the TestRunner"""
|
||||
assert isinstance(self.state, RunnerManagerState.restarting)
|
||||
self.stop_runner()
|
||||
self.stop_runner(force=self.state.force_stop)
|
||||
return RunnerManagerState.initializing(self.state.test, self.state.test_group, self.state.group_metadata, 0)
|
||||
|
||||
def log(self, data):
|
||||
|
@ -792,7 +800,7 @@ class TestRunnerManager(threading.Thread):
|
|||
|
||||
def runner_teardown(self):
|
||||
self.ensure_runner_stopped()
|
||||
return RunnerManagerState.stop()
|
||||
return RunnerManagerState.stop(False)
|
||||
|
||||
def send_message(self, command, *args):
|
||||
"""Send a message to the remote queue (to Executor)."""
|
||||
|
|
23
testing/xpcshell/example/unit/test_add_setup.js
Normal file
23
testing/xpcshell/example/unit/test_add_setup.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
let someVar = 1;
|
||||
|
||||
add_task(() => {
|
||||
Assert.ok(false, "I should not be called!");
|
||||
});
|
||||
|
||||
/* eslint-disable mozilla/reject-addtask-only */
|
||||
add_task(() => {
|
||||
Assert.equal(
|
||||
someVar,
|
||||
2,
|
||||
"Setup should have run, even though this is the only test."
|
||||
);
|
||||
}).only();
|
||||
|
||||
add_setup(() => {
|
||||
someVar = 2;
|
||||
});
|
|
@ -15,6 +15,7 @@ support-files =
|
|||
# NOTE: Do NOT set prefs= here. If you do, move test_prefs_no_defaults.js and
|
||||
# test_prefs_no_defaults_with_file.js to a new file without "prefs =".
|
||||
|
||||
[test_add_setup.js]
|
||||
[test_check_nsIException.js]
|
||||
skip-if = os == "win" && debug
|
||||
[test_check_nsIException_failing.js]
|
||||
|
|
|
@ -1534,6 +1534,9 @@ async function schedulePreciseGCAndForceCC(maxCount) {
|
|||
* A function to be run only if the funcOrProperies is not a function.
|
||||
* @param isTask
|
||||
* Optional flag that indicates whether `func` is a task. Defaults to `false`.
|
||||
* @param isSetup
|
||||
* Optional flag that indicates whether `func` is a setup task. Defaults to `false`.
|
||||
* Implies isTask.
|
||||
*
|
||||
* Each test function must call run_next_test() when it's done. Test files
|
||||
* should call run_next_test() in their run_test function to execute all
|
||||
|
@ -1544,9 +1547,17 @@ async function schedulePreciseGCAndForceCC(maxCount) {
|
|||
var _gSupportedProperties = ["skip_if", "pref_set"];
|
||||
var _gTests = [];
|
||||
var _gRunOnlyThisTest = null;
|
||||
function add_test(properties, func = properties, isTask = false) {
|
||||
function add_test(
|
||||
properties,
|
||||
func = properties,
|
||||
isTask = false,
|
||||
isSetup = false
|
||||
) {
|
||||
if (isSetup) {
|
||||
isTask = true;
|
||||
}
|
||||
if (typeof properties == "function") {
|
||||
properties = { isTask };
|
||||
properties = { isTask, isSetup };
|
||||
_gTests.push([properties, func]);
|
||||
} else if (typeof properties == "object") {
|
||||
// Ensure only documented properties are in the object.
|
||||
|
@ -1556,6 +1567,7 @@ function add_test(properties, func = properties, isTask = false) {
|
|||
}
|
||||
}
|
||||
properties.isTask = isTask;
|
||||
properties.isSetup = isSetup;
|
||||
_gTests.push([properties, func]);
|
||||
} else {
|
||||
do_throw("add_test() should take a function or an object and a function");
|
||||
|
@ -1623,6 +1635,13 @@ function add_task(properties, func = properties) {
|
|||
return add_test(properties, func, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* add_setup is like add_task, but creates setup tasks.
|
||||
*/
|
||||
function add_setup(properties, func = properties) {
|
||||
return add_test(properties, func, true, true);
|
||||
}
|
||||
|
||||
const _setTaskPrefs = prefs => {
|
||||
for (let [pref, value] of prefs) {
|
||||
if (value === undefined) {
|
||||
|
@ -1675,11 +1694,20 @@ const _getTaskPrefs = prefs => {
|
|||
var _gRunningTest = null;
|
||||
var _gTestIndex = 0; // The index of the currently running test.
|
||||
var _gTaskRunning = false;
|
||||
var _gSetupRunning = false;
|
||||
function run_next_test() {
|
||||
if (_gTaskRunning) {
|
||||
throw new Error(
|
||||
"run_next_test() called from an add_task() test function. " +
|
||||
"run_next_test() should not be called from inside add_task() " +
|
||||
"run_next_test() should not be called from inside add_setup() or add_task() " +
|
||||
"under any circumstances!"
|
||||
);
|
||||
}
|
||||
|
||||
if (_gSetupRunning) {
|
||||
throw new Error(
|
||||
"run_next_test() called from an add_setup() test function. " +
|
||||
"run_next_test() should not be called from inside add_setup() or add_task() " +
|
||||
"under any circumstances!"
|
||||
);
|
||||
}
|
||||
|
@ -1693,12 +1721,18 @@ function run_next_test() {
|
|||
|
||||
// Must set to pending before we check for skip, so that we keep the
|
||||
// running counts correct.
|
||||
_testLogger.info(_TEST_NAME + " | Starting " + _gRunningTest.name);
|
||||
_testLogger.info(
|
||||
`${_TEST_NAME} | Starting ${_properties.isSetup ? "setup " : ""}${
|
||||
_gRunningTest.name
|
||||
}`
|
||||
);
|
||||
do_test_pending(_gRunningTest.name);
|
||||
|
||||
if (
|
||||
(typeof _properties.skip_if == "function" && _properties.skip_if()) ||
|
||||
(_gRunOnlyThisTest && _gRunningTest != _gRunOnlyThisTest)
|
||||
(_gRunOnlyThisTest &&
|
||||
_gRunningTest != _gRunOnlyThisTest &&
|
||||
!_properties.isSetup)
|
||||
) {
|
||||
let _condition = _gRunOnlyThisTest
|
||||
? "only one task may run."
|
||||
|
@ -1730,11 +1764,15 @@ function run_next_test() {
|
|||
}
|
||||
|
||||
if (_properties.isTask) {
|
||||
_gTaskRunning = true;
|
||||
if (_properties.isSetup) {
|
||||
_gSetupRunning = true;
|
||||
} else {
|
||||
_gTaskRunning = true;
|
||||
}
|
||||
let startTime = Cu.now();
|
||||
(async () => _gRunningTest())().then(
|
||||
result => {
|
||||
_gTaskRunning = false;
|
||||
_gTaskRunning = _gSetupRunning = false;
|
||||
ChromeUtils.addProfilerMarker(
|
||||
"task",
|
||||
{ category: "Test", startTime },
|
||||
|
@ -1747,7 +1785,7 @@ function run_next_test() {
|
|||
run_next_test();
|
||||
},
|
||||
ex => {
|
||||
_gTaskRunning = false;
|
||||
_gTaskRunning = _gSetupRunning = false;
|
||||
ChromeUtils.addProfilerMarker(
|
||||
"task",
|
||||
{ category: "Test", startTime },
|
||||
|
@ -1781,6 +1819,16 @@ function run_next_test() {
|
|||
}
|
||||
}
|
||||
|
||||
function frontLoadSetups() {
|
||||
_gTests.sort(([propsA, funcA], [propsB, funcB]) => {
|
||||
return propsB.isSetup ? 1 : 0;
|
||||
});
|
||||
}
|
||||
|
||||
if (!_gTestIndex) {
|
||||
frontLoadSetups();
|
||||
}
|
||||
|
||||
// For sane stacks during failures, we execute this code soon, but not now.
|
||||
// We do this now, before we call do_test_finished(), to ensure the pending
|
||||
// counter (_tests_pending) never reaches 0 while we still have tests to run
|
||||
|
|
|
@ -2306,8 +2306,8 @@ class EventManager {
|
|||
EventManager._writePersistentListeners(extension);
|
||||
continue;
|
||||
}
|
||||
for (let [event, eventEntry] of moduleEntry) {
|
||||
for (let listener of eventEntry.values()) {
|
||||
for (let [event, listeners] of moduleEntry) {
|
||||
for (let [key, listener] of listeners) {
|
||||
let primed = { pendingEvents: [] };
|
||||
|
||||
let fireEvent = (...args) =>
|
||||
|
@ -2326,27 +2326,51 @@ class EventManager {
|
|||
async: fireEvent,
|
||||
};
|
||||
|
||||
let handler = api.primeListener(
|
||||
extension,
|
||||
event,
|
||||
fire,
|
||||
listener.params,
|
||||
isInStartup
|
||||
);
|
||||
if (handler) {
|
||||
listener.primed = primed;
|
||||
Object.assign(primed, handler);
|
||||
try {
|
||||
let handler = api.primeListener(
|
||||
extension,
|
||||
event,
|
||||
fire,
|
||||
listener.params,
|
||||
isInStartup
|
||||
);
|
||||
if (handler) {
|
||||
listener.primed = primed;
|
||||
Object.assign(primed, handler);
|
||||
}
|
||||
} catch (e) {
|
||||
Cu.reportError(
|
||||
`Error priming listener ${module}.${event}: ${e} :: ${e.stack}`
|
||||
);
|
||||
// Force this listener to be cleared.
|
||||
listener.error = true;
|
||||
}
|
||||
// If an attempt to prime a listener failed, ensure it is cleared now.
|
||||
// If a module is a startup blocking module, not all listeners may
|
||||
// get primed during early startup. For that reason, we don't clear
|
||||
// persisted listeners during early startup. At the end of background
|
||||
// execution any listeners that were not renewed will be cleared.
|
||||
if (listener.error || (!isInStartup && !listener.primed)) {
|
||||
EventManager.clearPersistentListener(extension, module, event, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove any primed listeners that were not re-registered.
|
||||
// This function is called after the background page has started.
|
||||
// The removed listeners are removed from the set of saved listeners, unless
|
||||
// `clearPersistent` is false. If false, the listeners are cleared from
|
||||
// memory, but not removed from the extension's startup data.
|
||||
/**
|
||||
* This is called as a result of background script startup-finished and shutdown.
|
||||
*
|
||||
* After startup, it removes any remaining primed listeners. These exist if the
|
||||
* listener was not renewed during startup. In this case the persisted listener
|
||||
* data is also removed.
|
||||
*
|
||||
* During shutdown, care should be taken to set clearPersistent to false.
|
||||
* persisted listener data should NOT be cleared during shutdown.
|
||||
*
|
||||
* @param {Extension} extension
|
||||
* @param {boolean} clearPersistent whether the persisted listener data should be cleared.
|
||||
*/
|
||||
static clearPrimedListeners(extension, clearPersistent = true) {
|
||||
if (!extension.persistentListeners) {
|
||||
return;
|
||||
|
@ -2356,19 +2380,29 @@ class EventManager {
|
|||
for (let [event, listeners] of moduleEntry) {
|
||||
for (let [key, listener] of listeners) {
|
||||
let { primed } = listener;
|
||||
// When a primed listener is renewed, primed is set to null
|
||||
// When a new listener has beed added, primed is undefined.
|
||||
// In both cases, we do not want to clear the persisted listener data.
|
||||
if (!primed) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// When a primed listener was not renewed, primed will still be truthy.
|
||||
// These need to be cleared on shutdown (important for event pages), but
|
||||
// we only clear the persisted listener data after the startup of a background.
|
||||
// Release any pending events and unregister the primed handler.
|
||||
listener.primed = null;
|
||||
|
||||
for (let evt of primed.pendingEvents) {
|
||||
evt.reject(new Error("listener not re-registered"));
|
||||
}
|
||||
primed.unregister();
|
||||
|
||||
// Clear any persisted events that were not renewed, should typically
|
||||
// only be done at the end of the background page load.
|
||||
if (clearPersistent) {
|
||||
EventManager.clearPersistentListener(extension, module, event, key);
|
||||
}
|
||||
primed.unregister();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,83 +27,126 @@ var { getSettingsAPI } = ExtensionPreferencesManager;
|
|||
|
||||
const CAPTIVE_URL_PREF = "captivedetect.canonicalURL";
|
||||
|
||||
function nameForCPSState(state) {
|
||||
switch (state) {
|
||||
case gCPS.UNKNOWN:
|
||||
return "unknown";
|
||||
case gCPS.NOT_CAPTIVE:
|
||||
return "not_captive";
|
||||
case gCPS.UNLOCKED_PORTAL:
|
||||
return "unlocked_portal";
|
||||
case gCPS.LOCKED_PORTAL:
|
||||
return "locked_portal";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
var { ExtensionError } = ExtensionUtils;
|
||||
|
||||
this.captivePortal = class extends ExtensionAPI {
|
||||
getAPI(context) {
|
||||
function checkEnabled() {
|
||||
if (!gCaptivePortalEnabled) {
|
||||
throw new ExtensionError("Captive Portal detection is not enabled");
|
||||
}
|
||||
checkCaptivePortalEnabled() {
|
||||
if (!gCaptivePortalEnabled) {
|
||||
throw new ExtensionError("Captive Portal detection is not enabled");
|
||||
}
|
||||
}
|
||||
|
||||
nameForCPSState(state) {
|
||||
switch (state) {
|
||||
case gCPS.UNKNOWN:
|
||||
return "unknown";
|
||||
case gCPS.NOT_CAPTIVE:
|
||||
return "not_captive";
|
||||
case gCPS.UNLOCKED_PORTAL:
|
||||
return "unlocked_portal";
|
||||
case gCPS.LOCKED_PORTAL:
|
||||
return "locked_portal";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
PERSISTENT_EVENTS = {
|
||||
onStateChanged: fire => {
|
||||
this.checkCaptivePortalEnabled();
|
||||
|
||||
let observer = (subject, topic) => {
|
||||
fire.async({ state: this.nameForCPSState(gCPS.state) });
|
||||
};
|
||||
|
||||
Services.obs.addObserver(
|
||||
observer,
|
||||
"ipc:network:captive-portal-set-state"
|
||||
);
|
||||
return {
|
||||
unregister: () => {
|
||||
Services.obs.removeObserver(
|
||||
observer,
|
||||
"ipc:network:captive-portal-set-state"
|
||||
);
|
||||
},
|
||||
convert(_fire, context) {
|
||||
fire = _fire;
|
||||
},
|
||||
};
|
||||
},
|
||||
onConnectivityAvailable: fire => {
|
||||
this.checkCaptivePortalEnabled();
|
||||
|
||||
let observer = (subject, topic, data) => {
|
||||
fire.async({ status: data });
|
||||
};
|
||||
|
||||
Services.obs.addObserver(observer, "network:captive-portal-connectivity");
|
||||
return {
|
||||
unregister: () => {
|
||||
Services.obs.removeObserver(
|
||||
observer,
|
||||
"network:captive-portal-connectivity"
|
||||
);
|
||||
},
|
||||
convert(_fire, context) {
|
||||
fire = _fire;
|
||||
},
|
||||
};
|
||||
},
|
||||
"captiveURL.onChange": fire => {
|
||||
let listener = (text, id) => {
|
||||
fire.async({
|
||||
levelOfControl: "not_controllable",
|
||||
value: Services.prefs.getStringPref(CAPTIVE_URL_PREF),
|
||||
});
|
||||
};
|
||||
Services.prefs.addObserver(CAPTIVE_URL_PREF, listener);
|
||||
return {
|
||||
unregister: () => {
|
||||
Services.prefs.removeObserver(CAPTIVE_URL_PREF, listener);
|
||||
},
|
||||
convert(_fire, context) {
|
||||
fire = _fire;
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
primeListener(extension, event, fire) {
|
||||
if (Object.hasOwn(this.PERSISTENT_EVENTS, event)) {
|
||||
return this.PERSISTENT_EVENTS[event](fire);
|
||||
}
|
||||
}
|
||||
|
||||
getAPI(context) {
|
||||
let self = this;
|
||||
return {
|
||||
captivePortal: {
|
||||
getState() {
|
||||
checkEnabled();
|
||||
return nameForCPSState(gCPS.state);
|
||||
self.checkCaptivePortalEnabled();
|
||||
return self.nameForCPSState(gCPS.state);
|
||||
},
|
||||
getLastChecked() {
|
||||
checkEnabled();
|
||||
self.checkCaptivePortalEnabled();
|
||||
return gCPS.lastChecked;
|
||||
},
|
||||
onStateChanged: new EventManager({
|
||||
context,
|
||||
name: "captivePortal.onStateChanged",
|
||||
module: "captivePortal",
|
||||
event: "onStateChanged",
|
||||
register: fire => {
|
||||
checkEnabled();
|
||||
|
||||
let observer = (subject, topic) => {
|
||||
fire.async({ state: nameForCPSState(gCPS.state) });
|
||||
};
|
||||
|
||||
Services.obs.addObserver(
|
||||
observer,
|
||||
"ipc:network:captive-portal-set-state"
|
||||
);
|
||||
return () => {
|
||||
Services.obs.removeObserver(
|
||||
observer,
|
||||
"ipc:network:captive-portal-set-state"
|
||||
);
|
||||
};
|
||||
return self.PERSISTENT_EVENTS.onStateChanged(fire).unregister;
|
||||
},
|
||||
}).api(),
|
||||
onConnectivityAvailable: new EventManager({
|
||||
context,
|
||||
name: "captivePortal.onConnectivityAvailable",
|
||||
module: "captivePortal",
|
||||
event: "onConnectivityAvailable",
|
||||
register: fire => {
|
||||
checkEnabled();
|
||||
|
||||
let observer = (subject, topic, data) => {
|
||||
fire.async({ status: data });
|
||||
};
|
||||
|
||||
Services.obs.addObserver(
|
||||
observer,
|
||||
"network:captive-portal-connectivity"
|
||||
);
|
||||
return () => {
|
||||
Services.obs.removeObserver(
|
||||
observer,
|
||||
"network:captive-portal-connectivity"
|
||||
);
|
||||
};
|
||||
return self.PERSISTENT_EVENTS.onConnectivityAvailable(fire)
|
||||
.unregister;
|
||||
},
|
||||
}).api(),
|
||||
canonicalURL: getSettingsAPI({
|
||||
|
@ -115,18 +158,11 @@ this.captivePortal = class extends ExtensionAPI {
|
|||
readOnly: true,
|
||||
onChange: new ExtensionCommon.EventManager({
|
||||
context,
|
||||
name: "captiveURL.onChange",
|
||||
module: "captivePortal",
|
||||
event: "captiveURL.onChange",
|
||||
register: fire => {
|
||||
let listener = (text, id) => {
|
||||
fire.async({
|
||||
levelOfControl: "not_controllable",
|
||||
value: Services.prefs.getStringPref(CAPTIVE_URL_PREF),
|
||||
});
|
||||
};
|
||||
Services.prefs.addObserver(CAPTIVE_URL_PREF, listener);
|
||||
return () => {
|
||||
Services.prefs.removeObserver(CAPTIVE_URL_PREF, listener);
|
||||
};
|
||||
return self.PERSISTENT_EVENTS["captiveURL.onChange"](fire)
|
||||
.unregister;
|
||||
},
|
||||
}).api(),
|
||||
}),
|
||||
|
|
|
@ -64,7 +64,7 @@
|
|||
{
|
||||
"id": "PlatformArch",
|
||||
"type": "string",
|
||||
"enum": ["aarch64", "arm", "ppc64", "s390x", "sparc64", "x86-32", "x86-64"],
|
||||
"enum": ["aarch64", "arm", "ppc64", "s390x", "sparc64", "x86-32", "x86-64", "noarch"],
|
||||
"allowedContexts": ["content", "devtools"],
|
||||
"description": "The machine's processor architecture."
|
||||
},
|
||||
|
|
|
@ -1,5 +1,19 @@
|
|||
"use strict";
|
||||
|
||||
Services.prefs.setBoolPref(
|
||||
"extensions.webextensions.background-delayed-startup",
|
||||
true
|
||||
);
|
||||
|
||||
AddonTestUtils.init(this);
|
||||
AddonTestUtils.overrideCertDB();
|
||||
AddonTestUtils.createAppInfo(
|
||||
"xpcshell@tests.mozilla.org",
|
||||
"XPCShell",
|
||||
"1",
|
||||
"43"
|
||||
);
|
||||
|
||||
/**
|
||||
* This duplicates the test from netwerk/test/unit/test_captive_portal_service.js
|
||||
* however using an extension to gather the captive portal information.
|
||||
|
@ -30,7 +44,7 @@ registerCleanupFunction(() => {
|
|||
Services.prefs.clearUserPref(PREF_DNS_NATIVE_IS_LOCALHOST);
|
||||
});
|
||||
|
||||
add_task(function setup() {
|
||||
add_task(async function setup() {
|
||||
Services.prefs.setCharPref(
|
||||
PREF_CAPTIVE_ENDPOINT,
|
||||
`http://localhost:${httpserver.identity.primaryPort}/captive.txt`
|
||||
|
@ -38,6 +52,9 @@ add_task(function setup() {
|
|||
Services.prefs.setBoolPref(PREF_CAPTIVE_TESTMODE, true);
|
||||
Services.prefs.setIntPref(PREF_CAPTIVE_MINTIME, 0);
|
||||
Services.prefs.setBoolPref(PREF_DNS_NATIVE_IS_LOCALHOST, true);
|
||||
|
||||
Services.prefs.setBoolPref("extensions.eventPages.enabled", true);
|
||||
await AddonTestUtils.promiseStartupManager();
|
||||
});
|
||||
|
||||
add_task(async function test_captivePortal_basic() {
|
||||
|
@ -46,8 +63,10 @@ add_task(async function test_captivePortal_basic() {
|
|||
);
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
useAddonManager: "permanent",
|
||||
manifest: {
|
||||
permissions: ["captivePortal"],
|
||||
background: { persistent: false },
|
||||
},
|
||||
isPrivileged: true,
|
||||
async background() {
|
||||
|
@ -63,6 +82,10 @@ add_task(async function test_captivePortal_basic() {
|
|||
browser.test.sendMessage("state", details);
|
||||
});
|
||||
|
||||
browser.captivePortal.canonicalURL.onChange.addListener(details => {
|
||||
browser.test.sendMessage("url", details);
|
||||
});
|
||||
|
||||
browser.test.onMessage.addListener(async msg => {
|
||||
if (msg == "getstate") {
|
||||
browser.test.sendMessage(
|
||||
|
@ -71,21 +94,20 @@ add_task(async function test_captivePortal_basic() {
|
|||
);
|
||||
}
|
||||
});
|
||||
browser.test.assertEq(
|
||||
"unknown",
|
||||
await browser.captivePortal.getState(),
|
||||
"initial state unknown"
|
||||
);
|
||||
},
|
||||
});
|
||||
await extension.startup();
|
||||
|
||||
extension.sendMessage("getstate");
|
||||
let details = await extension.awaitMessage("getstate");
|
||||
equal(details, "unknown", "initial state");
|
||||
|
||||
// The captive portal service is started by nsIOService when the pref becomes true, so we
|
||||
// toggle the pref. We cannot set to false before the extension loads above.
|
||||
Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, false);
|
||||
Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, true);
|
||||
|
||||
let details = await extension.awaitMessage("connectivity");
|
||||
details = await extension.awaitMessage("connectivity");
|
||||
equal(details.status, "clear", "initial connectivity");
|
||||
extension.sendMessage("getstate");
|
||||
details = await extension.awaitMessage("getstate");
|
||||
|
@ -106,5 +128,65 @@ add_task(async function test_captivePortal_basic() {
|
|||
details = await extension.awaitMessage("state");
|
||||
equal(details.state, "unlocked_portal", "state after unlocking portal");
|
||||
|
||||
assertPersistentListeners(
|
||||
extension,
|
||||
"captivePortal",
|
||||
"onConnectivityAvailable",
|
||||
{
|
||||
primed: false,
|
||||
}
|
||||
);
|
||||
|
||||
assertPersistentListeners(extension, "captivePortal", "onStateChanged", {
|
||||
primed: false,
|
||||
});
|
||||
|
||||
assertPersistentListeners(extension, "captivePortal", "captiveURL.onChange", {
|
||||
primed: false,
|
||||
});
|
||||
|
||||
info("Test event page terminate/waken");
|
||||
await extension.terminateBackground();
|
||||
|
||||
assertPersistentListeners(extension, "captivePortal", "onStateChanged", {
|
||||
primed: true,
|
||||
});
|
||||
assertPersistentListeners(
|
||||
extension,
|
||||
"captivePortal",
|
||||
"onConnectivityAvailable",
|
||||
{
|
||||
primed: true,
|
||||
}
|
||||
);
|
||||
|
||||
assertPersistentListeners(extension, "captivePortal", "captiveURL.onChange", {
|
||||
primed: true,
|
||||
});
|
||||
|
||||
info("REFRESH 2nd pass to other");
|
||||
cpResponse = "other";
|
||||
cps.recheckCaptivePortal();
|
||||
details = await extension.awaitMessage("state");
|
||||
equal(details.state, "locked_portal", "state in portal");
|
||||
|
||||
info("Test event page terminate/waken with settings");
|
||||
await extension.terminateBackground();
|
||||
|
||||
assertPersistentListeners(extension, "captivePortal", "captiveURL.onChange", {
|
||||
primed: true,
|
||||
});
|
||||
|
||||
Services.prefs.setStringPref(
|
||||
"captivedetect.canonicalURL",
|
||||
"http://example.com"
|
||||
);
|
||||
let url = await extension.awaitMessage("url");
|
||||
equal(
|
||||
url.value,
|
||||
"http://example.com",
|
||||
"The canonicalURL setting has the expected value."
|
||||
);
|
||||
|
||||
await extension.unload();
|
||||
});
|
||||
|
|
|
@ -2,24 +2,6 @@
|
|||
|
||||
const { ExtensionAPI } = ExtensionCommon;
|
||||
|
||||
const SCHEMA = [
|
||||
{
|
||||
namespace: "eventtest",
|
||||
events: [
|
||||
{
|
||||
name: "onEvent1",
|
||||
type: "function",
|
||||
extraParameters: [{ type: "any" }],
|
||||
},
|
||||
{
|
||||
name: "onEvent2",
|
||||
type: "function",
|
||||
extraParameters: [{ type: "any" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// The code in this class does not actually run in this test scope, it is
|
||||
// serialized into a string which is later loaded by the WebExtensions
|
||||
// framework in the same context as other extension APIs. By writing it
|
||||
|
@ -28,13 +10,28 @@ const SCHEMA = [
|
|||
// where the EventManager class is available so just tell it here:
|
||||
/* global EventManager */
|
||||
const API = class extends ExtensionAPI {
|
||||
static namespace = undefined;
|
||||
primeListener(extension, event, fire, params) {
|
||||
// eslint-disable-next-line no-undef
|
||||
let { eventName, throwError, ignoreListener } =
|
||||
this.constructor.testOptions || {};
|
||||
let { namespace } = this.constructor;
|
||||
|
||||
if (eventName == event) {
|
||||
if (throwError) {
|
||||
throw new Error(throwError);
|
||||
}
|
||||
if (ignoreListener) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Services.obs.notifyObservers(
|
||||
{ event, fire, params },
|
||||
{ namespace, event, fire, params },
|
||||
"prime-event-listener"
|
||||
);
|
||||
|
||||
const FIRE_TOPIC = `fire-${event}`;
|
||||
const FIRE_TOPIC = `fire-${namespace}.${event}`;
|
||||
|
||||
async function listener(subject, topic, data) {
|
||||
try {
|
||||
|
@ -43,7 +40,7 @@ const API = class extends ExtensionAPI {
|
|||
}
|
||||
await fire.async(subject.wrappedJSObject.listenerArgs);
|
||||
} catch (err) {
|
||||
let errSubject = { event, errorMessage: err.toString() };
|
||||
let errSubject = { namespace, event, errorMessage: err.toString() };
|
||||
Services.obs.notifyObservers(errSubject, "listener-callback-exception");
|
||||
}
|
||||
}
|
||||
|
@ -52,14 +49,14 @@ const API = class extends ExtensionAPI {
|
|||
return {
|
||||
unregister() {
|
||||
Services.obs.notifyObservers(
|
||||
{ event, params },
|
||||
{ namespace, event, params },
|
||||
"unregister-primed-listener"
|
||||
);
|
||||
Services.obs.removeObserver(listener, FIRE_TOPIC);
|
||||
},
|
||||
convert(_fire) {
|
||||
Services.obs.notifyObservers(
|
||||
{ event, params },
|
||||
{ namespace, event, params },
|
||||
"convert-event-listener"
|
||||
);
|
||||
fire = _fire;
|
||||
|
@ -68,14 +65,23 @@ const API = class extends ExtensionAPI {
|
|||
}
|
||||
|
||||
getAPI(context) {
|
||||
let self = this;
|
||||
let { namespace } = this.constructor;
|
||||
return {
|
||||
eventtest: {
|
||||
[namespace]: {
|
||||
testOptions(options) {
|
||||
// We want to be able to test errors on startup.
|
||||
// We use a global here because we test restarting AOM,
|
||||
// which causes the instance of this class to be destroyed.
|
||||
// eslint-disable-next-line no-undef
|
||||
self.constructor.testOptions = options;
|
||||
},
|
||||
onEvent1: new EventManager({
|
||||
context,
|
||||
module: "eventtest",
|
||||
module: namespace,
|
||||
event: "onEvent1",
|
||||
register: (fire, ...params) => {
|
||||
let data = { event: "onEvent1", params };
|
||||
let data = { namespace, event: "onEvent1", params };
|
||||
Services.obs.notifyObservers(data, "register-event-listener");
|
||||
return () => {
|
||||
Services.obs.notifyObservers(data, "unregister-event-listener");
|
||||
|
@ -85,10 +91,10 @@ const API = class extends ExtensionAPI {
|
|||
|
||||
onEvent2: new EventManager({
|
||||
context,
|
||||
module: "eventtest",
|
||||
module: namespace,
|
||||
event: "onEvent2",
|
||||
register: (fire, ...params) => {
|
||||
let data = { event: "onEvent2", params };
|
||||
let data = { namespace, event: "onEvent2", params };
|
||||
Services.obs.notifyObservers(data, "register-event-listener");
|
||||
return () => {
|
||||
Services.obs.notifyObservers(data, "unregister-event-listener");
|
||||
|
@ -100,16 +106,61 @@ const API = class extends ExtensionAPI {
|
|||
}
|
||||
};
|
||||
|
||||
const API_SCRIPT = `this.eventtest = ${API.toString()}`;
|
||||
function makeModule(namespace, options = {}) {
|
||||
const SCHEMA = [
|
||||
{
|
||||
namespace,
|
||||
functions: [
|
||||
{
|
||||
name: "testOptions",
|
||||
type: "function",
|
||||
async: true,
|
||||
parameters: [
|
||||
{
|
||||
name: "options",
|
||||
type: "object",
|
||||
additionalProperties: {
|
||||
type: "any",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
events: [
|
||||
{
|
||||
name: "onEvent1",
|
||||
type: "function",
|
||||
extraParameters: [{ type: "any" }],
|
||||
},
|
||||
{
|
||||
name: "onEvent2",
|
||||
type: "function",
|
||||
extraParameters: [{ type: "any" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const MODULE_INFO = {
|
||||
eventtest: {
|
||||
const API_SCRIPT = `
|
||||
this.${namespace} = ${API.toString()};
|
||||
this.${namespace}.namespace = "${namespace}";
|
||||
`;
|
||||
|
||||
// MODULE_INFO for registerModules
|
||||
let { startupBlocking } = options;
|
||||
return {
|
||||
schema: `data:,${JSON.stringify(SCHEMA)}`,
|
||||
scopes: ["addon_parent"],
|
||||
paths: [["eventtest"]],
|
||||
startupBlocking: true,
|
||||
paths: [[namespace]],
|
||||
startupBlocking,
|
||||
url: URL.createObjectURL(new Blob([API_SCRIPT])),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Two modules, primary test module is startupBlocking
|
||||
const MODULE_INFO = {
|
||||
eventtest: makeModule("eventtest", { startupBlocking: true }),
|
||||
eventtest2: makeModule("eventtest2"),
|
||||
};
|
||||
|
||||
const global = this;
|
||||
|
@ -125,9 +176,13 @@ async function promiseObservable(topic, count, fn = null) {
|
|||
let _countResolve;
|
||||
let results = [];
|
||||
function listener(subject, _topic, data) {
|
||||
results.push(subject.wrappedJSObject);
|
||||
const eventDetails = subject.wrappedJSObject;
|
||||
results.push(eventDetails);
|
||||
if (results.length > count) {
|
||||
ok(false, `Got unexpected ${topic} event`);
|
||||
ok(
|
||||
false,
|
||||
`Got unexpected ${topic} event with ${JSON.stringify(eventDetails)}`
|
||||
);
|
||||
} else if (results.length == count) {
|
||||
_countResolve();
|
||||
}
|
||||
|
@ -158,6 +213,16 @@ function trackEvents(wrapper) {
|
|||
}
|
||||
|
||||
add_task(async function setup() {
|
||||
// The blob:-URL registered above in MODULE_INFO gets loaded at
|
||||
// https://searchfox.org/mozilla-central/rev/0fec57c05d3996cc00c55a66f20dd5793a9bfb5d/toolkit/components/extensions/ExtensionCommon.jsm#1649
|
||||
Services.prefs.setBoolPref(
|
||||
"security.allow_parent_unrestricted_js_loads",
|
||||
true
|
||||
);
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref("security.allow_parent_unrestricted_js_loads");
|
||||
});
|
||||
|
||||
AddonTestUtils.init(global);
|
||||
AddonTestUtils.overrideCertDB();
|
||||
AddonTestUtils.createAppInfo(
|
||||
|
@ -171,16 +236,6 @@ add_task(async function setup() {
|
|||
});
|
||||
|
||||
add_task(async function test_persistent_events() {
|
||||
// The blob:-URL registered above in MODULE_INFO gets loaded at
|
||||
// https://searchfox.org/mozilla-central/rev/0fec57c05d3996cc00c55a66f20dd5793a9bfb5d/toolkit/components/extensions/ExtensionCommon.jsm#1649
|
||||
Services.prefs.setBoolPref(
|
||||
"security.allow_parent_unrestricted_js_loads",
|
||||
true
|
||||
);
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref("security.allow_parent_unrestricted_js_loads");
|
||||
});
|
||||
|
||||
await AddonTestUtils.promiseStartupManager();
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
|
@ -263,44 +318,44 @@ add_task(async function test_persistent_events() {
|
|||
|
||||
// Check that the regular event registration process occurs when
|
||||
// the extension is installed.
|
||||
let [info] = await Promise.all([
|
||||
let [observed] = await Promise.all([
|
||||
promiseObservable("register-event-listener", 3),
|
||||
extension.startup(),
|
||||
]);
|
||||
check(info, "register");
|
||||
check(observed, "register");
|
||||
|
||||
await extension.awaitMessage("ready");
|
||||
|
||||
// Check that the regular unregister process occurs when
|
||||
// the browser shuts down.
|
||||
[info] = await Promise.all([
|
||||
[observed] = await Promise.all([
|
||||
promiseObservable("unregister-event-listener", 3),
|
||||
new Promise(resolve => extension.extension.once("shutdown", resolve)),
|
||||
AddonTestUtils.promiseShutdownManager(),
|
||||
]);
|
||||
check(info, "unregister");
|
||||
check(observed, "unregister");
|
||||
|
||||
// Check that listeners are primed at the next browser startup.
|
||||
[info] = await Promise.all([
|
||||
[observed] = await Promise.all([
|
||||
promiseObservable("prime-event-listener", 3),
|
||||
AddonTestUtils.promiseStartupManager(),
|
||||
]);
|
||||
check(info, "prime");
|
||||
check(observed, "prime");
|
||||
|
||||
// Check that primed listeners are converted to regular listeners
|
||||
// when the background page is started after browser startup.
|
||||
let p = promiseObservable("convert-event-listener", 3);
|
||||
Services.obs.notifyObservers(null, "sessionstore-windows-restored");
|
||||
info = await p;
|
||||
AddonTestUtils.notifyLateStartup();
|
||||
observed = await p;
|
||||
|
||||
check(info, "convert");
|
||||
check(observed, "convert");
|
||||
|
||||
await extension.awaitMessage("ready");
|
||||
|
||||
// Check that when the event is triggered, all the plumbing worked
|
||||
// correctly for the primed-then-converted listener.
|
||||
let listenerArgs = { test: "kaboom" };
|
||||
Services.obs.notifyObservers({ listenerArgs }, "fire-onEvent1");
|
||||
Services.obs.notifyObservers({ listenerArgs }, "fire-eventtest.onEvent1");
|
||||
|
||||
let details = await extension.awaitMessage("listener1");
|
||||
deepEqual(details, listenerArgs, "Listener 1 fired");
|
||||
|
@ -309,28 +364,28 @@ add_task(async function test_persistent_events() {
|
|||
|
||||
// Check that the converted listener is properly unregistered at
|
||||
// browser shutdown.
|
||||
[info] = await Promise.all([
|
||||
[observed] = await Promise.all([
|
||||
promiseObservable("unregister-primed-listener", 3),
|
||||
AddonTestUtils.promiseShutdownManager(),
|
||||
]);
|
||||
check(info, "unregister");
|
||||
check(observed, "unregister");
|
||||
|
||||
// Start up again, listener should be primed
|
||||
[info] = await Promise.all([
|
||||
[observed] = await Promise.all([
|
||||
promiseObservable("prime-event-listener", 3),
|
||||
AddonTestUtils.promiseStartupManager(),
|
||||
]);
|
||||
check(info, "prime");
|
||||
check(observed, "prime");
|
||||
|
||||
// Check that triggering the event before the listener has been converted
|
||||
// causes the background page to be loaded and the listener to be converted,
|
||||
// and the listener is invoked.
|
||||
p = promiseObservable("convert-event-listener", 3);
|
||||
listenerArgs.test = "startup event";
|
||||
Services.obs.notifyObservers({ listenerArgs }, "fire-onEvent2");
|
||||
info = await p;
|
||||
Services.obs.notifyObservers({ listenerArgs }, "fire-eventtest.onEvent2");
|
||||
observed = await p;
|
||||
|
||||
check(info, "convert");
|
||||
check(observed, "convert");
|
||||
|
||||
details = await extension.awaitMessage("listener3");
|
||||
deepEqual(details, listenerArgs, "Listener 3 fired for event during startup");
|
||||
|
@ -341,22 +396,22 @@ add_task(async function test_persistent_events() {
|
|||
// a listener.
|
||||
p = promiseObservable("unregister-primed-listener", 1);
|
||||
extension.sendMessage("unregister2");
|
||||
info = await p;
|
||||
check(info, "unregister", { listener1: false, listener2: false });
|
||||
observed = await p;
|
||||
check(observed, "unregister", { listener1: false, listener2: false });
|
||||
|
||||
// Check that we only get unregisters for the remaining events after
|
||||
// one listener has been removed.
|
||||
info = await promiseObservable("unregister-primed-listener", 2, () =>
|
||||
observed = await promiseObservable("unregister-primed-listener", 2, () =>
|
||||
AddonTestUtils.promiseShutdownManager()
|
||||
);
|
||||
check(info, "unregister", { listener3: false });
|
||||
check(observed, "unregister", { listener3: false });
|
||||
|
||||
// Check that after restart, only listeners that were present at
|
||||
// the end of the last session are primed.
|
||||
info = await promiseObservable("prime-event-listener", 2, () =>
|
||||
observed = await promiseObservable("prime-event-listener", 2, () =>
|
||||
AddonTestUtils.promiseStartupManager()
|
||||
);
|
||||
check(info, "prime", { listener3: false });
|
||||
check(observed, "prime", { listener3: false });
|
||||
|
||||
// Check that if the background script does not re-register listeners,
|
||||
// the primed listeners are unregistered after the background page
|
||||
|
@ -364,13 +419,14 @@ add_task(async function test_persistent_events() {
|
|||
p = promiseObservable("unregister-primed-listener", 1, () =>
|
||||
extension.awaitMessage("ready")
|
||||
);
|
||||
Services.obs.notifyObservers(null, "sessionstore-windows-restored");
|
||||
info = await p;
|
||||
check(info, "unregister", { listener1: false, listener3: false });
|
||||
|
||||
AddonTestUtils.notifyLateStartup();
|
||||
observed = await p;
|
||||
check(observed, "unregister", { listener1: false, listener3: false });
|
||||
|
||||
// Just listener1 should be registered now, fire event1 to confirm.
|
||||
listenerArgs.test = "third time";
|
||||
Services.obs.notifyObservers({ listenerArgs }, "fire-onEvent1");
|
||||
Services.obs.notifyObservers({ listenerArgs }, "fire-eventtest.onEvent1");
|
||||
details = await extension.awaitMessage("listener1");
|
||||
deepEqual(details, listenerArgs, "Listener 1 fired");
|
||||
|
||||
|
@ -379,22 +435,22 @@ add_task(async function test_persistent_events() {
|
|||
await extension.awaitMessage("unregistered");
|
||||
|
||||
// Shut down, start up
|
||||
info = await promiseObservable("unregister-primed-listener", 1, () =>
|
||||
observed = await promiseObservable("unregister-primed-listener", 1, () =>
|
||||
AddonTestUtils.promiseShutdownManager()
|
||||
);
|
||||
check(info, "unregister", { listener2: false, listener3: false });
|
||||
check(observed, "unregister", { listener2: false, listener3: false });
|
||||
|
||||
info = await promiseObservable("prime-event-listener", 1, () =>
|
||||
observed = await promiseObservable("prime-event-listener", 1, () =>
|
||||
AddonTestUtils.promiseStartupManager()
|
||||
);
|
||||
check(info, "register", { listener2: false, listener3: false });
|
||||
check(observed, "register", { listener2: false, listener3: false });
|
||||
|
||||
// Check that firing event1 causes the listener fire callback to
|
||||
// reject.
|
||||
p = promiseObservable("listener-callback-exception", 1);
|
||||
Services.obs.notifyObservers(
|
||||
{ listenerArgs, waitForBackground: true },
|
||||
"fire-onEvent1"
|
||||
"fire-eventtest.onEvent1"
|
||||
);
|
||||
equal(
|
||||
(await p)[0].errorMessage,
|
||||
|
@ -498,7 +554,10 @@ add_task(async function test_shutdown_before_background_loaded() {
|
|||
AddonTestUtils.promiseStartupManager({ earlyStartup: false }),
|
||||
]);
|
||||
info("Triggering persistent event to force the background page to start");
|
||||
Services.obs.notifyObservers({ listenerArgs: 123 }, "fire-onEvent1");
|
||||
Services.obs.notifyObservers(
|
||||
{ listenerArgs: 123 },
|
||||
"fire-eventtest.onEvent1"
|
||||
);
|
||||
AddonTestUtils.notifyEarlyStartup();
|
||||
await extension.awaitMessage("bg_started");
|
||||
equal(await extension.awaitMessage("triggered"), 123, "triggered event");
|
||||
|
@ -558,7 +617,10 @@ add_task(async function test_background_restarted() {
|
|||
});
|
||||
|
||||
info("Triggering persistent event to force the background page to start");
|
||||
Services.obs.notifyObservers({ listenerArgs: 123 }, "fire-onEvent1");
|
||||
Services.obs.notifyObservers(
|
||||
{ listenerArgs: 123 },
|
||||
"fire-eventtest.onEvent1"
|
||||
);
|
||||
await extension.awaitMessage("bg_started");
|
||||
equal(await extension.awaitMessage("triggered"), 123, "triggered event");
|
||||
|
||||
|
@ -583,6 +645,8 @@ add_task(
|
|||
background() {
|
||||
let listener = arg => browser.test.sendMessage("triggered", arg);
|
||||
browser.eventtest.onEvent1.addListener(listener, "triggered");
|
||||
let listenerNs = arg => browser.test.sendMessage("triggered-et2", arg);
|
||||
browser.eventtest2.onEvent1.addListener(listenerNs, "triggered-et2");
|
||||
browser.test.onMessage.addListener(() => {
|
||||
let listener = arg => browser.test.sendMessage("triggered2", arg);
|
||||
browser.eventtest.onEvent2.addListener(listener, "triggered2");
|
||||
|
@ -592,7 +656,7 @@ add_task(
|
|||
},
|
||||
});
|
||||
await Promise.all([
|
||||
promiseObservable("register-event-listener", 1),
|
||||
promiseObservable("register-event-listener", 2),
|
||||
extension.startup(),
|
||||
]);
|
||||
await extension.awaitMessage("bg_started");
|
||||
|
@ -621,7 +685,10 @@ add_task(
|
|||
|
||||
info("Triggering persistent event to force the background page to start");
|
||||
let converted = promiseObservable("convert-event-listener", 1);
|
||||
Services.obs.notifyObservers({ listenerArgs: 123 }, "fire-onEvent1");
|
||||
Services.obs.notifyObservers(
|
||||
{ listenerArgs: 123 },
|
||||
"fire-eventtest.onEvent1"
|
||||
);
|
||||
await extension.awaitMessage("bg_started");
|
||||
await converted;
|
||||
equal(await extension.awaitMessage("triggered"), 123, "triggered event");
|
||||
|
@ -637,12 +704,16 @@ add_task(
|
|||
|
||||
// Shutdown the background page
|
||||
await Promise.all([
|
||||
promiseObservable("unregister-event-listener", 2),
|
||||
promiseObservable("unregister-event-listener", 3),
|
||||
new Promise(resolve => extension.extension.once("shutdown", resolve)),
|
||||
AddonTestUtils.promiseShutdownManager(),
|
||||
]);
|
||||
await AddonTestUtils.promiseStartupManager({ lateStartup: false });
|
||||
await extension.awaitStartup();
|
||||
assertPersistentListeners(extension, "eventtest2", "onEvent1", {
|
||||
primed: false,
|
||||
persisted: true,
|
||||
});
|
||||
await testAfterRestart();
|
||||
|
||||
extension.sendMessage("async-register-listener");
|
||||
|
@ -651,20 +722,28 @@ add_task(
|
|||
// We sleep twice to ensure startup and shutdown work correctly
|
||||
info("test event listener registration during termination");
|
||||
let registrationEvents = Promise.all([
|
||||
promiseObservable("unregister-event-listener", 1),
|
||||
promiseObservable("unregister-event-listener", 2),
|
||||
promiseObservable("unregister-primed-listener", 1),
|
||||
promiseObservable("prime-event-listener", 1),
|
||||
promiseObservable("prime-event-listener", 2),
|
||||
]);
|
||||
await extension.terminateBackground();
|
||||
await registrationEvents;
|
||||
|
||||
assertPersistentListeners(extension, "eventtest2", "onEvent1", {
|
||||
primed: true,
|
||||
persisted: true,
|
||||
});
|
||||
|
||||
// Ensure onEvent2 does not fire, testAfterRestart will fail otherwise.
|
||||
Services.obs.notifyObservers({ listenerArgs: 123 }, "fire-onEvent2");
|
||||
Services.obs.notifyObservers(
|
||||
{ listenerArgs: 123 },
|
||||
"fire-eventtest.onEvent2"
|
||||
);
|
||||
await testAfterRestart();
|
||||
|
||||
registrationEvents = Promise.all([
|
||||
promiseObservable("unregister-primed-listener", 1),
|
||||
promiseObservable("prime-event-listener", 1),
|
||||
promiseObservable("unregister-primed-listener", 2),
|
||||
promiseObservable("prime-event-listener", 2),
|
||||
]);
|
||||
await extension.terminateBackground();
|
||||
await registrationEvents;
|
||||
|
@ -674,3 +753,302 @@ add_task(
|
|||
await AddonTestUtils.promiseShutdownManager();
|
||||
}
|
||||
);
|
||||
|
||||
// This test verifies primeListener behavior for errors or ignored listeners.
|
||||
add_task(async function test_background_primeListener_errors() {
|
||||
await AddonTestUtils.promiseStartupManager();
|
||||
|
||||
// The internal APIs to shutdown the background work with any
|
||||
// background, and in the shutdown case, events will be persisted
|
||||
// and primed for a restart.
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
useAddonManager: "permanent",
|
||||
background() {
|
||||
// Listen for options being set so a restart will have them.
|
||||
browser.test.onMessage.addListener(async (message, options) => {
|
||||
if (message == "set-options") {
|
||||
await browser.eventtest.testOptions(options);
|
||||
browser.test.sendMessage("set-options:done");
|
||||
}
|
||||
});
|
||||
let listener = arg => browser.test.sendMessage("triggered", arg);
|
||||
browser.eventtest.onEvent1.addListener(listener, "triggered");
|
||||
let listener2 = arg => browser.test.sendMessage("triggered", arg);
|
||||
browser.eventtest.onEvent2.addListener(listener2, "triggered");
|
||||
browser.test.sendMessage("bg_started");
|
||||
},
|
||||
});
|
||||
await Promise.all([
|
||||
promiseObservable("register-event-listener", 1),
|
||||
extension.startup(),
|
||||
]);
|
||||
await extension.awaitMessage("bg_started");
|
||||
assertPersistentListeners(extension, "eventtest", "onEvent1", {
|
||||
primed: false,
|
||||
});
|
||||
|
||||
// If an event is removed from an api, a permission is removed,
|
||||
// or some other option prevents priming, ensure that
|
||||
// primelistener works correctly.
|
||||
// In this scenario we are testing that an event is not renewed
|
||||
// on startup because the API does not re-prime it. The result
|
||||
// is that the event is also not persisted. However the other
|
||||
// events that are renewed should still be primed and persisted.
|
||||
extension.sendMessage("set-options", {
|
||||
eventName: "onEvent1",
|
||||
ignoreListener: true,
|
||||
});
|
||||
await extension.awaitMessage("set-options:done");
|
||||
|
||||
// Shutdown the background page
|
||||
await Promise.all([
|
||||
promiseObservable("unregister-event-listener", 2),
|
||||
extension.terminateBackground(),
|
||||
]);
|
||||
// eventtest.onEvent1 was not re-primed and should not be persisted, but
|
||||
// onEvent2 should still be primed and persisted.
|
||||
assertPersistentListeners(extension, "eventtest", "onEvent1", {
|
||||
primed: false,
|
||||
persisted: false,
|
||||
});
|
||||
assertPersistentListeners(extension, "eventtest", "onEvent2", {
|
||||
primed: true,
|
||||
});
|
||||
|
||||
info("Triggering persistent event to force the background page to start");
|
||||
Services.obs.notifyObservers(
|
||||
{ listenerArgs: 123 },
|
||||
"fire-eventtest.onEvent2"
|
||||
);
|
||||
await extension.awaitMessage("bg_started");
|
||||
equal(await extension.awaitMessage("triggered"), 123, "triggered event");
|
||||
|
||||
// On restart, test an exception, it should not be re-primed.
|
||||
extension.sendMessage("set-options", {
|
||||
eventName: "onEvent1",
|
||||
throwError: "error",
|
||||
});
|
||||
await extension.awaitMessage("set-options:done");
|
||||
|
||||
// Shutdown the background page
|
||||
await Promise.all([
|
||||
promiseObservable("unregister-event-listener", 1),
|
||||
extension.terminateBackground(),
|
||||
]);
|
||||
// eventtest.onEvent1 failed and should not be persisted
|
||||
assertPersistentListeners(extension, "eventtest", "onEvent1", {
|
||||
primed: false,
|
||||
persisted: false,
|
||||
});
|
||||
|
||||
info("Triggering event to verify background starts after prior error");
|
||||
Services.obs.notifyObservers(
|
||||
{ listenerArgs: 123 },
|
||||
"fire-eventtest.onEvent2"
|
||||
);
|
||||
await extension.awaitMessage("bg_started");
|
||||
equal(await extension.awaitMessage("triggered"), 123, "triggered event");
|
||||
|
||||
info("reset options for next test");
|
||||
extension.sendMessage("set-options", {});
|
||||
await extension.awaitMessage("set-options:done");
|
||||
|
||||
// Test errors on app restart
|
||||
info("Test errors during app startup");
|
||||
await AddonTestUtils.promiseRestartManager();
|
||||
await extension.awaitStartup();
|
||||
await extension.awaitMessage("bg_started");
|
||||
|
||||
info("restart AOM and verify primed listener");
|
||||
await AddonTestUtils.promiseRestartManager({ earlyStartup: false });
|
||||
await extension.awaitStartup();
|
||||
assertPersistentListeners(extension, "eventtest", "onEvent1", {
|
||||
primed: true,
|
||||
persisted: true,
|
||||
});
|
||||
AddonTestUtils.notifyEarlyStartup();
|
||||
|
||||
Services.obs.notifyObservers(
|
||||
{ listenerArgs: 123 },
|
||||
"fire-eventtest.onEvent1"
|
||||
);
|
||||
await extension.awaitMessage("bg_started");
|
||||
equal(await extension.awaitMessage("triggered"), 123, "triggered event");
|
||||
|
||||
// Test that an exception happening during priming clears the
|
||||
// event from being persisted when restarting the browser, and that
|
||||
// the background correctly starts.
|
||||
info("test exception during primeListener on startup");
|
||||
extension.sendMessage("set-options", {
|
||||
eventName: "onEvent1",
|
||||
throwError: "error",
|
||||
});
|
||||
await extension.awaitMessage("set-options:done");
|
||||
|
||||
await AddonTestUtils.promiseRestartManager({ earlyStartup: false });
|
||||
await extension.awaitStartup();
|
||||
AddonTestUtils.notifyEarlyStartup();
|
||||
|
||||
// At this point, the exception results in the persisted entry
|
||||
// being cleared.
|
||||
assertPersistentListeners(extension, "eventtest", "onEvent1", {
|
||||
primed: false,
|
||||
persisted: false,
|
||||
});
|
||||
|
||||
AddonTestUtils.notifyLateStartup();
|
||||
|
||||
await extension.awaitMessage("bg_started");
|
||||
|
||||
// The background added the listener back during top level execution,
|
||||
// verify it is in the persisted list.
|
||||
assertPersistentListeners(extension, "eventtest", "onEvent1", {
|
||||
primed: false,
|
||||
persisted: true,
|
||||
});
|
||||
|
||||
await extension.unload();
|
||||
await AddonTestUtils.promiseShutdownManager();
|
||||
});
|
||||
|
||||
add_task(async function test_non_background_context_listener_not_persisted() {
|
||||
await AddonTestUtils.promiseStartupManager();
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
useAddonManager: "permanent",
|
||||
background() {
|
||||
let listener = arg => browser.test.sendMessage("triggered", arg);
|
||||
browser.eventtest.onEvent1.addListener(listener, "triggered");
|
||||
browser.test.sendMessage(
|
||||
"bg_started",
|
||||
browser.runtime.getURL("extpage.html")
|
||||
);
|
||||
},
|
||||
files: {
|
||||
"extpage.html": `<script src="extpage.js"></script>`,
|
||||
"extpage.js": function() {
|
||||
let listener = arg =>
|
||||
browser.test.sendMessage("extpage-triggered", arg);
|
||||
browser.eventtest.onEvent2.addListener(listener, "extpage-triggered");
|
||||
// Send a message to signal the extpage has registered the listener,
|
||||
// after calling an async method and wait it to be resolved to make sure
|
||||
// the addListener call to have been handled in the parent process by
|
||||
// the time we will assert the persisted listeners.
|
||||
browser.runtime.getPlatformInfo().then(() => {
|
||||
browser.test.sendMessage("extpage_started");
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await extension.startup();
|
||||
const extpage_url = await extension.awaitMessage("bg_started");
|
||||
|
||||
assertPersistentListeners(extension, "eventtest", "onEvent1", {
|
||||
persisted: true,
|
||||
primed: false,
|
||||
});
|
||||
|
||||
assertPersistentListeners(extension, "eventtest", "onEvent2", {
|
||||
persisted: false,
|
||||
});
|
||||
|
||||
const page = await ExtensionTestUtils.loadContentPage(extpage_url);
|
||||
await extension.awaitMessage("extpage_started");
|
||||
|
||||
// Expect the onEvent2 listener subscribed by the extpage to not be persisted.
|
||||
assertPersistentListeners(extension, "eventtest", "onEvent2", {
|
||||
persisted: false,
|
||||
});
|
||||
|
||||
await page.close();
|
||||
await extension.unload();
|
||||
await AddonTestUtils.promiseShutdownManager();
|
||||
});
|
||||
|
||||
add_task(
|
||||
{ pref_set: [["extensions.eventPages.enabled", true]] },
|
||||
async function test_startupblocking_behavior() {
|
||||
await AddonTestUtils.promiseStartupManager();
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
useAddonManager: "permanent",
|
||||
manifest: {
|
||||
background: { persistent: false },
|
||||
},
|
||||
async background() {
|
||||
let listener2 = () =>
|
||||
browser.test.sendMessage("triggered:non-startupblocking");
|
||||
browser.eventtest.onEvent1.addListener(() => {}, "triggered");
|
||||
browser.eventtest2.onEvent2.addListener(listener2, "triggered");
|
||||
// Clear test options other tests may have already set.
|
||||
await browser.eventtest.testOptions({});
|
||||
await browser.eventtest2.testOptions({});
|
||||
browser.test.sendMessage("bg_started");
|
||||
},
|
||||
});
|
||||
|
||||
await extension.startup();
|
||||
await extension.awaitMessage("bg_started");
|
||||
|
||||
assertPersistentListeners(extension, "eventtest", "onEvent1", {
|
||||
persisted: true,
|
||||
primed: false,
|
||||
});
|
||||
|
||||
assertPersistentListeners(extension, "eventtest2", "onEvent2", {
|
||||
persisted: true,
|
||||
primed: false,
|
||||
});
|
||||
|
||||
info("Test after mocked browser restart");
|
||||
await Promise.all([
|
||||
new Promise(resolve => extension.extension.once("shutdown", resolve)),
|
||||
AddonTestUtils.promiseShutdownManager(),
|
||||
]);
|
||||
await AddonTestUtils.promiseStartupManager({ lateStartup: false });
|
||||
await extension.awaitStartup();
|
||||
|
||||
// Startup blocking event is expected to be persisted and primed.
|
||||
assertPersistentListeners(extension, "eventtest", "onEvent1", {
|
||||
persisted: true,
|
||||
primed: true,
|
||||
});
|
||||
|
||||
// Non "Startup blocking" event is expected to be persisted but not primed yet.
|
||||
assertPersistentListeners(extension, "eventtest2", "onEvent2", {
|
||||
persisted: true,
|
||||
primed: false,
|
||||
});
|
||||
|
||||
// Complete the browser startup and fire the startup blocking event
|
||||
// to let the backgrund script to run.
|
||||
AddonTestUtils.notifyLateStartup();
|
||||
Services.obs.notifyObservers({}, "fire-eventtest.onEvent1");
|
||||
await extension.awaitMessage("bg_started");
|
||||
|
||||
info("Test after terminate background script");
|
||||
await extension.terminateBackground();
|
||||
|
||||
// After the background is terminated, we expect both events to
|
||||
// be persisted and primed.
|
||||
|
||||
assertPersistentListeners(extension, "eventtest", "onEvent1", {
|
||||
persisted: true,
|
||||
primed: true,
|
||||
});
|
||||
|
||||
assertPersistentListeners(extension, "eventtest2", "onEvent2", {
|
||||
persisted: true,
|
||||
primed: true,
|
||||
});
|
||||
|
||||
info("Notify event for the non-startupBlocking API event");
|
||||
Services.obs.notifyObservers({}, "fire-eventtest2.onEvent2");
|
||||
await extension.awaitMessage("bg_started");
|
||||
await extension.awaitMessage("triggered:non-startupblocking");
|
||||
|
||||
await extension.unload();
|
||||
await AddonTestUtils.promiseShutdownManager();
|
||||
}
|
||||
);
|
||||
|
|
|
@ -17,8 +17,6 @@
|
|||
#define IDI_DOCUMENT 2
|
||||
#define IDI_NEWWINDOW 3
|
||||
#define IDI_NEWTAB 4
|
||||
// If IDI_PBMODE's index changes, PRIVATE_BROWSING_ICON_INDEX
|
||||
// in BrowserContentHandler.jsm must also be updated.
|
||||
#define IDI_PBMODE 5
|
||||
#ifndef IDI_APPLICATION
|
||||
# define IDI_APPLICATION 32512
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#ifdef MOZ_WAYLAND
|
||||
|
||||
# include "WaylandVsyncSource.h"
|
||||
# include "mozilla/UniquePtr.h"
|
||||
# include "nsThreadUtils.h"
|
||||
# include "nsISupportsImpl.h"
|
||||
# include "MainThreadUtils.h"
|
||||
|
@ -50,6 +51,27 @@ static float GetFPS(TimeDuration aVsyncRate) {
|
|||
return 1000.0 / aVsyncRate.ToMilliseconds();
|
||||
}
|
||||
|
||||
static UniquePtr<LinkedList<WaylandVsyncSource::WaylandDisplay>>
|
||||
gWaylandDisplays;
|
||||
|
||||
Maybe<TimeDuration> WaylandVsyncSource::GetFastestVsyncRate() {
|
||||
Maybe<TimeDuration> retVal;
|
||||
if (gWaylandDisplays) {
|
||||
for (auto* display : *gWaylandDisplays) {
|
||||
if (display->IsVsyncEnabled()) {
|
||||
TimeDuration rate = display->GetVsyncRate();
|
||||
if (!retVal.isSome()) {
|
||||
retVal.emplace(rate);
|
||||
} else if (rate < *retVal) {
|
||||
retVal.ref() = rate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
WaylandVsyncSource::WaylandDisplay::WaylandDisplay()
|
||||
: mMutex("WaylandVsyncSource"),
|
||||
mIsShutdown(false),
|
||||
|
@ -60,6 +82,19 @@ WaylandVsyncSource::WaylandDisplay::WaylandDisplay()
|
|||
mVsyncRate(TimeDuration::FromMilliseconds(1000.0 / 60.0)),
|
||||
mLastVsyncTimeStamp(TimeStamp::Now()) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (!gWaylandDisplays) {
|
||||
gWaylandDisplays =
|
||||
MakeUnique<LinkedList<WaylandVsyncSource::WaylandDisplay>>();
|
||||
}
|
||||
gWaylandDisplays->insertBack(this);
|
||||
}
|
||||
|
||||
WaylandVsyncSource::WaylandDisplay::~WaylandDisplay() {
|
||||
remove(); // Remove from the linked list.
|
||||
if (gWaylandDisplays->isEmpty()) {
|
||||
gWaylandDisplays = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void WaylandVsyncSource::WaylandDisplay::MaybeUpdateSource(
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
#include "base/thread.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/LinkedList.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "mozilla/Monitor.h"
|
||||
#include "mozilla/layers/NativeLayerWayland.h"
|
||||
|
@ -48,7 +50,10 @@ class WaylandVsyncSource final : public gfx::VsyncSource {
|
|||
|
||||
virtual Display& GetGlobalDisplay() override { return *mGlobalDisplay; }
|
||||
|
||||
class WaylandDisplay final : public mozilla::gfx::VsyncSource::Display {
|
||||
static Maybe<TimeDuration> GetFastestVsyncRate();
|
||||
|
||||
class WaylandDisplay final : public mozilla::gfx::VsyncSource::Display,
|
||||
public LinkedListElement<WaylandDisplay> {
|
||||
public:
|
||||
WaylandDisplay();
|
||||
|
||||
|
@ -72,7 +77,7 @@ class WaylandVsyncSource final : public gfx::VsyncSource {
|
|||
virtual void Shutdown() override;
|
||||
|
||||
private:
|
||||
virtual ~WaylandDisplay() = default;
|
||||
~WaylandDisplay() override;
|
||||
void Refresh(const MutexAutoLock& aProofOfLock);
|
||||
void SetupFrameCallback(const MutexAutoLock& aProofOfLock);
|
||||
void CalculateVsyncRate(const MutexAutoLock& aProofOfLock,
|
||||
|
|
|
@ -156,6 +156,4 @@ interface nsIJumpListBuilder : nsISupports
|
|||
* @throw NS_ERROR_UNEXPECTED on internal errors.
|
||||
*/
|
||||
boolean deleteActiveList();
|
||||
|
||||
void setAppUserModelID(in AString aAppUserModelId);
|
||||
};
|
||||
|
|
|
@ -66,12 +66,6 @@ interface nsIWinTaskbar : nsISupports
|
|||
*/
|
||||
readonly attribute AString defaultGroupId;
|
||||
|
||||
/**
|
||||
* Same as above, but a different value so that Private Browsing windows
|
||||
* can be separated in the Taskbar.
|
||||
*/
|
||||
readonly attribute AString defaultPrivateGroupId;
|
||||
|
||||
/**
|
||||
* Taskbar window and tab preview management
|
||||
*/
|
||||
|
@ -129,7 +123,7 @@ interface nsIWinTaskbar : nsISupports
|
|||
* @throw NS_ERROR_ALREADY_INITIALIZED if an nsIJumpListBuilder instance is
|
||||
* currently building a list.
|
||||
*/
|
||||
nsIJumpListBuilder createJumpListBuilder(in boolean aPrivateBrowsing);
|
||||
nsIJumpListBuilder createJumpListBuilder();
|
||||
|
||||
/**
|
||||
* Application window taskbar group settings
|
||||
|
|
|
@ -17,8 +17,6 @@ interface nsIWindowsUIUtils : nsISupports
|
|||
|
||||
void setWindowIcon(in mozIDOMWindowProxy aWindow, in imgIContainer aSmallIcon, in imgIContainer aLargeIcon);
|
||||
|
||||
void setWindowIconFromExe(in mozIDOMWindowProxy aWindow, in AString aExe, in unsigned short aIndex);
|
||||
|
||||
void setWindowIconNoData(in mozIDOMWindowProxy aWindow);
|
||||
|
||||
/**
|
||||
|
|
|
@ -107,8 +107,7 @@ struct nsWidgetInitData {
|
|||
mAlwaysOnTop(false),
|
||||
mPIPWindow(false),
|
||||
mFissionWindow(false),
|
||||
mResizable(false),
|
||||
mIsPrivate(false) {}
|
||||
mResizable(false) {}
|
||||
|
||||
nsWindowType mWindowType;
|
||||
nsBorderStyle mBorderStyle;
|
||||
|
@ -138,7 +137,6 @@ struct nsWidgetInitData {
|
|||
bool mFissionWindow;
|
||||
// True if the window is user-resizable.
|
||||
bool mResizable;
|
||||
bool mIsPrivate;
|
||||
};
|
||||
|
||||
#endif // nsWidgetInitData_h__
|
||||
|
|
|
@ -242,10 +242,7 @@ async function test_jumplist() {
|
|||
Ci.nsIWinTaskbar
|
||||
);
|
||||
|
||||
// Since we're only testing the general functionality of the JumpListBuilder
|
||||
// et. al, we can just test the non-private browsing version.
|
||||
// (The only difference between the two at this level is the App User Model ID.)
|
||||
var builder = taskbar.createJumpListBuilder(false);
|
||||
var builder = taskbar.createJumpListBuilder();
|
||||
|
||||
Assert.notEqual(builder, null);
|
||||
|
||||
|
|
|
@ -132,31 +132,21 @@ JumpListBuilder::JumpListBuilder()
|
|||
if (!jumpListMgr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// GetAppUserModelID can only be called once we're back on the main thread.
|
||||
nsString modelId;
|
||||
// MSIX packages explicitly do not support setting the appid from within
|
||||
// the app, as it is set in the package manifest instead.
|
||||
if (mozilla::widget::WinTaskbar::GetAppUserModelID(modelId) &&
|
||||
!mozilla::widget::WinUtils::HasPackageIdentity()) {
|
||||
jumpListMgr->SetAppID(modelId.get());
|
||||
}
|
||||
}
|
||||
|
||||
JumpListBuilder::~JumpListBuilder() {
|
||||
Preferences::RemoveObserver(this, kPrefTaskbarEnabled);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP JumpListBuilder::SetAppUserModelID(
|
||||
const nsAString& aAppUserModelId) {
|
||||
if (!mJumpListMgr) return NS_ERROR_NOT_AVAILABLE;
|
||||
|
||||
RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr;
|
||||
if (!jumpListMgr) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
mAppUserModelId.Assign(aAppUserModelId);
|
||||
// MSIX packages explicitly do not support setting the appid from within
|
||||
// the app, as it is set in the package manifest instead.
|
||||
if (!mozilla::widget::WinUtils::HasPackageIdentity()) {
|
||||
jumpListMgr->SetAppID(mAppUserModelId.get());
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP JumpListBuilder::GetAvailable(int16_t* aAvailable) {
|
||||
*aAvailable = false;
|
||||
|
||||
|
@ -524,12 +514,15 @@ NS_IMETHODIMP JumpListBuilder::DeleteActiveList(bool* _retval) {
|
|||
AbortListBuild();
|
||||
}
|
||||
|
||||
nsAutoString uid;
|
||||
if (!WinTaskbar::GetAppUserModelID(uid)) return NS_OK;
|
||||
|
||||
RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr;
|
||||
if (!jumpListMgr) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
if (SUCCEEDED(jumpListMgr->DeleteList(mAppUserModelId.get()))) {
|
||||
if (SUCCEEDED(jumpListMgr->DeleteList(uid.get()))) {
|
||||
*_retval = true;
|
||||
}
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue