Update On Sun Feb 27 19:29:20 CET 2022

This commit is contained in:
github-action[bot] 2022-02-27 19:29:21 +01:00
parent e8d802fee6
commit 44b99fd55d
108 changed files with 2412 additions and 1182 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

@ -49,6 +49,10 @@ UNIFIED_SOURCES += [
"PerformanceWorker.cpp",
]
IPDL_SOURCES += [
"PerformanceTimingTypes.ipdlh",
]
include("/ipc/chromium/chromium-config.mozbuild")
MOCHITEST_MANIFESTS += ["tests/mochitest.ini"]

View file

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

View file

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

View file

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

View file

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

View file

@ -42,7 +42,7 @@ class FetchEventOpProxyChild final : public PFetchEventOpProxyChild {
~FetchEventOpProxyChild() = default;
mozilla::ipc::IPCResult RecvPreloadResponse(
ParentToChildInternalResponse&& aResponse);
ParentToChildResponseWithTiming&& aResponse);
void ActorDestroy(ActorDestroyReason) override;

View file

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

View file

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

View file

@ -23,7 +23,7 @@ protocol PFetchEventOpProxy {
async __delete__(ServiceWorkerFetchEventOpResult aResult);
child:
async PreloadResponse(ParentToChildInternalResponse aResponse);
async PreloadResponse(ParentToChildResponseWithTiming aResponse);
};
} // namespace dom

View file

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

View file

@ -93,12 +93,12 @@ struct ServiceWorkerFetchEventOpArgsCommon {
struct ParentToParentServiceWorkerFetchEventOpArgs {
ServiceWorkerFetchEventOpArgsCommon common;
ParentToParentInternalResponse? preloadResponse;
ParentToParentResponseWithTiming? preloadResponse;
};
struct ParentToChildServiceWorkerFetchEventOpArgs {
ServiceWorkerFetchEventOpArgsCommon common;
ParentToChildInternalResponse? preloadResponse;
ParentToChildResponseWithTiming? preloadResponse;
};
union ServiceWorkerOpArgs {

View file

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

View file

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

View file

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

View file

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

View file

@ -5375,7 +5375,6 @@ void WorkerPrivate::DumpCrashInformation(nsACString& aString) {
}
PerformanceStorage* WorkerPrivate::GetPerformanceStorage() {
AssertIsOnMainThread();
MOZ_ASSERT(mPerformanceStorage);
return mPerformanceStorage;
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

Before After
Before After

View 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

View 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

View file

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

View file

@ -601,6 +601,7 @@ bool CachePerfStats::IsCacheSlow() {
// static
void CachePerfStats::GetSlowStats(uint32_t* aSlow, uint32_t* aNotSlow) {
StaticMutexAutoLock lock(sLock);
*aSlow = sCacheSlowCnt;
*aNotSlow = sCacheNotSlowCnt;
}

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -156,6 +156,4 @@ interface nsIJumpListBuilder : nsISupports
* @throw NS_ERROR_UNEXPECTED on internal errors.
*/
boolean deleteActiveList();
void setAppUserModelID(in AString aAppUserModelId);
};

View file

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

View file

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

View file

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

View file

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

View file

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