Merge mozilla-central to inbound. a=merge CLOSED TREE
Merge mozilla-central to inbound. a=merge CLOSED TREE
--- a/accessible/android/AccessibleWrap.cpp
+++ b/accessible/android/AccessibleWrap.cpp
@@ -1,23 +1,509 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "AccessibleWrap.h"
+#include "Accessible-inl.h"
+#include "DocAccessibleWrap.h"
+#include "IDSet.h"
+#include "JavaBuiltins.h"
+#include "SessionAccessibility.h"
+#include "nsAccessibilityService.h"
+#include "nsIPersistentProperties2.h"
+#include "nsIStringBundle.h"
+#include "nsAccUtils.h"
+
+#define ROLE_STRINGS_URL "chrome://global/locale/AccessFu.properties"
+
using namespace mozilla::a11y;
+// IDs should be a positive 32bit integer.
+IDSet sIDSet(31UL);
+
//-----------------------------------------------------
// construction
//-----------------------------------------------------
AccessibleWrap::AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc)
: Accessible(aContent, aDoc)
{
+ if (aDoc) {
+ mID = AcquireID();
+ DocAccessibleWrap* doc = static_cast<DocAccessibleWrap*>(aDoc);
+ doc->AddID(mID, this);
+ }
}
//-----------------------------------------------------
// destruction
//-----------------------------------------------------
-AccessibleWrap::~AccessibleWrap()
+AccessibleWrap::~AccessibleWrap() {}
+
+nsresult
+AccessibleWrap::HandleAccEvent(AccEvent* aEvent)
+{
+ nsresult rv = Accessible::HandleAccEvent(aEvent);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (IPCAccessibilityActive()) {
+ return NS_OK;
+ }
+
+ auto accessible = static_cast<AccessibleWrap*>(aEvent->GetAccessible());
+ NS_ENSURE_TRUE(accessible, NS_ERROR_FAILURE);
+
+ // The accessible can become defunct if we have an xpcom event listener
+ // which decides it would be fun to change the DOM and flush layout.
+ if (accessible->IsDefunct() || !accessible->IsBoundToParent()) {
+ return NS_OK;
+ }
+
+ if (DocAccessible* doc = accessible->Document()) {
+ if (!nsCoreUtils::IsContentDocument(doc->DocumentNode())) {
+ return NS_OK;
+ }
+ }
+
+ SessionAccessibility* sessionAcc =
+ SessionAccessibility::GetInstanceFor(accessible);
+ if (!sessionAcc) {
+ return NS_OK;
+ }
+
+ switch (aEvent->GetEventType()) {
+ case nsIAccessibleEvent::EVENT_FOCUS:
+ sessionAcc->SendFocusEvent(accessible);
+ break;
+ case nsIAccessibleEvent::EVENT_VIRTUALCURSOR_CHANGED: {
+ AccVCChangeEvent* vcEvent = downcast_accEvent(aEvent);
+ auto newPosition = static_cast<AccessibleWrap*>(vcEvent->NewAccessible());
+ auto oldPosition = static_cast<AccessibleWrap*>(vcEvent->OldAccessible());
+
+ if (sessionAcc && newPosition) {
+ if (oldPosition != newPosition) {
+ if (vcEvent->Reason() == nsIAccessiblePivot::REASON_POINT) {
+ sessionAcc->SendHoverEnterEvent(newPosition);
+ } else {
+ sessionAcc->SendAccessibilityFocusedEvent(newPosition);
+ }
+ }
+
+ if (vcEvent->BoundaryType() != nsIAccessiblePivot::NO_BOUNDARY) {
+ sessionAcc->SendTextTraversedEvent(
+ newPosition, vcEvent->NewStartOffset(), vcEvent->NewEndOffset());
+ }
+ }
+ break;
+ }
+ case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: {
+ AccCaretMoveEvent* event = downcast_accEvent(aEvent);
+ sessionAcc->SendTextSelectionChangedEvent(accessible,
+ event->GetCaretOffset());
+ break;
+ }
+ case nsIAccessibleEvent::EVENT_TEXT_INSERTED:
+ case nsIAccessibleEvent::EVENT_TEXT_REMOVED: {
+ AccTextChangeEvent* event = downcast_accEvent(aEvent);
+ sessionAcc->SendTextChangedEvent(accessible,
+ event->ModifiedText(),
+ event->GetStartOffset(),
+ event->GetLength(),
+ event->IsTextInserted(),
+ event->IsFromUserInput());
+ break;
+ }
+ case nsIAccessibleEvent::EVENT_STATE_CHANGE: {
+ AccStateChangeEvent* event = downcast_accEvent(aEvent);
+ auto state = event->GetState();
+ if (state & states::CHECKED) {
+ sessionAcc->SendClickedEvent(accessible);
+ }
+
+ if (state & states::SELECTED) {
+ sessionAcc->SendSelectedEvent(accessible);
+ }
+
+ if (state & states::BUSY) {
+ sessionAcc->SendWindowStateChangedEvent(accessible);
+ }
+ break;
+ }
+ case nsIAccessibleEvent::EVENT_SCROLLING: {
+ AccScrollingEvent* event = downcast_accEvent(aEvent);
+ sessionAcc->SendScrollingEvent(accessible,
+ event->ScrollX(),
+ event->ScrollY(),
+ event->MaxScrollX(),
+ event->MaxScrollY());
+ break;
+ }
+ case nsIAccessibleEvent::EVENT_SHOW:
+ case nsIAccessibleEvent::EVENT_HIDE: {
+ AccMutationEvent* event = downcast_accEvent(aEvent);
+ auto parent = static_cast<AccessibleWrap*>(event->Parent());
+ sessionAcc->SendWindowContentChangedEvent(parent);
+ break;
+ }
+ default:
+ break;
+ }
+
+ return NS_OK;
+}
+
+void
+AccessibleWrap::Shutdown()
+{
+ if (mDoc) {
+ if (mID > 0) {
+ if (auto doc = static_cast<DocAccessibleWrap*>(mDoc.get())) {
+ doc->RemoveID(mID);
+ }
+ ReleaseID(mID);
+ mID = 0;
+ }
+ }
+
+ Accessible::Shutdown();
+}
+
+int32_t
+AccessibleWrap::AcquireID()
+{
+ return sIDSet.GetID();
+}
+
+void
+AccessibleWrap::ReleaseID(int32_t aID)
+{
+ sIDSet.ReleaseID(aID);
+}
+
+void
+AccessibleWrap::SetTextContents(const nsAString& aText) {
+ if (IsHyperText()) {
+ AsHyperText()->ReplaceText(aText);
+ }
+}
+
+void
+AccessibleWrap::GetTextContents(nsAString& aText) {
+ // For now it is a simple wrapper for getting entire range of TextSubstring.
+ // In the future this may be smarter and retrieve a flattened string.
+ if (IsHyperText()) {
+ AsHyperText()->TextSubstring(0, -1, aText);
+ }
+}
+
+bool
+AccessibleWrap::GetSelectionBounds(int32_t* aStartOffset, int32_t* aEndOffset) {
+ if (IsHyperText()) {
+ return AsHyperText()->SelectionBoundsAt(0, aStartOffset, aEndOffset);
+ }
+
+ return false;
+}
+
+mozilla::java::GeckoBundle::LocalRef
+AccessibleWrap::CreateBundle(int32_t aParentID,
+ role aRole,
+ uint64_t aState,
+ const nsString& aName,
+ const nsString& aTextValue,
+ const nsString& aDOMNodeID,
+ const nsIntRect& aBounds,
+ double aCurVal,
+ double aMinVal,
+ double aMaxVal,
+ double aStep,
+ nsIPersistentProperties* aAttributes,
+ const nsTArray<int32_t>& aChildren) const
{
+ GECKOBUNDLE_START(nodeInfo);
+ GECKOBUNDLE_PUT(nodeInfo, "id", java::sdk::Integer::ValueOf(VirtualViewID()));
+ GECKOBUNDLE_PUT(nodeInfo, "parentId", java::sdk::Integer::ValueOf(aParentID));
+ uint64_t flags = GetFlags(aRole, aState);
+ GECKOBUNDLE_PUT(nodeInfo, "flags", java::sdk::Integer::ValueOf(flags));
+
+ nsAutoString geckoRole;
+ nsAutoString roleDescription;
+ nsAutoString className;
+ GetAndroidRoleAndClass(aRole, geckoRole, roleDescription, className);
+ if (VirtualViewID() == kNoID) {
+ className.AssignLiteral("android.webkit.WebView");
+ roleDescription.AssignLiteral("");
+ }
+ GECKOBUNDLE_PUT(
+ nodeInfo, "roleDescription", jni::StringParam(roleDescription));
+ GECKOBUNDLE_PUT(nodeInfo, "geckoRole", jni::StringParam(geckoRole));
+ GECKOBUNDLE_PUT(nodeInfo, "className", jni::StringParam(className));
+
+ if (!aTextValue.IsEmpty() &&
+ (flags & java::SessionAccessibility::FLAG_EDITABLE)) {
+ GECKOBUNDLE_PUT(nodeInfo, "hint", jni::StringParam(aName));
+ GECKOBUNDLE_PUT(nodeInfo, "text", jni::StringParam(aTextValue));
+ } else {
+ GECKOBUNDLE_PUT(nodeInfo, "text", jni::StringParam(aName));
+ }
+
+ if (!aDOMNodeID.IsEmpty()) {
+ GECKOBUNDLE_PUT(
+ nodeInfo, "viewIdResourceName", jni::StringParam(aDOMNodeID));
+ }
+
+ const int32_t data[4] = {
+ aBounds.x, aBounds.y, aBounds.x + aBounds.width, aBounds.y + aBounds.height
+ };
+ GECKOBUNDLE_PUT(nodeInfo, "bounds", jni::IntArray::New(data, 4));
+
+ if (HasNumericValue()) {
+ GECKOBUNDLE_START(rangeInfo);
+ if (aMaxVal == 1 && aMinVal == 0) {
+ GECKOBUNDLE_PUT(
+ rangeInfo, "type", java::sdk::Integer::ValueOf(2)); // percent
+ } else if (std::round(aStep) != aStep) {
+ GECKOBUNDLE_PUT(
+ rangeInfo, "type", java::sdk::Integer::ValueOf(1)); // float
+ } else {
+ GECKOBUNDLE_PUT(
+ rangeInfo, "type", java::sdk::Integer::ValueOf(0)); // integer
+ }
+
+ if (!IsNaN(aCurVal)) {
+ GECKOBUNDLE_PUT(rangeInfo, "current", java::sdk::Double::New(aCurVal));
+ }
+ if (!IsNaN(aMinVal)) {
+ GECKOBUNDLE_PUT(rangeInfo, "min", java::sdk::Double::New(aMinVal));
+ }
+ if (!IsNaN(aMaxVal)) {
+ GECKOBUNDLE_PUT(rangeInfo, "max", java::sdk::Double::New(aMaxVal));
+ }
+
+ GECKOBUNDLE_FINISH(rangeInfo);
+ GECKOBUNDLE_PUT(nodeInfo, "rangeInfo", rangeInfo);
+ }
+
+ nsString inputType;
+ nsAccUtils::GetAccAttr(aAttributes, nsGkAtoms::textInputType, inputType);
+ if (!inputType.IsEmpty()) {
+ GECKOBUNDLE_PUT(nodeInfo, "inputType", jni::StringParam(inputType));
+ }
+
+ nsString posinset;
+ nsresult rv = aAttributes->GetStringProperty(NS_LITERAL_CSTRING("posinset"), posinset);
+ if (NS_SUCCEEDED(rv)) {
+ int32_t rowIndex;
+ if (sscanf(NS_ConvertUTF16toUTF8(posinset).get(), "%d", &rowIndex) > 0) {
+ GECKOBUNDLE_START(collectionItemInfo);
+ GECKOBUNDLE_PUT(
+ collectionItemInfo, "rowIndex", java::sdk::Integer::ValueOf(rowIndex));
+ GECKOBUNDLE_PUT(
+ collectionItemInfo, "columnIndex", java::sdk::Integer::ValueOf(0));
+ GECKOBUNDLE_PUT(
+ collectionItemInfo, "rowSpan", java::sdk::Integer::ValueOf(1));
+ GECKOBUNDLE_PUT(
+ collectionItemInfo, "columnSpan", java::sdk::Integer::ValueOf(1));
+ GECKOBUNDLE_FINISH(collectionItemInfo);
+
+ GECKOBUNDLE_PUT(nodeInfo, "collectionItemInfo", collectionItemInfo);
+ }
+ }
+
+ nsString colSize;
+ rv = aAttributes->GetStringProperty(NS_LITERAL_CSTRING("child-item-count"),
+ colSize);
+ if (NS_SUCCEEDED(rv)) {
+ int32_t rowCount;
+ if (sscanf(NS_ConvertUTF16toUTF8(colSize).get(), "%d", &rowCount) > 0) {
+ GECKOBUNDLE_START(collectionInfo);
+ GECKOBUNDLE_PUT(
+ collectionInfo, "rowCount", java::sdk::Integer::ValueOf(rowCount));
+ GECKOBUNDLE_PUT(
+ collectionInfo, "columnCount", java::sdk::Integer::ValueOf(1));
+
+ nsString unused;
+ rv = aAttributes->GetStringProperty(NS_LITERAL_CSTRING("hierarchical"),
+ unused);
+ if (NS_SUCCEEDED(rv)) {
+ GECKOBUNDLE_PUT(
+ collectionInfo, "isHierarchical", java::sdk::Boolean::TRUE());
+ }
+
+ if (IsSelect()) {
+ int32_t selectionMode = (aState & states::MULTISELECTABLE) ? 2 : 1;
+ GECKOBUNDLE_PUT(collectionInfo,
+ "selectionMode",
+ java::sdk::Integer::ValueOf(selectionMode));
+ }
+ GECKOBUNDLE_FINISH(collectionInfo);
+
+ GECKOBUNDLE_PUT(nodeInfo, "collectionInfo", collectionInfo);
+ }
+ }
+
+ GECKOBUNDLE_PUT(nodeInfo,
+ "children",
+ jni::IntArray::New(aChildren.Elements(), aChildren.Length()));
+ GECKOBUNDLE_FINISH(nodeInfo);
+
+ return nodeInfo;
}
+
+uint64_t
+AccessibleWrap::GetFlags(role aRole, uint64_t aState)
+{
+ uint64_t flags = 0;
+ if (aState & states::CHECKABLE) {
+ flags |= java::SessionAccessibility::FLAG_CHECKABLE;
+ }
+
+ if (aState & states::CHECKED) {
+ flags |= java::SessionAccessibility::FLAG_CHECKED;
+ }
+
+ if (aState & states::INVALID) {
+ flags |= java::SessionAccessibility::FLAG_CONTENT_INVALID;
+ }
+
+ if (aState & states::EDITABLE) {
+ flags |= java::SessionAccessibility::FLAG_EDITABLE;
+ }
+
+ if (aState & states::SENSITIVE) {
+ flags |= java::SessionAccessibility::FLAG_CLICKABLE;
+ }
+
+ if (aState & states::ENABLED) {
+ flags |= java::SessionAccessibility::FLAG_ENABLED;
+ }
+
+ if (aState & states::FOCUSABLE) {
+ flags |= java::SessionAccessibility::FLAG_FOCUSABLE;
+ }
+
+ if (aState & states::FOCUSED) {
+ flags |= java::SessionAccessibility::FLAG_FOCUSED;
+ }
+
+ if (aState & states::MULTI_LINE) {
+ flags |= java::SessionAccessibility::FLAG_MULTI_LINE;
+ }
+
+ if (aState & states::SELECTABLE) {
+ flags |= java::SessionAccessibility::FLAG_SELECTABLE;
+ }
+
+ if (aState & states::SELECTED) {
+ flags |= java::SessionAccessibility::FLAG_SELECTED;
+ }
+
+ if ((aState & (states::INVISIBLE | states::OFFSCREEN)) == 0) {
+ flags |= java::SessionAccessibility::FLAG_VISIBLE_TO_USER;
+ }
+
+ if (aRole == roles::PASSWORD_TEXT) {
+ flags |= java::SessionAccessibility::FLAG_PASSWORD;
+ }
+
+ return flags;
+}
+
+void
+AccessibleWrap::GetAndroidRoleAndClass(role aRole,
+ nsAString& aGeckoRole,
+ nsAString& aRoleDescription,
+ nsAString& aClassStr)
+{
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIStringBundleService> sbs =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to get string bundle service");
+ return;
+ }
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = sbs->CreateBundle(ROLE_STRINGS_URL, getter_AddRefs(bundle));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to get string bundle");
+ return;
+ }
+
+#define ROLE(geckoRole, \
+ stringRole, \
+ atkRole, \
+ macRole, \
+ msaaRole, \
+ ia2Role, \
+ androidClass, \
+ nameRule) \
+ case roles::geckoRole: \
+ rv = bundle->GetStringFromName(stringRole, aRoleDescription); \
+ if (NS_FAILED(rv)) \
+ aRoleDescription.AssignLiteral(""); \
+ aGeckoRole.AssignLiteral(stringRole); \
+ aClassStr.AssignLiteral(androidClass); \
+ break;
+
+ switch (aRole) {
+#include "RoleMap.h"
+ default:
+ aRoleDescription.AssignLiteral("");
+ aGeckoRole.AssignLiteral("nothing");
+ aClassStr.AssignLiteral("android.view.View");
+ return;
+ }
+
+#undef ROLE
+}
+
+void
+AccessibleWrap::DOMNodeID(nsString& aDOMNodeID)
+{
+ if (mContent) {
+ nsAtom* id = mContent->GetID();
+ if (id) {
+ id->ToString(aDOMNodeID);
+ }
+ }
+}
+
+mozilla::java::GeckoBundle::LocalRef
+AccessibleWrap::ToBundle()
+{
+ AccessibleWrap* parent = static_cast<AccessibleWrap*>(Parent());
+
+ nsAutoString name;
+ Name(name);
+
+ nsAutoString value;
+ Value(value);
+
+ nsAutoString viewIdResourceName;
+ DOMNodeID(viewIdResourceName);
+
+ nsCOMPtr<nsIPersistentProperties> attributes = Attributes();
+
+ auto childCount = ChildCount();
+ nsTArray<int32_t> children(childCount);
+ for (uint32_t i = 0; i < childCount; i++) {
+ auto child = static_cast<AccessibleWrap*>(GetChildAt(i));
+ children.AppendElement(child->VirtualViewID());
+ }
+
+ return CreateBundle(parent ? parent->VirtualViewID() : 0,
+ Role(),
+ State(),
+ name,
+ value,
+ viewIdResourceName,
+ Bounds(),
+ CurValue(),
+ MinValue(),
+ MaxValue(),
+ Step(),
+ attributes,
+ children);
+}
--- a/accessible/android/AccessibleWrap.h
+++ b/accessible/android/AccessibleWrap.h
@@ -1,25 +1,80 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_a11y_AccessibleWrap_h_
#define mozilla_a11y_AccessibleWrap_h_
+#include "Accessible.h"
+#include "GeneratedJNIWrappers.h"
+#include "mozilla/a11y/ProxyAccessible.h"
#include "nsCOMPtr.h"
-#include "Accessible.h"
namespace mozilla {
namespace a11y {
class AccessibleWrap : public Accessible
{
-public: // construction, destruction
+public:
AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc);
virtual ~AccessibleWrap();
+
+ virtual nsresult HandleAccEvent(AccEvent* aEvent) override;
+ virtual void Shutdown() override;
+
+ int32_t VirtualViewID() const { return mID; }
+
+ virtual void SetTextContents(const nsAString& aText);
+
+ virtual void GetTextContents(nsAString& aText);
+
+ virtual bool GetSelectionBounds(int32_t* aStartOffset, int32_t* aEndOffset);
+
+ virtual mozilla::java::GeckoBundle::LocalRef ToBundle();
+
+ static const int32_t kNoID = -1;
+
+protected:
+ mozilla::java::GeckoBundle::LocalRef CreateBundle(
+ int32_t aParentID,
+ role aRole,
+ uint64_t aState,
+ const nsString& aName,
+ const nsString& aTextValue,
+ const nsString& aDOMNodeID,
+ const nsIntRect& aBounds,
+ double aCurVal,
+ double aMinVal,
+ double aMaxVal,
+ double aStep,
+ nsIPersistentProperties* aAttributes,
+ const nsTArray<int32_t>& aChildren) const;
+
+ // IDs should be a positive 32bit integer.
+ static int32_t AcquireID();
+ static void ReleaseID(int32_t aID);
+
+ int32_t mID;
+
+private:
+ void DOMNodeID(nsString& aDOMNodeID);
+
+ static void GetAndroidRoleAndClass(role aRole,
+ nsAString& aGeckoRole,
+ nsAString& aRoleDescription,
+ nsAString& aClassStr);
+
+ static uint64_t GetFlags(role aRole, uint64_t aState);
};
+static inline AccessibleWrap*
+WrapperFor(const ProxyAccessible* aProxy)
+{
+ return reinterpret_cast<AccessibleWrap*>(aProxy->GetWrapper());
+}
+
} // namespace a11y
} // namespace mozilla
#endif
new file mode 100644
--- /dev/null
+++ b/accessible/android/DocAccessibleWrap.cpp
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=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/. */
+
+#include "DocAccessibleWrap.h"
+#include "nsIDocShell.h"
+
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// DocAccessibleWrap
+////////////////////////////////////////////////////////////////////////////////
+
+DocAccessibleWrap::DocAccessibleWrap(nsIDocument* aDocument,
+ nsIPresShell* aPresShell)
+ : DocAccessible(aDocument, aPresShell)
+{
+ nsCOMPtr<nsIDocShellTreeItem> treeItem(aDocument->GetDocShell());
+
+ nsCOMPtr<nsIDocShellTreeItem> parentTreeItem;
+ treeItem->GetParent(getter_AddRefs(parentTreeItem));
+
+ if (treeItem->ItemType() == nsIDocShellTreeItem::typeContent &&
+ (!parentTreeItem ||
+ parentTreeItem->ItemType() == nsIDocShellTreeItem::typeChrome)) {
+ // The top-level content document gets this special ID.
+ mID = kNoID;
+ } else {
+ mID = AcquireID();
+ }
+}
+
+DocAccessibleWrap::~DocAccessibleWrap() {}
+
+AccessibleWrap*
+DocAccessibleWrap::GetAccessibleByID(int32_t aID) const
+{
+ if (AccessibleWrap* acc = mIDToAccessibleMap.Get(aID)) {
+ return acc;
+ }
+
+ // If the ID is not in the hash table, check the IDs of the child docs.
+ for (uint32_t i = 0; i < ChildDocumentCount(); i++) {
+ auto childDoc = reinterpret_cast<AccessibleWrap*>(GetChildDocumentAt(i));
+ if (childDoc->VirtualViewID() == aID) {
+ return childDoc;
+ }
+ }
+
+ return nullptr;
+}
--- a/accessible/android/DocAccessibleWrap.h
+++ b/accessible/android/DocAccessibleWrap.h
@@ -6,14 +6,35 @@
#ifndef mozilla_a11y_DocAccessibleWrap_h__
#define mozilla_a11y_DocAccessibleWrap_h__
#include "DocAccessible.h"
namespace mozilla {
namespace a11y {
-typedef DocAccessible DocAccessibleWrap;
+class DocAccessibleWrap : public DocAccessible
+{
+public:
+ DocAccessibleWrap(nsIDocument* aDocument, nsIPresShell* aPresShell);
+ virtual ~DocAccessibleWrap();
+
+ /**
+ * Manage the mapping from id to Accessible.
+ */
+ void AddID(uint32_t aID, AccessibleWrap* aAcc)
+ {
+ mIDToAccessibleMap.Put(aID, aAcc);
+ }
+ void RemoveID(uint32_t aID) { mIDToAccessibleMap.Remove(aID); }
+ AccessibleWrap* GetAccessibleByID(int32_t aID) const;
+
+protected:
+ /*
+ * This provides a mapping from 32 bit id to accessible objects.
+ */
+ nsDataHashtable<nsUint32HashKey, AccessibleWrap*> mIDToAccessibleMap;
+};
} // namespace a11y
} // namespace mozilla
#endif
--- a/accessible/android/Platform.cpp
+++ b/accessible/android/Platform.cpp
@@ -1,89 +1,200 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=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/. */
#include "Platform.h"
+#include "ProxyAccessibleWrap.h"
+#include "SessionAccessibility.h"
+#include "mozilla/a11y/ProxyAccessible.h"
+#include "nsIAccessibleEvent.h"
+#include "nsIAccessiblePivot.h"
using namespace mozilla;
using namespace mozilla::a11y;
void
a11y::PlatformInit()
{
}
void
a11y::PlatformShutdown()
{
}
void
-a11y::ProxyCreated(ProxyAccessible*, uint32_t)
+a11y::ProxyCreated(ProxyAccessible* aProxy, uint32_t aInterfaces)
{
+ AccessibleWrap* wrapper = nullptr;
+ if (aProxy->IsDoc()) {
+ wrapper = new DocProxyAccessibleWrap(aProxy->AsDoc());
+ } else {
+ wrapper = new ProxyAccessibleWrap(aProxy);
+ }
+
+ wrapper->AddRef();
+ aProxy->SetWrapper(reinterpret_cast<uintptr_t>(wrapper));
+}
+
+void
+a11y::ProxyDestroyed(ProxyAccessible* aProxy)
+{
+ AccessibleWrap* wrapper =
+ reinterpret_cast<AccessibleWrap*>(aProxy->GetWrapper());
+
+ // If aProxy is a document that was created, but
+ // RecvPDocAccessibleConstructor failed then aProxy->GetWrapper() will be
+ // null.
+ if (!wrapper) {
+ return;
+ }
+
+ wrapper->Shutdown();
+ aProxy->SetWrapper(0);
+ wrapper->Release();
}
void
-a11y::ProxyDestroyed(ProxyAccessible*)
+a11y::ProxyEvent(ProxyAccessible* aTarget, uint32_t aEventType)
{
+ SessionAccessibility* sessionAcc =
+ SessionAccessibility::GetInstanceFor(aTarget);
+ if (!sessionAcc) {
+ return;
+ }
+
+ switch (aEventType) {
+ case nsIAccessibleEvent::EVENT_FOCUS:
+ sessionAcc->SendFocusEvent(WrapperFor(aTarget));
+ break;
+ }
}
void
-a11y::ProxyEvent(ProxyAccessible*, uint32_t)
+a11y::ProxyStateChangeEvent(ProxyAccessible* aTarget,
+ uint64_t aState,
+ bool aEnabled)
{
-}
+ SessionAccessibility* sessionAcc =
+ SessionAccessibility::GetInstanceFor(aTarget);
+
+ if (!sessionAcc) {
+ return;
+ }
-void
-a11y::ProxyStateChangeEvent(ProxyAccessible*, uint64_t, bool)
-{
+ if (aState & states::CHECKED) {
+ sessionAcc->SendClickedEvent(WrapperFor(aTarget));
+ }
+
+ if (aState & states::SELECTED) {
+ sessionAcc->SendSelectedEvent(WrapperFor(aTarget));
+ }
+
+ if (aState & states::BUSY) {
+ sessionAcc->SendWindowStateChangedEvent(WrapperFor(aTarget));
+ }
}
void
a11y::ProxyCaretMoveEvent(ProxyAccessible* aTarget, int32_t aOffset)
{
+ SessionAccessibility* sessionAcc =
+ SessionAccessibility::GetInstanceFor(aTarget);
+
+ if (sessionAcc) {
+ sessionAcc->SendTextSelectionChangedEvent(WrapperFor(aTarget), aOffset);
+ }
}
void
-a11y::ProxyTextChangeEvent(ProxyAccessible*,
- const nsString&,
- int32_t,
- uint32_t,
- bool,
- bool)
+a11y::ProxyTextChangeEvent(ProxyAccessible* aTarget,
+ const nsString& aStr,
+ int32_t aStart,
+ uint32_t aLen,
+ bool aIsInsert,
+ bool aFromUser)
{
+ SessionAccessibility* sessionAcc =
+ SessionAccessibility::GetInstanceFor(aTarget);
+
+ if (sessionAcc) {
+ sessionAcc->SendTextChangedEvent(
+ WrapperFor(aTarget), aStr, aStart, aLen, aIsInsert, aFromUser);
+ }
}
void
-a11y::ProxyShowHideEvent(ProxyAccessible*, ProxyAccessible*, bool, bool)
+a11y::ProxyShowHideEvent(ProxyAccessible* aTarget,
+ ProxyAccessible* aParent,
+ bool aInsert,
+ bool aFromUser)
{
+ SessionAccessibility* sessionAcc =
+ SessionAccessibility::GetInstanceFor(aTarget);
+ if (sessionAcc) {
+ sessionAcc->SendWindowContentChangedEvent(WrapperFor(aParent));
+ }
}
void
a11y::ProxySelectionEvent(ProxyAccessible*, ProxyAccessible*, uint32_t)
{
}
void
-a11y::ProxyVirtualCursorChangeEvent(ProxyAccessible*,
- ProxyAccessible*,
- int32_t,
- int32_t,
- ProxyAccessible*,
- int32_t,
- int32_t,
- int16_t,
- int16_t,
- bool)
+a11y::ProxyVirtualCursorChangeEvent(ProxyAccessible* aTarget,
+ ProxyAccessible* aOldPosition,
+ int32_t aOldStartOffset,
+ int32_t aOldEndOffset,
+ ProxyAccessible* aNewPosition,
+ int32_t aNewStartOffset,
+ int32_t aNewEndOffset,
+ int16_t aReason,
+ int16_t aBoundaryType,
+ bool aFromUser)
{
+ if (!aNewPosition) {
+ return;
+ }
+
+ SessionAccessibility* sessionAcc =
+ SessionAccessibility::GetInstanceFor(aTarget);
+
+ if (!sessionAcc) {
+ return;
+ }
+
+ if (aOldPosition != aNewPosition) {
+ if (aReason == nsIAccessiblePivot::REASON_POINT) {
+ sessionAcc->SendHoverEnterEvent(WrapperFor(aNewPosition));
+ } else {
+ sessionAcc->SendAccessibilityFocusedEvent(WrapperFor(aNewPosition));
+ }
+ }
+
+ if (aBoundaryType != nsIAccessiblePivot::NO_BOUNDARY) {
+ sessionAcc->SendTextTraversedEvent(
+ WrapperFor(aNewPosition), aNewStartOffset, aNewEndOffset);
+ }
}
void
-a11y::ProxyScrollingEvent(ProxyAccessible*,
- uint32_t,
- uint32_t,
- uint32_t,
- uint32_t,
- uint32_t)
+a11y::ProxyScrollingEvent(ProxyAccessible* aTarget,
+ uint32_t aEventType,
+ uint32_t aScrollX,
+ uint32_t aScrollY,
+ uint32_t aMaxScrollX,
+ uint32_t aMaxScrollY)
{
+ if (aEventType == nsIAccessibleEvent::EVENT_SCROLLING) {
+ SessionAccessibility* sessionAcc =
+ SessionAccessibility::GetInstanceFor(aTarget);
+
+ if (sessionAcc) {
+ sessionAcc->SendScrollingEvent(
+ WrapperFor(aTarget), aScrollX, aScrollY, aMaxScrollX, aMaxScrollY);
+ }
+ }
}
new file mode 100644
--- /dev/null
+++ b/accessible/android/ProxyAccessibleWrap.cpp
@@ -0,0 +1,147 @@
+/* -*- Mode: c++; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ProxyAccessibleWrap.h"
+#include "nsPersistentProperties.h"
+
+using namespace mozilla::a11y;
+
+ProxyAccessibleWrap::ProxyAccessibleWrap(ProxyAccessible* aProxy)
+ : AccessibleWrap(nullptr, nullptr)
+{
+ mType = eProxyType;
+ mBits.proxy = aProxy;
+
+ if (aProxy->mHasValue) {
+ mStateFlags |= eHasNumericValue;
+ }
+
+ if (aProxy->mIsSelection) {
+ mGenericTypes |= eSelect;
+ }
+
+ if (aProxy->mIsHyperText) {
+ mGenericTypes |= eHyperText;
+ }
+
+ auto doc = reinterpret_cast<DocProxyAccessibleWrap*>(
+ Proxy()->Document()->GetWrapper());
+ if (doc) {
+ mID = AcquireID();
+ doc->AddID(mID, this);
+ }
+}
+
+void
+ProxyAccessibleWrap::Shutdown()
+{
+ auto doc = reinterpret_cast<DocProxyAccessibleWrap*>(
+ Proxy()->Document()->GetWrapper());
+ if (mID && doc) {
+ doc->RemoveID(mID);
+ ReleaseID(mID);
+ mID = 0;
+ }
+
+ mBits.proxy = nullptr;
+ mStateFlags |= eIsDefunct;
+}
+
+// Accessible
+
+already_AddRefed<nsIPersistentProperties>
+ProxyAccessibleWrap::Attributes()
+{
+ RefPtr<nsPersistentProperties> attributes = new nsPersistentProperties();
+ nsAutoString unused;
+ AutoTArray<Attribute, 10> attrs;
+ Proxy()->Attributes(&attrs);
+ for (size_t i = 0; i < attrs.Length(); i++) {
+ attributes->SetStringProperty(
+ attrs.ElementAt(i).Name(), attrs.ElementAt(i).Value(), unused);
+ }
+
+ return attributes.forget();
+}
+
+uint32_t
+ProxyAccessibleWrap::ChildCount() const
+{
+ return Proxy()->ChildrenCount();
+}
+
+void
+ProxyAccessibleWrap::ScrollTo(uint32_t aHow) const
+{
+ Proxy()->ScrollTo(aHow);
+}
+
+// Other
+
+void
+ProxyAccessibleWrap::SetTextContents(const nsAString& aText)
+{
+ Proxy()->ReplaceText(PromiseFlatString(aText));
+}
+
+void
+ProxyAccessibleWrap::GetTextContents(nsAString& aText)
+{
+ nsAutoString text;
+ Proxy()->TextSubstring(0, -1, text);
+ aText.Assign(text);
+}
+
+bool
+ProxyAccessibleWrap::GetSelectionBounds(int32_t* aStartOffset,
+ int32_t* aEndOffset)
+{
+ nsAutoString unused;
+ return Proxy()->SelectionBoundsAt(0, unused, aStartOffset, aEndOffset);
+}
+
+mozilla::java::GeckoBundle::LocalRef
+ProxyAccessibleWrap::ToBundle()
+{
+ ProxyAccessible* proxy = Proxy();
+ if (!proxy) {
+ return nullptr;
+ }
+
+ int32_t parentID = proxy->Parent() ?
+ WrapperFor(proxy->Parent())->VirtualViewID() : 0;
+
+ nsAutoString name;
+ proxy->Name(name);
+
+ nsAutoString value;
+ proxy->Value(value);
+
+ nsAutoString viewIdResourceName;
+ proxy->DOMNodeID(viewIdResourceName);
+
+ nsCOMPtr<nsIPersistentProperties> attributes = Attributes();
+
+ auto childCount = proxy->ChildrenCount();
+ nsTArray<int32_t> children(childCount);
+ for (uint32_t i = 0; i < childCount; i++) {
+ auto child = WrapperFor(proxy->ChildAt(i));
+ children.AppendElement(child->VirtualViewID());
+ }
+
+ return CreateBundle(parentID,
+ proxy->Role(),
+ proxy->State(),
+ name,
+ value,
+ viewIdResourceName,
+ proxy->Bounds(),
+ proxy->CurValue(),
+ proxy->MinValue(),
+ proxy->MaxValue(),
+ proxy->Step(),
+ attributes,
+ children);
+}
new file mode 100644
--- /dev/null
+++ b/accessible/android/ProxyAccessibleWrap.h
@@ -0,0 +1,123 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=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/. *
+ */
+
+#ifndef MOZILLA_A11Y_ProxyAccessibleWrap_h
+#define MOZILLA_A11Y_ProxyAccessibleWrap_h
+
+#include "AccessibleWrap.h"
+#include "DocAccessibleParent.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * A wrapper for Accessible proxies. The public methods here should be overriden
+ * from AccessibleWrap or its super classes.
+ * This gives us an abstraction layer so SessionAccessibility doesn't have
+ * to distinguish between a local or remote accessibles.
+ * NOTE: This shouldn't be regarded as a full Accessible implementation.
+ */
+class ProxyAccessibleWrap : public AccessibleWrap
+{
+public:
+ explicit ProxyAccessibleWrap(ProxyAccessible* aProxy);
+
+ virtual void Shutdown() override;
+
+ // Accessible
+
+ virtual already_AddRefed<nsIPersistentProperties> Attributes() override;
+
+ virtual uint32_t ChildCount() const override;
+
+ virtual void ScrollTo(uint32_t aHow) const override;
+
+ // AccessibleWrap
+
+ virtual void SetTextContents(const nsAString& aText) override;
+
+ virtual void GetTextContents(nsAString& aText) override;
+
+ virtual bool GetSelectionBounds(int32_t* aStartOffset, int32_t* aEndOffset) override;
+
+ virtual mozilla::java::GeckoBundle::LocalRef ToBundle() override;
+};
+
+class DocProxyAccessibleWrap : public ProxyAccessibleWrap
+{
+public:
+ explicit DocProxyAccessibleWrap(DocAccessibleParent* aProxy)
+ : ProxyAccessibleWrap(aProxy)
+ {
+ mGenericTypes |= eDocument;
+
+ if (auto parent = ParentDocument()) {
+ mID = AcquireID();
+ parent->AddID(mID, this);
+ } else {
+ // top level
+ mID = kNoID;
+ }
+ }
+
+ virtual void Shutdown() override
+ {
+ if (mID) {
+ auto parent = ParentDocument();
+ if (parent) {
+ MOZ_ASSERT(mID != kNoID, "A non root accessible always has a parent");
+ parent->RemoveID(mID);
+ ReleaseID(mID);
+ }
+ }
+ mID = 0;
+ mBits.proxy = nullptr;
+ mStateFlags |= eIsDefunct;
+ }
+
+ DocProxyAccessibleWrap* ParentDocument()
+ {
+ DocAccessibleParent* proxy = static_cast<DocAccessibleParent*>(Proxy());
+ MOZ_ASSERT(proxy);
+ if (DocAccessibleParent* parent = proxy->ParentDoc()) {
+ return reinterpret_cast<DocProxyAccessibleWrap*>(parent->GetWrapper());
+ }
+
+ return nullptr;
+ }
+
+ DocProxyAccessibleWrap* GetChildDocumentAt(uint32_t aIndex)
+ {
+ auto doc = Proxy()->AsDoc();
+ if (doc && doc->ChildDocCount() > aIndex) {
+ return reinterpret_cast<DocProxyAccessibleWrap*>(
+ doc->ChildDocAt(aIndex)->GetWrapper());
+ }
+
+ return nullptr;
+ }
+
+ void AddID(uint32_t aID, AccessibleWrap* aAcc)
+ {
+ mIDToAccessibleMap.Put(aID, aAcc);
+ }
+ void RemoveID(uint32_t aID) { mIDToAccessibleMap.Remove(aID); }
+ AccessibleWrap* GetAccessibleByID(uint32_t aID) const
+ {
+ return mIDToAccessibleMap.Get(aID);
+ }
+
+private:
+ /*
+ * This provides a mapping from 32 bit id to accessible objects.
+ */
+ nsDataHashtable<nsUint32HashKey, AccessibleWrap*> mIDToAccessibleMap;
+};
+}
+}
+
+#endif
new file mode 100644
--- /dev/null
+++ b/accessible/android/RootAccessibleWrap.cpp
@@ -0,0 +1,92 @@
+/* -*- Mode: c++; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "RootAccessibleWrap.h"
+
+#include "AccessibleOrProxy.h"
+#include "DocAccessibleParent.h"
+#include "ProxyAccessibleWrap.h"
+#include "SessionAccessibility.h"
+
+using namespace mozilla::a11y;
+
+RootAccessibleWrap::RootAccessibleWrap(nsIDocument* aDoc,
+ nsIPresShell* aPresShell)
+ : RootAccessible(aDoc, aPresShell)
+{
+}
+
+RootAccessibleWrap::~RootAccessibleWrap() {}
+
+AccessibleWrap*
+RootAccessibleWrap::GetContentAccessible()
+{
+ if (ProxyAccessible* proxy = GetPrimaryRemoteTopLevelContentDoc()) {
+ return WrapperFor(proxy);
+ }
+
+ if (ChildDocumentCount()) {
+ return GetChildDocumentAt(0);
+ }
+
+ return nullptr;
+}
+
+AccessibleWrap*
+RootAccessibleWrap::FindAccessibleById(int32_t aID)
+{
+ AccessibleWrap* contentAcc = GetContentAccessible();
+
+ if (!contentAcc) {
+ return nullptr;
+ }
+
+ if (aID == AccessibleWrap::kNoID) {
+ return contentAcc;
+ }
+
+ if (contentAcc->IsProxy()) {
+ return FindAccessibleById(static_cast<DocProxyAccessibleWrap*>(contentAcc),
+ aID);
+ }
+
+ return FindAccessibleById(
+ static_cast<DocAccessibleWrap*>(contentAcc->AsDoc()), aID);
+}
+
+AccessibleWrap*
+RootAccessibleWrap::FindAccessibleById(DocProxyAccessibleWrap* aDoc,
+ int32_t aID)
+{
+ AccessibleWrap* acc = aDoc->GetAccessibleByID(aID);
+ uint32_t index = 0;
+ while (!acc) {
+ auto child =
+ static_cast<DocProxyAccessibleWrap*>(aDoc->GetChildDocumentAt(index++));
+ if (!child) {
+ break;
+ }
+ acc = FindAccessibleById(child, aID);
+ }
+
+ return acc;
+}
+
+AccessibleWrap*
+RootAccessibleWrap::FindAccessibleById(DocAccessibleWrap* aDoc, int32_t aID)
+{
+ AccessibleWrap* acc = aDoc->GetAccessibleByID(aID);
+ uint32_t index = 0;
+ while (!acc) {
+ auto child =
+ static_cast<DocAccessibleWrap*>(aDoc->GetChildDocumentAt(index++));
+ if (!child) {
+ break;
+ }
+ acc = FindAccessibleById(child, aID);
+ }
+
+ return acc;
+}
--- a/accessible/android/RootAccessibleWrap.h
+++ b/accessible/android/RootAccessibleWrap.h
@@ -6,14 +6,32 @@
#ifndef mozilla_a11y_RootAccessibleWrap_h__
#define mozilla_a11y_RootAccessibleWrap_h__
#include "RootAccessible.h"
namespace mozilla {
namespace a11y {
-typedef RootAccessible RootAccessibleWrap;
+class DocProxyAccessibleWrap;
+
+class RootAccessibleWrap : public RootAccessible
+{
+public:
+ RootAccessibleWrap(nsIDocument* aDocument, nsIPresShell* aPresShell);
+ virtual ~RootAccessibleWrap();
+
+ AccessibleWrap* GetContentAccessible();
+
+ AccessibleWrap* FindAccessibleById(int32_t aID);
+
+ // Recursively searches for the accessible ID within the document tree.
+ AccessibleWrap* FindAccessibleById(DocAccessibleWrap* aDocument, int32_t aID);
+
+ // Recursively searches for the accessible ID within the proxy document tree.
+ AccessibleWrap* FindAccessibleById(DocProxyAccessibleWrap* aDocument,
+ int32_t aID);
+};
} // namespace a11y
} // namespace mozilla
#endif
--- a/accessible/android/SessionAccessibility.cpp
+++ b/accessible/android/SessionAccessibility.cpp
@@ -1,16 +1,22 @@
/* -*- Mode: c++; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "SessionAccessibility.h"
#include "AndroidUiThread.h"
#include "nsThreadUtils.h"
+#include "AccessibilityEvent.h"
+#include "HyperTextAccessible.h"
+#include "JavaBuiltins.h"
+#include "RootAccessibleWrap.h"
+#include "nsAccessibilityService.h"
+#include "nsViewManager.h"
#ifdef DEBUG
#include <android/log.h>
#define AALOG(args...) \
__android_log_print(ANDROID_LOG_INFO, "GeckoAccessibilityNative", ##args)
#else
#define AALOG(args...) \
do { \
@@ -18,16 +24,30 @@
#endif
template<>
const char nsWindow::NativePtr<mozilla::a11y::SessionAccessibility>::sName[] =
"SessionAccessibility";
using namespace mozilla::a11y;
+class Settings final
+ : public mozilla::java::SessionAccessibility::Settings::Natives<Settings>
+{
+public:
+ static void ToggleNativeAccessibility(bool aEnable)
+ {
+ if (aEnable) {
+ GetOrCreateAccService();
+ } else {
+ MaybeShutdownAccService(nsAccessibilityService::ePlatformAPI);
+ }
+ }
+};
+
void
SessionAccessibility::SetAttached(bool aAttached,
already_AddRefed<Runnable> aRunnable)
{
if (RefPtr<nsThread> uiThread = GetAndroidUiThread()) {
uiThread->Dispatch(NS_NewRunnableFunction(
"SessionAccessibility::Attach",
[aAttached,
@@ -36,8 +56,264 @@ SessionAccessibility::SetAttached(bool a
runnable = RefPtr<Runnable>(aRunnable)] {
sa->SetAttached(aAttached);
if (runnable) {
runnable->Run();
}
}));
}
}
+
+void
+SessionAccessibility::Init()
+{
+ java::SessionAccessibility::NativeProvider::Natives<
+ SessionAccessibility>::Init();
+ Settings::Init();
+}
+
+mozilla::jni::Object::LocalRef
+SessionAccessibility::GetNodeInfo(int32_t aID)
+{
+ java::GeckoBundle::GlobalRef ret = nullptr;
+ RefPtr<SessionAccessibility> self(this);
+ nsAppShell::SyncRunEvent([this, self, aID, &ret] {
+ if (RootAccessibleWrap* rootAcc = GetRoot()) {
+ AccessibleWrap* acc = rootAcc->FindAccessibleById(aID);
+ if (acc) {
+ ret = acc->ToBundle();
+ } else {
+ AALOG("oops, nothing for %d", aID);
+ }
+ }
+ });
+
+ return mozilla::jni::Object::Ref::From(ret);
+}
+
+RootAccessibleWrap*
+SessionAccessibility::GetRoot()
+{
+ if (!mWindow) {
+ return nullptr;
+ }
+
+ return static_cast<RootAccessibleWrap*>(mWindow->GetRootAccessible());
+}
+
+void
+SessionAccessibility::SetText(int32_t aID, jni::String::Param aText)
+{
+ if (RootAccessibleWrap* rootAcc = GetRoot()) {
+ AccessibleWrap* acc = rootAcc->FindAccessibleById(aID);
+ if (!acc) {
+ return;
+ }
+
+ acc->SetTextContents(aText->ToString());
+ }
+}
+
+SessionAccessibility*
+SessionAccessibility::GetInstanceFor(ProxyAccessible* aAccessible)
+{
+ Accessible* outerDoc = aAccessible->OuterDocOfRemoteBrowser();
+ if (!outerDoc) {
+ return nullptr;
+ }
+
+ return GetInstanceFor(outerDoc);
+}
+
+SessionAccessibility*
+SessionAccessibility::GetInstanceFor(Accessible* aAccessible)
+{
+ RootAccessible* rootAcc = aAccessible->RootAccessible();
+ nsIPresShell* shell = rootAcc->PresShell();
+ nsViewManager* vm = shell->GetViewManager();
+ if (!vm) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIWidget> rootWidget;
+ vm->GetRootWidget(getter_AddRefs(rootWidget));
+ // `rootWidget` can be one of several types. Here we make sure it is an
+ // android nsWindow that implemented NS_NATIVE_WIDGET to return itself.
+ if (rootWidget &&
+ rootWidget->WindowType() == nsWindowType::eWindowType_toplevel &&
+ rootWidget->GetNativeData(NS_NATIVE_WIDGET) == rootWidget) {
+ return static_cast<nsWindow*>(rootWidget.get())->GetSessionAccessibility();
+ }
+
+ return nullptr;
+}
+
+void
+SessionAccessibility::SendAccessibilityFocusedEvent(AccessibleWrap* aAccessible)
+{
+ mSessionAccessibility->SendEvent(
+ java::sdk::AccessibilityEvent::TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+ aAccessible->VirtualViewID(), nullptr, aAccessible->ToBundle());
+ aAccessible->ScrollTo(nsIAccessibleScrollType::SCROLL_TYPE_ANYWHERE);
+}
+
+void
+SessionAccessibility::SendHoverEnterEvent(AccessibleWrap* aAccessible)
+{
+ mSessionAccessibility->SendEvent(
+ java::sdk::AccessibilityEvent::TYPE_VIEW_HOVER_ENTER,
+ aAccessible->VirtualViewID(), nullptr, aAccessible->ToBundle());
+}
+
+void
+SessionAccessibility::SendFocusEvent(AccessibleWrap* aAccessible)
+{
+ // Suppress focus events from about:blank pages.
+ // This is important for tests.
+ if (aAccessible->IsDoc() && aAccessible->ChildCount() == 0) {
+ return;
+ }
+
+ mSessionAccessibility->SendEvent(
+ java::sdk::AccessibilityEvent::TYPE_VIEW_FOCUSED,
+ aAccessible->VirtualViewID(), nullptr, aAccessible->ToBundle());
+}
+
+void
+SessionAccessibility::SendScrollingEvent(AccessibleWrap* aAccessible,
+ int32_t aScrollX,
+ int32_t aScrollY,
+ int32_t aMaxScrollX,
+ int32_t aMaxScrollY)
+{
+ int32_t virtualViewId = aAccessible->VirtualViewID();
+
+ if (virtualViewId != AccessibleWrap::kNoID) {
+ // XXX: Support scrolling in subframes
+ return;
+ }
+
+ GECKOBUNDLE_START(eventInfo);
+ GECKOBUNDLE_PUT(eventInfo, "scrollX", java::sdk::Integer::ValueOf(aScrollX));
+ GECKOBUNDLE_PUT(eventInfo, "scrollY", java::sdk::Integer::ValueOf(aScrollY));
+ GECKOBUNDLE_PUT(eventInfo, "maxScrollX", java::sdk::Integer::ValueOf(aMaxScrollX));
+ GECKOBUNDLE_PUT(eventInfo, "maxScrollY", java::sdk::Integer::ValueOf(aMaxScrollY));
+ GECKOBUNDLE_FINISH(eventInfo);
+
+ mSessionAccessibility->SendEvent(
+ java::sdk::AccessibilityEvent::TYPE_VIEW_SCROLLED, virtualViewId,
+ eventInfo, aAccessible->ToBundle());
+
+ SendWindowContentChangedEvent(aAccessible);
+}
+
+void
+SessionAccessibility::SendWindowContentChangedEvent(AccessibleWrap* aAccessible)
+{
+ mSessionAccessibility->SendEvent(
+ java::sdk::AccessibilityEvent::TYPE_WINDOW_CONTENT_CHANGED,
+ aAccessible->VirtualViewID(), nullptr, aAccessible->ToBundle());
+}
+
+void
+SessionAccessibility::SendWindowStateChangedEvent(AccessibleWrap* aAccessible)
+{
+ // Suppress window state changed events from about:blank pages.
+ // This is important for tests.
+ if (aAccessible->IsDoc() && aAccessible->ChildCount() == 0) {
+ return;
+ }
+
+ mSessionAccessibility->SendEvent(
+ java::sdk::AccessibilityEvent::TYPE_WINDOW_STATE_CHANGED,
+ aAccessible->VirtualViewID(), nullptr, aAccessible->ToBundle());
+}
+
+void
+SessionAccessibility::SendTextSelectionChangedEvent(AccessibleWrap* aAccessible,
+ int32_t aCaretOffset)
+{
+ int32_t fromIndex = aCaretOffset;
+ int32_t startSel = -1;
+ int32_t endSel = -1;
+ if (aAccessible->GetSelectionBounds(&startSel, &endSel)) {
+ fromIndex = startSel == aCaretOffset ? endSel : startSel;
+ }
+
+ GECKOBUNDLE_START(eventInfo);
+ GECKOBUNDLE_PUT(eventInfo, "fromIndex", java::sdk::Integer::ValueOf(fromIndex));
+ GECKOBUNDLE_PUT(eventInfo, "toIndex", java::sdk::Integer::ValueOf(aCaretOffset));
+ GECKOBUNDLE_FINISH(eventInfo);
+
+ mSessionAccessibility->SendEvent(
+ java::sdk::AccessibilityEvent::TYPE_VIEW_TEXT_SELECTION_CHANGED,
+ aAccessible->VirtualViewID(), eventInfo, aAccessible->ToBundle());
+}
+
+void
+SessionAccessibility::SendTextChangedEvent(AccessibleWrap* aAccessible,
+ const nsString& aStr,
+ int32_t aStart,
+ uint32_t aLen,
+ bool aIsInsert,
+ bool aFromUser)
+{
+ if (!aFromUser) {
+ // Only dispatch text change events from users, for now.
+ return;
+ }
+
+ nsAutoString text;
+ aAccessible->GetTextContents(text);
+ nsAutoString beforeText(text);
+ if (aIsInsert) {
+ beforeText.Cut(aStart, aLen);
+ } else {
+ beforeText.Insert(aStr, aStart);
+ }
+
+ GECKOBUNDLE_START(eventInfo);
+ GECKOBUNDLE_PUT(eventInfo, "text", jni::StringParam(text));
+ GECKOBUNDLE_PUT(eventInfo, "beforeText", jni::StringParam(beforeText));
+ GECKOBUNDLE_PUT(eventInfo, "addedCount", java::sdk::Integer::ValueOf(aIsInsert ? aLen : 0));
+ GECKOBUNDLE_PUT(eventInfo, "removedCount", java::sdk::Integer::ValueOf(aIsInsert ? 0 : aLen));
+ GECKOBUNDLE_FINISH(eventInfo);
+
+ mSessionAccessibility->SendEvent(
+ java::sdk::AccessibilityEvent::TYPE_VIEW_TEXT_CHANGED,
+ aAccessible->VirtualViewID(), eventInfo, aAccessible->ToBundle());
+}
+
+void
+SessionAccessibility::SendTextTraversedEvent(AccessibleWrap* aAccessible,
+ int32_t aStartOffset,
+ int32_t aEndOffset)
+{
+ nsAutoString text;
+ aAccessible->GetTextContents(text);
+
+ GECKOBUNDLE_START(eventInfo);
+ GECKOBUNDLE_PUT(eventInfo, "text", jni::StringParam(text));
+ GECKOBUNDLE_PUT(eventInfo, "fromIndex", java::sdk::Integer::ValueOf(aStartOffset));
+ GECKOBUNDLE_PUT(eventInfo, "toIndex", java::sdk::Integer::ValueOf(aEndOffset));
+ GECKOBUNDLE_FINISH(eventInfo);
+
+ mSessionAccessibility->SendEvent(
+ java::sdk::AccessibilityEvent::
+ TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY,
+ aAccessible->VirtualViewID(), eventInfo, aAccessible->ToBundle());
+}
+
+void
+SessionAccessibility::SendClickedEvent(AccessibleWrap* aAccessible)
+{
+ mSessionAccessibility->SendEvent(
+ java::sdk::AccessibilityEvent::TYPE_VIEW_CLICKED,
+ aAccessible->VirtualViewID(), nullptr, aAccessible->ToBundle());
+}
+
+void
+SessionAccessibility::SendSelectedEvent(AccessibleWrap* aAccessible)
+{
+ mSessionAccessibility->SendEvent(
+ java::sdk::AccessibilityEvent::TYPE_VIEW_SELECTED,
+ aAccessible->VirtualViewID(), nullptr, aAccessible->ToBundle());
+}
--- a/accessible/android/SessionAccessibility.h
+++ b/accessible/android/SessionAccessibility.h
@@ -2,21 +2,51 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_a11y_SessionAccessibility_h_
#define mozilla_a11y_SessionAccessibility_h_
#include "GeneratedJNINatives.h"
+#include "GeneratedJNIWrappers.h"
+#include "nsAppShell.h"
+#include "nsThreadUtils.h"
#include "nsWindow.h"
+#define GECKOBUNDLE_START(name) \
+ nsTArray<jni::String::LocalRef> _##name##_keys; \
+ nsTArray<jni::Object::LocalRef> _##name##_values;
+
+#define GECKOBUNDLE_PUT(name, key, value) \
+ _##name##_keys.AppendElement(jni::StringParam(NS_LITERAL_STRING(key))); \
+ _##name##_values.AppendElement(value);
+
+#define GECKOBUNDLE_FINISH(name) \
+ MOZ_ASSERT(_##name##_keys.Length() == _##name##_values.Length()); \
+ auto _##name##_jkeys = \
+ jni::ObjectArray::New<jni::String>(_##name##_keys.Length()); \
+ auto _##name##_jvalues = \
+ jni::ObjectArray::New<jni::Object>(_##name##_values.Length()); \
+ for (size_t i = 0; \
+ i < _##name##_keys.Length() && i < _##name##_values.Length(); \
+ i++) { \
+ _##name##_jkeys->SetElement(i, _##name##_keys.ElementAt(i)); \
+ _##name##_jvalues->SetElement(i, _##name##_values.ElementAt(i)); \
+ } \
+ auto name = \
+ mozilla::java::GeckoBundle::New(_##name##_jkeys, _##name##_jvalues);
+
namespace mozilla {
namespace a11y {
+class AccessibleWrap;
+class ProxyAccessible;
+class RootAccessibleWrap;
+
class SessionAccessibility final
: public java::SessionAccessibility::NativeProvider::Natives<SessionAccessibility>
{
public:
typedef java::SessionAccessibility::NativeProvider::Natives<SessionAccessibility> Base;
SessionAccessibility(
nsWindow::NativePtr<SessionAccessibility>* aPtr,
@@ -33,26 +63,59 @@ public:
SetAttached(false, std::move(aDisposer));
}
const java::SessionAccessibility::NativeProvider::Ref& GetJavaAccessibility()
{
return mSessionAccessibility;
}
+ static void Init();
+ static SessionAccessibility* GetInstanceFor(ProxyAccessible* aAccessible);
+ static SessionAccessibility* GetInstanceFor(Accessible* aAccessible);
+
// Native implementations
using Base::AttachNative;
using Base::DisposeNative;
+ jni::Object::LocalRef GetNodeInfo(int32_t aID);
+ void SetText(int32_t aID, jni::String::Param aText);
+ void StartNativeAccessibility();
- NS_INLINE_DECL_REFCOUNTING(SessionAccessibility)
+ // Event methods
+ void SendFocusEvent(AccessibleWrap* aAccessible);
+ void SendScrollingEvent(AccessibleWrap* aAccessible,
+ int32_t aScrollX,
+ int32_t aScrollY,
+ int32_t aMaxScrollX,
+ int32_t aMaxScrollY);
+ void SendAccessibilityFocusedEvent(AccessibleWrap* aAccessible);
+ void SendHoverEnterEvent(AccessibleWrap* aAccessible);
+ void SendTextSelectionChangedEvent(AccessibleWrap* aAccessible,
+ int32_t aCaretOffset);
+ void SendTextTraversedEvent(AccessibleWrap* aAccessible,
+ int32_t aStartOffset,
+ int32_t aEndOffset);
+ void SendTextChangedEvent(AccessibleWrap* aAccessible,
+ const nsString& aStr,
+ int32_t aStart,
+ uint32_t aLen,
+ bool aIsInsert,
+ bool aFromUser);
+ void SendSelectedEvent(AccessibleWrap* aAccessible);
+ void SendClickedEvent(AccessibleWrap* aAccessible);
+ void SendWindowContentChangedEvent(AccessibleWrap* aAccessible);
+ void SendWindowStateChangedEvent(AccessibleWrap* aAccessible);
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SessionAccessibility)
private:
~SessionAccessibility() {}
void SetAttached(bool aAttached, already_AddRefed<Runnable> aRunnable);
+ RootAccessibleWrap* GetRoot();
nsWindow::WindowPtr<SessionAccessibility> mWindow; // Parent only
java::SessionAccessibility::NativeProvider::GlobalRef mSessionAccessibility;
};
} // namespace a11y
} // namespace mozilla
--- a/accessible/android/moz.build
+++ b/accessible/android/moz.build
@@ -6,17 +6,20 @@
EXPORTS.mozilla.a11y += ['AccessibleWrap.h',
'HyperTextAccessibleWrap.h',
'SessionAccessibility.h',
]
SOURCES += [
'AccessibleWrap.cpp',
+ 'DocAccessibleWrap.cpp',
'Platform.cpp',
+ 'ProxyAccessibleWrap.cpp',
+ 'RootAccessibleWrap.cpp',
'SessionAccessibility.cpp',
]
LOCAL_INCLUDES += [
'/accessible/base',
'/accessible/generic',
'/accessible/html',
'/accessible/ipc',
--- a/accessible/generic/Accessible.h
+++ b/accessible/generic/Accessible.h
@@ -550,17 +550,17 @@ public:
/**
* Focus the accessible.
*/
virtual void TakeFocus() const;
/**
* Scroll the accessible into view.
*/
- void ScrollTo(uint32_t aHow) const;
+ virtual void ScrollTo(uint32_t aHow) const;
/**
* Scroll the accessible to the given point.
*/
void ScrollToPoint(uint32_t aCoordinateType, int32_t aX, int32_t aY);
/**
* Get a pointer to accessibility interface for this node, which is specific
--- a/accessible/ipc/ProxyAccessibleBase.h
+++ b/accessible/ipc/ProxyAccessibleBase.h
@@ -163,23 +163,24 @@ protected:
, mWrapper(0)
, mID(aID)
, mRole(aRole)
, mOuterDoc(false)
, mIsDoc(false)
, mHasValue(aInterfaces & Interfaces::VALUE)
, mIsHyperLink(aInterfaces & Interfaces::HYPERLINK)
, mIsHyperText(aInterfaces & Interfaces::HYPERTEXT)
+ , mIsSelection(aInterfaces & Interfaces::SELECTION)
{
}
explicit ProxyAccessibleBase(DocAccessibleParent* aThisAsDoc) :
mParent(kNoParent), mDoc(aThisAsDoc), mWrapper(0), mID(0),
mRole(roles::DOCUMENT), mOuterDoc(false), mIsDoc(true), mHasValue(false),
- mIsHyperLink(false), mIsHyperText(false)
+ mIsHyperLink(false), mIsHyperText(false), mIsSelection(false)
{}
protected:
void SetParent(Derived* aParent);
private:
uintptr_t mParent;
static const uintptr_t kNoParent = UINTPTR_MAX;
@@ -199,16 +200,17 @@ protected:
private:
bool mOuterDoc : 1;
public:
const bool mIsDoc: 1;
const bool mHasValue: 1;
const bool mIsHyperLink: 1;
const bool mIsHyperText: 1;
+ const bool mIsSelection: 1;
};
extern template class ProxyAccessibleBase<ProxyAccessible>;
}
}
#endif
--- a/accessible/jsat/AccessFu.jsm
+++ b/accessible/jsat/AccessFu.jsm
@@ -14,29 +14,29 @@ ChromeUtils.defineModuleGetter(this, "Re
if (Utils.MozBuildApp === "mobile/android") {
ChromeUtils.import("resource://gre/modules/Messaging.jsm");
}
const GECKOVIEW_MESSAGE = {
ACTIVATE: "GeckoView:AccessibilityActivate",
BY_GRANULARITY: "GeckoView:AccessibilityByGranularity",
CLIPBOARD: "GeckoView:AccessibilityClipboard",
+ CURSOR_TO_FOCUSED: "GeckoView:AccessibilityCursorToFocused",
EXPLORE_BY_TOUCH: "GeckoView:AccessibilityExploreByTouch",
LONG_PRESS: "GeckoView:AccessibilityLongPress",
NEXT: "GeckoView:AccessibilityNext",
PREVIOUS: "GeckoView:AccessibilityPrevious",
SCROLL_BACKWARD: "GeckoView:AccessibilityScrollBackward",
SCROLL_FORWARD: "GeckoView:AccessibilityScrollForward",
SELECT: "GeckoView:AccessibilitySelect",
SET_SELECTION: "GeckoView:AccessibilitySetSelection",
VIEW_FOCUSED: "GeckoView:AccessibilityViewFocused",
};
const ACCESSFU_MESSAGE = {
- PRESENT: "AccessFu:Present",
DOSCROLL: "AccessFu:DoScroll",
};
const FRAME_SCRIPT = "chrome://global/content/accessibility/content-script.js";
var AccessFu = {
/**
* A lazy getter for event handler that binds the scope to AccessFu object.
@@ -52,21 +52,16 @@ var AccessFu = {
*/
enable: function enable() {
if (this._enabled) {
return;
}
this._enabled = true;
ChromeUtils.import("resource://gre/modules/accessibility/Utils.jsm");
- ChromeUtils.import("resource://gre/modules/accessibility/Presentation.jsm");
-
- // Check for output notification
- this._notifyOutputPref =
- new PrefCache("accessibility.accessfu.notify_output");
Services.obs.addObserver(this, "remote-browser-shown");
Services.obs.addObserver(this, "inprocess-browser-shown");
Services.ww.registerNotification(this);
for (let win of Services.wm.getEnumerator(null)) {
this._attachWindow(win);
}
@@ -87,35 +82,30 @@ var AccessFu = {
Services.obs.removeObserver(this, "remote-browser-shown");
Services.obs.removeObserver(this, "inprocess-browser-shown");
Services.ww.unregisterNotification(this);
for (let win of Services.wm.getEnumerator(null)) {
this._detachWindow(win);
}
- delete this._notifyOutputPref;
-
if (this.doneCallback) {
this.doneCallback();
delete this.doneCallback;
}
Logger.info("AccessFu:Disabled");
},
receiveMessage: function receiveMessage(aMessage) {
Logger.debug(() => {
return ["Recieved", aMessage.name, JSON.stringify(aMessage.json)];
});
switch (aMessage.name) {
- case ACCESSFU_MESSAGE.PRESENT:
- this._output(aMessage.json, aMessage.target);
- break;
case ACCESSFU_MESSAGE.DOSCROLL:
this.Input.doScroll(aMessage.json, aMessage.target);
break;
}
},
_attachWindow: function _attachWindow(win) {
let wtype = win.document.documentElement.getAttribute("windowtype");
@@ -150,48 +140,16 @@ var AccessFu = {
win.removeEventListener("TabSelect", this);
if (win.WindowEventDispatcher) {
// desktop mochitests don't have this.
win.WindowEventDispatcher.unregisterListener(this,
Object.values(GECKOVIEW_MESSAGE));
}
},
- _output: function _output(aPresentationData, aBrowser) {
- if (!aPresentationData) {
- // Either no android events to send or a string used for testing only.
- return;
- }
-
- if (!Utils.isAliveAndVisible(Utils.AccService.getAccessibleFor(aBrowser))) {
- return;
- }
-
- let win = aBrowser.ownerGlobal;
-
- for (let evt of aPresentationData) {
- if (typeof evt == "string") {
- continue;
- }
-
- if (win.WindowEventDispatcher) {
- // desktop mochitests don't have this.
- win.WindowEventDispatcher.sendRequest({
- ...evt,
- type: "GeckoView:AccessibilityEvent"
- });
- }
- }
-
- if (this._notifyOutputPref.value) {
- Services.obs.notifyObservers(null, "accessibility-output",
- JSON.stringify(aPresentationData));
- }
- },
-
onEvent(event, data, callback) {
switch (event) {
case GECKOVIEW_MESSAGE.SETTINGS:
if (data.enabled) {
this._enable();
} else {
this._disable();
}
@@ -214,21 +172,18 @@ var AccessFu = {
// XXX: Advertize long press on supported objects and implement action
break;
case GECKOVIEW_MESSAGE.SCROLL_FORWARD:
this.Input.androidScroll("forward");
break;
case GECKOVIEW_MESSAGE.SCROLL_BACKWARD:
this.Input.androidScroll("backward");
break;
- case GECKOVIEW_MESSAGE.VIEW_FOCUSED:
- this._focused = data.gainFocus;
- if (this._focused) {
- this.autoMove({ forcePresent: true, noOpIfOnScreen: true });
- }
+ case GECKOVIEW_MESSAGE.CURSOR_TO_FOCUSED:
+ this.autoMove({ moveToFocused: true });
break;
case GECKOVIEW_MESSAGE.BY_GRANULARITY:
this.Input.moveByGranularity(data);
break;
case GECKOVIEW_MESSAGE.EXPLORE_BY_TOUCH:
this.Input.moveToPoint("Simple", ...data.coordinates);
break;
case GECKOVIEW_MESSAGE.SET_SELECTION:
@@ -276,20 +231,16 @@ var AccessFu = {
}
},
autoMove: function autoMove(aOptions) {
const mm = Utils.getMessageManager();
mm.sendAsyncMessage("AccessFu:AutoMove", aOptions);
},
- announce: function announce(aAnnouncement) {
- this._output(Presentation.announce(aAnnouncement), Utils.getCurrentBrowser());
- },
-
// So we don't enable/disable twice
_enabled: false,
// Layerview is focused
_focused: false,
/**
* Adjusts the given bounds that are defined in device display pixels
--- a/accessible/jsat/ContentControl.jsm
+++ b/accessible/jsat/ContentControl.jsm
@@ -9,18 +9,16 @@ ChromeUtils.defineModuleGetter(this, "Lo
ChromeUtils.defineModuleGetter(this, "Roles",
"resource://gre/modules/accessibility/Constants.jsm");
ChromeUtils.defineModuleGetter(this, "States",
"resource://gre/modules/accessibility/Constants.jsm");
ChromeUtils.defineModuleGetter(this, "TraversalRules",
"resource://gre/modules/accessibility/Traversal.jsm");
ChromeUtils.defineModuleGetter(this, "TraversalHelper",
"resource://gre/modules/accessibility/Traversal.jsm");
-ChromeUtils.defineModuleGetter(this, "Presentation",
- "resource://gre/modules/accessibility/Presentation.jsm");
var EXPORTED_SYMBOLS = ["ContentControl"];
const MOVEMENT_GRANULARITY_CHARACTER = 1;
const MOVEMENT_GRANULARITY_WORD = 2;
const MOVEMENT_GRANULARITY_LINE = 4;
const CLIPBOARD_COPY = 0x4000;
@@ -150,19 +148,16 @@ this.ContentControl.prototype = {
// new position.
this.sendToChild(vc, aMessage, { action: childAction }, true);
}
} else if (!this._childMessageSenders.has(aMessage.target) &&
origin !== "top") {
// We failed to move, and the message is not from a parent, so forward
// to it.
this.sendToParent(aMessage);
- } else {
- this._contentScope.get().sendAsyncMessage("AccessFu:Present",
- Presentation.noMove(action));
}
},
handleMoveToPoint: function cc_handleMoveToPoint(aMessage) {
let [x, y] = [aMessage.json.x, aMessage.json.y];
let rule = TraversalRules[aMessage.json.rule];
this.vc.moveToPoint(rule, x, y, true);
@@ -223,39 +218,28 @@ this.ContentControl.prototype = {
for (let eventType of ["mousedown", "mouseup"]) {
let evt = this.document.createEvent("MouseEvents");
evt.initMouseEvent(eventType, true, true, this.window,
x, y, 0, 0, 0, false, false, false, false, 0, null);
node.dispatchEvent(evt);
}
}
-
- // Action invoked will be presented on checked/selected state change.
- if (!Utils.getState(aAccessible).contains(States.CHECKABLE) &&
- !Utils.getState(aAccessible).contains(States.SELECTABLE)) {
- this._contentScope.get().sendAsyncMessage("AccessFu:Present",
- Presentation.actionInvoked());
- }
};
let focusedAcc = Utils.AccService.getAccessibleFor(
this.document.activeElement);
if (focusedAcc && this.vc.position === focusedAcc
&& focusedAcc.role === Roles.ENTRY) {
let accText = focusedAcc.QueryInterface(Ci.nsIAccessibleText);
- let oldOffset = accText.caretOffset;
let newOffset = aMessage.json.offset;
- let text = accText.getText(0, accText.characterCount);
-
if (newOffset >= 0 && newOffset <= accText.characterCount) {
accText.caretOffset = newOffset;
}
- this.presentCaretChange(text, oldOffset, accText.caretOffset);
return;
}
// recursively find a descendant that is activatable.
let getActivatableDescendant = (aAccessible) => {
if (aAccessible.actionCount > 0) {
return aAccessible;
}
@@ -394,25 +378,16 @@ this.ContentControl.prototype = {
if (startSel != endSel) {
editText.cutText(startSel, endSel);
}
break;
}
}
},
- presentCaretChange: function cc_presentCaretChange(
- aText, aOldOffset, aNewOffset) {
- if (aOldOffset !== aNewOffset) {
- let msg = Presentation.textSelectionChanged(aText, aNewOffset, aNewOffset,
- aOldOffset, aOldOffset, true);
- this._contentScope.get().sendAsyncMessage("AccessFu:Present", msg);
- }
- },
-
getChildCursor: function cc_getChildCursor(aAccessible) {
let acc = aAccessible || this.vc.position;
if (Utils.isAliveAndVisible(acc) && acc.role === Roles.INTERNAL_FRAME) {
let domNode = acc.DOMNode;
let mm = this._childMessageSenders.get(domNode, null);
if (!mm) {
mm = Utils.getMessageManager(domNode);
mm.addWeakMessageListener("AccessFu:MoveCursor", this);
@@ -451,48 +426,37 @@ this.ContentControl.prototype = {
sendToParent: function cc_sendToParent(aMessage) {
// XXX: This is a silly way to make a deep copy
let newJSON = JSON.parse(JSON.stringify(aMessage.json));
newJSON.origin = "child";
aMessage.target.sendAsyncMessage(aMessage.name, newJSON);
},
/**
- * Move cursor and/or present its location.
+ * Move cursor.
* aOptions could have any of these fields:
* - delay: in ms, before actual move is performed. Another autoMove call
* would cancel it. Useful if we want to wait for a possible trailing
* focus move. Default 0.
* - noOpIfOnScreen: if accessible is alive and visible, don't do anything.
- * - forcePresent: present cursor location, whether we move or don't.
* - moveToFocused: if there is a focused accessible move to that. This takes
* precedence over given anchor.
* - moveMethod: pivot move method to use, default is 'moveNext',
*/
autoMove: function cc_autoMove(aAnchor, aOptions = {}) {
this.cancelAutoMove();
let moveFunc = () => {
let vc = this.vc;
let acc = aAnchor;
let rule = aOptions.onScreenOnly ?
TraversalRules.SimpleOnScreen : TraversalRules.Simple;
- let forcePresentFunc = () => {
- if (aOptions.forcePresent) {
- this._contentScope.get().sendAsyncMessage(
- "AccessFu:Present", Presentation.pivotChanged(
- vc.position, null, vc.startOffset, vc.endOffset,
- Ci.nsIAccessiblePivot.REASON_NONE,
- Ci.nsIAccessiblePivot.NO_BOUNDARY));
- }
- };
if (aOptions.noOpIfOnScreen &&
Utils.isAliveAndVisible(vc.position, true)) {
- forcePresentFunc();
return;
}
if (aOptions.moveToFocused) {
acc = Utils.AccService.getAccessibleFor(
this.document.activeElement) || acc;
}
@@ -504,29 +468,24 @@ this.ContentControl.prototype = {
moved = vc[moveFirstOrLast ? "moveNext" : moveMethod](rule, acc, true,
true);
}
if (moveFirstOrLast && !moved) {
// We move to first/last after no anchor move happened or succeeded.
moved = vc[moveMethod](rule, true);
}
- let sentToChild = this.sendToChild(vc, {
+ this.sendToChild(vc, {
name: "AccessFu:AutoMove",
json: {
moveMethod: aOptions.moveMethod,
moveToFocused: aOptions.moveToFocused,
noOpIfOnScreen: true,
- forcePresent: true
}
}, null, true);
-
- if (!moved && !sentToChild) {
- forcePresentFunc();
- }
};
if (aOptions.delay) {
this._autoMove = this.window.setTimeout(moveFunc, aOptions.delay);
} else {
moveFunc();
}
},
--- a/accessible/jsat/EventManager.jsm
+++ b/accessible/jsat/EventManager.jsm
@@ -5,125 +5,73 @@
"use strict";
ChromeUtils.defineModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
ChromeUtils.defineModuleGetter(this, "Utils",
"resource://gre/modules/accessibility/Utils.jsm");
ChromeUtils.defineModuleGetter(this, "Logger",
"resource://gre/modules/accessibility/Utils.jsm");
-ChromeUtils.defineModuleGetter(this, "Presentation",
- "resource://gre/modules/accessibility/Presentation.jsm");
ChromeUtils.defineModuleGetter(this, "Roles",
"resource://gre/modules/accessibility/Constants.jsm");
ChromeUtils.defineModuleGetter(this, "Events",
"resource://gre/modules/accessibility/Constants.jsm");
ChromeUtils.defineModuleGetter(this, "States",
"resource://gre/modules/accessibility/Constants.jsm");
-ChromeUtils.defineModuleGetter(this, "clearTimeout",
- "resource://gre/modules/Timer.jsm");
-ChromeUtils.defineModuleGetter(this, "setTimeout",
- "resource://gre/modules/Timer.jsm");
var EXPORTED_SYMBOLS = ["EventManager"];
function EventManager(aContentScope) {
this.contentScope = aContentScope;
this.addEventListener = this.contentScope.addEventListener.bind(
this.contentScope);
this.removeEventListener = this.contentScope.removeEventListener.bind(
this.contentScope);
this.sendMsgFunc = this.contentScope.sendAsyncMessage.bind(
this.contentScope);
- this.webProgress = this.contentScope.docShell.
- QueryInterface(Ci.nsIInterfaceRequestor).
- getInterface(Ci.nsIWebProgress);
}
this.EventManager.prototype = {
start: function start() {
try {
if (!this._started) {
Logger.debug("EventManager.start");
this._started = true;
AccessibilityEventObserver.addListener(this);
- this.webProgress.addProgressListener(this,
- (Ci.nsIWebProgress.NOTIFY_STATE_ALL |
- Ci.nsIWebProgress.NOTIFY_LOCATION));
- this.addEventListener("wheel", this, true);
- this.addEventListener("scroll", this, true);
- this.addEventListener("resize", this, true);
this._preDialogPosition = new WeakMap();
}
- this.present(Presentation.tabStateChanged(null, "newtab"));
-
} catch (x) {
Logger.logException(x, "Failed to start EventManager");
}
},
// XXX: Stop is not called when the tab is closed (|TabClose| event is too
// late). It is only called when the AccessFu is disabled explicitly.
stop: function stop() {
if (!this._started) {
return;
}
Logger.debug("EventManager.stop");
AccessibilityEventObserver.removeListener(this);
try {
this._preDialogPosition = new WeakMap();
- this.webProgress.removeProgressListener(this);
- this.removeEventListener("wheel", this, true);
- this.removeEventListener("scroll", this, true);
- this.removeEventListener("resize", this, true);
} catch (x) {
// contentScope is dead.
} finally {
this._started = false;
}
},
get contentControl() {
return this.contentScope._jsat_contentControl;
},
- handleEvent: function handleEvent(aEvent) {
- Logger.debug(() => {
- return ["DOMEvent", aEvent.type];
- });
-
- // The target could be an element, document or window
- const win = aEvent.target.ownerGlobal;
- try {
- switch (aEvent.type) {
- case "wheel":
- {
- let delta = aEvent.deltaX || aEvent.deltaY;
- this.contentControl.autoMove(
- null,
- { moveMethod: delta > 0 ? "moveNext" : "movePrevious",
- onScreenOnly: true, noOpIfOnScreen: true, delay: 500 });
- break;
- }
- case "scroll":
- this.present(Presentation.viewportScrolled(win));
- case "resize":
- {
- this.present(Presentation.viewportChanged(win));
- break;
- }
- }
- } catch (x) {
- Logger.logException(x, "Error handling DOM event");
- }
- },
-
handleAccEvent: function handleAccEvent(aEvent) {
Logger.debug(() => {
return ["A11yEvent", Logger.eventToString(aEvent),
Logger.accessibleToString(aEvent.accessible)];
});
// Don't bother with non-content events in firefox.
if (Utils.MozBuildApp == "browser" &&
@@ -151,358 +99,47 @@ this.EventManager.prototype = {
if (position && position.role == Roles.INTERNAL_FRAME) {
break;
}
// Blur to document if new position is not explicitly focused.
if (!position || !Utils.getState(position).contains(States.FOCUSED)) {
aEvent.accessibleDocument.takeFocus();
}
-
- this.present(
- Presentation.pivotChanged(position, event.oldAccessible,
- event.newStartOffset, event.newEndOffset,
- event.reason, event.boundaryType));
-
- break;
- }
- case Events.STATE_CHANGE:
- {
- const event = aEvent.QueryInterface(Ci.nsIAccessibleStateChangeEvent);
- const state = Utils.getState(event);
- if (state.contains(States.CHECKED)) {
- this.present(Presentation.checked(aEvent.accessible));
- } else if (state.contains(States.SELECTED)) {
- this.present(Presentation.selected(aEvent.accessible));
- }
break;
}
case Events.NAME_CHANGE:
{
- let acc = aEvent.accessible;
- if (acc === this.contentControl.vc.position) {
- this.present(Presentation.nameChanged(acc));
- } else {
- let {liveRegion, isPolite} = this._handleLiveRegion(aEvent,
- ["text", "all"]);
- if (liveRegion) {
- this.present(Presentation.nameChanged(acc, isPolite));
- }
- }
+ // XXX: Port to Android
break;
}
case Events.SCROLLING_START:
{
this.contentControl.autoMove(aEvent.accessible);
break;
}
- case Events.TEXT_CARET_MOVED:
- {
- let acc = aEvent.accessible.QueryInterface(Ci.nsIAccessibleText);
- let caretOffset = aEvent.
- QueryInterface(Ci.nsIAccessibleCaretMoveEvent).caretOffset;
-
- // We could get a caret move in an accessible that is not focused,
- // it doesn't mean we are not on any editable accessible. just not
- // on this one..
- let state = Utils.getState(acc);
- if (state.contains(States.FOCUSED) && state.contains(States.EDITABLE)) {
- let fromIndex = caretOffset;
- if (acc.selectionCount) {
- const [startSel, endSel] = Utils.getTextSelection(acc);
- fromIndex = startSel == caretOffset ? endSel : startSel;
- }
- this.present(Presentation.textSelectionChanged(
- acc.getText(0, -1), fromIndex, caretOffset, 0, 0,
- aEvent.isFromUserInput));
- }
- break;
- }
case Events.SHOW:
{
- this._handleShow(aEvent);
+ // XXX: Port to Android
break;
}
case Events.HIDE:
{
- let evt = aEvent.QueryInterface(Ci.nsIAccessibleHideEvent);
- this._handleHide(evt);
- break;
- }
- case Events.TEXT_INSERTED:
- case Events.TEXT_REMOVED:
- {
- let {liveRegion, isPolite} = this._handleLiveRegion(aEvent,
- ["text", "all"]);
- if (aEvent.isFromUserInput || liveRegion) {
- // Handle all text mutations coming from the user or if they happen
- // on a live region.
- this._handleText(aEvent, liveRegion, isPolite);
- }
+ // XXX: Port to Android
break;
}
- case Events.FOCUS:
- {
- // Put vc where the focus is at
- let acc = aEvent.accessible;
- if (![Roles.CHROME_WINDOW,
- Roles.DOCUMENT,
- Roles.APPLICATION].includes(acc.role)) {
- this.contentControl.autoMove(acc);
- }
-
- this.present(Presentation.focused(acc));
-
- if (Utils.inTest) {
- this.sendMsgFunc("AccessFu:Focused");
- }
- break;
- }
- case Events.DOCUMENT_LOAD_COMPLETE:
- {
- let position = this.contentControl.vc.position;
- // Check if position is in the subtree of the DOCUMENT_LOAD_COMPLETE
- // event's dialog accessible or accessible document
- let subtreeRoot = aEvent.accessible.role === Roles.DIALOG ?
- aEvent.accessible : aEvent.accessibleDocument;
- if (aEvent.accessible === aEvent.accessibleDocument ||
- (position && Utils.isInSubtree(position, subtreeRoot))) {
- // Do not automove into the document if the virtual cursor is already
- // positioned inside it.
- break;
- }
- this._preDialogPosition.set(aEvent.accessible.DOMNode, position);
- this.contentControl.autoMove(aEvent.accessible, { delay: 500 });
- break;
- }
- case Events.TEXT_VALUE_CHANGE:
- // We handle this events in TEXT_INSERTED/TEXT_REMOVED.
- break;
case Events.VALUE_CHANGE:
{
- let position = this.contentControl.vc.position;
- let target = aEvent.accessible;
- if (position === target ||
- Utils.getEmbeddedControl(position) === target) {
- this.present(Presentation.valueChanged(target));
- } else {
- let {liveRegion, isPolite} = this._handleLiveRegion(aEvent,
- ["text", "all"]);
- if (liveRegion) {
- this.present(Presentation.valueChanged(target, isPolite));
- }
- }
- }
- }
- },
-
- _handleShow: function _handleShow(aEvent) {
- let {liveRegion, isPolite} = this._handleLiveRegion(aEvent,
- ["additions", "all"]);
- // Only handle show if it is a relevant live region.
- if (!liveRegion) {
- return;
- }
- // Show for text is handled by the EVENT_TEXT_INSERTED handler.
- if (aEvent.accessible.role === Roles.TEXT_LEAF) {
- return;
- }
- this._dequeueLiveEvent(Events.HIDE, liveRegion);
- this.present(Presentation.liveRegion(liveRegion, isPolite, false));
- },
-
- _handleHide: function _handleHide(aEvent) {
- let {liveRegion, isPolite} = this._handleLiveRegion(
- aEvent, ["removals", "all"]);
- let acc = aEvent.accessible;
- if (liveRegion) {
- // Hide for text is handled by the EVENT_TEXT_REMOVED handler.
- if (acc.role === Roles.TEXT_LEAF) {
- return;
- }
- this._queueLiveEvent(Events.HIDE, liveRegion, isPolite);
- } else {
- let vc = Utils.getVirtualCursor(this.contentScope.content.document);
- if (vc.position &&
- (Utils.getState(vc.position).contains(States.DEFUNCT) ||
- Utils.isInSubtree(vc.position, acc))) {
- let position = this._preDialogPosition.get(aEvent.accessible.DOMNode) ||
- aEvent.targetPrevSibling || aEvent.targetParent;
- if (!position) {
- try {
- position = acc.previousSibling;
- } catch (x) {
- // Accessible is unattached from the accessible tree.
- position = acc.parent;
- }
- }
- this.contentControl.autoMove(position,
- { moveToFocused: true, delay: 500 });
+ // XXX: Port to Android
+ break;
}
}
},
- _handleText: function _handleText(aEvent, aLiveRegion, aIsPolite) {
- let event = aEvent.QueryInterface(Ci.nsIAccessibleTextChangeEvent);
- let isInserted = event.isInserted;
- let txtIface = aEvent.accessible.QueryInterface(Ci.nsIAccessibleText);
-
- let text = "";
- try {
- text = txtIface.getText(0, Ci.nsIAccessibleText.TEXT_OFFSET_END_OF_TEXT);
- } catch (x) {
- // XXX we might have gotten an exception with of a
- // zero-length text. If we did, ignore it (bug #749810).
- if (txtIface.characterCount) {
- throw x;
- }
- }
- // If there are embedded objects in the text, ignore them.
- // Assuming changes to the descendants would already be handled by the
- // show/hide event.
- let modifiedText = event.modifiedText.replace(/\uFFFC/g, "");
- if (modifiedText != event.modifiedText && !modifiedText.trim()) {
- return;
- }
-
- if (aLiveRegion) {
- if (aEvent.eventType === Events.TEXT_REMOVED) {
- this._queueLiveEvent(Events.TEXT_REMOVED, aLiveRegion, aIsPolite,
- modifiedText);
- } else {
- this._dequeueLiveEvent(Events.TEXT_REMOVED, aLiveRegion);
- this.present(Presentation.liveRegion(aLiveRegion, aIsPolite, false,
- modifiedText));
- }
- } else {
- this.present(Presentation.textChanged(aEvent.accessible, isInserted,
- event.start, event.length, text, modifiedText));
- }
- },
-
- _handleLiveRegion: function _handleLiveRegion(aEvent, aRelevant) {
- if (aEvent.isFromUserInput) {
- return {};
- }
- let parseLiveAttrs = function parseLiveAttrs(aAccessible) {
- let attrs = Utils.getAttributes(aAccessible);
- if (attrs["container-live"]) {
- return {
- live: attrs["container-live"],
- relevant: attrs["container-relevant"] || "additions text",
- busy: attrs["container-busy"],
- atomic: attrs["container-atomic"],
- memberOf: attrs["member-of"]
- };
- }
- return null;
- };
- // XXX live attributes are not set for hidden accessibles yet. Need to
- // climb up the tree to check for them.
- let getLiveAttributes = function getLiveAttributes(aEvent) {
- let liveAttrs = parseLiveAttrs(aEvent.accessible);
- if (liveAttrs) {
- return liveAttrs;
- }
- let parent = aEvent.targetParent;
- while (parent) {
- liveAttrs = parseLiveAttrs(parent);
- if (liveAttrs) {
- return liveAttrs;
- }
- parent = parent.parent;
- }
- return {};
- };
- let {live, relevant, /* busy, atomic, memberOf */ } = getLiveAttributes(aEvent);
- // If container-live is not present or is set to |off| ignore the event.
- if (!live || live === "off") {
- return {};
- }
- // XXX: support busy and atomic.
-
- // Determine if the type of the mutation is relevant. Default is additions
- // and text.
- let isRelevant = Utils.matchAttributeValue(relevant, aRelevant);
- if (!isRelevant) {
- return {};
- }
- return {
- liveRegion: aEvent.accessible,
- isPolite: live === "polite"
- };
- },
-
- _dequeueLiveEvent: function _dequeueLiveEvent(aEventType, aLiveRegion) {
- let domNode = aLiveRegion.DOMNode;
- if (this._liveEventQueue && this._liveEventQueue.has(domNode)) {
- let queue = this._liveEventQueue.get(domNode);
- let nextEvent = queue[0];
- if (nextEvent.eventType === aEventType) {
- clearTimeout(nextEvent.timeoutID);
- queue.shift();
- if (queue.length === 0) {
- this._liveEventQueue.delete(domNode);
- }
- }
- }
- },
-
- _queueLiveEvent: function _queueLiveEvent(aEventType, aLiveRegion, aIsPolite, aModifiedText) {
- if (!this._liveEventQueue) {
- this._liveEventQueue = new WeakMap();
- }
- let eventHandler = {
- eventType: aEventType,
- timeoutID: setTimeout(this.present.bind(this),
- 20, // Wait for a possible EVENT_SHOW or EVENT_TEXT_INSERTED event.
- Presentation.liveRegion(aLiveRegion, aIsPolite, true, aModifiedText))
- };
-
- let domNode = aLiveRegion.DOMNode;
- if (this._liveEventQueue.has(domNode)) {
- this._liveEventQueue.get(domNode).push(eventHandler);
- } else {
- this._liveEventQueue.set(domNode, [eventHandler]);
- }
- },
-
- present: function present(aPresentationData) {
- if (aPresentationData && aPresentationData.length > 0) {
- this.sendMsgFunc("AccessFu:Present", aPresentationData);
- }
- },
-
- onStateChange: function onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
- let tabstate = "";
-
- let loadingState = Ci.nsIWebProgressListener.STATE_TRANSFERRING |
- Ci.nsIWebProgressListener.STATE_IS_DOCUMENT;
- let loadedState = Ci.nsIWebProgressListener.STATE_STOP |
- Ci.nsIWebProgressListener.STATE_IS_NETWORK;
-
- if ((aStateFlags & loadingState) == loadingState) {
- tabstate = "loading";
- } else if ((aStateFlags & loadedState) == loadedState &&
- !aWebProgress.isLoadingDocument) {
- tabstate = "loaded";
- }
-
- if (tabstate) {
- let docAcc = Utils.AccService.getAccessibleFor(aWebProgress.DOMWindow.document);
- this.present(Presentation.tabStateChanged(docAcc, tabstate));
- }
- },
-
- onLocationChange: function onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
- let docAcc = Utils.AccService.getAccessibleFor(aWebProgress.DOMWindow.document);
- this.present(Presentation.tabStateChanged(docAcc, "newdoc"));
- },
-
- QueryInterface: ChromeUtils.generateQI([Ci.nsIWebProgressListener, Ci.nsISupportsWeakReference, Ci.nsIObserver])
+ QueryInterface: ChromeUtils.generateQI([Ci.nsISupportsWeakReference, Ci.nsIObserver])
};
const AccessibilityEventObserver = {
/**
* A WeakMap containing [content, EventManager] pairs.
*/
eventManagers: new WeakMap(),
deleted file mode 100644
--- a/accessible/jsat/OutputGenerator.jsm
+++ /dev/null
@@ -1,824 +0,0 @@
-/* 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/. */
-
-/* exported UtteranceGenerator */
-
-"use strict";
-
-const INCLUDE_DESC = 0x01;
-const INCLUDE_NAME = 0x02;
-const INCLUDE_VALUE = 0x04;
-const NAME_FROM_SUBTREE_RULE = 0x10;
-const IGNORE_EXPLICIT_NAME = 0x20;
-
-const OUTPUT_DESC_FIRST = 0;
-const OUTPUT_DESC_LAST = 1;
-
-ChromeUtils.defineModuleGetter(this, "Utils", // jshint ignore:line
- "resource://gre/modules/accessibility/Utils.jsm");
-ChromeUtils.defineModuleGetter(this, "PrefCache", // jshint ignore:line
- "resource://gre/modules/accessibility/Utils.jsm");
-ChromeUtils.defineModuleGetter(this, "Logger", // jshint ignore:line
- "resource://gre/modules/accessibility/Utils.jsm");
-ChromeUtils.defineModuleGetter(this, "Roles", // jshint ignore:line
- "resource://gre/modules/accessibility/Constants.jsm");
-ChromeUtils.defineModuleGetter(this, "States", // jshint ignore:line
- "resource://gre/modules/accessibility/Constants.jsm");
-
-var EXPORTED_SYMBOLS = ["UtteranceGenerator"]; // jshint ignore:line
-
-var OutputGenerator = {
-
- defaultOutputOrder: OUTPUT_DESC_LAST,
-
- /**
- * Generates output for a PivotContext.
- * @param {PivotContext} aContext object that generates and caches
- * context information for a given accessible and its relationship with
- * another accessible.
- * @return {Object} An array of speech data. Depending on the utterance order,
- * the data describes the context for an accessible object either
- * starting from the accessible's ancestry or accessible's subtree.
- */
- genForContext: function genForContext(aContext) {
- let output = [];
- let self = this;
- let addOutput = function addOutput(aAccessible) {
- output.push.apply(output, self.genForObject(aAccessible, aContext));
- };
- let ignoreSubtree = function ignoreSubtree(aAccessible) {
- let roleString = Utils.AccService.getStringRole(aAccessible.role);
- let nameRule = self.roleRuleMap[roleString] || 0;
- // Ignore subtree if the name is explicit and the role's name rule is the
- // NAME_FROM_SUBTREE_RULE.
- return (((nameRule & INCLUDE_VALUE) && aAccessible.value) ||
- ((nameRule & NAME_FROM_SUBTREE_RULE) &&
- (Utils.getAttributes(aAccessible)["explicit-name"] === "true" &&
- !(nameRule & IGNORE_EXPLICIT_NAME))));
- };
-
- let contextStart = this._getContextStart(aContext);
-
- if (this.outputOrder === OUTPUT_DESC_FIRST) {
- contextStart.forEach(addOutput);
- addOutput(aContext.accessible);
- for (let node of aContext.subtreeGenerator(true, ignoreSubtree)) {
- addOutput(node);
- }
- } else {
- for (let node of aContext.subtreeGenerator(false, ignoreSubtree)) {
- addOutput(node);
- }
- addOutput(aContext.accessible);
-
- // If there are any documents in new ancestry, find a first one and place
- // it in the beginning of the utterance.
- let doc, docIndex = contextStart.findIndex(
- ancestor => ancestor.role === Roles.DOCUMENT);
-
- if (docIndex > -1) {
- doc = contextStart.splice(docIndex, 1)[0];
- }
-
- contextStart.reverse().forEach(addOutput);
- if (doc) {
- output.unshift.apply(output, self.genForObject(doc, aContext));
- }
- }
-
- return output;
- },
-
-
- /**
- * Generates output for an object.
- * @param {nsIAccessible} aAccessible accessible object to generate output
- * for.
- * @param {PivotContext} aContext object that generates and caches
- * context information for a given accessible and its relationship with
- * another accessible.
- * @return {Array} A 2 element array of speech data. The first element
- * describes the object and its state. The second element is the object's
- * name. Whether the object's description or it's role is included is
- * determined by {@link roleRuleMap}.
- */
- genForObject: function genForObject(aAccessible, aContext) {
- let roleString = Utils.AccService.getStringRole(aAccessible.role);
- let func = this.objectOutputFunctions[
- OutputGenerator._getOutputName(roleString)] ||
- this.objectOutputFunctions.defaultFunc;
-
- let flags = this.roleRuleMap[roleString] || 0;
-
- if (aAccessible.childCount === 0) {
- flags |= INCLUDE_NAME;
- }
-
- return func.apply(this, [aAccessible, roleString,
- Utils.getState(aAccessible), flags, aContext]);
- },
-
- /**
- * Generates output for an action performed.
- * @param {nsIAccessible} aAccessible accessible object that the action was
- * invoked in.
- * @param {string} aActionName the name of the action, one of the keys in
- * {@link gActionMap}.
- * @return {Array} A one element array with action data.
- */
- genForAction: function genForAction(aObject, aActionName) {}, // jshint ignore:line
-
- /**
- * Generates output for an announcement.
- * @param {string} aAnnouncement unlocalized announcement.
- * @return {Array} An announcement speech data to be localized.
- */
- genForAnnouncement: function genForAnnouncement(aAnnouncement) {}, // jshint ignore:line
-
- /**
- * Generates output for a tab state change.
- * @param {nsIAccessible} aAccessible accessible object of the tab's attached
- * document.
- * @param {string} aTabState the tab state name, see
- * {@link Presenter.tabStateChanged}.
- * @return {Array} The tab state utterace.
- */
- genForTabStateChange: function genForTabStateChange(aObject, aTabState) {}, // jshint ignore:line
-
- /**
- * Generates output for announcing entering and leaving editing mode.
- * @param {aIsEditing} boolean true if we are in editing mode
- * @return {Array} The mode utterance
- */
- genForEditingMode: function genForEditingMode(aIsEditing) {}, // jshint ignore:line
-
- _getContextStart: function getContextStart(aContext) {}, // jshint ignore:line
-
- /**
- * Adds an accessible name and description to the output if available.
- * @param {Array} aOutput Output array.
- * @param {nsIAccessible} aAccessible current accessible object.
- * @param {Number} aFlags output flags.
- */
- _addName: function _addName(aOutput, aAccessible, aFlags) {
- let name;
- if ((Utils.getAttributes(aAccessible)["explicit-name"] === "true" &&
- !(aFlags & IGNORE_EXPLICIT_NAME)) || (aFlags & INCLUDE_NAME)) {
- name = aAccessible.name;
- }
-
- let description = aAccessible.description;
- if (description) {
- // Compare against the calculated name unconditionally, regardless of name rule,
- // so we can make sure we don't speak duplicated descriptions
- let tmpName = name || aAccessible.name;
- if (tmpName && (description !== tmpName)) {
- if (name) {
- name = this.outputOrder === OUTPUT_DESC_FIRST ?
- description + " - " + name :
- name + " - " + description;
- } else {
- name = description;
- }
- }
- }
-
- if (!name || !name.trim()) {
- return;
- }
- aOutput[this.outputOrder === OUTPUT_DESC_FIRST ? "push" : "unshift"](name);
- },
-
- /**
- * Adds a landmark role to the output if available.
- * @param {Array} aOutput Output array.
- * @param {nsIAccessible} aAccessible current accessible object.
- */
- _addLandmark: function _addLandmark(aOutput, aAccessible) {
- let landmarkName = Utils.getLandmarkName(aAccessible);
- if (!landmarkName) {
- return;
- }
- aOutput[this.outputOrder === OUTPUT_DESC_FIRST ? "unshift" : "push"]({
- string: landmarkName
- });
- },
-
- /**
- * Adds math roles to the output, for a MathML accessible.
- * @param {Array} aOutput Output array.
- * @param {nsIAccessible} aAccessible current accessible object.
- * @param {String} aRoleStr aAccessible's role string.
- */
- _addMathRoles: function _addMathRoles(aOutput, aAccessible, aRoleStr) {
- // First, determine the actual role to use (e.g. mathmlfraction).
- let roleStr = aRoleStr;
- switch (aAccessible.role) {
- case Roles.MATHML_CELL:
- case Roles.MATHML_ENCLOSED:
- case Roles.MATHML_LABELED_ROW:
- case Roles.MATHML_ROOT:
- case Roles.MATHML_SQUARE_ROOT:
- case Roles.MATHML_TABLE:
- case Roles.MATHML_TABLE_ROW:
- // Use the default role string.
- break;
- case Roles.MATHML_MULTISCRIPTS:
- case Roles.MATHML_OVER:
- case Roles.MATHML_SUB:
- case Roles.MATHML_SUB_SUP:
- case Roles.MATHML_SUP:
- case Roles.MATHML_UNDER:
- case Roles.MATHML_UNDER_OVER:
- // For scripted accessibles, use the string 'mathmlscripted'.
- roleStr = "mathmlscripted";
- break;
- case Roles.MATHML_FRACTION:
- // From a semantic point of view, the only important point is to
- // distinguish between fractions that have a bar and those that do not.
- // Per the MathML 3 spec, the latter happens iff the linethickness
- // attribute is of the form [zero-float][optional-unit]. In that case,
- // we use the string 'mathmlfractionwithoutbar'.
- let linethickness = Utils.getAttributes(aAccessible).linethickness;
- if (linethickness) {
- let numberMatch = linethickness.match(/^(?:\d|\.)+/);
- if (numberMatch && !parseFloat(numberMatch[0])) {
- roleStr += "withoutbar";
- }
- }
- break;
- default:
- // Otherwise, do not output the actual role.
- roleStr = null;
- break;
- }
-
- // Get the math role based on the position in the parent accessible
- // (e.g. numerator for the first child of a mathmlfraction).
- let mathRole = Utils.getMathRole(aAccessible);
- if (mathRole) {
- aOutput[this.outputOrder === OUTPUT_DESC_FIRST ? "push" : "unshift"]({
- string: this._getOutputName(mathRole)});
- }
- if (roleStr) {
- aOutput[this.outputOrder === OUTPUT_DESC_FIRST ? "push" : "unshift"]({
- string: this._getOutputName(roleStr)});
- }
- },
-
- /**
- * Adds MathML menclose notations to the output.
- * @param {Array} aOutput Output array.
- * @param {nsIAccessible} aAccessible current accessible object.
- */
- _addMencloseNotations: function _addMencloseNotations(aOutput, aAccessible) {
- let notations = Utils.getAttributes(aAccessible).notation || "longdiv";
- aOutput[this.outputOrder === OUTPUT_DESC_FIRST ? "push" : "unshift"].apply(
- aOutput, notations.split(" ").map(notation => {
- return { string: this._getOutputName("notation-" + notation) };
- }));
- },
-
- /**
- * Adds an entry type attribute to the description if available.
- * @param {Array} aOutput Output array.
- * @param {nsIAccessible} aAccessible current accessible object.
- * @param {String} aRoleStr aAccessible's role string.
- */
- _addType: function _addType(aOutput, aAccessible, aRoleStr) {
- if (aRoleStr !== "entry") {
- return;
- }
-
- let typeName = Utils.getAttributes(aAccessible)["text-input-type"];
- // Ignore the the input type="text" case.
- if (!typeName || typeName === "text") {
- return;
- }
- aOutput.push({string: "textInputType_" + typeName});
- },
-
- _addState: function _addState(aOutput, aState, aRoleStr) {}, // jshint ignore:line
-
- _addRole: function _addRole(aOutput, aAccessible, aRoleStr) {}, // jshint ignore:line
-
- get outputOrder() {
- if (!this._utteranceOrder) {
- this._utteranceOrder = new PrefCache("accessibility.accessfu.utterance");
- }
- return typeof this._utteranceOrder.value === "number" ?
- this._utteranceOrder.value : this.defaultOutputOrder;
- },
-
- _getOutputName: function _getOutputName(aName) {
- return aName.replace(/\s/g, "");
- },
-
- roleRuleMap: {
- "menubar": INCLUDE_DESC,
- "scrollbar": INCLUDE_DESC,
- "grip": INCLUDE_DESC,
- "alert": INCLUDE_DESC | INCLUDE_NAME,
- "menupopup": INCLUDE_DESC,
- "menuitem": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
- "tooltip": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
- "columnheader": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
- "rowheader": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
- "column": NAME_FROM_SUBTREE_RULE,
- "row": NAME_FROM_SUBTREE_RULE,
- "cell": INCLUDE_DESC | INCLUDE_NAME,
- "application": INCLUDE_NAME,
- "document": INCLUDE_NAME | NAME_FROM_SUBTREE_RULE, // don't use the subtree of entire document
- "grouping": INCLUDE_DESC | INCLUDE_NAME,
- "toolbar": INCLUDE_DESC,
- "table": INCLUDE_DESC | INCLUDE_NAME,
- "link": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
- "helpballoon": NAME_FROM_SUBTREE_RULE,
- "list": INCLUDE_DESC | INCLUDE_NAME,
- "listitem": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
- "outline": INCLUDE_DESC,
- "outlineitem": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
- "pagetab": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
- "graphic": INCLUDE_DESC,
- "switch": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
- "pushbutton": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
- "checkbutton": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
- "radiobutton": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
- "buttondropdown": NAME_FROM_SUBTREE_RULE,
- "combobox": INCLUDE_DESC | INCLUDE_VALUE,
- "droplist": INCLUDE_DESC,
- "progressbar": INCLUDE_DESC | INCLUDE_VALUE,
- "slider": INCLUDE_DESC | INCLUDE_VALUE,
- "spinbutton": INCLUDE_DESC | INCLUDE_VALUE,
- "diagram": INCLUDE_DESC,
- "animation": INCLUDE_DESC,
- "equation": INCLUDE_DESC,
- "buttonmenu": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
- "buttondropdowngrid": NAME_FROM_SUBTREE_RULE,
- "pagetablist": INCLUDE_DESC,
- "canvas": INCLUDE_DESC,
- "check menu item": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
- "label": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
- "password text": INCLUDE_DESC,
- "popup menu": INCLUDE_DESC,
- "radio menu item": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
- "table column header": NAME_FROM_SUBTREE_RULE,
- "table row header": NAME_FROM_SUBTREE_RULE,
- "tear off menu item": NAME_FROM_SUBTREE_RULE,
- "toggle button": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
- "parent menuitem": NAME_FROM_SUBTREE_RULE,
- "header": INCLUDE_DESC,
- "footer": INCLUDE_DESC,
- "entry": INCLUDE_DESC | INCLUDE_NAME | INCLUDE_VALUE,
- "caption": INCLUDE_DESC,
- "document frame": INCLUDE_DESC,
- "heading": INCLUDE_DESC,
- "calendar": INCLUDE_DESC | INCLUDE_NAME,
- "combobox option": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
- "listbox option": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
- "listbox rich option": NAME_FROM_SUBTREE_RULE,
- "gridcell": NAME_FROM_SUBTREE_RULE,
- "check rich option": NAME_FROM_SUBTREE_RULE,
- "term": NAME_FROM_SUBTREE_RULE,
- "definition": NAME_FROM_SUBTREE_RULE,
- "key": NAME_FROM_SUBTREE_RULE,
- "image map": INCLUDE_DESC,
- "option": INCLUDE_DESC,
- "listbox": INCLUDE_DESC,
- "definitionlist": INCLUDE_DESC | INCLUDE_NAME,
- "dialog": INCLUDE_DESC | INCLUDE_NAME,
- "chrome window": IGNORE_EXPLICIT_NAME,
- "app root": IGNORE_EXPLICIT_NAME,
- "statusbar": NAME_FROM_SUBTREE_RULE,
- "mathml table": INCLUDE_DESC | INCLUDE_NAME,
- "mathml labeled row": NAME_FROM_SUBTREE_RULE,
- "mathml table row": NAME_FROM_SUBTREE_RULE,
- "mathml cell": INCLUDE_DESC | INCLUDE_NAME,
- "mathml fraction": INCLUDE_DESC,
- "mathml square root": INCLUDE_DESC,
- "mathml root": INCLUDE_DESC,
- "mathml enclosed": INCLUDE_DESC,
- "mathml sub": INCLUDE_DESC,
- "mathml sup": INCLUDE_DESC,
- "mathml sub sup": INCLUDE_DESC,
- "mathml under": INCLUDE_DESC,
- "mathml over": INCLUDE_DESC,
- "mathml under over": INCLUDE_DESC,
- "mathml multiscripts": INCLUDE_DESC,
- "mathml identifier": INCLUDE_DESC,
- "mathml number": INCLUDE_DESC,
- "mathml operator": INCLUDE_DESC,
- "mathml text": INCLUDE_DESC,
- "mathml string literal": INCLUDE_DESC,
- "mathml row": INCLUDE_DESC,
- "mathml style": INCLUDE_DESC,
- "mathml error": INCLUDE_DESC },
-
- mathmlRolesSet: new Set([
- Roles.MATHML_MATH,
- Roles.MATHML_IDENTIFIER,
- Roles.MATHML_NUMBER,
- Roles.MATHML_OPERATOR,
- Roles.MATHML_TEXT,
- Roles.MATHML_STRING_LITERAL,
- Roles.MATHML_GLYPH,
- Roles.MATHML_ROW,
- Roles.MATHML_FRACTION,
- Roles.MATHML_SQUARE_ROOT,
- Roles.MATHML_ROOT,
- Roles.MATHML_FENCED,
- Roles.MATHML_ENCLOSED,
- Roles.MATHML_STYLE,
- Roles.MATHML_SUB,
- Roles.MATHML_SUP,
- Roles.MATHML_SUB_SUP,
- Roles.MATHML_UNDER,
- Roles.MATHML_OVER,
- Roles.MATHML_UNDER_OVER,
- Roles.MATHML_MULTISCRIPTS,
- Roles.MATHML_TABLE,
- Roles.LABELED_ROW,
- Roles.MATHML_TABLE_ROW,
- Roles.MATHML_CELL,
- Roles.MATHML_ACTION,
- Roles.MATHML_ERROR,
- Roles.MATHML_STACK,
- Roles.MATHML_LONG_DIVISION,
- Roles.MATHML_STACK_GROUP,
- Roles.MATHML_STACK_ROW,
- Roles.MATHML_STACK_CARRIES,
- Roles.MATHML_STACK_CARRY,
- Roles.MATHML_STACK_LINE
- ]),
-
- objectOutputFunctions: {
- _generateBaseOutput:
- function _generateBaseOutput(aAccessible, aRoleStr, aState, aFlags) {
- let output = [];
-
- if (aFlags & INCLUDE_DESC) {
- this._addState(output, aState, aRoleStr);
- this._addType(output, aAccessible, aRoleStr);
- this._addRole(output, aAccessible, aRoleStr);
- }
-
- if (aFlags & INCLUDE_VALUE && aAccessible.value.trim()) {
- output[this.outputOrder === OUTPUT_DESC_FIRST ? "push" : "unshift"](
- aAccessible.value);
- }
-
- this._addName(output, aAccessible, aFlags);
- this._addLandmark(output, aAccessible);
-
- return output;
- },
-
- label: function label(aAccessible, aRoleStr, aState, aFlags, aContext) {
- if (aContext.isNestedControl ||
- aContext.accessible == Utils.getEmbeddedControl(aAccessible)) {
- // If we are on a nested control, or a nesting label,
- // we don't need the context.
- return [];
- }
-
- return this.objectOutputFunctions.defaultFunc.apply(this, arguments);
- },
-
- entry: function entry(aAccessible, aRoleStr, aState, aFlags) {
- let rolestr = aState.contains(States.MULTI_LINE) ? "textarea" : "entry";
- return this.objectOutputFunctions.defaultFunc.apply(
- this, [aAccessible, rolestr, aState, aFlags]);
- },
-
- pagetab: function pagetab(aAccessible, aRoleStr, aState, aFlags) {
- let itemno = {};
- let itemof = {};
- aAccessible.groupPosition({}, itemof, itemno);
- let output = [];
- this._addState(output, aState);
- this._addRole(output, aAccessible, aRoleStr);
- output.push({
- string: "objItemOfN",
- args: [itemno.value, itemof.value]
- });
-
- this._addName(output, aAccessible, aFlags);
- this._addLandmark(output, aAccessible);
-
- return output;
- },
-
- table: function table(aAccessible, aRoleStr, aState, aFlags) {
- let output = [];
- let table;
- try {
- table = aAccessible.QueryInterface(Ci.nsIAccessibleTable);
- } catch (x) {
- Logger.logException(x);
- return output;
- } finally {
- // Check if it's a layout table, and bail out if true.
- // We don't want to speak any table information for layout tables.
- if (table.isProbablyForLayout()) {
- return output;
- }
- this._addRole(output, aAccessible, aRoleStr);
- output.push.call(output, {
- string: this._getOutputName("tblColumnInfo"),
- count: table.columnCount
- }, {
- string: this._getOutputName("tblRowInfo"),
- count: table.rowCount
- });
- this._addName(output, aAccessible, aFlags);
- this._addLandmark(output, aAccessible);
- return output;
- }
- },
-
- gridcell: function gridcell(aAccessible, aRoleStr, aState, aFlags) {
- let output = [];
- this._addState(output, aState);
- this._addName(output, aAccessible, aFlags);
- this._addLandmark(output, aAccessible);
- return output;
- },
-
- // Use the table output functions for MathML tabular elements.
- mathmltable: function mathmltable() {
- return this.objectOutputFunctions.table.apply(this, arguments);
- },
-
- mathmlcell: function mathmlcell() {
- return this.objectOutputFunctions.cell.apply(this, arguments);
- },
-
- mathmlenclosed: function mathmlenclosed(aAccessible, aRoleStr, aState,
- aFlags, aContext) {
- let output = this.objectOutputFunctions.defaultFunc.
- apply(this, [aAccessible, aRoleStr, aState, aFlags, aContext]);
- this._addMencloseNotations(output, aAccessible);
- return output;
- }
- }
-};
-
-/**
- * Generates speech utterances from objects, actions and state changes.
- * An utterance is an array of speech data.
- *
- * It should not be assumed that flattening an utterance array would create a
- * gramatically correct sentence. For example, {@link genForObject} might
- * return: ['graphic', 'Welcome to my home page'].
- * Each string element in an utterance should be gramatically correct in itself.
- * Another example from {@link genForObject}: ['list item 2 of 5', 'Alabama'].
- *
- * An utterance is ordered from the least to the most important. Speaking the
- * last string usually makes sense, but speaking the first often won't.
- * For example {@link genForAction} might return ['button', 'clicked'] for a
- * clicked event. Speaking only 'clicked' makes sense. Speaking 'button' does
- * not.
- */
-var UtteranceGenerator = { // jshint ignore:line
- __proto__: OutputGenerator, // jshint ignore:line
-
- gActionMap: {
- jump: "jumpAction",
- press: "pressAction",
- check: "checkAction",
- uncheck: "uncheckAction",
- on: "onAction",
- off: "offAction",
- select: "selectAction",
- unselect: "unselectAction",
- open: "openAction",
- close: "closeAction",
- switch: "switchAction",
- click: "clickAction",
- collapse: "collapseAction",
- expand: "expandAction",
- activate: "activateAction",
- cycle: "cycleAction"
- },
-
- // TODO: May become more verbose in the future.
- genForAction: function genForAction(aObject, aActionName) {
- return [{string: this.gActionMap[aActionName]}];
- },
-
- genForLiveRegion:
- function genForLiveRegion(aContext, aIsHide, aModifiedText) {
- let utterance = [];
- if (aIsHide) {
- utterance.push({string: "hidden"});
- }
- return utterance.concat(aModifiedText || this.genForContext(aContext));
- },
-
- genForAnnouncement: function genForAnnouncement(aAnnouncement) {
- return [{
- string: aAnnouncement
- }];
- },
-
- genForTabStateChange: function genForTabStateChange(aObject, aTabState) {
- switch (aTabState) {
- case "newtab":
- return [{string: "tabNew"}];
- case "loading":
- return [{string: "tabLoading"}];
- case "loaded":
- return [aObject.name, {string: "tabLoaded"}];
- case "loadstopped":
- return [{string: "tabLoadStopped"}];
- case "reload":
- return [{string: "tabReload"}];
- default:
- return [];
- }
- },
-
- genForEditingMode: function genForEditingMode(aIsEditing) {
- return [{string: aIsEditing ? "editingMode" : "navigationMode"}];
- },
-
- objectOutputFunctions: {
-
- __proto__: OutputGenerator.objectOutputFunctions, // jshint ignore:line
-
- defaultFunc: function defaultFunc() {
- return this.objectOutputFunctions._generateBaseOutput.apply(
- this, arguments);
- },
-
- heading: function heading(aAccessible, aRoleStr, aState, aFlags) {
- let level = {};
- aAccessible.groupPosition(level, {}, {});
- let utterance = [{string: "headingLevel", args: [level.value]}];
-
- this._addName(utterance, aAccessible, aFlags);
- this._addLandmark(utterance, aAccessible);
-
- return utterance;
- },
-
- listitem: function listitem(aAccessible, aRoleStr, aState, aFlags) {
- let itemno = {};
- let itemof = {};
- aAccessible.groupPosition({}, itemof, itemno);
- let utterance = [];
- if (itemno.value == 1) {
- // Start of list
- utterance.push({string: "listStart"});
- } else if (itemno.value == itemof.value) {
- // last item
- utterance.push({string: "listEnd"});
- }
-
- this._addName(utterance, aAccessible, aFlags);
- this._addLandmark(utterance, aAccessible);
-
- return utterance;
- },
-
- list: function list(aAccessible, aRoleStr, aState, aFlags) {
- return this._getListUtterance(aAccessible, aRoleStr, aFlags,
- aAccessible.childCount);
- },
-
- definitionlist:
- function definitionlist(aAccessible, aRoleStr, aState, aFlags) {
- return this._getListUtterance(aAccessible, aRoleStr, aFlags,
- aAccessible.childCount / 2);
- },
-
- application: function application(aAccessible, aRoleStr, aState, aFlags) {
- // Don't utter location of applications, it gets tiring.
- if (aAccessible.name != aAccessible.DOMNode.location) {
- return this.objectOutputFunctions.defaultFunc.apply(this,
- [aAccessible, aRoleStr, aState, aFlags]);
- }
-
- return [];
- },
-
- cell: function cell(aAccessible, aRoleStr, aState, aFlags, aContext) {
- let utterance = [];
- let cell = aContext.getCellInfo(aAccessible);
- if (cell) {
- let addCellChanged =
- function addCellChanged(aUtterance, aChanged, aString, aIndex) {
- if (aChanged) {
- aUtterance.push({string: aString, args: [aIndex + 1]});
- }
- };
- let addExtent = function addExtent(aUtterance, aExtent, aString) {
- if (aExtent > 1) {
- aUtterance.push({string: aString, args: [aExtent]});
- }
- };
- let addHeaders = function addHeaders(aUtterance, aHeaders) {
- if (aHeaders.length > 0) {
- aUtterance.push.apply(aUtterance, aHeaders);
- }
- };
-
- addCellChanged(utterance, cell.columnChanged, "columnInfo",
- cell.columnIndex);
- addCellChanged(utterance, cell.rowChanged, "rowInfo", cell.rowIndex);
-
- addExtent(utterance, cell.columnExtent, "spansColumns");
- addExtent(utterance, cell.rowExtent, "spansRows");
-
- addHeaders(utterance, cell.columnHeaders);
- addHeaders(utterance, cell.rowHeaders);
- }
-
- this._addName(utterance, aAccessible, aFlags);
- this._addLandmark(utterance, aAccessible);
-
- return utterance;
- },
-
- columnheader: function columnheader() {
- return this.objectOutputFunctions.cell.apply(this, arguments);
- },
-
- rowheader: function rowheader() {
- return this.objectOutputFunctions.cell.apply(this, arguments);
- },
-
- statictext: function statictext(aAccessible) {
- if (Utils.isListItemDecorator(aAccessible, true)) {
- return [];
- }
-
- return this.objectOutputFunctions.defaultFunc.apply(this, arguments);
- }
- },
-
- _getContextStart: function _getContextStart(aContext) {
- return aContext.newAncestry;
- },
-
- _addRole: function _addRole(aOutput, aAccessible, aRoleStr) {
- if (this.mathmlRolesSet.has(aAccessible.role)) {
- this._addMathRoles(aOutput, aAccessible, aRoleStr);
- } else {
- aOutput.push({string: this._getOutputName(aRoleStr)});
- }
- },
-
- /**
- * Add localized state information to output data.
- * Note: We do not expose checked and selected states, we let TalkBack do it for us
- * there. This is because we expose the checked information on the node info itself.
- */
- _addState: function _addState(aOutput, aState, aRoleStr) {
- if (aState.contains(States.UNAVAILABLE)) {
- aOutput.push({string: "stateUnavailable"});
- }
-
- if (aState.contains(States.READONLY)) {
- aOutput.push({string: "stateReadonly"});
- }
-
- if (aState.contains(States.PRESSED)) {
- aOutput.push({string: "statePressed"});
- }
-
- if (aState.contains(States.EXPANDABLE)) {
- let statetr = aState.contains(States.EXPANDED) ?
- "stateExpanded" : "stateCollapsed";
- aOutput.push({string: statetr});
- }
-
- if (aState.contains(States.REQUIRED)) {
- aOutput.push({string: "stateRequired"});
- }
-
- if (aState.contains(States.TRAVERSED)) {
- aOutput.push({string: "stateTraversed"});
- }
-
- if (aState.contains(States.HASPOPUP)) {
- aOutput.push({string: "stateHasPopup"});
- }
- },
-
- _getListUtterance:
- function _getListUtterance(aAccessible, aRoleStr, aFlags, aItemCount) {
- let utterance = [];
- this._addRole(utterance, aAccessible, aRoleStr);
- utterance.push({
- string: this._getOutputName("listItemsCount"),
- count: aItemCount
- });
-
- this._addName(utterance, aAccessible, aFlags);
- this._addLandmark(utterance, aAccessible);
-
- return utterance;
- }
-};
deleted file mode 100644
--- a/accessible/jsat/Presentation.jsm
+++ /dev/null
@@ -1,332 +0,0 @@
-/* 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/. */
-
-/* exported Presentation */
-
-"use strict";
-
-ChromeUtils.import("resource://gre/modules/accessibility/Utils.jsm");
-ChromeUtils.defineModuleGetter(this, "PivotContext", // jshint ignore:line
- "resource://gre/modules/accessibility/Utils.jsm");
-ChromeUtils.defineModuleGetter(this, "UtteranceGenerator", // jshint ignore:line
- "resource://gre/modules/accessibility/OutputGenerator.jsm");
-ChromeUtils.defineModuleGetter(this, "States", // jshint ignore:line
- "resource://gre/modules/accessibility/Constants.jsm");
-ChromeUtils.defineModuleGetter(this, "Roles", // jshint ignore:line
- "resource://gre/modules/accessibility/Constants.jsm");
-ChromeUtils.defineModuleGetter(this, "AndroidEvents", // jshint ignore:line
- "resource://gre/modules/accessibility/Constants.jsm");
-
-var EXPORTED_SYMBOLS = ["Presentation"]; // jshint ignore:line
-
-const EDIT_TEXT_ROLES = new Set([
- Roles.SPINBUTTON, Roles.PASSWORD_TEXT,
- Roles.AUTOCOMPLETE, Roles.ENTRY, Roles.EDITCOMBOBOX]);
-
-class AndroidPresentor {
- constructor() {
- this.type = "Android";
- this.displayedAccessibles = new WeakMap();
- }
-
- /**
- * The virtual cursor's position changed.
- * @param {PivotContext} aContext the context object for the new pivot
- * position.
- * @param {int} aReason the reason for the pivot change.
- * See nsIAccessiblePivot.
- * @param {bool} aBoundaryType the boundary type for the text movement
- * or NO_BOUNDARY if it was not a text movement. See nsIAccessiblePivot.
- */
- pivotChanged(aPosition, aOldPosition, aStartOffset, aEndOffset, aReason, aBoundaryType) {
- let context = new PivotContext(
- aPosition, aOldPosition, aStartOffset, aEndOffset);
- if (!context.accessible) {
- return null;
- }
-
- let androidEvents = [];
-
- const isExploreByTouch = aReason == Ci.nsIAccessiblePivot.REASON_POINT;
-
- if (isExploreByTouch) {
- // This isn't really used by TalkBack so this is a half-hearted attempt
- // for now.
- androidEvents.push({eventType: AndroidEvents.VIEW_HOVER_EXIT, text: []});
- }
-
- if (aPosition != aOldPosition) {
- let info = this._infoFromContext(context);
- let eventType = isExploreByTouch ?
- AndroidEvents.VIEW_HOVER_ENTER :
- AndroidEvents.VIEW_ACCESSIBILITY_FOCUSED;
- androidEvents.push({...info, eventType});
-
- try {
- context.accessibleForBounds.scrollTo(
- Ci.nsIAccessibleScrollType.SCROLL_TYPE_ANYWHERE);
- } catch (e) {}
- }
-
- if (aBoundaryType != Ci.nsIAccessiblePivot.NO_BOUNDARY) {
- const adjustedText = context.textAndAdjustedOffsets;
-
- androidEvents.push({
- eventType: AndroidEvents.VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY,
- text: [adjustedText.text],
- fromIndex: adjustedText.startOffset,
- toIndex: adjustedText.endOffset
- });
-
- aPosition.QueryInterface(Ci.nsIAccessibleText).scrollSubstringTo(
- aStartOffset, aEndOffset,
- Ci.nsIAccessibleScrollType.SCROLL_TYPE_ANYWHERE);
- }
-
- if (context.accessible) {
- this.displayedAccessibles.set(context.accessible.document.window, context);
- }
-
- return androidEvents;
- }
-
- focused(aObject) {
- let info = this._infoFromContext(
- new PivotContext(aObject, null, -1, -1, true, false));
- return [{ eventType: AndroidEvents.VIEW_FOCUSED, ...info }];
- }
-
- /**
- * An object's check action has been invoked.
- * Note: Checkable objects use TalkBack's text derived from the event state, so we don't
- * populate the text here.
- * @param {nsIAccessible} aAccessible the object that has been invoked.
- */
- checked(aAccessible) {
- return [{
- eventType: AndroidEvents.VIEW_CLICKED,
- checked: Utils.getState(aAccessible).contains(States.CHECKED)
- }];
- }
-
- /**
- * An object's select action has been invoked.
- * @param {nsIAccessible} aAccessible the object that has been invoked.
- */
- selected(aAccessible) {
- return [{
- eventType: AndroidEvents.VIEW_SELECTED,
- selected: Utils.getState(aAccessible).contains(States.SELECTED)
- }];
- }
-
- /**
- * An object's action has been invoked.
- */
- actionInvoked() {
- return [{ eventType: AndroidEvents.VIEW_CLICKED }];
- }
-
- /**
- * Text has changed, either by the user or by the system. TODO.
- */
- textChanged(aAccessible, aIsInserted, aStart, aLength, aText, aModifiedText) {
- let androidEvent = {
- eventType: AndroidEvents.VIEW_TEXT_CHANGED,
- text: [aText],
- fromIndex: aStart,
- removedCount: 0,
- addedCount: 0
- };
-
- if (aIsInserted) {
- androidEvent.addedCount = aLength;
- androidEvent.beforeText =
- aText.substring(0, aStart) + aText.substring(aStart + aLength);
- } else {
- androidEvent.removedCount = aLength;
- androidEvent.beforeText =
- aText.substring(0, aStart) + aModifiedText + aText.substring(aStart);
- }
-
- return [androidEvent];
- }
-
- /**
- * Text selection has changed. TODO.
- */
- textSelectionChanged(aText, aStart, aEnd, aOldStart, aOldEnd, aIsFromUserInput) {
- let androidEvents = [];
-
- if (aIsFromUserInput) {
- let [from, to] = aOldStart < aStart ?
- [aOldStart, aStart] : [aStart, aOldStart];
- androidEvents.push({
- eventType: AndroidEvents.VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY,
- text: [aText],
- fromIndex: from,
- toIndex: to
- });
- } else {
- androidEvents.push({
- eventType: AndroidEvents.VIEW_TEXT_SELECTION_CHANGED,
- text: [aText],
- fromIndex: aStart,
- toIndex: aEnd,
- itemCount: aText.length
- });
- }
-
- return androidEvents;
- }
-
- /**
- * Selection has changed.
- * XXX: Implement android event?
- * @param {nsIAccessible} aObject the object that has been selected.
- */
- selectionChanged(aObject) {
- return ["todo.selection-changed"];
- }
-
- /**
- * Name has changed.
- * XXX: Implement android event?
- * @param {nsIAccessible} aAccessible the object whose value has changed.
- */
- nameChanged(aAccessible) {
- return ["todo.name-changed"];
- }
-
- /**
- * Value has changed.
- * XXX: Implement android event?
- * @param {nsIAccessible} aAccessible the object whose value has changed.
- */
- valueChanged(aAccessible) {
- return ["todo.value-changed"];
- }
-
- /**
- * The tab, or the tab's document state has changed.
- * @param {nsIAccessible} aDocObj the tab document accessible that has had its
- * state changed, or null if the tab has no associated document yet.
- * @param {string} aPageState the state name for the tab, valid states are:
- * 'newtab', 'loading', 'newdoc', 'loaded', 'stopped', and 'reload'.
- */
- tabStateChanged(aDocObj, aPageState) {
- return this.announce(
- UtteranceGenerator.genForTabStateChange(aDocObj, aPageState));
- }
-
- /**
- * The viewport has changed because of scroll.
- * @param {Window} aWindow window of viewport that changed.
- */
- viewportScrolled(aWindow) {
- const { windowUtils, devicePixelRatio } = aWindow;
- const resolution = { value: 1 };
- windowUtils.getResolution(resolution);
- const scale = devicePixelRatio * resolution.value;
- return [{
- eventType: AndroidEvents.VIEW_SCROLLED,
- scrollX: aWindow.scrollX * scale,
- scrollY: aWindow.scrollY * scale,
- maxScrollX: aWindow.scrollMaxX * scale,
- maxScrollY: aWindow.scrollMaxY * scale,
- }];
- }
-
- /**
- * The viewport has changed, either a pan, zoom, or landscape/portrait toggle.
- * @param {Window} aWindow window of viewport that changed.
- */
- viewportChanged(aWindow) {
- const currentContext = this.displayedAccessibles.get(aWindow);
- if (!currentContext) {
- return;
- }
-
- const currentAcc = currentContext.accessibleForBounds;
- if (Utils.isAliveAndVisible(currentAcc)) {
- return [{
- eventType: AndroidEvents.WINDOW_CONTENT_CHANGED,
- bounds: Utils.getBounds(currentAcc)
- }];
- }
- }
-
- /**
- * Announce something. Typically an app state change.
- */
- announce(aAnnouncement) {
- let localizedAnnouncement = Utils.localize(aAnnouncement).join(" ");
- return [{
- eventType: AndroidEvents.ANNOUNCEMENT,
- text: [localizedAnnouncement],
- addedCount: localizedAnnouncement.length,
- removedCount: 0,
- fromIndex: 0
- }];
- }
-
-
- /**
- * User tried to move cursor forward or backward with no success.
- * @param {string} aMoveMethod move method that was used (eg. 'moveNext').
- */
- noMove(aMoveMethod) {
- return [{
- eventType: AndroidEvents.VIEW_ACCESSIBILITY_FOCUSED,
- exitView: aMoveMethod,
- text: [""]
- }];
- }
-
- /**
- * Announce a live region.
- * @param {PivotContext} aContext context object for an accessible.
- * @param {boolean} aIsPolite A politeness level for a live region.
- * @param {boolean} aIsHide An indicator of hide/remove event.
- * @param {string} aModifiedText Optional modified text.
- */
- liveRegion(aAccessible, aIsPolite, aIsHide, aModifiedText) {
- let context = !aModifiedText ?
- new PivotContext(aAccessible, null, -1, -1, true, !!aIsHide) : null;
- return this.announce(
- UtteranceGenerator.genForLiveRegion(context, aIsHide, aModifiedText));
- }
-
- _infoFromContext(aContext) {
- const state = Utils.getState(aContext.accessible);
- const info = {
- bounds: aContext.bounds,
- focusable: state.contains(States.FOCUSABLE),
- focused: state.contains(States.FOCUSED),
- clickable: aContext.accessible.actionCount > 0,
- checkable: state.contains(States.CHECKABLE),
- checked: state.contains(States.CHECKED),
- editable: state.contains(States.EDITABLE),
- selected: state.contains(States.SELECTED)
- };
-
- if (EDIT_TEXT_ROLES.has(aContext.accessible.role)) {
- let textAcc = aContext.accessible.QueryInterface(Ci.nsIAccessibleText);
- return {
- ...info,
- className: "android.widget.EditText",
- hint: aContext.accessible.name,
- text: [textAcc.getText(0, -1)]
- };
- }
-
- return {
- ...info,
- className: "android.view.View",
- text: Utils.localize(UtteranceGenerator.genForContext(aContext)),
- };
- }
-}
-
-const Presentation = new AndroidPresentor();
--- a/accessible/jsat/moz.build
+++ b/accessible/jsat/moz.build
@@ -4,15 +4,13 @@
# 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/.
EXTRA_JS_MODULES.accessibility += [
'AccessFu.jsm',
'Constants.jsm',
'ContentControl.jsm',
'EventManager.jsm',
- 'OutputGenerator.jsm',
- 'Presentation.jsm',
'Traversal.jsm',
'Utils.jsm'
]
JAR_MANIFESTS += ['jar.mn']
--- a/accessible/tests/mochitest/jsat/a11y.ini
+++ b/accessible/tests/mochitest/jsat/a11y.ini
@@ -5,24 +5,33 @@ support-files =
doc_traversal.html
doc_content_integration.html
doc_content_text.html
!/accessible/tests/mochitest/*.js
!/accessible/tests/mochitest/moz.png
[test_alive.html]
[test_content_integration.html]
+skip-if = true
[test_explicit_names.html]
+skip-if = true
[test_hints.html]
+skip-if = true
[test_landmarks.html]
+skip-if = true
[test_live_regions.html]
+skip-if = true
[test_output_mathml.html]
+skip-if = true
[test_output.html]
+skip-if = true
[test_tables.html]
+skip-if = true
[test_text_editable_navigation.html]
-skip-if = (verify && !debug && (os == 'linux'))
+skip-if = true
[test_text_editing.html]
-skip-if = (verify && !debug && (os == 'linux'))
+skip-if = true
[test_text_navigation_focus.html]
-skip-if = (verify && !debug && (os == 'linux'))
+skip-if = true
[test_text_navigation.html]
+skip-if = true
[test_traversal.html]
[test_traversal_helper.html]
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1498,21 +1498,31 @@ pref("browser.ping-centre.log", false);
pref("browser.ping-centre.staging.endpoint", "https://onyx_tiles.stage.mozaws.net/v3/links/ping-centre");
pref("browser.ping-centre.production.endpoint", "https://tiles.services.mozilla.com/v3/links/ping-centre");
// Enable GMP support in the addon manager.
pref("media.gmp-provider.enabled", true);
pref("browser.contentblocking.allowlist.storage.enabled", true);
+#ifdef NIGHTLY_BUILD
pref("browser.contentblocking.global-toggle.enabled", true);
+#else
+pref("browser.contentblocking.global-toggle.enabled", false);
+#endif
// Define a set of default features for the Content Blocking UI
+#ifdef EARLY_BETA_OR_EARLIER
pref("browser.contentblocking.fastblock.ui.enabled", true);
pref("browser.contentblocking.fastblock.control-center.ui.enabled", true);
+#else
+pref("browser.contentblocking.fastblock.ui.enabled", false);
+pref("browser.contentblocking.fastblock.control-center.ui.enabled", false);
+#endif
+
pref("browser.contentblocking.trackingprotection.ui.enabled", true);
pref("browser.contentblocking.trackingprotection.control-center.ui.enabled", true);
pref("browser.contentblocking.rejecttrackers.ui.enabled", true);
pref("browser.contentblocking.rejecttrackers.ui.recommended", true);
pref("browser.contentblocking.rejecttrackers.control-center.ui.enabled", true);
pref("browser.contentblocking.cookies-site-data.ui.reject-trackers.recommended", true);
pref("browser.contentblocking.cookies-site-data.ui.reject-trackers.enabled", true);
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -1426,16 +1426,17 @@ var BookmarkingUI = {
CustomizableUI.removeListener(this);
this.star.removeEventListener("mouseover", this);
this._uninitView();
if (this._hasBookmarksObserver) {
PlacesUtils.bookmarks.removeObserver(this);
+ PlacesUtils.observers.removeListener(["bookmark-added"], this.handlePlacesEvents);
}
if (this._pendingUpdate) {
delete this._pendingUpdate;
}
},
onLocationChange: function BUI_onLocationChange() {
@@ -1456,31 +1457,33 @@ var BookmarkingUI = {
PlacesUtils.bookmarks.fetch({url: this._uri}, b => guids.add(b.guid), { concurrent: true })
.catch(Cu.reportError)
.then(() => {
if (pendingUpdate != this._pendingUpdate) {
return;
}
- // It's possible that onItemAdded gets called before the async statement
+ // It's possible that "bookmark-added" gets called before the async statement
// calls back. For such an edge case, retain all unique entries from the
// array.
if (this._itemGuids.size > 0) {
this._itemGuids = new Set(...this._itemGuids, ...guids);
} else {
this._itemGuids = guids;
}
this._updateStar();
// Start observing bookmarks if needed.
if (!this._hasBookmarksObserver) {
try {
PlacesUtils.bookmarks.addObserver(this);
+ this.handlePlacesEvents = this.handlePlacesEvents.bind(this);
+ PlacesUtils.observers.addListener(["bookmark-added"], this.handlePlacesEvents);
this._hasBookmarksObserver = true;
} catch (ex) {
Cu.reportError("BookmarkingUI failed adding a bookmarks observer: " + ex);
}
}
delete this._pendingUpdate;
});
@@ -1705,30 +1708,32 @@ var BookmarkingUI = {
} else {
// Move it back to the palette.
CustomizableUI.removeWidgetFromArea(this.BOOKMARK_BUTTON_ID);
}
triggerNode.setAttribute("checked", !placement);
updateToggleControlLabel(triggerNode);
},
- // nsINavBookmarkObserver
- onItemAdded(aItemId, aParentId, aIndex, aItemType, aURI, aTitle, aDateAdded, aGuid) {
- if (aURI && aURI.equals(this._uri)) {
- // If a new bookmark has been added to the tracked uri, register it.
- if (!this._itemGuids.has(aGuid)) {
- this._itemGuids.add(aGuid);
- // Only need to update the UI if it wasn't marked as starred before:
- if (this._itemGuids.size == 1) {
- this._updateStar();
+ handlePlacesEvents(aEvents) {
+ // Only need to update the UI if it wasn't marked as starred before:
+ if (this._itemGuids.size == 0) {
+ for (let {url, guid} of aEvents) {
+ if (url && url == this._uri.spec) {
+ // If a new bookmark has been added to the tracked uri, register it.
+ if (!this._itemGuids.has(guid)) {
+ this._itemGuids.add(guid);
+ this._updateStar();
+ }
}
}
}
},
+ // nsINavBookmarkObserver
onItemRemoved(aItemId, aParentId, aIndex, aItemType, aURI, aGuid) {
// If one of the tracked bookmarks has been removed, unregister it.
if (this._itemGuids.has(aGuid)) {
this._itemGuids.delete(aGuid);
// Only need to update the UI if the page is no longer starred
if (this._itemGuids.size == 0) {
this._updateStar();
}
--- a/browser/base/content/browser-siteIdentity.js
+++ b/browser/base/content/browser-siteIdentity.js
@@ -110,25 +110,24 @@ var gIdentityHandler = {
get _identityPopupMultiView() {
delete this._identityPopupMultiView;
return this._identityPopupMultiView = document.getElementById("identity-popup-multiView");
},
get _identityPopupMainView() {
delete this._identityPopupMainView;
return this._identityPopupMainView = document.getElementById("identity-popup-mainView");
},
- get _identityPopupContentHosts() {
- delete this._identityPopupContentHosts;
- return this._identityPopupContentHosts =
- [...document.querySelectorAll(".identity-popup-host")];
+ get _identityPopupMainViewHeaderLabel() {
+ delete this._identityPopupMainViewHeaderLabel;
+ return this._identityPopupMainViewHeaderLabel =
+ document.getElementById("identity-popup-mainView-panel-header-span");
},
- get _identityPopupContentHostless() {
- delete this._identityPopupContentHostless;
- return this._identityPopupContentHostless =
- [...document.querySelectorAll(".identity-popup-hostless")];
+ get _identityPopupContentHost() {
+ delete this._identityPopupContentHost;
+ return this._identityPopupContentHost = document.getElementById("identity-popup-host");
},
get _identityPopupContentOwner() {
delete this._identityPopupContentOwner;
return this._identityPopupContentOwner =
document.getElementById("identity-popup-content-owner");
},
get _identityPopupContentSupp() {
delete this._identityPopupContentSupp;
@@ -698,34 +697,30 @@ var gIdentityHandler = {
updateAttribute(element, "isbroken", this._isBroken);
}
// Initialize the optional strings to empty values
let supplemental = "";
let verifier = "";
let host = "";
let owner = "";
- let hostless = false;
try {
host = this.getEffectiveHost();
} catch (e) {
// Some URIs might have no hosts.
}
+ if (this._pageExtensionPolicy) {
+ host = this._pageExtensionPolicy.name;
+ }
+
// Fallback for special protocols.
if (!host) {
host = this._uri.specIgnoringRef;
- // Special URIs without a host (eg, about:) should crop the end so
- // the protocol can be seen.
- hostless = true;
- }
-
- if (this._pageExtensionPolicy) {
- host = this._pageExtensionPolicy.name;
}
// Fill in the CA name if we have a valid TLS certificate.
if (this._isSecure || this._isCertUserOverridden) {
verifier = this._identityIconLabels.tooltipText;
}
// Fill in organization information if we have a valid EV certificate.
@@ -742,24 +737,19 @@ var gIdentityHandler = {
[iData.state, iData.country]);
else if (iData.state) // State only
supplemental += iData.state;
else if (iData.country) // Country only
supplemental += iData.country;
}
// Push the appropriate strings out to the UI.
- this._identityPopupContentHosts.forEach((el) => {
- el.textContent = host;
- el.hidden = hostless;
- });
- this._identityPopupContentHostless.forEach((el) => {
- el.setAttribute("value", host);
- el.hidden = !hostless;
- });
+ this._identityPopupMainViewHeaderLabel.textContent =
+ gNavigatorBundle.getFormattedString("identity.headerWithHost", [host]);
+ this._identityPopupContentHost.textContent = host;
this._identityPopupContentOwner.textContent = owner;
this._identityPopupContentSupp.textContent = supplemental;
this._identityPopupContentVerif.textContent = verifier;
// Update per-site permissions section.
this.updateSitePermissions();
},
--- a/browser/base/content/tabbrowser.js
+++ b/browser/base/content/tabbrowser.js
@@ -2756,17 +2756,20 @@ window._gBrowser = {
var isLastTab = (this.tabs.length - this._removingTabs.length == 1);
let windowUtils = window.windowUtils;
// We have to sample the tab width now, since _beginRemoveTab might
// end up modifying the DOM in such a way that aTab gets a new
// frame created for it (for example, by updating the visually selected
// state).
let tabWidth = windowUtils.getBoundsWithoutFlushing(aTab).width;
- if (!this._beginRemoveTab(aTab, null, null, true, skipPermitUnload)) {
+ if (!this._beginRemoveTab(aTab, {
+ closeWindowFastpath: true,
+ skipPermitUnload,
+ })) {
TelemetryStopwatch.cancel("FX_TAB_CLOSE_TIME_ANIM_MS", aTab);
TelemetryStopwatch.cancel("FX_TAB_CLOSE_TIME_NO_ANIM_MS", aTab);
return;
}
if (!aTab.pinned && !aTab.hidden && aTab._fullyOpen && byMouse)
this.tabContainer._lockTabSizing(aTab, tabWidth);
else
@@ -2804,23 +2807,29 @@ window._gBrowser = {
_hasBeforeUnload(aTab) {
let browser = aTab.linkedBrowser;
return browser.isRemoteBrowser && browser.frameLoader &&
browser.frameLoader.tabParent &&
browser.frameLoader.tabParent.hasBeforeUnload;
},
- _beginRemoveTab(aTab, aAdoptedByTab, aCloseWindowWithLastTab, aCloseWindowFastpath, aSkipPermitUnload) {
+ _beginRemoveTab(aTab, {
+ adoptedByTab,
+ closeWindowWithLastTab,
+ closeWindowFastpath,
+ skipPermitUnload,
+ } = {}) {
if (aTab.closing ||
- this._windowIsClosing)
+ this._windowIsClosing) {
return false;
+ }
var browser = this.getBrowserForTab(aTab);
- if (!aSkipPermitUnload && !aAdoptedByTab &&
+ if (!skipPermitUnload && !adoptedByTab &&
aTab.linkedPanel && !aTab._pendingPermitUnload &&
(!browser.isRemoteBrowser || this._hasBeforeUnload(aTab))) {
TelemetryStopwatch.start("FX_TAB_CLOSE_PERMIT_UNLOAD_TIME_MS", aTab);
// We need to block while calling permitUnload() because it
// processes the event queue and may lead to another removeTab()
// call before permitUnload() returns.
aTab._pendingPermitUnload = true;
@@ -2844,44 +2853,44 @@ window._gBrowser = {
this._tabLayerCache.splice(tabCacheIndex, 1);
}
this._blurTab(aTab);
var closeWindow = false;
var newTab = false;
if (this.tabs.length - this._removingTabs.length == 1) {
- closeWindow = aCloseWindowWithLastTab != null ? aCloseWindowWithLastTab :
+ closeWindow = closeWindowWithLastTab != null ? closeWindowWithLastTab :
!window.toolbar.visible ||
Services.prefs.getBoolPref("browser.tabs.closeWindowWithLastTab");
if (closeWindow) {
// We've already called beforeunload on all the relevant tabs if we get here,
// so avoid calling it again:
window.skipNextCanClose = true;
}
// Closing the tab and replacing it with a blank one is notably slower
// than closing the window right away. If the caller opts in, take
// the fast path.
if (closeWindow &&
- aCloseWindowFastpath &&
+ closeWindowFastpath &&
this._removingTabs.length == 0) {
// This call actually closes the window, unless the user
// cancels the operation. We are finished here in both cases.
this._windowIsClosing = window.closeWindow(true, window.warnAboutClosingWindow);
return false;
}
newTab = true;
}
aTab._endRemoveArgs = [closeWindow, newTab];
// swapBrowsersAndCloseOther will take care of closing the window without animation.
- if (closeWindow && aAdoptedByTab) {
+ if (closeWindow && adoptedByTab) {
// Remove the tab's filter and progress listener to avoid leaking.
if (aTab.linkedPanel) {
const filter = this._tabFilters.get(aTab);
browser.webProgress.removeProgressListener(filter);
const listener = this._tabListeners.get(aTab);
filter.removeProgressListener(listener);
listener.destroy();
this._tabListeners.delete(aTab);
@@ -2894,17 +2903,17 @@ window._gBrowser = {
// If the opening tab animation hasn't finished before we start closing the
// tab, decrement the animation count since _handleNewTab will not get called.
this.tabAnimationsInProgress--;
}
this.tabAnimationsInProgress++;
// Mute audio immediately to improve perceived speed of tab closure.
- if (!aAdoptedByTab && aTab.hasAttribute("soundplaying")) {
+ if (!adoptedByTab && aTab.hasAttribute("soundplaying")) {
// Don't persist the muted state as this wasn't a user action.
// This lets undo-close-tab return it to an unmuted state.
aTab.linkedBrowser.mute(true);
}
aTab.closing = true;
this._removingTabs.push(aTab);
this._visibleTabs = null; // invalidate cache
@@ -2919,43 +2928,43 @@ window._gBrowser = {
});
else
TabBarVisibility.update();
// We're committed to closing the tab now.
// Dispatch a notification.
// We dispatch it before any teardown so that event listeners can
// inspect the tab that's about to close.
- var evt = new CustomEvent("TabClose", { bubbles: true, detail: { adoptedBy: aAdoptedByTab } });
+ let evt = new CustomEvent("TabClose", { bubbles: true, detail: { adoptedBy: adoptedByTab } });
aTab.dispatchEvent(evt);
if (this.tabs.length == 2) {
// We're closing one of our two open tabs, inform the other tab that its
// sibling is going away.
window.messageManager
.broadcastAsyncMessage("Browser:HasSiblings", false);
}
if (aTab.linkedPanel) {
- if (!aAdoptedByTab && !gMultiProcessBrowser) {
+ if (!adoptedByTab && !gMultiProcessBrowser) {
// Prevent this tab from showing further dialogs, since we're closing it
browser.contentWindow.windowUtils.disableDialogs();
}
// Remove the tab's filter and progress listener.
const filter = this._tabFilters.get(aTab);
browser.webProgress.removeProgressListener(filter);
const listener = this._tabListeners.get(aTab);
filter.removeProgressListener(listener);
listener.destroy();
}
- if (browser.registeredOpenURI && !aAdoptedByTab) {
+ if (browser.registeredOpenURI && !adoptedByTab) {
let userContextId = browser.getAttribute("usercontextid") || 0;
this.UrlbarProviderOpenTabs.unregisterOpenTab(browser.registeredOpenURI.spec,
userContextId);
delete browser.registeredOpenURI;
}
// We are no longer the primary content area.
browser.removeAttribute("primary");
@@ -3131,29 +3140,35 @@ window._gBrowser = {
return tab;
},
_blurTab(aTab) {
this.selectedTab = this._findTabToBlurTo(aTab);
},
+ /**
+ * @returns {boolean}
+ * False if swapping isn't permitted, true otherwise.
+ */
swapBrowsersAndCloseOther(aOurTab, aOtherTab) {
// Do not allow transfering a private tab to a non-private window
// and vice versa.
if (PrivateBrowsingUtils.isWindowPrivate(window) !=
- PrivateBrowsingUtils.isWindowPrivate(aOtherTab.ownerGlobal))
- return;
+ PrivateBrowsingUtils.isWindowPrivate(aOtherTab.ownerGlobal)) {
+ return false;
+ }
let ourBrowser = this.getBrowserForTab(aOurTab);
let otherBrowser = aOtherTab.linkedBrowser;
// Can't swap between chrome and content processes.
- if (ourBrowser.isRemoteBrowser != otherBrowser.isRemoteBrowser)
- return;
+ if (ourBrowser.isRemoteBrowser != otherBrowser.isRemoteBrowser) {
+ return false;
+ }
// Keep the userContextId if set on other browser
if (otherBrowser.hasAttribute("usercontextid")) {
ourBrowser.setAttribute("usercontextid", otherBrowser.getAttribute("usercontextid"));
}
// That's gBrowser for the other window, not the tab's browser!
var remoteBrowser = aOtherTab.ownerGlobal.gBrowser;
@@ -3168,18 +3183,22 @@ window._gBrowser = {
aOtherTab._soundPlayingAttrRemovalTimer = 0;
aOtherTab.removeAttribute("soundplaying");
remoteBrowser._tabAttrModified(aOtherTab, ["soundplaying"]);
}
// First, start teardown of the other browser. Make sure to not
// fire the beforeunload event in the process. Close the other
// window if this was its last tab.
- if (!remoteBrowser._beginRemoveTab(aOtherTab, aOurTab, true))
- return;
+ if (!remoteBrowser._beginRemoveTab(aOtherTab, {
+ adoptedByTab: aOurTab,
+ closeWindowWithLastTab: true,
+ })) {
+ return false;
+ }
// If this is the last tab of the window, hide the window
// immediately without animation before the docshell swap, to avoid
// about:blank being painted.
let [closeWindow] = aOtherTab._endRemoveArgs;
if (closeWindow) {
let win = aOtherTab.ownerGlobal;
win.windowUtils.suppressAnimation(true);
@@ -3274,16 +3293,18 @@ window._gBrowser = {
// If the tab was already selected (this happpens in the scenario
// of replaceTabWithWindow), notify onLocationChange, etc.
if (aOurTab.selected)
this.updateCurrentBrowser(true);
if (modifiedAttrs.length) {
this._tabAttrModified(aOurTab, modifiedAttrs);
}
+
+ return true;
},
swapBrowsers(aOurTab, aOtherTab, aFlags) {
let otherBrowser = aOtherTab.linkedBrowser;
let otherTabBrowser = otherBrowser.getTabBrowser();
// We aren't closing the other tab so, we also need to swap its tablisteners.
let filter = otherTabBrowser._tabFilters.get(aOtherTab);
@@ -3669,16 +3690,19 @@ window._gBrowser = {
if (nextTab)
this.moveTabTo(this.selectedTab, nextTab._tPos);
else if (this.arrowKeysShouldWrap)
this.moveTabToStart();
},
/**
* Adopts a tab from another browser window, and inserts it at aIndex
+ *
+ * @returns {object}
+ * The new tab in the current window, null if the tab couldn't be adopted.
*/
adoptTab(aTab, aIndex, aSelectTab) {
// Swap the dropped tab with a new one we create and then close
// it in the other window (making it seem to have moved between
// windows). We also ensure that the tab we create to swap into has
// the same remote type and process as the one we're swapping in.
// This makes sure we don't get a short-lived process for the new tab.
let linkedBrowser = aTab.linkedBrowser;
@@ -3697,39 +3721,33 @@ window._gBrowser = {
if (aTab.hasAttribute("usercontextid")) {
// new tab must have the same usercontextid as the old one
params.userContextId = aTab.getAttribute("usercontextid");
}
let newTab = this.addWebTab("about:blank", params);
let newBrowser = this.getBrowserForTab(newTab);
+ aTab.parentNode._finishAnimateTabMove();
+
// Stop the about:blank load.
newBrowser.stop();
// Make sure it has a docshell.
newBrowser.docShell;
- // We need to select the tab before calling swapBrowsersAndCloseOther
- // so that window.content in chrome windows points to the right tab
- // when pagehide/show events are fired. This is no longer necessary
- // for any exiting browser code, but it may be necessary for add-on
- // compatibility.
+ if (!this.swapBrowsersAndCloseOther(newTab, aTab)) {
+ // Swapping wasn't permitted. Bail out.
+ this.removeTab(newTab);
+ return null;
+ }
+
if (aSelectTab) {
this.selectedTab = newTab;
}
- aTab.parentNode._finishAnimateTabMove();
- this.swapBrowsersAndCloseOther(newTab, aTab);
-
- if (aSelectTab) {
- // Call updateCurrentBrowser to make sure the URL bar is up to date
- // for our new tab after we've done swapBrowsersAndCloseOther.
- this.updateCurrentBrowser(true);
- }
-
return newTab;
},
moveTabBackward() {
let previousTab = this.selectedTab.previousElementSibling;
while (previousTab && previousTab.hidden)
previousTab = previousTab.previousElementSibling;
--- a/browser/base/content/test/general/browser_bookmark_titles.js
+++ b/browser/base/content/test/general/browser_bookmark_titles.js
@@ -73,18 +73,19 @@ add_task(async function check_default_bo
});
// Bookmark the current page and confirm that the new bookmark has the expected
// title. (Then delete the bookmark.)
async function checkBookmark(url, expected_title) {
Assert.equal(gBrowser.selectedBrowser.currentURI.spec, url,
"Trying to bookmark the expected uri");
- let promiseBookmark = PlacesTestUtils.waitForNotification("onItemAdded",
- (id, parentId, index, type, itemUrl) => itemUrl.equals(gBrowser.selectedBrowser.currentURI));
+ let promiseBookmark = PlacesTestUtils.waitForNotification("bookmark-added",
+ (events) => events.some(({url: eventUrl}) => eventUrl == gBrowser.selectedBrowser.currentURI.spec),
+ "places");
PlacesCommandHook.bookmarkPage();
await promiseBookmark;
let bookmark = await PlacesUtils.bookmarks.fetch({url});
Assert.ok(bookmark, "Found the expected bookmark");
Assert.equal(bookmark.title, expected_title, "Bookmark got a good default title.");
--- a/browser/base/content/test/general/head.js
+++ b/browser/base/content/test/general/head.js
@@ -460,38 +460,28 @@ function promiseNotificationShown(notifi
return panelPromise;
}
/**
* Resolves when a bookmark with the given uri is added.
*/
function promiseOnBookmarkItemAdded(aExpectedURI) {
return new Promise((resolve, reject) => {
- let bookmarksObserver = {
- onItemAdded(aItemId, aFolderId, aIndex, aItemType, aURI) {
- info("Added a bookmark to " + aURI.spec);
- PlacesUtils.bookmarks.removeObserver(bookmarksObserver);
- if (aURI.equals(aExpectedURI)) {
- resolve();
- } else {
- reject(new Error("Added an unexpected bookmark"));
- }
- },
- onBeginUpdateBatch() {},
- onEndUpdateBatch() {},
- onItemRemoved() {},
- onItemChanged() {},
- onItemVisited() {},
- onItemMoved() {},
- QueryInterface: ChromeUtils.generateQI([
- Ci.nsINavBookmarkObserver,
- ]),
+ let listener = events => {
+ is(events.length, 1, "Should only receive one event.");
+ info("Added a bookmark to " + events[0].url);
+ PlacesUtils.observers.removeListener(["bookmark-added"], listener);
+ if (events[0].url == aExpectedURI.spec) {
+ resolve();
+ } else {
+ reject(new Error("Added an unexpected bookmark"));
+ }
};
info("Waiting for a bookmark to be added");
- PlacesUtils.bookmarks.addObserver(bookmarksObserver);
+ PlacesUtils.observers.addListener(["bookmark-added"], listener);
});
}
async function loadBadCertPage(url) {
const EXCEPTION_DIALOG_URI = "chrome://pippki/content/exceptionDialog.xul";
let exceptionDialogResolved = new Promise(function(resolve) {
// When the certificate exception dialog has opened, click the button to add
// an exception.
--- a/browser/base/content/test/siteIdentity/browser_identity_UI.js
+++ b/browser/base/content/test/siteIdentity/browser_identity_UI.js
@@ -1,21 +1,11 @@
/* Tests for correct behaviour of getEffectiveHost on identity handler */
-function test() {
- waitForExplicitFinish();
- requestLongerTimeout(2);
-
- ok(gIdentityHandler, "gIdentityHandler should exist");
-
- BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank", true).then(() => {
- BrowserTestUtils.addContentEventListener(gBrowser.selectedBrowser, "load", checkResult, true);
- nextTest();
- });
-}
+requestLongerTimeout(2);
// Greek IDN for 'example.test'.
var idnDomain = "\u03C0\u03B1\u03C1\u03AC\u03B4\u03B5\u03B9\u03B3\u03BC\u03B1.\u03B4\u03BF\u03BA\u03B9\u03BC\u03AE";
var tests = [
{
name: "normal domain",
location: "http://test1.example.org/",
effectiveHost: "test1.example.org",
@@ -24,127 +14,106 @@ var tests = [
name: "view-source",
location: "view-source:http://example.com/",
effectiveHost: null,
},
{
name: "normal HTTPS",
location: "https://example.com/",
effectiveHost: "example.com",
- isHTTPS: true,
},
{
name: "IDN subdomain",
location: "http://sub1.xn--hxajbheg2az3al.xn--jxalpdlp/",
effectiveHost: "sub1." + idnDomain,
},
{
name: "subdomain with port",
location: "http://sub1.test1.example.org:8000/",
effectiveHost: "sub1.test1.example.org",
},
{
name: "subdomain HTTPS",
location: "https://test1.example.com/",
effectiveHost: "test1.example.com",
- isHTTPS: true,
},
{
name: "view-source HTTPS",
location: "view-source:https://example.com/",
effectiveHost: null,
- isHTTPS: true,
},
{
name: "IP address",
location: "http://127.0.0.1:8888/",
effectiveHost: "127.0.0.1",
},
];
-var gCurrentTest, gCurrentTestIndex = -1, gTestDesc, gPopupHidden;
-// Go through the tests in both directions, to add additional coverage for
-// transitions between different states.
-var gForward = true;
-var gCheckETLD = false;
-function nextTest() {
- if (!gCheckETLD) {
- if (gForward)
- gCurrentTestIndex++;
- else
- gCurrentTestIndex--;
+add_task(async function test() {
+ ok(gIdentityHandler, "gIdentityHandler should exist");
+
+ await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ for (let i = 0; i < tests.length; i++) {
+ await runTest(i, true);
+ }
- if (gCurrentTestIndex == tests.length) {
- // Went too far, reverse
- gCurrentTestIndex--;
- gForward = false;
- }
+ gBrowser.removeCurrentTab();
+ await BrowserTestUtils.openNewForegroundTab(gBrowser);
- if (gCurrentTestIndex == -1) {
- gBrowser.removeCurrentTab();
- finish();
- return;
- }
+ for (let i = tests.length - 1; i >= 0; i--) {
+ await runTest(i, false);
+ }
- gCurrentTest = tests[gCurrentTestIndex];
- gTestDesc = "#" + gCurrentTestIndex + " (" + gCurrentTest.name + ")";
- if (!gForward)
- gTestDesc += " (second time)";
- if (gCurrentTest.isHTTPS) {
- gCheckETLD = true;
- }
+ gBrowser.removeCurrentTab();
+});
- // Navigate to the next page, which will cause checkResult to fire.
- let spec = gBrowser.selectedBrowser.currentURI.spec;
- if (spec == "about:blank" || spec == gCurrentTest.location) {
- BrowserTestUtils.loadURI(gBrowser.selectedBrowser, gCurrentTest.location);
- } else {
- // Open the Control Center and make sure it closes after nav (Bug 1207542).
- let popupShown = BrowserTestUtils.waitForEvent(gIdentityHandler._identityPopup, "popupshown");
- gPopupHidden = BrowserTestUtils.waitForEvent(gIdentityHandler._identityPopup, "popuphidden");
- gIdentityHandler._identityBox.click();
- info("Waiting for the Control Center to be shown");
- popupShown.then(async () => {
- ok(!BrowserTestUtils.is_hidden(gIdentityHandler._identityPopup), "Control Center is visible");
- // Show the subview, which is an easy way in automation to reproduce
- // Bug 1207542, where the CC wouldn't close on navigation.
- let promiseViewShown = BrowserTestUtils.waitForEvent(gIdentityHandler._identityPopup, "ViewShown");
- gBrowser.ownerDocument.querySelector("#identity-popup-security-expander").click();
- await promiseViewShown;
- BrowserTestUtils.loadURI(gBrowser.selectedBrowser, gCurrentTest.location);
- });
- }
- } else {
- gCheckETLD = false;
- gTestDesc = "#" + gCurrentTestIndex + " (" + gCurrentTest.name + " without eTLD in identity icon label)";
- if (!gForward)
- gTestDesc += " (second time)";
- gBrowser.selectedBrowser.reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE |
- Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY);
+async function runTest(i, forward) {
+ let currentTest = tests[i];
+ let testDesc = "#" + i + " (" + currentTest.name + ")";
+ if (!forward) {
+ testDesc += " (second time)";
}
-}
+
+ info("Running test " + testDesc);
-function checkResult() {
- if (gBrowser.selectedBrowser.currentURI.spec == "about:blank")
- return;
+ let popupHidden = null;
+ if ((forward && i > 0) || (!forward && i < tests.length - 1)) {
+ popupHidden = BrowserTestUtils.waitForEvent(gIdentityHandler._identityPopup, "popuphidden");
+ }
+
+ let loaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, currentTest.location);
+ BrowserTestUtils.loadURI(gBrowser.selectedBrowser, currentTest.location);
+ await loaded;
+ await popupHidden;
+ ok(BrowserTestUtils.is_hidden(gIdentityHandler._identityPopup), "Control Center is hidden");
// Sanity check other values, and the value of gIdentityHandler.getEffectiveHost()
- is(gIdentityHandler._uri.spec, gCurrentTest.location, "location matches for test " + gTestDesc);
+ is(gIdentityHandler._uri.spec, currentTest.location, "location matches for test " + testDesc);
// getEffectiveHost can't be called for all modes
- if (gCurrentTest.effectiveHost === null) {
+ if (currentTest.effectiveHost === null) {
let identityBox = document.getElementById("identity-box");
ok(identityBox.className == "unknownIdentity" ||
identityBox.className == "chromeUI", "mode matched");
} else {
- is(gIdentityHandler.getEffectiveHost(), gCurrentTest.effectiveHost, "effectiveHost matches for test " + gTestDesc);
+ is(gIdentityHandler.getEffectiveHost(), currentTest.effectiveHost, "effectiveHost matches for test " + testDesc);
}
- if (gPopupHidden) {
- info("Waiting for the Control Center to hide");
- gPopupHidden.then(() => {
- gPopupHidden = null;
- ok(BrowserTestUtils.is_hidden(gIdentityHandler._identityPopup), "Control Center is hidden");
- executeSoon(nextTest);
- });
- } else {
- executeSoon(nextTest);
- }
+ // Open the Control Center and make sure it closes after nav (Bug 1207542).
+ let popupShown = BrowserTestUtils.waitForEvent(gIdentityHandler._identityPopup, "popupshown");
+ gIdentityHandler._identityBox.click();
+ info("Waiting for the Control Center to be shown");
+ await popupShown;
+ ok(!BrowserTestUtils.is_hidden(gIdentityHandler._identityPopup), "Control Center is visible");
+ let displayedHost = currentTest.effectiveHost || currentTest.location;
+ ok(gIdentityHandler._identityPopupMainViewHeaderLabel.textContent.includes(displayedHost),
+ "identity UI header shows the host for test " + testDesc);
+
+ // Show the subview, which is an easy way in automation to reproduce
+ // Bug 1207542, where the CC wouldn't close on navigation.
+ let promiseViewShown = BrowserTestUtils.waitForEvent(gIdentityHandler._identityPopup, "ViewShown");
+ gBrowser.ownerDocument.querySelector("#identity-popup-security-expander").click();
+ await promiseViewShown;
+
+ displayedHost = currentTest.effectiveHost || currentTest.location;
+ ok(gIdentityHandler._identityPopupContentHost.textContent.includes(displayedHost),
+ "security subview header shows the host for test " + testDesc);
}
--- a/browser/components/controlcenter/content/panel.inc.xul
+++ b/browser/components/controlcenter/content/panel.inc.xul
@@ -10,22 +10,27 @@
onpopupshown="gIdentityHandler.onPopupShown(event);"
onpopuphidden="gIdentityHandler.onPopupHidden(event);"
orient="vertical">
<panelmultiview id="identity-popup-multiView"
mainViewId="identity-popup-mainView">
<panelview id="identity-popup-mainView"
descriptionheightworkaround="true">
+ <vbox id="identity-popup-mainView-panel-header">
+ <label>
+ <html:span id="identity-popup-mainView-panel-header-span"/>
+ </label>
+ </vbox>
+
<!-- Security Section -->
<hbox id="identity-popup-security" class="identity-popup-section">
<vbox class="identity-popup-security-content" flex="1">
<label class="plain">
- <label class="identity-popup-headline identity-popup-host"></label>
- <label class="identity-popup-headline identity-popup-hostless" crop="end"/>
+ <label class="identity-popup-headline">&identity.connection;</label>
</label>
<description class="identity-popup-connection-not-secure"
when-connection="not-secure secure-cert-user-overridden">&identity.connectionNotSecure;</description>
<description class="identity-popup-connection-secure"
when-connection="secure secure-ev">&identity.connectionSecure;</description>
<description when-connection="chrome">&identity.connectionInternal;</description>
<description when-connection="file">&identity.connectionFile;</description>
<description when-connection="extension">&identity.extensionPage;</description>
@@ -160,18 +165,17 @@
</panelview>
<!-- Security SubView -->
<panelview id="identity-popup-securityView"
title="&identity.securityView.label;"
descriptionheightworkaround="true">
<vbox class="identity-popup-security-content">
<label class="plain">
- <label class="identity-popup-headline identity-popup-host"></label>
- <label class="identity-popup-headline identity-popup-hostless" crop="end"/>
+ <label class="identity-popup-headline" id="identity-popup-host"></label>
</label>
<description class="identity-popup-connection-not-secure"
when-connection="not-secure secure-cert-user-overridden">&identity.connectionNotSecure;</description>
<description class="identity-popup-connection-secure"
when-connection="secure secure-ev">&identity.connectionSecure;</description>
</vbox>
<vbox id="identity-popup-securityView-body" class="panel-view-body-unscrollable">
--- a/browser/components/enterprisepolicies/tests/browser/browser_policy_bookmarks.js
+++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_bookmarks.js
@@ -64,40 +64,43 @@ function findBookmarkInPolicy(bookmark)
return entry;
}
}
return null;
}
async function promiseAllChangesMade({itemsToAdd, itemsToRemove}) {
return new Promise(resolve => {
+ let listener = events => {
+ is(events.length, 1, "Should only have 1 event.");
+ itemsToAdd--;
+ if (itemsToAdd == 0 && itemsToRemove == 0) {
+ PlacesUtils.bookmarks.removeObserver(bmObserver);
+ PlacesUtils.observers.removeListener(["bookmark-added"], listener);
+ resolve();
+ }
+ };
let bmObserver = {
- onItemAdded() {
- itemsToAdd--;
- if (itemsToAdd == 0 && itemsToRemove == 0) {
- PlacesUtils.bookmarks.removeObserver(bmObserver);
- resolve();
- }
- },
-
onItemRemoved() {
itemsToRemove--;
if (itemsToAdd == 0 && itemsToRemove == 0) {
PlacesUtils.bookmarks.removeObserver(bmObserver);
+ PlacesUtils.observers.removeListener(["bookmark-added"], listener);
resolve();
}
},
onBeginUpdateBatch() {},
onEndUpdateBatch() {},
onItemChanged() {},
onItemVisited() {},
onItemMoved() {},
};
PlacesUtils.bookmarks.addObserver(bmObserver);
+ PlacesUtils.observers.addListener(["bookmark-added"], listener);
});
}
/*
* ==================
* = CHECK FUNCTION =
* ==================
*
--- a/browser/components/extensions/parent/ext-bookmarks.js
+++ b/browser/components/extensions/parent/ext-bookmarks.js
@@ -102,37 +102,44 @@ const convertBookmarks = result => {
};
let observer = new class extends EventEmitter {
constructor() {
super();
this.skipTags = true;
this.skipDescendantsOnItemRemoval = true;
+
+ this.handlePlacesEvents = this.handlePlacesEvents.bind(this);
}
onBeginUpdateBatch() {}
onEndUpdateBatch() {}
- onItemAdded(id, parentId, index, itemType, uri, title, dateAdded, guid, parentGuid, source) {
- let bookmark = {
- id: guid,
- parentId: parentGuid,
- index,
- title,
- dateAdded: dateAdded / 1000,
- type: BOOKMARKS_TYPES_TO_API_TYPES_MAP.get(itemType),
- url: getUrl(itemType, uri && uri.spec),
- };
+ handlePlacesEvents(events) {
+ for (let event of events) {
+ if (event.isTagging) {
+ continue;
+ }
+ let bookmark = {
+ id: event.guid,
+ parentId: event.parentGuid,
+ index: event.index,
+ title: event.title,
+ dateAdded: event.dateAdded,
+ type: BOOKMARKS_TYPES_TO_API_TYPES_MAP.get(event.itemType),
+ url: getUrl(event.itemType, event.url),
+ };
- if (itemType == TYPE_FOLDER) {
- bookmark.dateGroupModified = bookmark.dateAdded;
+ if (event.itemType == TYPE_FOLDER) {
+ bookmark.dateGroupModified = bookmark.dateAdded;
+ }
+
+ this.emit("created", bookmark);
}
-
- this.emit("created", bookmark);
}
onItemVisited() {}
onItemMoved(id, oldParentId, oldIndex, newParentId, newIndex, itemType, guid, oldParentGuid, newParentGuid, source) {
let info = {
parentId: newParentGuid,
index: newIndex,
@@ -168,23 +175,25 @@ let observer = new class extends EventEm
this.emit("changed", {guid, info});
}
}();
const decrementListeners = () => {
listenerCount -= 1;
if (!listenerCount) {
PlacesUtils.bookmarks.removeObserver(observer);
+ PlacesUtils.observers.removeListener(["bookmark-added"], observer.handlePlacesEvents);
}
};
const incrementListeners = () => {
listenerCount++;
if (listenerCount == 1) {
PlacesUtils.bookmarks.addObserver(observer);
+ PlacesUtils.observers.addListener(["bookmark-added"], observer.handlePlacesEvents);
}
};
this.bookmarks = class extends ExtensionAPI {
getAPI(context) {
return {
bookmarks: {
async get(idOrIdList) {
--- a/browser/components/migration/tests/unit/test_360se_bookmarks.js
+++ b/browser/components/migration/tests/unit/test_360se_bookmarks.js
@@ -16,43 +16,37 @@ add_task(async function() {
// folders are created on the toolbar.
let source = MigrationUtils.getLocalizedString("sourceName360se");
let label = MigrationUtils.getLocalizedString("importedBookmarksFolder", [source]);
let expectedParents = [ PlacesUtils.toolbarFolderId ];
let itemCount = 0;
let gotFolder = false;
- let bmObserver = {
- onItemAdded(aItemId, aParentId, aIndex, aItemType, aURI, aTitle) {
- if (aTitle != label) {
+ let listener = events => {
+ for (let event of events) {
+ if (event.title != label) {
itemCount++;
}
- if (aItemType == PlacesUtils.bookmarks.TYPE_FOLDER && aTitle == "360 \u76f8\u5173") {
+ if (event.itemType == PlacesUtils.bookmarks.TYPE_FOLDER && event.title == "360 \u76f8\u5173") {
gotFolder = true;
}
- if (expectedParents.length > 0 && aTitle == label) {
- let index = expectedParents.indexOf(aParentId);
+ if (expectedParents.length > 0 && event.title == label) {
+ let index = expectedParents.indexOf(event.parentId);
Assert.ok(index != -1, "Found expected parent");
expectedParents.splice(index, 1);
}
- },
- onBeginUpdateBatch() {},
- onEndUpdateBatch() {},
- onItemRemoved() {},
- onItemChanged() {},
- onItemVisited() {},
- onItemMoved() {},
+ }
};
- PlacesUtils.bookmarks.addObserver(bmObserver);
+ PlacesUtils.observers.addListener(["bookmark-added"], listener);
await promiseMigration(migrator, MigrationUtils.resourceTypes.BOOKMARKS, {
id: "default",
});
- PlacesUtils.bookmarks.removeObserver(bmObserver);
+ PlacesUtils.observers.removeListener(["bookmark-added"], listener);
// Check the bookmarks have been imported to all the expected parents.
Assert.ok(!expectedParents.length, "No more expected parents");
Assert.ok(gotFolder, "Should have seen the folder get imported");
Assert.equal(itemCount, 10, "Should import all 10 items.");
// Check that the telemetry matches:
Assert.equal(MigrationUtils._importQuantities.bookmarks, itemCount, "Telemetry reporting correct.");
});
--- a/browser/components/migration/tests/unit/test_Chrome_bookmarks.js
+++ b/browser/components/migration/tests/unit/test_Chrome_bookmarks.js
@@ -72,34 +72,28 @@ add_task(async function() {
await OS.File.writeAtomic(target.path, JSON.stringify(bookmarksData), {encoding: "utf-8"});
let migrator = await MigrationUtils.getMigrator("chrome");
// Sanity check for the source.
Assert.ok(await migrator.isSourceAvailable());
let itemsSeen = {bookmarks: 0, folders: 0};
- let bmObserver = {
- onItemAdded(aItemId, aParentId, aIndex, aItemType, aURI, aTitle) {
- if (!aTitle.includes("Chrome")) {
- itemsSeen[aItemType == PlacesUtils.bookmarks.TYPE_FOLDER ? "folders" : "bookmarks"]++;
+ let listener = events => {
+ for (let event of events) {
+ if (!event.title.includes("Chrome")) {
+ itemsSeen[event.itemType == PlacesUtils.bookmarks.TYPE_FOLDER ? "folders" : "bookmarks"]++;
}
- },
- onBeginUpdateBatch() {},
- onEndUpdateBatch() {},
- onItemRemoved() {},
- onItemChanged() {},
- onItemVisited() {},
- onItemMoved() {},
+ }
};
- PlacesUtils.bookmarks.addObserver(bmObserver);
+ PlacesUtils.observers.addListener(["bookmark-added"], listener);
const PROFILE = {
id: "Default",
name: "Default",
};
await promiseMigration(migrator, MigrationUtils.resourceTypes.BOOKMARKS, PROFILE);
- PlacesUtils.bookmarks.removeObserver(bmObserver);
+ PlacesUtils.observers.removeListener(["bookmark-added"], listener);
Assert.equal(itemsSeen.bookmarks, 200, "Should have seen 200 bookmarks.");
Assert.equal(itemsSeen.folders, 10, "Should have seen 10 folders.");
Assert.equal(MigrationUtils._importQuantities.bookmarks, itemsSeen.bookmarks + itemsSeen.folders, "Telemetry reporting correct.");
});
--- a/browser/components/migration/tests/unit/test_Edge_db_migration.js
+++ b/browser/components/migration/tests/unit/test_Edge_db_migration.js
@@ -418,50 +418,55 @@ add_task(async function() {
.createInstance(Ci.nsIBrowserProfileMigrator);
let bookmarksMigrator = migrator.wrappedJSObject.getBookmarksMigratorForTesting(db);
Assert.ok(bookmarksMigrator.exists, "Should recognize db we just created");
let source = MigrationUtils.getLocalizedString("sourceNameEdge");
let sourceLabel = MigrationUtils.getLocalizedString("importedBookmarksFolder", [source]);
let seenBookmarks = [];
- let bookmarkObserver = {
- onItemAdded(itemId, parentId, index, itemType, url, title, dateAdded, itemGuid, parentGuid) {
+ let listener = events => {
+ for (let event of events) {
+ let {
+ id,
+ itemType,
+ url,
+ title,
+ dateAdded,
+ guid,
+ index,
+ parentGuid,
+ parentId,
+ } = event;
if (title.startsWith("Deleted")) {
ok(false, "Should not see deleted items being bookmarked!");
}
- seenBookmarks.push({itemId, parentId, index, itemType, url, title, dateAdded, itemGuid, parentGuid});
- },
- onBeginUpdateBatch() {},
- onEndUpdateBatch() {},
- onItemRemoved() {},
- onItemChanged() {},
- onItemVisited() {},
- onItemMoved() {},
+ seenBookmarks.push({id, parentId, index, itemType, url, title, dateAdded, guid, parentGuid});
+ }
};
- PlacesUtils.bookmarks.addObserver(bookmarkObserver);
+ PlacesUtils.observers.addListener(["bookmark-added"], listener);
let migrateResult = await new Promise(resolve => bookmarksMigrator.migrate(resolve)).catch(ex => {
Cu.reportError(ex);
Assert.ok(false, "Got an exception trying to migrate data! " + ex);
return false;
});
- PlacesUtils.bookmarks.removeObserver(bookmarkObserver);
+ PlacesUtils.observers.removeListener(["bookmark-added"], listener);
Assert.ok(migrateResult, "Migration should succeed");
Assert.equal(seenBookmarks.length, 7, "Should have seen 7 items being bookmarked.");
Assert.equal(seenBookmarks.filter(bm => bm.title != sourceLabel).length,
MigrationUtils._importQuantities.bookmarks,
"Telemetry should have items except for 'From Microsoft Edge' folders");
let menuParents = seenBookmarks.filter(item => item.parentGuid == PlacesUtils.bookmarks.menuGuid);
Assert.equal(menuParents.length, 1, "Should have a single folder added to the menu");
let toolbarParents = seenBookmarks.filter(item => item.parentGuid == PlacesUtils.bookmarks.toolbarGuid);
Assert.equal(toolbarParents.length, 1, "Should have a single item added to the toolbar");
- let menuParentGuid = menuParents[0].itemGuid;
- let toolbarParentGuid = toolbarParents[0].itemGuid;
+ let menuParentGuid = menuParents[0].guid;
+ let toolbarParentGuid = toolbarParents[0].guid;
let expectedTitlesInMenu = bookmarkReferenceItems.filter(item => item.ParentId == kEdgeMenuParent).map(item => item.Title);
// Hacky, but seems like much the simplest way:
expectedTitlesInMenu.push("Item in deleted folder (should be in root)");
let expectedTitlesInToolbar = bookmarkReferenceItems.filter(item => item.ParentId == "921dc8a0-6c83-40ef-8df1-9bd1c5c56aaf").map(item => item.Title);
let edgeNameStr = MigrationUtils.getLocalizedString("sourceNameEdge");
let importParentFolderName = MigrationUtils.getLocalizedString("importedBookmarksFolder", [edgeNameStr]);
@@ -476,68 +481,73 @@ add_task(async function() {
Assert.notEqual(bookmark.itemType, PlacesUtils.bookmarks.TYPE_FOLDER,
"Bookmark " + bookmark.title + " should not be a folder");
}
if (shouldBeInMenu) {
Assert.equal(bookmark.parentGuid, menuParentGuid, "Item '" + bookmark.title + "' should be in menu");
} else if (shouldBeInToolbar) {
Assert.equal(bookmark.parentGuid, toolbarParentGuid, "Item '" + bookmark.title + "' should be in toolbar");
- } else if (bookmark.itemGuid == menuParentGuid || bookmark.itemGuid == toolbarParentGuid) {
+ } else if (bookmark.guid == menuParentGuid || bookmark.guid == toolbarParentGuid) {
Assert.ok(true, "Expect toolbar and menu folders to not be in menu or toolbar");
} else {
// Bit hacky, but we do need to check this.
Assert.equal(bookmark.title, "Item in folder", "Subfoldered item shouldn't be in menu or toolbar");
- let parent = seenBookmarks.find(maybeParent => maybeParent.itemGuid == bookmark.parentGuid);
+ let parent = seenBookmarks.find(maybeParent => maybeParent.guid == bookmark.parentGuid);
Assert.equal(parent && parent.title, "Folder", "Subfoldered item should be in subfolder labeled 'Folder'");
}
let dbItem = bookmarkReferenceItems.find(someItem => bookmark.title == someItem.Title);
if (!dbItem) {
Assert.equal(bookmark.title, importParentFolderName, "Only the extra layer of folders isn't in the input we stuck in the DB.");
- Assert.ok([menuParentGuid, toolbarParentGuid].includes(bookmark.itemGuid), "This item should be one of the containers");
+ Assert.ok([menuParentGuid, toolbarParentGuid].includes(bookmark.guid), "This item should be one of the containers");
} else {
- Assert.equal(dbItem.URL || null, bookmark.url && bookmark.url.spec, "URL is correct");
- Assert.equal(dbItem.DateUpdated.valueOf(), (new Date(bookmark.dateAdded / 1000)).valueOf(), "Date added is correct");
+ Assert.equal(dbItem.URL || "", bookmark.url, "URL is correct");
+ Assert.equal(dbItem.DateUpdated.valueOf(), (new Date(bookmark.dateAdded)).valueOf(), "Date added is correct");
}
}
MigrationUtils._importQuantities.bookmarks = 0;
seenBookmarks = [];
- bookmarkObserver = {
- onItemAdded(itemId, parentId, index, itemType, url, title, dateAdded, itemGuid, parentGuid) {
- seenBookmarks.push({itemId, parentId, index, itemType, url, title, dateAdded, itemGuid, parentGuid});
- },
- onBeginUpdateBatch() {},
- onEndUpdateBatch() {},
- onItemRemoved() {},
- onItemChanged() {},
- onItemVisited() {},
- onItemMoved() {},
+ listener = events => {
+ for (let event of events) {
+ let {
+ id,
+ itemType,
+ url,
+ title,
+ dateAdded,
+ guid,
+ index,
+ parentGuid,
+ parentId,
+ } = event;
+ seenBookmarks.push({id, parentId, index, itemType, url, title, dateAdded, guid, parentGuid});
+ }
};
- PlacesUtils.bookmarks.addObserver(bookmarkObserver);
+ PlacesUtils.observers.addListener(["bookmark-added"], listener);
let readingListMigrator = migrator.wrappedJSObject.getReadingListMigratorForTesting(db);
Assert.ok(readingListMigrator.exists, "Should recognize db we just created");
migrateResult = await new Promise(resolve => readingListMigrator.migrate(resolve)).catch(ex => {
Cu.reportError(ex);
Assert.ok(false, "Got an exception trying to migrate data! " + ex);
return false;
});
- PlacesUtils.bookmarks.removeObserver(bookmarkObserver);
+ PlacesUtils.observers.removeListener(["bookmark-added"], listener);
Assert.ok(migrateResult, "Migration should succeed");
Assert.equal(seenBookmarks.length, 3, "Should have seen 3 items being bookmarked (2 items + 1 folder).");
Assert.equal(seenBookmarks.filter(bm => bm.title != sourceLabel).length,
MigrationUtils._importQuantities.bookmarks,
"Telemetry should have items except for 'From Microsoft Edge' folders");
let readingListContainerLabel = MigrationUtils.getLocalizedString("importedEdgeReadingList");
for (let bookmark of seenBookmarks) {
if (readingListContainerLabel == bookmark.title) {
continue;
}
let referenceItem = readingListReferenceItems.find(item => item.Title == bookmark.title);
Assert.ok(referenceItem, "Should have imported what we expected");
- Assert.equal(referenceItem.URL, bookmark.url.spec, "Should have the right URL");
+ Assert.equal(referenceItem.URL, bookmark.url, "Should have the right URL");
readingListReferenceItems.splice(readingListReferenceItems.findIndex(item => item.Title == bookmark.title), 1);
}
Assert.ok(!readingListReferenceItems.length, "Should have seen all expected items.");
});
--- a/browser/components/migration/tests/unit/test_IE_bookmarks.js
+++ b/browser/components/migration/tests/unit/test_IE_bookmarks.js
@@ -9,36 +9,30 @@ add_task(async function() {
// folders are created in the menu and on the toolbar.
let source = MigrationUtils.getLocalizedString("sourceNameIE");
let label = MigrationUtils.getLocalizedString("importedBookmarksFolder", [source]);
let expectedParents = [ PlacesUtils.bookmarksMenuFolderId,
PlacesUtils.toolbarFolderId ];
let itemCount = 0;
- let bmObserver = {
- onItemAdded(aItemId, aParentId, aIndex, aItemType, aURI, aTitle) {
- if (aTitle != label) {
+ let listener = events => {
+ for (let event of events) {
+ if (event.title != label) {
itemCount++;
}
- if (expectedParents.length > 0 && aTitle == label) {
- let index = expectedParents.indexOf(aParentId);
+ if (expectedParents.length > 0 && event.title == label) {
+ let index = expectedParents.indexOf(event.parentId);
Assert.notEqual(index, -1);
expectedParents.splice(index, 1);
}
- },
- onBeginUpdateBatch() {},
- onEndUpdateBatch() {},
- onItemRemoved() {},
- onItemChanged() {},
- onItemVisited() {},
- onItemMoved() {},
+ }
};
- PlacesUtils.bookmarks.addObserver(bmObserver);
+ PlacesUtils.observers.addListener(["bookmark-added"], listener);
await promiseMigration(migrator, MigrationUtils.resourceTypes.BOOKMARKS);
- PlacesUtils.bookmarks.removeObserver(bmObserver);
+ PlacesUtils.observers.removeListener(["bookmark-added"], listener);
Assert.equal(MigrationUtils._importQuantities.bookmarks, itemCount,
"Ensure telemetry matches actual number of imported items.");
// Check the bookmarks have been imported to all the expected parents.
Assert.equal(expectedParents.length, 0, "Got all the expected parents");
});
--- a/browser/components/migration/tests/unit/test_Safari_bookmarks.js
+++ b/browser/components/migration/tests/unit/test_Safari_bookmarks.js
@@ -11,41 +11,35 @@ add_task(async function() {
// folders are created on the toolbar.
let source = MigrationUtils.getLocalizedString("sourceNameSafari");
let label = MigrationUtils.getLocalizedString("importedBookmarksFolder", [source]);
let expectedParents = [ PlacesUtils.toolbarFolderId ];
let itemCount = 0;
let gotFolder = false;
- let bmObserver = {
- onItemAdded(aItemId, aParentId, aIndex, aItemType, aURI, aTitle) {
- if (aTitle != label) {
+ let listener = events => {
+ for (let event of events) {
+ if (event.title != label) {
itemCount++;
}
- if (aItemType == PlacesUtils.bookmarks.TYPE_FOLDER && aTitle == "Stuff") {
+ if (event.itemType == PlacesUtils.bookmarks.TYPE_FOLDER && event.title == "Stuff") {
gotFolder = true;
}
- if (expectedParents.length > 0 && aTitle == label) {
- let index = expectedParents.indexOf(aParentId);
+ if (expectedParents.length > 0 && event.title == label) {
+ let index = expectedParents.indexOf(event.parentId);
Assert.ok(index != -1, "Found expected parent");
expectedParents.splice(index, 1);
}
- },
- onBeginUpdateBatch() {},
- onEndUpdateBatch() {},
- onItemRemoved() {},
- onItemChanged() {},
- onItemVisited() {},
- onItemMoved() {},
+ }
};
- PlacesUtils.bookmarks.addObserver(bmObserver);
+ PlacesUtils.observers.addListener(["bookmark-added"], listener);
await promiseMigration(migrator, MigrationUtils.resourceTypes.BOOKMARKS);
- PlacesUtils.bookmarks.removeObserver(bmObserver);
+ PlacesUtils.observers.removeListener(["bookmark-added"], listener);
// Check the bookmarks have been imported to all the expected parents.
Assert.ok(!expectedParents.length, "No more expected parents");
Assert.ok(gotFolder, "Should have seen the folder get imported");
Assert.equal(itemCount, 13, "Should import all 13 items.");
// Check that the telemetry matches:
Assert.equal(MigrationUtils._importQuantities.bookmarks, itemCount, "Telemetry reporting correct.");
});
--- a/browser/components/newtab/lib/PlacesFeed.jsm
+++ b/browser/components/newtab/lib/PlacesFeed.jsm
@@ -75,56 +75,16 @@ class HistoryObserver extends Observer {
*/
class BookmarksObserver extends Observer {
constructor(dispatch) {
super(dispatch, Ci.nsINavBookmarkObserver);
this.skipTags = true;
}
/**
- * onItemAdded - Called when a bookmark is added
- *
- * @param {str} id
- * @param {str} folderId
- * @param {int} index
- * @param {int} type Indicates if the bookmark is an actual bookmark,
- * a folder, or a separator.
- * @param {str} uri
- * @param {str} title
- * @param {int} dateAdded
- * @param {str} guid The unique id of the bookmark
- * @param {str} parent guid
- * @param {int} source Used to distinguish bookmarks made by different
- * actions: sync, bookmarks import, other.
- */
- onItemAdded(id, folderId, index, type, uri, bookmarkTitle, dateAdded, bookmarkGuid, parentGuid, source) { // eslint-disable-line max-params
- // Skips items that are not bookmarks (like folders), about:* pages or
- // default bookmarks, added when the profile is created.
- if (type !== PlacesUtils.bookmarks.TYPE_BOOKMARK ||
- source === PlacesUtils.bookmarks.SOURCES.IMPORT ||
- source === PlacesUtils.bookmarks.SOURCES.RESTORE ||
- source === PlacesUtils.bookmarks.SOURCES.RESTORE_ON_STARTUP ||
- source === PlacesUtils.bookmarks.SOURCES.SYNC ||
- (uri.scheme !== "http" && uri.scheme !== "https")) {
- return;
- }
-
- this.dispatch({type: at.PLACES_LINKS_CHANGED});
- this.dispatch({
- type: at.PLACES_BOOKMARK_ADDED,
- data: {
- bookmarkGuid,
- bookmarkTitle,
- dateAdded,
- url: uri.spec,
- },
- });
- }
-
- /**
* onItemRemoved - Called when a bookmark is removed
*
* @param {str} id
* @param {str} folderId
* @param {int} index
* @param {int} type Indicates if the bookmark is an actual bookmark,
* a folder, or a separator.
* @param {str} uri
@@ -153,32 +113,72 @@ class BookmarksObserver extends Observer
onItemMoved() {}
// Disabled due to performance cost, see Issue 3203 /
// https://bugzilla.mozilla.org/show_bug.cgi?id=1392267.
onItemChanged() {}
}
+/**
+ * PlacesObserver - observes events from PlacesUtils.observers
+ */
+class PlacesObserver extends Observer {
+ constructor(dispatch) {
+ super(dispatch, Ci.nsINavBookmarkObserver);
+ this.handlePlacesEvents = this.handlePlacesEvents.bind(this);
+ }
+
+ handlePlacesEvents(events) {
+ for (let {itemType, source, dateAdded, guid, title, url, isTagging} of events) {
+ // Skips items that are not bookmarks (like folders), about:* pages or
+ // default bookmarks, added when the profile is created.
+ if (isTagging ||
+ itemType !== PlacesUtils.bookmarks.TYPE_BOOKMARK ||
+ source === PlacesUtils.bookmarks.SOURCES.IMPORT ||
+ source === PlacesUtils.bookmarks.SOURCES.RESTORE ||
+ source === PlacesUtils.bookmarks.SOURCES.RESTORE_ON_STARTUP ||
+ source === PlacesUtils.bookmarks.SOURCES.SYNC ||
+ (!url.startsWith("http://") && !url.startsWith("https://"))) {
+ return;
+ }
+
+ this.dispatch({type: at.PLACES_LINKS_CHANGED});
+ this.dispatch({
+ type: at.PLACES_BOOKMARK_ADDED,
+ data: {
+ bookmarkGuid: guid,
+ bookmarkTitle: title,
+ dateAdded: dateAdded * 1000,
+ url
+ }
+ });
+ }
+ }
+}
+
class PlacesFeed {
constructor() {
this.placesChangedTimer = null;
this.customDispatch = this.customDispatch.bind(this);
this.historyObserver = new HistoryObserver(this.customDispatch);
this.bookmarksObserver = new BookmarksObserver(this.customDispatch);
+ this.placesObserver = new PlacesObserver(this.customDispatch);
}
addObservers() {
// NB: Directly get services without importing the *BIG* PlacesUtils module
Cc["@mozilla.org/browser/nav-history-service;1"]
.getService(Ci.nsINavHistoryService)
.addObserver(this.historyObserver, true);
Cc["@mozilla.org/browser/nav-bookmarks-service;1"]
.getService(Ci.nsINavBookmarksService)
.addObserver(this.bookmarksObserver, true);
+ PlacesUtils.observers.addListener(["bookmark-added"],
+ this.placesObserver.handlePlacesEvents);
Services.obs.addObserver(this, LINK_BLOCKED_EVENT);
}
/**
* setTimeout - A custom function that creates an nsITimer that can be cancelled
*
* @param {func} callback A function to be executed after the timer expires
@@ -209,16 +209,18 @@ class PlacesFeed {
removeObservers() {
if (this.placesChangedTimer) {
this.placesChangedTimer.cancel();
this.placesChangedTimer = null;
}
PlacesUtils.history.removeObserver(this.historyObserver);
PlacesUtils.bookmarks.removeObserver(this.bookmarksObserver);
+ PlacesUtils.observers.removeListener(["bookmark-added"],
+ this.placesObserver.handlePlacesEvents);
Services.obs.removeObserver(this, LINK_BLOCKED_EVENT);
}
/**
* observe - An observer for the LINK_BLOCKED_EVENT.
* Called when a link is blocked.
*
* @param {null} subject
--- a/browser/components/newtab/test/unit/lib/PlacesFeed.test.js
+++ b/browser/components/newtab/test/unit/lib/PlacesFeed.test.js
@@ -339,24 +339,28 @@ describe("PlacesFeed", () => {
observer.onPageChanged();
observer.onDeleteVisits();
});
});
});
describe("Custom dispatch", () => {
it("should only dispatch 1 PLACES_LINKS_CHANGED action if many onItemAdded notifications happened at once", async () => {
- // Yes, onItemAdded has at least 8 arguments. See function definition for docs.
- const args = [null, null, null, TYPE_BOOKMARK,
- {spec: FAKE_BOOKMARK.url, scheme: "http"}, FAKE_BOOKMARK.bookmarkTitle,
- FAKE_BOOKMARK.dateAdded, FAKE_BOOKMARK.bookmarkGuid, "", SOURCES.DEFAULT];
- await feed.bookmarksObserver.onItemAdded(...args);
- await feed.bookmarksObserver.onItemAdded(...args);
- await feed.bookmarksObserver.onItemAdded(...args);
- await feed.bookmarksObserver.onItemAdded(...args);
+ const args = {
+ type: "bookmark-added",
+ itemType: TYPE_BOOKMARK,
+ url: "https://" + FAKE_BOOKMARK.url,
+ title: FAKE_BOOKMARK.bookmarkTitle,
+ dateAdded: FAKE_BOOKMARK.dateAdded,
+ guid: FAKE_BOOKMARK.bookmarkGuid,
+ source: SOURCES.DEFAULT,
+ };
+ await feed.placesObserver.handlePlacesEvents([args]);
+ await feed.placesObserver.handlePlacesEvents([args]);
+ await feed.placesObserver.handlePlacesEvents([args]);
assert.calledOnce(feed.store.dispatch.withArgs(ac.OnlyToMain({type: at.PLACES_LINKS_CHANGED})));
});
it("should only dispatch 1 PLACES_LINKS_CHANGED action if many onItemRemoved notifications happened at once", async () => {
const args = [null, null, null, TYPE_BOOKMARK, {spec: "foo.com"}, "123foo", "", SOURCES.DEFAULT];
await feed.bookmarksObserver.onItemRemoved(...args);
await feed.bookmarksObserver.onItemRemoved(...args);
await feed.bookmarksObserver.onItemRemoved(...args);
await feed.bookmarksObserver.onItemRemoved(...args);
@@ -367,98 +371,152 @@ describe("PlacesFeed", () => {
await feed.historyObserver.onDeleteURI({spec: "foo.com"});
await feed.historyObserver.onDeleteURI({spec: "foo1.com"});
await feed.historyObserver.onDeleteURI({spec: "foo2.com"});
assert.calledOnce(feed.store.dispatch.withArgs(ac.OnlyToMain({type: at.PLACES_LINKS_CHANGED})));
});
});
+ describe("PlacesObserver", () => {
+ let dispatch;
+ let observer;
+ beforeEach(() => {
+ dispatch = sandbox.spy();
+ observer = new PlacesObserver(dispatch);
+ });
+
+ describe("#handlePlacesEvents", () => {
+ beforeEach(() => {
+ });
+
+ it("should dispatch a PLACES_BOOKMARK_ADDED action with the bookmark data - http", async () => {
+ const args = {
+ type: "bookmark-added",
+ itemType: TYPE_BOOKMARK,
+ url: "http://" + FAKE_BOOKMARK.url,
+ title: FAKE_BOOKMARK.bookmarkTitle,
+ dateAdded: FAKE_BOOKMARK.dateAdded,
+ guid: FAKE_BOOKMARK.bookmarkGuid,
+ source: SOURCES.DEFAULT,
+ };
+ await observer.handlePlacesEvents([args]);
+
+ assert.calledWith(dispatch, {type: at.PLACES_BOOKMARK_ADDED, data: FAKE_BOOKMARK});
+ });
+ it("should dispatch a PLACES_BOOKMARK_ADDED action with the bookmark data - https", async () => {
+ const args = {
+ type: "bookmark-added",
+ itemType: TYPE_BOOKMARK,
+ url: "https://" + FAKE_BOOKMARK.url,
+ title: FAKE_BOOKMARK.bookmarkTitle,
+ dateAdded: FAKE_BOOKMARK.dateAdded,
+ guid: FAKE_BOOKMARK.bookmarkGuid,
+ source: SOURCES.DEFAULT,
+ };
+ await observer.handlePlacesEvents([args]);
+
+ assert.calledWith(dispatch, {type: at.PLACES_BOOKMARK_ADDED, data: FAKE_BOOKMARK});
+ });
+ it("should not dispatch a PLACES_BOOKMARK_ADDED action - not http/https", async () => {
+ const args = {
+ type: "bookmark-added",
+ itemType: TYPE_BOOKMARK,
+ url: "places://" + FAKE_BOOKMARK.url,
+ title: FAKE_BOOKMARK.bookmarkTitle,
+ dateAdded: FAKE_BOOKMARK.dateAdded,
+ guid: FAKE_BOOKMARK.bookmarkGuid,
+ source: SOURCES.DEFAULT,
+ };
+ await observer.handlePlacesEvents([args]);
+ assert.notCalled(dispatch);
+ });
+ it("should not dispatch a PLACES_BOOKMARK_ADDED action - has IMPORT source", async () => {
+ const args = {
+ type: "bookmark-added",
+ itemType: TYPE_BOOKMARK,
+ url: "http://" + FAKE_BOOKMARK.url,
+ title: FAKE_BOOKMARK.bookmarkTitle,
+ dateAdded: FAKE_BOOKMARK.dateAdded,
+ guid: FAKE_BOOKMARK.bookmarkGuid,
+ source: SOURCES.IMPORT,
+ };
+ await observer.handlePlacesEvents([args]);
+
+ assert.notCalled(dispatch);
+ });
+ it("should not dispatch a PLACES_BOOKMARK_ADDED action - has RESTORE source", async () => {
+ const args = {
+ type: "bookmark-added",
+ itemType: TYPE_BOOKMARK,
+ url: "http://" + FAKE_BOOKMARK.url,
+ title: FAKE_BOOKMARK.bookmarkTitle,
+ dateAdded: FAKE_BOOKMARK.dateAdded,
+ guid: FAKE_BOOKMARK.bookmarkGuid,
+ source: SOURCES.RESTORE,
+ };
+ await observer.handlePlacesEvents([args]);
+
+ assert.notCalled(dispatch);
+ });
+ it("should not dispatch a PLACES_BOOKMARK_ADDED action - has RESTORE_ON_STARTUP source", async () => {
+ const args = {
+ type: "bookmark-added",
+ itemType: TYPE_BOOKMARK,
+ url: "http://" + FAKE_BOOKMARK.url,
+ title: FAKE_BOOKMARK.bookmarkTitle,
+ dateAdded: FAKE_BOOKMARK.dateAdded,
+ guid: FAKE_BOOKMARK.bookmarkGuid,
+ source: SOURCES.RESTORE_ON_STARTUP,
+ };
+ await observer.handlePlacesEvents([args]);
+
+ assert.notCalled(dispatch);
+ });
+ it("should not dispatch a PLACES_BOOKMARK_ADDED action - has SYNC source", async () => {
+ const args = {
+ type: "bookmark-added",
+ itemType: TYPE_BOOKMARK,
+ url: "http://" + FAKE_BOOKMARK.url,
+ title: FAKE_BOOKMARK.bookmarkTitle,
+ dateAdded: FAKE_BOOKMARK.dateAdded,
+ guid: FAKE_BOOKMARK.bookmarkGuid,
+ source: SOURCES.SYNC,
+ };
+ await observer.handlePlacesEvents([args]);
+
+ assert.notCalled(dispatch);
+ });
+ it("should ignore events that are not of TYPE_BOOKMARK", async () => {
+ const args = {
+ type: "bookmark-added",
+ itemType: "nottypebookmark",
+ url: "http://" + FAKE_BOOKMARK.url,
+ title: FAKE_BOOKMARK.bookmarkTitle,
+ dateAdded: FAKE_BOOKMARK.dateAdded,
+ guid: FAKE_BOOKMARK.bookmarkGuid,
+ source: SOURCES.SYNC,
+ };
+ await observer.handlePlacesEvents([args]);
+
+ assert.notCalled(dispatch);
+ });
+ });
+ });
+
describe("BookmarksObserver", () => {
let dispatch;
let observer;
beforeEach(() => {
dispatch = sandbox.spy();
observer = new BookmarksObserver(dispatch);
});
it("should have a QueryInterface property", () => {
assert.property(observer, "QueryInterface");
});
- describe("#onItemAdded", () => {
- beforeEach(() => {
- });
- it("should dispatch a PLACES_BOOKMARK_ADDED action with the bookmark data - http", async () => {
- // Yes, onItemAdded has at least 8 arguments. See function definition for docs.
- const args = [null, null, null, TYPE_BOOKMARK,
- {spec: FAKE_BOOKMARK.url, scheme: "http"}, FAKE_BOOKMARK.bookmarkTitle,
- FAKE_BOOKMARK.dateAdded, FAKE_BOOKMARK.bookmarkGuid, "", SOURCES.DEFAULT];
- await observer.onItemAdded(...args);
-
- assert.calledWith(dispatch, {type: at.PLACES_BOOKMARK_ADDED, data: FAKE_BOOKMARK});
- });
- it("should dispatch a PLACES_BOOKMARK_ADDED action with the bookmark data - https", async () => {
- // Yes, onItemAdded has at least 8 arguments. See function definition for docs.
- const args = [null, null, null, TYPE_BOOKMARK,
- {spec: FAKE_BOOKMARK.url, scheme: "https"}, FAKE_BOOKMARK.bookmarkTitle,
- FAKE_BOOKMARK.dateAdded, FAKE_BOOKMARK.bookmarkGuid, "", SOURCES.DEFAULT];
- await observer.onItemAdded(...args);
-
- assert.calledWith(dispatch, {type: at.PLACES_BOOKMARK_ADDED, data: FAKE_BOOKMARK});
- });
- it("should not dispatch a PLACES_BOOKMARK_ADDED action - not http/https", async () => {
- // Yes, onItemAdded has at least 8 arguments. See function definition for docs.
- const args = [null, null, null, TYPE_BOOKMARK,
- {spec: FAKE_BOOKMARK.url, scheme: "places"}, FAKE_BOOKMARK.bookmarkTitle,
- FAKE_BOOKMARK.dateAdded, FAKE_BOOKMARK.bookmarkGuid, "", SOURCES.DEFAULT];
- await observer.onItemAdded(...args);
-
- assert.notCalled(dispatch);
- });
- it("should not dispatch a PLACES_BOOKMARK_ADDED action - has IMPORT source", async () => {
- // Yes, onItemAdded has at least 8 arguments. See function definition for docs.
- const args = [null, null, null, TYPE_BOOKMARK,
- {spec: FAKE_BOOKMARK.url, scheme: "http"}, FAKE_BOOKMARK.bookmarkTitle,
- FAKE_BOOKMARK.dateAdded, FAKE_BOOKMARK.bookmarkGuid, "", SOURCES.IMPORT];
- await observer.onItemAdded(...args);
-
- assert.notCalled(dispatch);
- });
- it("should not dispatch a PLACES_BOOKMARK_ADDED action - has RESTORE source", async () => {
- // Yes, onItemAdded has at least 8 arguments. See function definition for docs.
- const args = [null, null, null, TYPE_BOOKMARK,
- {spec: FAKE_BOOKMARK.url, scheme: "http"}, FAKE_BOOKMARK.bookmarkTitle,
- FAKE_BOOKMARK.dateAdded, FAKE_BOOKMARK.bookmarkGuid, "", SOURCES.RESTORE];
- await observer.onItemAdded(...args);
-
- assert.notCalled(dispatch);
- });
- it("should not dispatch a PLACES_BOOKMARK_ADDED action - has RESTORE_ON_STARTUP source", async () => {
- // Yes, onItemAdded has at least 8 arguments. See function definition for docs.
- const args = [null, null, null, TYPE_BOOKMARK,
- {spec: FAKE_BOOKMARK.url, scheme: "http"}, FAKE_BOOKMARK.bookmarkTitle,
- FAKE_BOOKMARK.dateAdded, FAKE_BOOKMARK.bookmarkGuid, "", SOURCES.RESTORE_ON_STARTUP];
- await observer.onItemAdded(...args);
-
- assert.notCalled(dispatch);
- });
- it("should not dispatch a PLACES_BOOKMARK_ADDED action - has SYNC source", async () => {
- // Yes, onItemAdded has at least 8 arguments. See function definition for docs.
- const args = [null, null, null, TYPE_BOOKMARK,
- {spec: FAKE_BOOKMARK.url, scheme: "http"}, FAKE_BOOKMARK.bookmarkTitle,
- FAKE_BOOKMARK.dateAdded, FAKE_BOOKMARK.bookmarkGuid, "", SOURCES.SYNC];
- await observer.onItemAdded(...args);
-
- assert.notCalled(dispatch);
- });
- it("should ignore events that are not of TYPE_BOOKMARK", async () => {
- const args = [null, null, null, "nottypebookmark"];
- await observer.onItemAdded(...args);
- assert.notCalled(dispatch);
- });
- });
describe("#onItemRemoved", () => {
it("should ignore events that are not of TYPE_BOOKMARK", async () => {
await observer.onItemRemoved(null, null, null, "nottypebookmark", null, "123foo", "", SOURCES.DEFAULT);
assert.notCalled(dispatch);
});
it("should not dispatch a PLACES_BOOKMARK_REMOVED action - has SYNC source", async () => {
const args = [null, null, null, TYPE_BOOKMARK, {spec: "foo.com"}, "123foo", "", SOURCES.SYNC];
await observer.onItemRemoved(...args);
--- a/browser/components/places/content/editBookmark.js
+++ b/browser/components/places/content/editBookmark.js
@@ -1022,17 +1022,16 @@ var gEditItemOverlay = {
// Just setting selectItem _does not_ trigger oncommand, so we don't
// recurse.
PlacesUtils.bookmarks.fetch(newParentGuid).then(bm => {
this._folderMenuList.selectedItem = this._getFolderMenuItem(newParentGuid,
bm.title);
});
},
- onItemAdded() {},
onItemRemoved() { },
onBeginUpdateBatch() { },
onEndUpdateBatch() { },
onItemVisited() { },
};
for (let elt of ["folderMenuList", "folderTree", "namePicker",
"locationField", "keywordField",
--- a/browser/components/places/tests/browser/browser_bookmarkProperties_newFolder.js
+++ b/browser/components/places/tests/browser/browser_bookmarkProperties_newFolder.js
@@ -38,21 +38,27 @@ add_task(async function test_newFolder()
let folderTree = document.getElementById("editBMPanel_folderTree");
// Create new folder.
let newFolderButton = document.getElementById("editBMPanel_newFolderButton");
newFolderButton.click();
let newFolderGuid;
- let newFolderObserver = PlacesTestUtils.waitForNotification("onItemAdded",
- (id, parentId, index, type, uri, title, dateAdded, guid) => {
- newFolderGuid = guid;
- return type == PlacesUtils.bookmarks.TYPE_FOLDER;
- });
+ let newFolderObserver =
+ PlacesTestUtils.waitForNotification("bookmark-added",
+ events => {
+ for (let {guid, itemType} of events) {
+ newFolderGuid = guid;
+ if (itemType == PlacesUtils.bookmarks.TYPE_FOLDER) {
+ return true;
+ }
+ }
+ return false;
+ }, "places");
let menulist = document.getElementById("editBMPanel_folderMenuList");
await newFolderObserver;
// Wait for the folder to be created and for editing to start.
await BrowserTestUtils.waitForCondition(() => folderTree.hasAttribute("editing"),
"Should be in edit mode for the new folder");
--- a/browser/components/places/tests/browser/browser_bookmark_add_tags.js
+++ b/browser/components/places/tests/browser/browser_bookmark_add_tags.js
@@ -45,22 +45,20 @@ add_task(async function test_add_bookmar
await clickBookmarkStar();
Assert.equal(bookmarkPanelTitle.value, gNavigatorBundle.getString("editBookmarkPanel.newBookmarkTitle"), "Bookmark title is correct");
Assert.equal(bookmarkStar.getAttribute("starred"), "true", "Page is starred");
});
// Click the bookmark star again to add tags.
await clickBookmarkStar();
Assert.equal(bookmarkPanelTitle.value, gNavigatorBundle.getString("editBookmarkPanel.editBookmarkTitle"), "Bookmark title is correct");
- let promiseNotification = PlacesTestUtils.waitForNotification("onItemAdded", (id, parentId, index, type, itemUrl) => {
- if (itemUrl !== null) {
- return itemUrl.equals(Services.io.newURI(TEST_URL));
- }
- return true;
- });
+ let promiseNotification =
+ PlacesTestUtils.waitForNotification("bookmark-added",
+ events => events.some(({url}) => !url || url == TEST_URL),
+ "places");
await fillBookmarkTextField("editBMPanel_tagsField", "tag1", window);
await promiseNotification;
let bookmarks = [];
await PlacesUtils.bookmarks.fetch({ url: TEST_URL }, bm => bookmarks.push(bm));
Assert.equal(PlacesUtils.tagging.getTagsForURI(Services.io.newURI(TEST_URL)).length, 1, "Found the right number of tags");
Assert.deepEqual(PlacesUtils.tagging.getTagsForURI(Services.io.newURI(TEST_URL)), ["tag1"]);
let doneButton = document.getElementById("editBookmarkPanelDoneButton");
await hideBookmarksPanel(() => doneButton.click());
--- a/browser/components/places/tests/browser/browser_bookmark_backup_export_import.js
+++ b/browser/components/places/tests/browser/browser_bookmark_backup_export_import.js
@@ -120,20 +120,20 @@ add_task(async function test_export_json
add_task(async function test_import_json() {
let libraryWindow = await promiseLibrary();
libraryWindow.document.querySelector("#maintenanceButtonPopup #restoreFromFile").click();
await promiseImportExport();
await BrowserTestUtils.promiseAlertDialogOpen("accept");
let restored = 0;
- let promiseBookmarksRestored = PlacesTestUtils.waitForNotification("onItemAdded", () => {
- restored++;
- return restored === actualBookmarks.length;
- });
+ let promiseBookmarksRestored =
+ PlacesTestUtils.waitForNotification("bookmark-added",
+ events => events.some(() => ++restored == actualBookmarks.length),
+ "places");
await promiseBookmarksRestored;
await validateImportedBookmarks(PLACES);
await promiseLibraryClosed(libraryWindow);
registerCleanupFunction(async () => {
if (saveDir) {
saveDir.remove(true);
--- a/browser/components/places/tests/browser/browser_bookmarksProperties.js
+++ b/browser/components/places/tests/browser/browser_bookmarksProperties.js
@@ -264,17 +264,17 @@ gTests.push({
action: ACTION_ADD,
historyView: SIDEBAR_HISTORY_BYLASTVISITED_VIEW,
window: null,
async setup() {
// Add a visit.
await PlacesTestUtils.addVisits(TEST_URL);
- this._addObserver = PlacesTestUtils.waitForNotification("onItemAdded");
+ this._addObserver = PlacesTestUtils.waitForNotification("bookmark-added", null, "places");
},
selectNode(tree) {
var visitNode = tree.view.nodeForTreeIndex(0);
tree.selectNode(visitNode);
Assert.equal(tree.selectedNode.uri, TEST_URL, "The correct visit has been selected");
Assert.equal(tree.selectedNode.itemId, -1, "The selected node is not bookmarked");
},
--- a/browser/components/places/tests/browser/browser_editBookmark_keywords.js
+++ b/browser/components/places/tests/browser/browser_editBookmark_keywords.js
@@ -3,17 +3,16 @@
const TEST_URL = "about:blank";
add_task(async function() {
function promiseOnItemChanged() {
return new Promise(resolve => {
PlacesUtils.bookmarks.addObserver({
onBeginUpdateBatch() {},
onEndUpdateBatch() {},
- onItemAdded() {},
onItemRemoved() {},
onItemVisited() {},
onItemMoved() {},
onItemChanged(id, property, isAnno, value) {
PlacesUtils.bookmarks.removeObserver(this);
resolve({ property, value });
},
QueryInterface: ChromeUtils.generateQI([Ci.nsINavBookmarkObserver]),
--- a/browser/components/places/tests/browser/browser_toolbar_drop_text.js
+++ b/browser/components/places/tests/browser/browser_toolbar_drop_text.js
@@ -28,18 +28,20 @@ add_task(async function test() {
*
* @param aEffect
* The effect to use for the drop operation: move, copy, or link.
* @param aMimeType
* The mime type to use for the drop operation.
*/
let simulateDragDrop = async function(aEffect, aMimeType) {
const url = "http://www.mozilla.org/D1995729-A152-4e30-8329-469B01F30AA7";
- let promiseItemAddedNotification = PlacesTestUtils.waitForNotification(
- "onItemAdded", (itemId, parentId, index, type, uri, guid) => uri.spec == url);
+ let promiseItemAddedNotification =
+ PlacesTestUtils.waitForNotification("bookmark-added",
+ events => events.some(({url: eventUrl}) => eventUrl == url),
+ "places");
// We use the toolbar as the drag source, as we just need almost any node
// to simulate the drag. The actual data for the drop is passed via the
// drag data. Note: The toolbar is used rather than another bookmark node,
// as we need something that is immovable from a places perspective, as this
// forces the move into a copy.
EventUtils.synthesizeDrop(toolbar,
placesItems,
@@ -74,18 +76,20 @@ add_task(async function test() {
"http://www.mozilla.org/091A88BD-5743-4C16-A005-3D2EA3A3B71E",
];
let data;
if (aMimeType == "text/x-moz-url")
data = urls.map(spec => spec + "\n" + spec).join("\n");
else
data = urls.join("\n");
- let promiseItemAddedNotification = PlacesTestUtils.waitForNotification(
- "onItemAdded", (itemId, parentId, index, type, uri, guid) => uri.spec == urls[2]);
+ let promiseItemAddedNotification =
+ PlacesTestUtils.waitForNotification("bookmark-added",
+ events => events.some(({url}) => url == urls[2]),
+ "places");
// See notes for EventUtils.synthesizeDrop in simulateDragDrop().
EventUtils.synthesizeDrop(toolbar,
placesItems,
[[{type: aMimeType,
data}]],
aEffect, window);
--- a/browser/components/places/tests/browser/browser_views_liveupdate.js
+++ b/browser/components/places/tests/browser/browser_views_liveupdate.js
@@ -92,17 +92,19 @@ add_task(async function test() {
// Open bookmarks menu.
var popup = document.getElementById("bookmarksMenuPopup");
ok(popup, "Menu popup element exists");
fakeOpenPopup(popup);
// Open bookmarks sidebar.
await withSidebarTree("bookmarks", async () => {
// Add observers.
+ bookmarksObserver.handlePlacesEvents = bookmarksObserver.handlePlacesEvents.bind(bookmarksObserver);
PlacesUtils.bookmarks.addObserver(bookmarksObserver);
+ PlacesUtils.observers.addListener(["bookmark-added"], bookmarksObserver.handlePlacesEvents);
var addedBookmarks = [];
// MENU
info("*** Acting on menu bookmarks");
addedBookmarks = addedBookmarks.concat(await testInFolder(PlacesUtils.bookmarks.menuGuid, "bm"));
// TOOLBAR
info("*** Acting on toolbar bookmarks");
@@ -119,16 +121,17 @@ add_task(async function test() {
try {
await PlacesUtils.bookmarks.remove(bm);
} catch (ex) {}
await bookmarksObserver.assertViewsUpdatedCorrectly();
}
// Remove observers.
PlacesUtils.bookmarks.removeObserver(bookmarksObserver);
+ PlacesUtils.observers.removeListener(["bookmark-added"], bookmarksObserver.handlePlacesEvents);
});
// Collapse the personal toolbar if needed.
if (wasCollapsed) {
await promiseSetToolbarVisibility(toolbar, false);
}
});
@@ -138,20 +141,20 @@ add_task(async function test() {
*/
var bookmarksObserver = {
_notifications: [],
QueryInterface: ChromeUtils.generateQI([
Ci.nsINavBookmarkObserver,
]),
- // nsINavBookmarkObserver
- onItemAdded(itemId, folderId, index, itemType, uri, title, dataAdded, guid,
- parentGuid) {
- this._notifications.push(["assertItemAdded", parentGuid, guid, index]);
+ handlePlacesEvents(events) {
+ for (let {parentGuid, guid, index} of events) {
+ this._notifications.push(["assertItemAdded", parentGuid, guid, index]);
+ }
},
onItemRemoved(itemId, folderId, index, itemType, uri, guid, parentGuid) {
this._notifications.push(["assertItemRemoved", parentGuid, guid]);
},
onItemMoved(itemId, oldFolderId, oldIndex, newFolderId, newIndex, itemType, guid,
oldParentGuid, newParentGuid) {
--- a/browser/components/urlbar/UrlbarProvidersManager.jsm
+++ b/browser/components/urlbar/UrlbarProvidersManager.jsm
@@ -24,16 +24,21 @@ XPCOMUtils.defineLazyGetter(this, "logge
Log.repository.getLogger("Places.Urlbar.ProvidersManager"));
// List of available local providers, each is implemented in its own jsm module
// and will track different queries internally by queryContext.
var localProviderModules = {
UrlbarProviderOpenTabs: "resource:///modules/UrlbarProviderOpenTabs.jsm",
};
+// To improve dataflow and reduce UI work, when a match is added by a
+// non-immediate provider, we notify it to the controller after a delay, so
+// that we can chunk matches coming in that timeframe into a single call.
+const CHUNK_MATCHES_DELAY_MS = 16;
+
/**
* Class used to create a manager.
* The manager is responsible to keep a list of providers, instantiate query
* objects and pass those to the providers.
*/
class ProvidersManager {
constructor() {
// Tracks the available providers.
@@ -79,17 +84,17 @@ class ProvidersManager {
/**
* Starts querying.
* @param {object} queryContext The query context object
* @param {object} controller a UrlbarController instance
*/
async startQuery(queryContext, controller) {
logger.info(`Query start ${queryContext.searchString}`);
- let query = Object.seal(new Query(queryContext, controller, this.providers));
+ let query = new Query(queryContext, controller, this.providers);
this.queries.set(queryContext, query);
await query.start();
}
/**
* Cancels a running query.
* @param {object} queryContext
*/
@@ -143,19 +148,16 @@ class Query {
* @param {object} providers
* Map of all the providers by type and name
*/
constructor(queryContext, controller, providers) {
this.context = queryContext;
this.context.results = [];
this.controller = controller;
this.providers = providers;
- // Track the delay timer.
- this.sleepResolve = Promise.resolve();
- this.sleepTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this.started = false;
this.canceled = false;
this.complete = false;
}
/**
* Starts querying.
*/
@@ -169,69 +171,149 @@ class Query {
let promises = [];
for (let provider of this.providers.get(UrlbarUtils.PROVIDER_TYPE.IMMEDIATE).values()) {
if (this.canceled) {
break;
}
promises.push(provider.startQuery(this.context, this.add));
}
- await new Promise(resolve => {
- let time = UrlbarPrefs.get("delay");
- this.sleepResolve = resolve;
- this.sleepTimer.initWithCallback(resolve, time, Ci.nsITimer.TYPE_ONE_SHOT);
- });
+ // Tracks the delay timer. We will fire (in this specific case, cancel would
+ // do the same, since the callback is empty) the timer when the search is
+ // canceled, unblocking start().
+ this._sleepTimer = new SkippableTimer(() => {}, UrlbarPrefs.get("delay"));
+ await this._sleepTimer.promise;
for (let providerType of [UrlbarUtils.PROVIDER_TYPE.NETWORK,
UrlbarUtils.PROVIDER_TYPE.PROFILE,
UrlbarUtils.PROVIDER_TYPE.EXTENSION]) {
for (let provider of this.providers.get(providerType).values()) {
if (this.canceled) {
break;
}
promises.push(provider.startQuery(this.context, this.add.bind(this)));
}
}
await Promise.all(promises.map(p => p.catch(Cu.reportError)));
+ if (this._chunkTimer) {
+ // All the providers are done returning results, so we can stop chunking.
+ await this._chunkTimer.fire();
+ }
+
// Nothing should be failing above, since we catch all the promises, thus
// this is not in a finally for now.
this.complete = true;
}
/**
* Cancels this query.
* @note Invoking cancel multiple times is a no-op.
*/
cancel() {
if (this.canceled) {
return;
}
this.canceled = true;
- this.sleepTimer.cancel();
for (let providers of this.providers.values()) {
for (let provider of providers.values()) {
provider.cancelQuery(this.context);
}
}
- this.sleepResolve();
+ if (this._chunkTimer) {
+ this._chunkTimer.cancel().catch(Cu.reportError);
+ }
+ if (this._sleepTimer) {
+ this._sleepTimer.fire().catch(Cu.reportError);
+ }
}
/**
* Adds a match returned from a provider to the results set.
* @param {object} provider
* @param {object} match
*/
add(provider, match) {
// Stop returning results as soon as we've been canceled.
if (this.canceled) {
return;
}
- // TODO:
- // * coalesce results in timed chunks: we don't want to notify every single
- // result as soon as it arrives, we'll rather collect results for a few
- // ms, then send them
- // * pass results to a muxer before sending them back to the controller.
this.context.results.push(match);
- this.controller.receiveResults(this.context);
+
+
+ let notifyResults = () => {
+ if (this._chunkTimer) {
+ this._chunkTimer.cancel().catch(Cu.reportError);
+ delete this._chunkTimer;
+ }
+ // TODO:
+ // * pass results to a muxer before sending them back to the controller.
+ this.controller.receiveResults(this.context);
+ };
+
+ // If the provider is not of immediate type, chunk results, to improve the
+ // dataflow and reduce UI flicker.
+ if (provider.type == UrlbarUtils.PROVIDER_TYPE.IMMEDIATE) {
+ notifyResults();
+ } else if (!this._chunkTimer) {
+ this._chunkTimer = new SkippableTimer(notifyResults, CHUNK_MATCHES_DELAY_MS);
+ }
}
}
+
+/**
+ * Class used to create a timer that can be manually fired, to immediately
+ * invoke the callback, or canceled, as necessary.
+ * Examples:
+ * let timer = new SkippableTimer();
+ * // Invokes the callback immediately without waiting for the delay.
+ * await timer.fire();
+ * // Cancel the timer, the callback won't be invoked.
+ * await timer.cancel();
+ * // Wait for the timer to have elapsed.
+ * await timer.promise;
+ */
+class SkippableTimer {
+ /**
+ * Creates a skippable timer for the given callback and time.
+ * @param {function} callback To be invoked when requested
+ * @param {number} time A delay in milliseconds to wait for
+ */
+ constructor(callback, time) {
+ let timerPromise = new Promise(resolve => {
+ this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ this._timer.initWithCallback(() => {
+ logger.debug(`Elapsed ${time}ms timer`);
+ resolve();
+ }, time, Ci.nsITimer.TYPE_ONE_SHOT);
+ logger.debug(`Started ${time}ms timer`);
+ });
+
+ let firePromise = new Promise(resolve => {
+ this.fire = () => {
+ logger.debug(`Skipped ${time}ms timer`);
+ resolve();
+ return this.promise;
+ };
+ });
+
+ this.promise = Promise.race([timerPromise, firePromise]).then(() => {
+ // If we've been canceled, don't call back.
+ if (this._timer) {
+ callback();
+ }
+ });
+ }
+
+ /**
+ * Allows to cancel the timer and the callback won't be invoked.
+ * It is not strictly necessary to await for this, the promise can just be
+ * used to ensure all the internal work is complete.
+ * @returns {promise} Resolved once all the cancelation work is complete.
+ */
+ cancel() {
+ logger.debug(`Canceling timer for ${this._timer.delay}ms`);
+ this._timer.cancel();
+ delete this._timer;
+ return this.fire();
+ }
+}
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -781,16 +781,17 @@ you can use these alternative items. Oth
<!ENTITY editBookmark.done.label "Done">
<!ENTITY editBookmark.showForNewBookmarks.label "Show editor when saving">
<!ENTITY editBookmark.showForNewBookmarks.accesskey "S">
<!-- LOCALIZATION NOTE (identity.securityView.label)
This is the header of the security subview in the Site Identity panel. -->
<!ENTITY identity.securityView.label "Site Security">
+<!ENTITY identity.connection "Connection">
<!ENTITY identity.connectionSecure "Secure Connection">
<!ENTITY identity.connectionNotSecure "Connection Is Not Secure">
<!ENTITY identity.connectionFile "This page is stored on your computer.">
<!ENTITY identity.connectionVerified2 "You are securely connected to this site, owned by:">
<!ENTITY identity.connectionInternal "This is a secure &brandShortName; page.">
<!ENTITY identity.extensionPage "This page is loaded from an extension.">
<!ENTITY identity.insecureLoginForms2 "Logins entered on this page could be compromised.">
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -485,16 +485,19 @@ webauthn.cancel=Cancel
webauthn.cancel.accesskey=c
webauthn.proceed=Proceed
webauthn.proceed.accesskey=p
webauthn.anonymize=Anonymize anyway
# Spoof Accept-Language prompt
privacy.spoof_english=Changing your language setting to English will make you more difficult to identify and enhance your privacy. Do you want to request English language versions of web pages?
+# LOCALIZATION NOTE (identity.headerWithHost):
+# %S is the hostname of the site that is being displayed.
+identity.headerWithHost=Site Information for %S
identity.identified.verifier=Verified by: %S
identity.identified.verified_by_you=You have added a security exception for this site.
identity.identified.state_and_country=%S, %S
# LOCALIZATION NOTE (identity.notSecure.label):
# Keep this string as short as possible, this is displayed in the URL bar
# use a synonym for "safe" or "private" if "secure" is too long.
identity.notSecure.label=Not Secure
--- a/browser/themes/shared/controlcenter/panel.inc.css
+++ b/browser/themes/shared/controlcenter/panel.inc.css
@@ -1,15 +1,13 @@
%if 0
/* 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/. */
%endif
-%filter substitution
-%define identityPopupExpanderWidth 38px
/* Hide all conditional elements by default. */
:-moz-any([when-connection],[when-mixedcontent],[when-ciphers],[when-loginforms]) {
display: none;
}
/* This is used by screenshots tests to hide intermittently different
* identity popup shadows (see bug 1425253). */
@@ -69,17 +67,17 @@
fill-opacity: .6;
}
#identity-popup-mainView {
min-width: 33em;
max-width: 33em;
}
-.identity-popup-section:not(:first-child) {
+.identity-popup-section {
border-top: 1px solid var(--panel-separator-color);
}
.identity-popup-security-content,
#identity-popup-permissions-content,
#identity-popup-content-blocking-content {
background-repeat: no-repeat;
background-position: 1em 1em;
@@ -100,17 +98,17 @@
}
/* EXPAND BUTTON */
.identity-popup-expander {
margin: 0;
padding: 4px 0;
min-width: auto;
- width: @identityPopupExpanderWidth@;
+ width: 38px;
border-style: none;
-moz-appearance: none;
background: url("chrome://browser/skin/arrow-left.svg") center no-repeat;
background-size: 16px, auto;
-moz-context-properties: fill;
fill: currentColor;
color: inherit;
}
@@ -142,52 +140,73 @@
}
.identity-popup-preferences-button > .toolbarbutton-text {
display: none;
}
/* CONTENT */
+#identity-popup-mainView-panel-header > label,
+#identity-popup-securityView > .panel-header,
+#identity-popup-breakageReportView > .panel-header,
#identity-popup-content-blocking-report-breakage,
#identity-popup-content-blocking-disabled-label,
.identity-popup-content-blocking-category-label,
.identity-popup-content-blocking-category-state-label,
.identity-popup-content-blocking-category-add-blocking,
.identity-popup-permission-label,
.identity-popup-permission-state-label,
.identity-popup-security-content > description,
#identity-popup-security-descriptions > description,
#identity-popup-securityView-body > description,
#identity-popup-permissions-content > description,
#identity-popup-content-blocking-content > description {
font-size: 110%;
margin: 0;
}
+#identity-popup-mainView-panel-header {
+ padding: 4px 1em;
+ min-height: 40px;
+ -moz-box-pack: center;
+ -moz-box-align: center;
+}
+
+#identity-popup-mainView-panel-header-span {
+ display: inline-block;
+ font-weight: 600;
+ text-align: center;
+ overflow-wrap: break-word;
+ /* This is needed for the overflow-wrap to work correctly.
+ * 33em is the panel width, panel-header has 1em padding on each side. */
+ max-width: calc(33rem - 2em);
+}
+
#identity-popup-permissions-content > description,
#identity-popup-content-blocking-content > description {
color: var(--panel-disabled-color);
}
/* This element needs the pre-wrap because we add newlines to it in the code. */
#identity-popup-content-supplemental {
white-space: pre-wrap;
}
.identity-popup-headline {
margin: 3px 0 4px;
font-size: 150%;
}
-.identity-popup-host {
- word-wrap: break-word;
- /* 1em + 2em + 24px is .identity-popup-security-content padding
- * 30em is the panel width */
- max-width: calc(30rem - 3rem - 24px - @identityPopupExpanderWidth@);
+#identity-popup-host {
+ overflow-wrap: break-word;
+ /* This is needed for the overflow-wrap to work correctly.
+ * 1em + 2em + 24px is .identity-popup-security-content padding
+ * 33em is the panel width */
+ max-width: calc(33rem - 3rem - 24px);
}
.identity-popup-warning-gray {
padding-inline-start: 24px;
background: url(chrome://browser/skin/controlcenter/warning.svg) no-repeat 0 50%;
fill: #808080;
stroke: #fff;
-moz-context-properties: fill, stroke;
--- a/build/docs/mozbuild-files.rst
+++ b/build/docs/mozbuild-files.rst
@@ -142,17 +142,17 @@ Filesystem reading mode is utilized to p
Technical Details
-----------------
The code for reading ``moz.build`` files lives in
:py:mod:`mozbuild.frontend.reader`. The Python sandboxes evaluation results
(:py:class:`mozbuild.frontend.context.Context`) are passed into
:py:mod:`mozbuild.frontend.emitter`, which converts them to classes defined
in :py:mod:`mozbuild.frontend.data`. Each class in this module defines a
-domain-specific component of tree metdata. e.g. there will be separate
+domain-specific component of tree metadata. e.g. there will be separate
classes that represent a JavaScript file vs a compiled C++ file or test
manifests. This means downstream consumers of this data can filter on class
types to only consume what they are interested in.
There is no well-defined mapping between ``moz.build`` file instances
and the number of :py:mod:`mozbuild.frontend.data` classes derived from
each. Depending on the content of the ``moz.build`` file, there may be 1
object derived or 100.
--- a/build/moz.configure/rust.configure
+++ b/build/moz.configure/rust.configure
@@ -289,17 +289,17 @@ cbindgen = check_prog('CBINDGEN', add_ru
when=depends(build_project)
(lambda build_project: build_project != 'js'))
@depends_if(cbindgen)
@checking('cbindgen version')
@imports(_from='textwrap', _import='dedent')
def cbindgen_version(cbindgen):
- cbindgen_min_version = Version('0.6.2')
+ cbindgen_min_version = Version('0.6.4')
# cbindgen x.y.z
version = Version(check_cmd_output(cbindgen, '--version').strip().split(" ")[1])
if version < cbindgen_min_version:
die(dedent('''\
cbindgen version {} is too old. At least version {} is required.
--- a/build/valgrind/x86_64-pc-linux-gnu.sup
+++ b/build/valgrind/x86_64-pc-linux-gnu.sup
@@ -521,16 +521,31 @@
Bug 1479055: style::properties::longhands::clip_path::cascade_property
Memcheck:Cond
fun:_ZN5style10properties9longhands9clip_path16cascade_property*
fun:_ZN5style10properties13cascade_rules*
fun:_ZN109_$LT$style*style_resolver*cascade_style_and_visited*
fun:_ZN109_$LT$style*cascade_primary_style*
}
+# Another suppression for Stylo, October 2018. See bug 1496486.
+# Conditional jump or move depends on uninitialised value(s)
+# at 0x108E2931: style::properties::longhands::font_language_override::cascade_property+81 (font.rs:2095)
+# by 0x107E95B4: style::properties::cascade::Cascade::apply_properties+580 (cascade.rs:463)
+# by 0x107E82D8: style::properties::cascade::cascade_rules+2440 (cascade.rs:303)
+# by 0x107E7206: <style::style_resolver::StyleResolverForElement<'a, 'ctx, 'le, E>>::cascade_style_and_visited+310 (cascade.rs:93)
+# Uninitialised value was created by a stack allocation
+# at 0x10A42A40: style::properties::shorthands::font::parse_value (font.rs:376)
+{
+ Bug 1496486: style::properties::longhands::font_language_override::cascade_property
+ Memcheck:Cond
+ fun:_ZN5style10properties9longhands22font_language_override16cascade_property*
+ fun:_ZN5style10properties7cascade7Cascade16apply_properties*
+ fun:_ZN5style10properties7cascade13cascade_rules*
+}
###################################################
# For valgrind-mochitest ("tc-M-V [tier 2]") runs on taskcluster.
# See bug 1248365.
# These are specific to Ubuntu 12.04.5, 64-bit.
###################################################
new file mode 100644
--- /dev/null
+++ b/dom/base/PlacesBookmark.h
@@ -0,0 +1,53 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_PlacesBookmark_h
+#define mozilla_dom_PlacesBookmark_h
+
+#include "mozilla/dom/PlacesEvent.h"
+
+namespace mozilla {
+namespace dom {
+
+class PlacesBookmark : public PlacesEvent
+{
+public:
+ explicit PlacesBookmark(PlacesEventType aEventType) : PlacesEvent(aEventType) {}
+
+ JSObject*
+ WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override
+ {
+ return PlacesBookmark_Binding::Wrap(aCx, this, aGivenProto);
+ }
+
+ const PlacesBookmark* AsPlacesBookmark() const override { return this; }
+
+ unsigned short ItemType() { return mItemType; }
+ int64_t Id() { return mId; }
+ int64_t ParentId() { return mParentId; }
+ void GetUrl(nsString& aUrl) { aUrl = mUrl; }
+ void GetGuid(nsCString& aGuid) { aGuid = mGuid; }
+ void GetParentGuid(nsCString& aParentGuid) { aParentGuid = mParentGuid; }
+ uint16_t Source() { return mSource; }
+ bool IsTagging() { return mIsTagging; }
+
+ unsigned short mItemType;
+ int64_t mId;
+ int64_t mParentId;
+ nsString mUrl;
+ nsCString mGuid;
+ nsCString mParentGuid;
+ uint16_t mSource;
+ bool mIsTagging;
+
+protected:
+ virtual ~PlacesBookmark() = default;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PlacesBookmark_h
new file mode 100644
--- /dev/null
+++ b/dom/base/PlacesBookmarkAddition.h
@@ -0,0 +1,62 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_PlacesBookmarkAddition_h
+#define mozilla_dom_PlacesBookmarkAddition_h
+
+#include "mozilla/dom/PlacesBookmark.h"
+
+namespace mozilla {
+namespace dom {
+
+class PlacesBookmarkAddition final : public PlacesBookmark
+{
+public:
+ explicit PlacesBookmarkAddition() : PlacesBookmark(PlacesEventType::Bookmark_added) {}
+
+ static already_AddRefed<PlacesBookmarkAddition>
+ Constructor(const GlobalObject& aGlobal,
+ const PlacesBookmarkAdditionInit& aInitDict,
+ ErrorResult& aRv) {
+ RefPtr<PlacesBookmarkAddition> event = new PlacesBookmarkAddition();
+ event->mItemType = aInitDict.mItemType;
+ event->mId = aInitDict.mId;
+ event->mParentId = aInitDict.mParentId;
+ event->mIndex = aInitDict.mIndex;
+ event->mUrl = aInitDict.mUrl;
+ event->mTitle = aInitDict.mTitle;
+ event->mDateAdded = aInitDict.mDateAdded;
+ event->mGuid = aInitDict.mGuid;
+ event->mParentGuid = aInitDict.mParentGuid;
+ event->mSource = aInitDict.mSource;
+ event->mIsTagging = aInitDict.mIsTagging;
+ return event.forget();
+ }
+
+ JSObject*
+ WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override
+ {
+ return PlacesBookmarkAddition_Binding::Wrap(aCx, this, aGivenProto);
+ }
+
+ const PlacesBookmarkAddition* AsPlacesBookmarkAddition() const override { return this; }
+
+ int32_t Index() { return mIndex; }
+ void GetTitle(nsString& aTitle) { aTitle = mTitle; }
+ uint64_t DateAdded() { return mDateAdded; }
+
+ int32_t mIndex;
+ nsString mTitle;
+ uint64_t mDateAdded;
+
+private:
+ ~PlacesBookmarkAddition() = default;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PlacesBookmarkAddition_h
--- a/dom/base/PlacesEvent.h
+++ b/dom/base/PlacesEvent.h
@@ -32,16 +32,18 @@ public:
nsISupports* GetParentObject() const;
JSObject*
WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
PlacesEventType Type() const { return mType; }
virtual const PlacesVisit* AsPlacesVisit() const { return nullptr; }
+ virtual const PlacesBookmark* AsPlacesBookmark() const { return nullptr; }
+ virtual const PlacesBookmarkAddition* AsPlacesBookmarkAddition() const { return nullptr; }
protected:
virtual ~PlacesEvent() = default;
PlacesEventType mType;
};
} // namespace dom
} // namespace mozilla
--- a/dom/base/PlacesObservers.cpp
+++ b/dom/base/PlacesObservers.cpp
@@ -34,28 +34,28 @@ template <class T>
using FlaggedArray = nsTArray<Flagged<T>>;
template <class T>
struct ListenerCollection
{
static StaticAutoPtr<FlaggedArray<T>> gListeners;
static StaticAutoPtr<FlaggedArray<T>> gListenersToRemove;
- static FlaggedArray<T>* GetListeners() {
+ static FlaggedArray<T>* GetListeners(bool aDoNotInit = false) {
MOZ_ASSERT(NS_IsMainThread());
- if (!gListeners) {
+ if (!gListeners && !aDoNotInit) {
gListeners = new FlaggedArray<T>();
ClearOnShutdown(&gListeners);
}
return gListeners;
}
- static FlaggedArray<T>* GetListenersToRemove() {
+ static FlaggedArray<T>* GetListenersToRemove(bool aDoNotInit = false) {
MOZ_ASSERT(NS_IsMainThread());
- if (!gListenersToRemove) {
+ if (!gListenersToRemove && !aDoNotInit) {
gListenersToRemove = new FlaggedArray<T>();
ClearOnShutdown(&gListenersToRemove);
}
return gListenersToRemove;
}
};
template <class T>
@@ -221,67 +221,76 @@ PlacesObservers::RemoveListener(const ns
RemoveListener(flags, aCallback);
}
}
void
PlacesObservers::RemoveListener(uint32_t aFlags,
PlacesEventCallback& aCallback)
{
- FlaggedArray<RefPtr<PlacesEventCallback>>& listeners =
- *JSListeners::GetListeners();
- for (uint32_t i = 0; i < listeners.Length(); i++) {
- Flagged<RefPtr<PlacesEventCallback>>& l = listeners[i];
+ FlaggedArray<RefPtr<PlacesEventCallback>>* listeners =
+ JSListeners::GetListeners(/* aDoNotInit: */ true);
+ if (!listeners) {
+ return;
+ }
+ for (uint32_t i = 0; i < listeners->Length(); i++) {
+ Flagged<RefPtr<PlacesEventCallback>>& l = listeners->ElementAt(i);
if (!(*l.value == aCallback)) {
continue;
}
- if (l.flags == aFlags) {
- listeners.RemoveElementAt(i);
+ if (l.flags == (aFlags & l.flags)) {
+ listeners->RemoveElementAt(i);
i--;
} else {
l.flags &= ~aFlags;
}
}
}
void
PlacesObservers::RemoveListener(uint32_t aFlags,
PlacesWeakCallbackWrapper& aCallback)
{
- FlaggedArray<WeakPtr<PlacesWeakCallbackWrapper>>& listeners =
- *WeakJSListeners::GetListeners();
- for (uint32_t i = 0; i < listeners.Length(); i++) {
- Flagged<WeakPtr<PlacesWeakCallbackWrapper>>& l = listeners[i];
+ FlaggedArray<WeakPtr<PlacesWeakCallbackWrapper>>* listeners =
+ WeakJSListeners::GetListeners(/* aDoNotInit: */ true);
+ if (!listeners) {
+ return;
+ }
+ for (uint32_t i = 0; i < listeners->Length(); i++) {
+ Flagged<WeakPtr<PlacesWeakCallbackWrapper>>& l = listeners->ElementAt(i);
RefPtr<PlacesWeakCallbackWrapper> unwrapped = l.value.get();
if (unwrapped != &aCallback) {
continue;
}
- if (l.flags == aFlags) {
- listeners.RemoveElementAt(i);
+ if (l.flags == (aFlags & l.flags)) {
+ listeners->RemoveElementAt(i);
i--;
} else {
l.flags &= ~aFlags;
}
}
}
void
PlacesObservers::RemoveListener(uint32_t aFlags,
places::INativePlacesEventCallback* aCallback)
{
- FlaggedArray<WeakPtr<places::INativePlacesEventCallback>>& listeners =
- *WeakNativeListeners::GetListeners();
- for (uint32_t i = 0; i < listeners.Length(); i++) {
- Flagged<WeakPtr<places::INativePlacesEventCallback>>& l = listeners[i];
+ FlaggedArray<WeakPtr<places::INativePlacesEventCallback>>* listeners =
+ WeakNativeListeners::GetListeners(/* aDoNotInit: */ true);
+ if (!listeners) {
+ return;
+ }
+ for (uint32_t i = 0; i < listeners->Length(); i++) {
+ Flagged<WeakPtr<places::INativePlacesEventCallback>>& l = listeners->ElementAt(i);
RefPtr<places::INativePlacesEventCallback> unwrapped = l.value.get();
if (unwrapped != aCallback) {
continue;
}
- if (l.flags == aFlags) {
- listeners.RemoveElementAt(i);
+ if (l.flags == (aFlags & l.flags)) {
+ listeners->RemoveElementAt(i);
i--;
} else {
l.flags &= ~aFlags;
}
}
}
void
@@ -323,28 +332,31 @@ PlacesObservers::NotifyListeners(const S
});
auto& listenersToRemove = *JSListeners::GetListenersToRemove();
if (listenersToRemove.Length() > 0) {
for (auto& listener : listenersToRemove) {
RemoveListener(listener.flags, *listener.value);
}
}
+ listenersToRemove.Clear();
auto& weakListenersToRemove = *WeakJSListeners::GetListenersToRemove();
if (weakListenersToRemove.Length() > 0) {
for (auto& listener : weakListenersToRemove) {
RemoveListener(listener.flags, *listener.value.get());
}
}
+ weakListenersToRemove.Clear();
auto& nativeListenersToRemove = *WeakNativeListeners::GetListenersToRemove();
if (nativeListenersToRemove.Length() > 0) {
for (auto& listener : nativeListenersToRemove) {
RemoveListener(listener.flags, listener.value.get());
}
}
+ nativeListenersToRemove.Clear();
gCallingListeners = false;
}
} // namespace dom
} // namespace mozilla
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -200,16 +200,18 @@ EXPORTS.mozilla.dom += [
'MimeType.h',
'MozQueryInterface.h',
'NameSpaceConstants.h',
'Navigator.h',
'NodeInfo.h',
'NodeInfoInlines.h',
'NodeIterator.h',
'ParentProcessMessageManager.h',
+ 'PlacesBookmark.h',
+ 'PlacesBookmarkAddition.h',
'PlacesEvent.h',
'PlacesObservers.h',
'PlacesVisit.h',
'PlacesWeakCallbackWrapper.h',
'Pose.h',
'ProcessMessageManager.h',
'ResponsiveImageSelector.h',
'SameProcessMessageQueue.h',
--- a/dom/chrome-webidl/PlacesEvent.webidl
+++ b/dom/chrome-webidl/PlacesEvent.webidl
@@ -1,15 +1,20 @@
enum PlacesEventType {
"none",
/**
* data: PlacesVisit. Fired whenever a page is visited.
*/
"page-visited",
+ /**
+ * data: PlacesBookmarkAddition. Fired whenever a bookmark
+ * (or a bookmark folder/separator) is created.
+ */
+ "bookmark-added",
};
[ChromeOnly, Exposed=(Window,System)]
interface PlacesEvent {
readonly attribute PlacesEventType type;
};
[ChromeOnly, Exposed=(Window,System)]
@@ -62,8 +67,88 @@ interface PlacesVisit : PlacesEvent {
readonly attribute unsigned long typedCount;
/**
* The last known title of the page. Might not be from the current visit,
* and might be null if it is not known.
*/
readonly attribute DOMString? lastKnownTitle;
};
+
+/**
+ * Base class for properties that are common to all bookmark events.
+ */
+[ChromeOnly, Exposed=(Window,System)]
+interface PlacesBookmark : PlacesEvent {
+ /**
+ * The id of the item.
+ */
+ readonly attribute long long id;
+
+ /**
+ * The id of the folder to which the item belongs.
+ */
+ readonly attribute long long parentId;
+
+ /**
+ * The type of the added item (see TYPE_* constants in nsINavBooksService.idl).
+ */
+ readonly attribute unsigned short itemType;
+
+ /**
+ * The URI of the added item if it was TYPE_BOOKMARK, "" otherwise.
+ */
+ readonly attribute DOMString url;
+
+ /**
+ * The unique ID associated with the item.
+ */
+ readonly attribute ByteString guid;
+
+ /**
+ * The unique ID associated with the item's parent.
+ */
+ readonly attribute ByteString parentGuid;
+
+ /**
+ * A change source constant from nsINavBookmarksService::SOURCE_*,
+ * passed to the method that notifies the observer.
+ */
+ readonly attribute unsigned short source;
+
+ /**
+ * True if the item is a tag or a tag folder.
+ * NOTE: this will go away with bug 424160.
+ */
+ readonly attribute boolean isTagging;
+};
+
+dictionary PlacesBookmarkAdditionInit {
+ required long long id;
+ required long long parentId;
+ required unsigned short itemType;
+ required DOMString url;
+ required ByteString guid;
+ required ByteString parentGuid;
+ required unsigned short source;
+ required long index;
+ required DOMString title;
+ required unsigned long long dateAdded;
+ required boolean isTagging;
+};
+
+[ChromeOnly, Exposed=(Window,System), Constructor(PlacesBookmarkAdditionInit initDict)]
+interface PlacesBookmarkAddition : PlacesBookmark {
+ /**
+ * The item's index in the folder.
+ */
+ readonly attribute long index;
+
+ /**
+ * The title of the added item.
+ */
+ readonly attribute DOMString title;
+
+ /**
+ * The time that the item was added, in milliseconds from the epoch.
+ */
+ readonly attribute unsigned long long dateAdded;
+};
--- a/dom/locales/en-US/chrome/dom/dom.properties
+++ b/dom/locales/en-US/chrome/dom/dom.properties
@@ -103,16 +103,18 @@ MediaDecodeAudioDataNoAudio=The buffer p
MediaElementAudioSourceNodeCrossOrigin=The HTMLMediaElement passed to createMediaElementSource has a cross-origin resource, the node will output silence.
# LOCALIZATION NOTE: Do not translate MediaStream and createMediaStreamSource.
MediaStreamAudioSourceNodeCrossOrigin=The MediaStream passed to createMediaStreamSource has a cross-origin resource, the node will output silence.
# LOCALIZATION NOTE: Do not translate HTMLMediaElement and MediaStream.
MediaElementAudioCaptureOfMediaStreamError=The captured HTMLMediaElement is playing a MediaStream. Applying volume or mute status is not currently supported.
MediaLoadExhaustedCandidates=All candidate resources failed to load. Media load paused.
MediaLoadSourceMissingSrc=<source> element has no “src” attribute. Media resource load failed.
MediaStreamAudioSourceNodeDifferentRate=Connecting AudioNodes from AudioContexts with different sample-rate is currently not supported.
+# LOCALIZATION NOTE: Do not translate ConvolverNode
+ConvolverNodeAllocationError=Out-of-memory error when instantiating a ConvolverNode: the node will output silence.
# LOCALIZATION NOTE: %1$S is the Http error code the server returned (e.g. 404, 500, etc), %2$S is the URL of the media resource which failed to load.
MediaLoadHttpError=HTTP load failed with status %1$S. Load of media resource %2$S failed.
# LOCALIZATION NOTE: %S is the URL of the media resource which failed to load.
MediaLoadInvalidURI=Invalid URI. Load of media resource %S failed.
# LOCALIZATION NOTE: %1$S is the media resource's format/codec type (basically equivalent to the file type, e.g. MP4,AVI,WMV,MOV etc), %2$S is the URL of the media resource which failed to load.
MediaLoadUnsupportedTypeAttribute=Specified “type” attribute of “%1$S” is not supported. Load of media resource %2$S failed.
# LOCALIZATION NOTE: %1$S is the "media" attribute value of the <source> element. It is a media query. %2$S is the URL of the media resource which failed to load.
MediaLoadSourceMediaNotMatched=Specified “media” attribute of “%1$S” does not match the environment. Load of media resource %2$S failed.
--- a/dom/media/webaudio/ConvolverNode.cpp
+++ b/dom/media/webaudio/ConvolverNode.cpp
@@ -23,18 +23,19 @@ NS_INTERFACE_MAP_END_INHERITING(AudioNod
NS_IMPL_ADDREF_INHERITED(ConvolverNode, AudioNode)
NS_IMPL_RELEASE_INHERITED(ConvolverNode, AudioNode)
class ConvolverNodeEngine final : public AudioNodeEngine
{
typedef PlayingRefChangeHandler PlayingRefChanged;
public:
- ConvolverNodeEngine(AudioNode* aNode, bool aNormalize)
+ ConvolverNodeEngine(AudioNode* aNode, bool aNormalize, uint64_t aWindowID)
: AudioNodeEngine(aNode)
+ , mWindowID(aWindowID)
, mUseBackgroundThreads(!aNode->Context()->IsOffline())
, mNormalize(aNormalize)
{
}
// Indicates how the right output channel is generated.
enum class RightConvolverMode {
// A right convolver is always used when there is more than one impulse
@@ -135,18 +136,26 @@ public:
}
// Assume for now that convolution of channel difference is not required.
// Direct may change to Difference during processing.
mRightConvolverMode =
aBuffer.ChannelCount() == 1 ? RightConvolverMode::Direct
: RightConvolverMode::Always;
+ bool allocationFailure = false;
mReverb = new WebCore::Reverb(aBuffer, MaxFFTSize, mUseBackgroundThreads,
- mNormalize, mSampleRate);
+ mNormalize, mSampleRate, &allocationFailure);
+ if (allocationFailure) {
+ // If the allocation failed, this AudioNodeEngine is going to output
+ // silence. This is signaled to developers in the console.
+ mReverb = nullptr;
+ WebAudioUtils::LogToDeveloperConsole(mWindowID,
+ "ConvolverNodeAllocationError");
+ }
}
void AllocateReverbInput(const AudioBlock& aInput,
uint32_t aTotalChannelCount)
{
uint32_t inputChannelCount = aInput.ChannelCount();
MOZ_ASSERT(inputChannelCount <= aTotalChannelCount);
mReverbInput.AllocateChannels(aTotalChannelCount);
@@ -191,16 +200,17 @@ public:
{
return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
}
private:
// Keeping mReverbInput across process calls avoids unnecessary reallocation.
AudioBlock mReverbInput;
nsAutoPtr<WebCore::Reverb> mReverb;
+ uint64_t mWindowID;
// Tracks samples of the tail remaining to be output. INT32_MIN is a
// special value to indicate that the end of any previous tail has been
// handled.
int32_t mRemainingLeftOutput = INT32_MIN;
// mRemainingRightOutput and mRemainingRightHistory are only used when
// mRightOutputMode != Always. There is no special handling required at the
// end of tail times and so INT32_MIN is not used.
// mRemainingRightOutput tracks how much longer this node needs to continue
@@ -392,17 +402,18 @@ ConvolverNodeEngine::ProcessBlock(AudioN
ConvolverNode::ConvolverNode(AudioContext* aContext)
: AudioNode(aContext,
2,
ChannelCountMode::Clamped_max,
ChannelInterpretation::Speakers)
, mNormalize(true)
{
- ConvolverNodeEngine* engine = new ConvolverNodeEngine(this, mNormalize);
+ uint64_t windowID = aContext->GetParentObject()->WindowID();
+ ConvolverNodeEngine* engine = new ConvolverNodeEngine(this, mNormalize, windowID);
mStream = AudioNodeStream::Create(aContext, engine,
AudioNodeStream::NO_STREAM_FLAGS,
aContext->Graph());
}
/* static */ already_AddRefed<ConvolverNode>
ConvolverNode::Create(JSContext* aCx, AudioContext& aAudioContext,
const ConvolverOptions& aOptions,
--- a/dom/media/webaudio/blink/Reverb.cpp
+++ b/dom/media/webaudio/blink/Reverb.cpp
@@ -72,30 +72,36 @@ static float calculateNormalizationScale
// True-stereo compensation
if (numberOfChannels == 4)
scale *= 0.5f;
return scale;
}
-Reverb::Reverb(const AudioChunk& impulseResponse, size_t maxFFTSize, bool useBackgroundThreads, bool normalize, float sampleRate)
+Reverb::Reverb(const AudioChunk& impulseResponse, size_t maxFFTSize, bool useBackgroundThreads, bool normalize, float sampleRate, bool* aAllocationFailure)
{
+ MOZ_ASSERT(aAllocationFailure);
size_t impulseResponseBufferLength = impulseResponse.mDuration;
float scale = impulseResponse.mVolume;
AutoTArray<const float*,4> irChannels(impulseResponse.ChannelData<float>());
AutoTArray<float,1024> tempBuf;
if (normalize) {
scale = calculateNormalizationScale(irChannels, impulseResponseBufferLength, sampleRate);
}
if (scale != 1.0f) {
- tempBuf.SetLength(irChannels.Length()*impulseResponseBufferLength);
+ bool rv = tempBuf.SetLength(irChannels.Length()*impulseResponseBufferLength, mozilla::fallible);
+ *aAllocationFailure = !rv;
+ if (*aAllocationFailure) {
+ return;
+ }
+
for (uint32_t i = 0; i < irChannels.Length(); ++i) {
float* buf = &tempBuf[i*impulseResponseBufferLength];
AudioBufferCopyWithScale(irChannels[i], scale, buf,
impulseResponseBufferLength);
irChannels[i] = buf;
}
}
--- a/dom/media/webaudio/blink/Reverb.h
+++ b/dom/media/webaudio/blink/Reverb.h
@@ -39,18 +39,22 @@ namespace WebCore {
// Multi-channel convolution reverb with channel matrixing - one or more ReverbConvolver objects are used internally.
class Reverb {
public:
enum { MaxFrameSize = 256 };
// renderSliceSize is a rendering hint, so the FFTs can be optimized to not all occur at the same time (very bad when rendering on a real-time thread).
+ // aAllocation failure is to be checked by the caller. If false, internal
+ // memory could not be allocated, and this Reverb instance is not to be
+ // used.
Reverb(const mozilla::AudioChunk& impulseResponseBuffer, size_t maxFFTSize,
- bool useBackgroundThreads, bool normalize, float sampleRate);
+ bool useBackgroundThreads, bool normalize, float sampleRate,
+ bool* aAllocationFailure);
void process(const mozilla::AudioBlock* sourceBus,
mozilla::AudioBlock* destinationBus);
size_t impulseResponseLength() const { return m_impulseResponseLength; }
size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
--- a/dom/media/webaudio/test/mochitest.ini
+++ b/dom/media/webaudio/test/mochitest.ini
@@ -114,16 +114,18 @@ skip-if = toolkit == 'android' # bug 105
[test_channelSplitterNodeWithVolume.html]
skip-if = (android_version == '18' && debug) # bug 1158417
[test_convolverNode.html]
[test_convolverNode_mono_mono.html]
[test_convolverNodeChannelCount.html]
[test_convolverNodeChannelInterpretationChanges.html]
[test_convolverNodeDelay.html]
[test_convolverNodeFiniteInfluence.html]
+[test_convolverNodeOOM.html]
+skip-if = toolkit == 'android'
[test_convolverNodeNormalization.html]
[test_convolverNodePassThrough.html]
[test_convolverNodeWithGain.html]
[test_convolver-upmixing-1-channel-response.html]
# This is a copy of
# testing/web-platform/tests/webaudio/the-audio-api/the-convolvernode-interface/convolver-upmixing-1-channel-response.html,
# but WPT are not run with ASan or Android builds.
skip-if = !asan && toolkit != android
new file mode 100644
--- /dev/null
+++ b/dom/media/webaudio/test/test_convolverNodeOOM.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test ConvolverNode with very large buffer that triggers an OOM</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+ length: 2048,
+ numberOfChannels: 1,
+ skipOfflineContextTests: true,
+ createGraph: function(context) {
+ var source = context.createOscillator();
+ var convolver = context.createConvolver();
+ // Very big buffer that results in an OOM
+ try {
+ var buffer = context.createBuffer(2, 300000000, context.sampleRate)
+ var channel = buffer.getChannelData(0);
+ } catch(e) {
+ // OOM when attempting to create the buffer, this can happen on 32bits
+ // OSes. Simply return here.
+ return convolver;
+ }
+ source.connect(convolver);
+ convolver.buffer = buffer;
+ source.start();
+ return convolver;
+ }
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_bug1473108.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1473108
+-->
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width; initial-scale=1.0">
+ <title>Test for Bug 1473108</title>
+ <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+ <script type="application/javascript" src="apz_test_utils.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
+ <style>
+ .a {
+ background: green;
+ height: 64px;
+ width: 32px;
+ display: block;
+ }
+ span::before {
+ content: "";
+ background: red;
+ height: 32px;
+ width: 32px;
+ display: block;
+ }
+ span:active::after {
+ content: "";
+ }
+</style>
+</head>
+
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1473108">Mozilla Bug 1473108</a>
+ <a class="a" id="event"><span id="target"></span></a>
+
+ <script type="application/javascript">
+
+ waitUntilApzStable().then(() => {
+ let target = document.getElementById("target");
+ target.addEventListener('click', function(e) {
+ is(e.target, target, `Clicked on at (${e.clientX}, ${e.clientY})`);
+ subtestDone();
+ });
+ synthesizeNativeTap(target, 5, 5);
+ });
+
+</script>
+</body>
+</html>
--- a/gfx/layers/apz/test/mochitest/test_group_touchevents-3.html
+++ b/gfx/layers/apz/test/mochitest/test_group_touchevents-3.html
@@ -18,16 +18,18 @@ var touch_action_prefs = [
var subtests = [
// Simple test to exercise touch-action CSS property
{'file': 'helper_touch_action.html', 'prefs': touch_action_prefs},
// More complex touch-action tests, with overlapping regions and such
{'file': 'helper_touch_action_complex.html', 'prefs': touch_action_prefs},
// Tests that touch-action CSS properties are handled in APZ without waiting
// on the main-thread, when possible
{'file': 'helper_touch_action_regions.html', 'prefs': touch_action_prefs},
+ // touch-action tests with :active::after CSS property
+ {'file': 'helper_bug1473108.html'},
// Add new subtests here. If this starts timing out because it's taking too
// long, create a test_group_touchevents-4.html file. Refer to 1423011#c57
// for more details.
];
if (isApzEnabled()) {
ok(window.TouchEvent, "Check if TouchEvent is supported (it should be, the test harness forces it on everywhere)");
if (getPlatform() == "android") {
--- a/gfx/src/FontPropertyTypes.h
+++ b/gfx/src/FontPropertyTypes.h
@@ -270,16 +270,26 @@ public:
{
return FontStretch(kExtraExpanded);
}
static FontStretch UltraExpanded()
{
return FontStretch(kUltraExpanded);
}
+ // The style system represents percentages in the 0.0..1.0 range, and
+ // FontStretch does it in the 0.0..100.0 range.
+ //
+ // TODO(emilio): We should consider changing this class to deal with the same
+ // range as the style system.
+ static FontStretch FromStyle(float aStylePercentage)
+ {
+ return FontStretch(std::min(aStylePercentage * 100.0f, float(kMax)));
+ }
+
bool IsNormal() const { return mValue == kNormal; }
float Percentage() const { return ToFloat(); }
typedef uint16_t InternalType;
private:
friend class StretchRange;
--- a/gfx/thebes/gfxFontConstants.h
+++ b/gfx/thebes/gfxFontConstants.h
@@ -36,22 +36,16 @@
#define NS_FONT_KERNING_AUTO 0
#define NS_FONT_KERNING_NONE 1
#define NS_FONT_KERNING_NORMAL 2
#define NS_FONT_SYNTHESIS_WEIGHT 0x1
#define NS_FONT_SYNTHESIS_STYLE 0x2
-#define NS_FONT_DISPLAY_AUTO 0
-#define NS_FONT_DISPLAY_BLOCK 1
-#define NS_FONT_DISPLAY_SWAP 2
-#define NS_FONT_DISPLAY_FALLBACK 3
-#define NS_FONT_DISPLAY_OPTIONAL 4
-
#define NS_FONT_OPTICAL_SIZING_AUTO 0
#define NS_FONT_OPTICAL_SIZING_NONE 1
#define NS_FONT_VARIANT_ALTERNATES_NORMAL 0
// alternates - simple enumerated values
#define NS_FONT_VARIANT_ALTERNATES_HISTORICAL (1 << 0)
// alternates - values that use functional syntax
--- a/gfx/thebes/gfxUserFontSet.cpp
+++ b/gfx/thebes/gfxUserFontSet.cpp
@@ -107,17 +107,17 @@ gfxUserFontEntry::gfxUserFontEntry(gfxUs
const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
WeightRange aWeight,
StretchRange aStretch,
SlantStyleRange aStyle,
const nsTArray<gfxFontFeature>& aFeatureSettings,
const nsTArray<gfxFontVariation>& aVariationSettings,
uint32_t aLanguageOverride,
gfxCharacterMap* aUnicodeRanges,
- uint8_t aFontDisplay,
+ StyleFontDisplay aFontDisplay,
RangeFlags aRangeFlags)
: gfxFontEntry(NS_LITERAL_CSTRING("userfont")),
mUserFontLoadState(STATUS_NOT_LOADED),
mFontDataLoadingState(NOT_LOADING),
mUnsupportedFormat(false),
mFontDisplay(aFontDisplay),
mLoader(nullptr),
mFontSet(aFontSet)
@@ -147,17 +147,17 @@ bool
gfxUserFontEntry::Matches(const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
WeightRange aWeight,
StretchRange aStretch,
SlantStyleRange aStyle,
const nsTArray<gfxFontFeature>& aFeatureSettings,
const nsTArray<gfxFontVariation>& aVariationSettings,
uint32_t aLanguageOverride,
gfxCharacterMap* aUnicodeRanges,
- uint8_t aFontDisplay,
+ StyleFontDisplay aFontDisplay,
RangeFlags aRangeFlags)
{
return Weight() == aWeight &&
Stretch() == aStretch &&
SlantStyle() == aStyle &&
mFeatureSettings == aFeatureSettings &&
mVariationSettings == aVariationSettings &&
mLanguageOverride == aLanguageOverride &&
@@ -948,17 +948,17 @@ gfxUserFontSet::FindOrCreateUserFontEntr
const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
WeightRange aWeight,
StretchRange aStretch,
SlantStyleRange aStyle,
const nsTArray<gfxFontFeature>& aFeatureSettings,
const nsTArray<gfxFontVariation>& aVariationSettings,
uint32_t aLanguageOverride,
gfxCharacterMap* aUnicodeRanges,
- uint8_t aFontDisplay,
+ StyleFontDisplay aFontDisplay,
RangeFlags aRangeFlags)
{
RefPtr<gfxUserFontEntry> entry;
// If there's already a userfont entry in the family whose descriptors all match,
// we can just move it to the end of the list instead of adding a new
// face that will always "shadow" the old one.
// Note that we can't do this for platform font entries, even if the
@@ -991,17 +991,17 @@ gfxUserFontSet::FindExistingUserFontEntr
const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
WeightRange aWeight,
StretchRange aStretch,
SlantStyleRange aStyle,
const nsTArray<gfxFontFeature>& aFeatureSettings,
const nsTArray<gfxFontVariation>& aVariationSettings,
uint32_t aLanguageOverride,
gfxCharacterMap* aUnicodeRanges,
- uint8_t aFontDisplay,
+ StyleFontDisplay aFontDisplay,
RangeFlags aRangeFlags)
{
nsTArray<RefPtr<gfxFontEntry>>& fontList = aFamily->GetFontList();
for (size_t i = 0, count = fontList.Length(); i < count; i++) {
if (!fontList[i]->mIsUserFontContainer) {
continue;
}
@@ -1037,17 +1037,17 @@ gfxUserFontSet::AddUserFontEntry(const n
aUserFontEntry->Stretch().ToString(stretchString);
LOG(("userfonts (%p) added to \"%s\" (%p) style: %s weight: %s "
"stretch: %s display: %d",
this, aFamilyName.get(), aUserFontEntry,
(aUserFontEntry->IsItalic() ? "italic" :
(aUserFontEntry->IsOblique() ? "oblique" : "normal")),
weightString.get(),
stretchString.get(),
- aUserFontEntry->GetFontDisplay()));
+ static_cast<int>(aUserFontEntry->GetFontDisplay())));
}
}
void
gfxUserFontSet::IncrementGeneration(bool aIsRebuild)
{
// add one, increment again if zero
++sFontSetGeneration;
--- a/gfx/thebes/gfxUserFontSet.h
+++ b/gfx/thebes/gfxUserFontSet.h
@@ -12,16 +12,17 @@
#include "gfxFontSrcURI.h"
#include "nsRefPtrHashtable.h"
#include "nsCOMPtr.h"
#include "nsIURI.h"
#include "nsIPrincipal.h"
#include "nsIScriptError.h"
#include "nsURIHashKey.h"
#include "mozilla/FontPropertyTypes.h"
+#include "mozilla/ServoStyleConsts.h"
#include "mozilla/net/ReferrerPolicy.h"
#include "gfxFontConstants.h"
namespace mozilla {
class PostTraversalTask;
} // namespace mozilla
class nsFontFaceLoader;
@@ -239,32 +240,32 @@ public:
const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
WeightRange aWeight,
StretchRange aStretch,
SlantStyleRange aStyle,
const nsTArray<gfxFontFeature>& aFeatureSettings,
const nsTArray<gfxFontVariation>& aVariationSettings,
uint32_t aLanguageOverride,
gfxCharacterMap* aUnicodeRanges,
- uint8_t aFontDisplay,
+ mozilla::StyleFontDisplay aFontDisplay,
RangeFlags aRangeFlags) = 0;
// creates a font face for the specified family, or returns an existing
// matching entry on the family if there is one
already_AddRefed<gfxUserFontEntry> FindOrCreateUserFontEntry(
const nsACString& aFamilyName,
const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
WeightRange aWeight,
StretchRange aStretch,
SlantStyleRange aStyle,
const nsTArray<gfxFontFeature>& aFeatureSettings,
const nsTArray<gfxFontVariation>& aVariationSettings,
uint32_t aLanguageOverride,
gfxCharacterMap* aUnicodeRanges,
- uint8_t aFontDisplay,
+ mozilla::StyleFontDisplay aFontDisplay,
RangeFlags aRangeFlags);
// add in a font face for which we have the gfxUserFontEntry already
void AddUserFontEntry(const nsCString& aFamilyName,
gfxUserFontEntry* aUserFontEntry);
// Whether there is a face with this family name
bool HasFamily(const nsACString& aFamilyName) const
@@ -510,17 +511,17 @@ protected:
const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
WeightRange aWeight,
StretchRange aStretch,
SlantStyleRange aStyle,
const nsTArray<gfxFontFeature>& aFeatureSettings,
const nsTArray<gfxFontVariation>& aVariationSettings,
uint32_t aLanguageOverride,
gfxCharacterMap* aUnicodeRanges,
- uint8_t aFontDisplay,
+ mozilla::StyleFontDisplay aFontDisplay,
RangeFlags aRangeFlags);
// creates a new gfxUserFontFamily in mFontFamilies, or returns an existing
// family if there is one
gfxUserFontFamily* GetFamily(const nsACString& aFamilyName);
// font families defined by @font-face rules
nsRefPtrHashtable<nsCStringHashKey, gfxUserFontFamily> mFontFamilies;
@@ -561,31 +562,31 @@ public:
const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
WeightRange aWeight,
StretchRange aStretch,
SlantStyleRange aStyle,
const nsTArray<gfxFontFeature>& aFeatureSettings,
const nsTArray<gfxFontVariation>& aVariationSettings,
uint32_t aLanguageOverride,
gfxCharacterMap* aUnicodeRanges,
- uint8_t aFontDisplay,
+ mozilla::StyleFontDisplay aFontDisplay,
RangeFlags aRangeFlags);
virtual ~gfxUserFontEntry();
// Return whether the entry matches the given list of attributes
bool Matches(const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
WeightRange aWeight,
StretchRange aStretch,
SlantStyleRange aStyle,
const nsTArray<gfxFontFeature>& aFeatureSettings,
const nsTArray<gfxFontVariation>& aVariationSettings,
uint32_t aLanguageOverride,
gfxCharacterMap* aUnicodeRanges,
- uint8_t aFontDisplay,
+ mozilla::StyleFontDisplay aFontDisplay,
RangeFlags aRangeFlags);
gfxFont* CreateFontInstance(const gfxFontStyle* aFontStyle) override;
gfxFontEntry* GetPlatformFontEntry() const { return mPlatformFontEntry; }
// is the font loading or loaded, or did it fail?
UserFontLoadState LoadState() const { return mUserFontLoadState; }
@@ -612,17 +613,17 @@ public:
}
return true;
}
gfxCharacterMap* GetUnicodeRangeMap() const {
return mCharacterMap.get();
}
- uint8_t GetFontDisplay() const { return mFontDisplay; }
+ mozilla::StyleFontDisplay GetFontDisplay() const { return mFontDisplay; }
// load the font - starts the loading of sources which continues until
// a valid font resource is found or all sources fail
void Load();
// methods to expose some information to FontFaceSet::UserFontSet
// since we can't make that class a friend
void SetLoader(nsFontFaceLoader* aLoader) { mLoader = aLoader; }
@@ -717,18 +718,18 @@ protected:
// so keep hiding fallback font
LOADING_SLOWLY, // timeout happened and we're not nearly done,
// so use the fallback font
LOADING_TIMED_OUT, // font load took too long
LOADING_FAILED // failed to load any source: use fallback
};
FontDataLoadingState mFontDataLoadingState;
- bool mUnsupportedFormat;
- uint8_t mFontDisplay; // timing of userfont fallback
+ bool mUnsupportedFormat;
+ mozilla::StyleFontDisplay mFontDisplay; // timing of userfont fallback
RefPtr<gfxFontEntry> mPlatformFontEntry;
nsTArray<gfxFontFaceSrc> mSrcList;
uint32_t mSrcIndex; // index of loading src item
// This field is managed by the nsFontFaceLoader. In the destructor and Cancel()
// methods of nsFontFaceLoader this reference is nulled out.
nsFontFaceLoader* MOZ_NON_OWNING_REF mLoader; // current loader for this entry, if any
gfxUserFontSet* MOZ_NON_OWNING_REF mFontSet; // font-set which owns this userfont entry
--- a/js/src/builtin/BigInt.cpp
+++ b/js/src/builtin/BigInt.cpp
@@ -21,22 +21,16 @@
using namespace js;
static MOZ_ALWAYS_INLINE bool
IsBigInt(HandleValue v)
{
return v.isBigInt() || (v.isObject() && v.toObject().is<BigIntObject>());
}
-static JSObject*
-CreateBigIntPrototype(JSContext* cx, JSProtoKey key)
-{
- return GlobalObject::createBlankPrototype<PlainObject>(cx, cx->global());
-}
-
// BigInt proposal section 5.1.3
static bool
BigIntConstructor(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
// Step 1.
if (args.isConstructing()) {
@@ -184,32 +178,39 @@ bool
BigIntObject::toLocaleString(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsBigInt, toLocaleString_impl>(cx, args);
}
const ClassSpec BigIntObject::classSpec_ = {
GenericCreateConstructor<BigIntConstructor, 1, gc::AllocKind::FUNCTION>,
- CreateBigIntPrototype,
+ GenericCreatePrototype<BigIntObject>,
nullptr,
nullptr,
BigIntObject::methods,
BigIntObject::properties
};
// The class is named "Object" as a workaround for bug 1277801.
const Class BigIntObject::class_ = {
"Object",
JSCLASS_HAS_CACHED_PROTO(JSProto_BigInt) |
JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS),
JS_NULL_CLASS_OPS,
&BigIntObject::classSpec_
};
+const Class BigIntObject::protoClass_ = {
+ js_Object_str,
+ JSCLASS_HAS_CACHED_PROTO(JSProto_BigInt),
+ JS_NULL_CLASS_OPS,
+ &BigIntObject::classSpec_
+};
+
const JSPropertySpec BigIntObject::properties[] = {
// BigInt proposal section 5.3.5
JS_STRING_SYM_PS(toStringTag, "BigInt", JSPROP_READONLY),
JS_PS_END
};
const JSFunctionSpec BigIntObject::methods[] = {
JS_FN("valueOf", valueOf, 0, 0),
--- a/js/src/builtin/BigInt.h
+++ b/js/src/builtin/BigInt.h
@@ -19,16 +19,17 @@ class GlobalObject;
class BigIntObject : public NativeObject
{
static const unsigned PRIMITIVE_VALUE_SLOT = 0;
static const unsigned RESERVED_SLOTS = 1;
public:
static const ClassSpec classSpec_;
static const Class class_;
+ static const Class protoClass_;
static JSObject* create(JSContext* cx, JS::Handle<JS::BigInt*> bi);
// Methods defined on BigInt.prototype.
static bool valueOf_impl(JSContext* cx, const CallArgs& args);
static bool valueOf(JSContext* cx, unsigned argc, JS::Value* vp);
static bool toString_impl(JSContext* cx, const CallArgs& args);
static bool toString(JSContext* cx, unsigned argc, JS::Value* vp);
--- a/js/src/builtin/DataViewObject.cpp
+++ b/js/src/builtin/DataViewObject.cpp
@@ -930,23 +930,16 @@ DataViewObject::byteOffsetGetterImpl(JSC
bool
DataViewObject::byteOffsetGetter(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<is, byteOffsetGetterImpl>(cx, args);
}
-const Class DataViewObject::protoClass_ = {
- js_Object_str,
- JSCLASS_HAS_CACHED_PROTO(JSProto_DataView),
- JS_NULL_CLASS_OPS,
- &DataViewObject::classSpec_
-};
-
JSObject*
DataViewObject::CreatePrototype(JSContext* cx, JSProtoKey key)
{
return GlobalObject::createBlankPrototype(cx, cx->global(), &DataViewObject::protoClass_);
}
static const ClassOps DataViewObjectClassOps = {
nullptr, /* addProperty */
@@ -959,32 +952,39 @@ static const ClassOps DataViewObjectClas
nullptr, /* call */
nullptr, /* hasInstance */
nullptr, /* construct */
ArrayBufferViewObject::trace
};
const ClassSpec DataViewObject::classSpec_ = {
GenericCreateConstructor<DataViewObject::construct, 3, gc::AllocKind::FUNCTION>,
- DataViewObject::CreatePrototype,
+ GenericCreatePrototype<DataViewObject>,
nullptr,
nullptr,
DataViewObject::methods,
DataViewObject::properties,
};
const Class DataViewObject::class_ = {
"DataView",
JSCLASS_HAS_PRIVATE |
JSCLASS_HAS_RESERVED_SLOTS(TypedArrayObject::RESERVED_SLOTS) |
JSCLASS_HAS_CACHED_PROTO(JSProto_DataView),
&DataViewObjectClassOps,
&DataViewObject::classSpec_
};
+const Class DataViewObject::protoClass_ = {
+ js_Object_str,
+ JSCLASS_HAS_CACHED_PROTO(JSProto_DataView),
+ JS_NULL_CLASS_OPS,
+ &DataViewObject::classSpec_
+};
+
const JSFunctionSpec DataViewObject::methods[] = {
JS_FN("getInt8", DataViewObject::fun_getInt8, 1,0),
JS_FN("getUint8", DataViewObject::fun_getUint8, 1,0),
JS_FN("getInt16", DataViewObject::fun_getInt16, 1,0),
JS_FN("getUint16", DataViewObject::fun_getUint16, 1,0),
JS_FN("getInt32", DataViewObject::fun_getInt32, 1,0),
JS_FN("getUint32", DataViewObject::fun_getUint32, 1,0),
JS_FN("getFloat32", DataViewObject::fun_getFloat32, 1,0),
--- a/js/src/builtin/DataViewObject.h
+++ b/js/src/builtin/DataViewObject.h
@@ -21,17 +21,16 @@ namespace js {
// In the DataViewObject, the private slot contains a raw pointer into
// the buffer. The buffer may be shared memory and the raw pointer
// should not be exposed without sharedness information accompanying
// it.
class DataViewObject : public NativeObject
{
private:
- static const Class protoClass_;
static const ClassSpec classSpec_;
static JSObject* CreatePrototype(JSContext* cx, JSProtoKey key);
static bool is(HandleValue v) {
return v.isObject() && v.toObject().hasClass(&class_);
}
@@ -55,16 +54,17 @@ class DataViewObject : public NativeObje
static bool constructWrapped(JSContext* cx, HandleObject bufobj, const CallArgs& args);
static DataViewObject*
create(JSContext* cx, uint32_t byteOffset, uint32_t byteLength,
Handle<ArrayBufferObjectMaybeShared*> arrayBuffer, HandleObject proto);
public:
static const Class class_;
+ static const Class protoClass_;
static Value byteOffsetValue(const DataViewObject* view) {
Value v = view->getFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT);
MOZ_ASSERT(v.toInt32() >= 0);
return v;
}
static Value byteLengthValue(const DataViewObject* view) {
--- a/js/src/builtin/MapObject.cpp
+++ b/js/src/builtin/MapObject.cpp
@@ -407,39 +407,33 @@ MapIteratorObject::createResultPair(JSCo
AddTypePropertyId(cx, resultPairObj, JSID_VOID, TypeSet::UnknownType());
return resultPairObj;
}
/*** Map *****************************************************************************************/
-static JSObject*
-CreateMapPrototype(JSContext* cx, JSProtoKey key)
-{
- return GlobalObject::createBlankPrototype(cx, cx->global(), &MapObject::protoClass_);
-}
-
const ClassOps MapObject::classOps_ = {
nullptr, // addProperty
nullptr, // delProperty
nullptr, // enumerate
nullptr, // newEnumerate
nullptr, // resolve
nullptr, // mayResolve
finalize,
nullptr, // call
nullptr, // hasInstance
nullptr, // construct
trace
};
const ClassSpec MapObject::classSpec_ = {
GenericCreateConstructor<MapObject::construct, 0, gc::AllocKind::FUNCTION>,
- CreateMapPrototype,
+ GenericCreatePrototype<MapObject>,
nullptr,
MapObject::staticProperties,
MapObject::methods,
MapObject::properties,
};
const Class MapObject::class_ = {
"Map",
@@ -1269,39 +1263,33 @@ SetIteratorObject::createResult(JSContex
AddTypePropertyId(cx, resultObj, JSID_VOID, TypeSet::UnknownType());
return resultObj;
}
/*** Set *****************************************************************************************/
-static JSObject*
-CreateSetPrototype(JSContext* cx, JSProtoKey key)
-{
- return GlobalObject::createBlankPrototype(cx, cx->global(), &SetObject::protoClass_);
-}
-
const ClassOps SetObject::classOps_ = {
nullptr, // addProperty
nullptr, // delProperty
nullptr, // enumerate
nullptr, // newEnumerate
nullptr, // resolve
nullptr, // mayResolve
finalize,
nullptr, // call
nullptr, // hasInstance
nullptr, // construct
trace
};
const ClassSpec SetObject::classSpec_ = {
GenericCreateConstructor<SetObject::construct, 0, gc::AllocKind::FUNCTION>,
- CreateSetPrototype,
+ GenericCreatePrototype<SetObject>,
nullptr,
SetObject::staticProperties,
SetObject::methods,
SetObject::properties,
};
const Class SetObject::class_ = {
"Set",
--- a/js/src/builtin/Promise.cpp
+++ b/js/src/builtin/Promise.cpp
@@ -5040,22 +5040,16 @@ OffThreadPromiseRuntimeState::shutdown(J
numCanceled_ = 0;
// After shutdown, there should be no OffThreadPromiseTask activity in this
// JSRuntime. Revert to the !initialized() state to catch bugs.
dispatchToEventLoopCallback_ = nullptr;
MOZ_ASSERT(!initialized());
}
-static JSObject*
-CreatePromisePrototype(JSContext* cx, JSProtoKey key)
-{
- return GlobalObject::createBlankPrototype(cx, cx->global(), &PromiseObject::protoClass_);
-}
-
const JSJitInfo promise_then_info = {
{ (JSJitGetterOp)Promise_then_noRetVal },
{ 0 }, /* unused */
{ 0 }, /* unused */
JSJitInfo::IgnoresReturnValueNative,
JSJitInfo::AliasEverything,
JSVAL_TYPE_UNDEFINED,
};
@@ -5091,17 +5085,17 @@ static const JSFunctionSpec promise_stat
static const JSPropertySpec promise_static_properties[] = {
JS_SYM_GET(species, Promise_static_species, 0),
JS_PS_END
};
static const ClassSpec PromiseObjectClassSpec = {
GenericCreateConstructor<PromiseConstructor, 1, gc::AllocKind::FUNCTION>,
- CreatePromisePrototype,
+ GenericCreatePrototype<PromiseObject>,
promise_static_methods,
promise_static_properties,
promise_methods,
promise_properties
};
const Class PromiseObject::class_ = {
"Promise",
--- a/js/src/builtin/Stream.cpp
+++ b/js/src/builtin/Stream.cpp
@@ -578,17 +578,17 @@ class TeeState : public NativeObject
const Class TeeState::class_ = {
"TeeState",
JSCLASS_HAS_RESERVED_SLOTS(SlotCount)
};
#define CLASS_SPEC(cls, nCtorArgs, nSlots, specFlags, classFlags, classOps) \
const ClassSpec cls::classSpec_ = { \
GenericCreateConstructor<cls::constructor, nCtorArgs, gc::AllocKind::FUNCTION>, \
- GenericCreatePrototype, \
+ GenericCreatePrototype<cls>, \
nullptr, \
nullptr, \
cls##_methods, \
cls##_properties, \
nullptr, \
specFlags \
}; \
\
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/bug1445854.js
@@ -0,0 +1,4 @@
+load(libdir + "asserts.js");
+if (typeof ReadableStream == "function") {
+ assertThrowsInstanceOf(TypeError, () => ReadableStream.prototype.tee());
+}
--- a/js/src/jsdate.cpp
+++ b/js/src/jsdate.cpp
@@ -3599,23 +3599,16 @@ DateConstructor(JSContext* cx, unsigned
if (args.length() == 1) {
return DateOneArgument(cx, args);
}
return DateMultipleArguments(cx, args);
}
-// ES6 final draft 20.3.4.
-static JSObject*
-CreateDatePrototype(JSContext* cx, JSProtoKey key)
-{
- return GlobalObject::createBlankPrototype(cx, cx->global(), &DateObject::protoClass_);
-}
-
static bool
FinishDateClassInit(JSContext* cx, HandleObject ctor, HandleObject proto)
{
/*
* Date.prototype.toGMTString has the same initial value as
* Date.prototype.toUTCString.
*/
RootedValue toUTCStringFun(cx);
@@ -3623,17 +3616,17 @@ FinishDateClassInit(JSContext* cx, Handl
RootedId toGMTStringId(cx, NameToId(cx->names().toGMTString));
return NativeGetProperty(cx, proto.as<NativeObject>(), toUTCStringId, &toUTCStringFun) &&
NativeDefineDataProperty(cx, proto.as<NativeObject>(), toGMTStringId, toUTCStringFun,
0);
}
static const ClassSpec DateObjectClassSpec = {
GenericCreateConstructor<DateConstructor, 7, gc::AllocKind::FUNCTION>,
- CreateDatePrototype,
+ GenericCreatePrototype<DateObject>,
date_static_methods,
nullptr,
date_methods,
nullptr,
FinishDateClassInit
};
const Class DateObject::class_ = {
--- a/js/src/vm/ArrayBufferObject.cpp
+++ b/js/src/vm/ArrayBufferObject.cpp
@@ -256,22 +256,16 @@ js::UnmapBufferMemory(void* base, size_t
* access. It can be created explicitly and passed to a TypedArrayObject, or
* can be created implicitly by constructing a TypedArrayObject with a size.
*/
/*
* ArrayBufferObject (base)
*/
-static JSObject*
-CreateArrayBufferPrototype(JSContext* cx, JSProtoKey key)
-{
- return GlobalObject::createBlankPrototype(cx, cx->global(), &ArrayBufferObject::protoClass_);
-}
-
static const ClassOps ArrayBufferObjectClassOps = {
nullptr, /* addProperty */
nullptr, /* delProperty */
nullptr, /* enumerate */
nullptr, /* newEnumerate */
nullptr, /* resolve */
nullptr, /* mayResolve */
ArrayBufferObject::finalize,
@@ -300,17 +294,17 @@ static const JSFunctionSpec arraybuffer_
static const JSPropertySpec arraybuffer_proto_properties[] = {
JS_PSG("byteLength", ArrayBufferObject::byteLengthGetter, 0),
JS_STRING_SYM_PS(toStringTag, "ArrayBuffer", JSPROP_READONLY),
JS_PS_END
};
static const ClassSpec ArrayBufferObjectClassSpec = {
GenericCreateConstructor<ArrayBufferObject::class_constructor, 1, gc::AllocKind::FUNCTION>,
- CreateArrayBufferPrototype,
+ GenericCreatePrototype<ArrayBufferObject>,
arraybuffer_functions,
arraybuffer_properties,
arraybuffer_proto_functions,
arraybuffer_proto_properties
};
static const ClassExtension ArrayBufferObjectClassExtension = {
nullptr, /* weakmapKeyDelegateOp */
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -11051,17 +11051,17 @@ DebuggerObject::getBoundArguments(JSCont
Debugger::getObjectAllocationSite(JSObject& obj)
{
JSObject* metadata = GetAllocationMetadata(&obj);
if (!metadata) {
return nullptr;
}
MOZ_ASSERT(!metadata->is<WrapperObject>());
- return SavedFrame::isSavedFrameAndNotProto(*metadata)
+ return metadata->is<SavedFrame>()
? &metadata->as<SavedFrame>()
: nullptr;
}
/* static */ bool
DebuggerObject::getAllocationSite(JSContext* cx, HandleDebuggerObject object,
MutableHandleObject result)
{
--- a/js/src/vm/GlobalObject.h
+++ b/js/src/vm/GlobalObject.h
@@ -904,28 +904,27 @@ JSObject*
GenericCreateConstructor(JSContext* cx, JSProtoKey key)
{
// Note - We duplicate the trick from ClassName() so that we don't need to
// include vm/JSAtom-inl.h here.
PropertyName* name = (&cx->names().Null)[key];
return GlobalObject::createConstructor(cx, ctor, name, length, kind, jitInfo);
}
-inline JSObject*
+template<typename T>
+JSObject*
GenericCreatePrototype(JSContext* cx, JSProtoKey key)
{
- MOZ_ASSERT(key != JSProto_Object);
- const Class* clasp = ProtoKeyToClass(key);
- MOZ_ASSERT(clasp);
- JSProtoKey protoKey = InheritanceProtoKeyForStandardClass(key);
- if (!GlobalObject::ensureConstructor(cx, cx->global(), protoKey)) {
- return nullptr;
- }
- RootedObject parentProto(cx, &cx->global()->getPrototype(protoKey).toObject());
- return GlobalObject::createBlankPrototypeInheriting(cx, clasp, parentProto);
+ static_assert(!std::is_same<T, PlainObject>::value,
+ "creating Object.prototype is very special and isn't handled here");
+ MOZ_ASSERT(&T::class_ == ProtoKeyToClass(key),
+ "type mismatch--probably too much copy/paste in your ClassSpec");
+ MOZ_ASSERT(InheritanceProtoKeyForStandardClass(key) == JSProto_Object,
+ "subclasses (of anything but Object) can't use GenericCreatePrototype");
+ return GlobalObject::createBlankPrototype(cx, cx->global(), &T::protoClass_);
}
inline JSProtoKey
StandardProtoKeyOrNull(const JSObject* obj)
{
return JSCLASS_CACHED_PROTO_KEY(obj->getClass());
}
--- a/js/src/vm/RegExpObject.cpp
+++ b/js/src/vm/RegExpObject.cpp
@@ -162,39 +162,33 @@ IsMarkingTrace(JSTracer* trc)
}
void
RegExpObject::trace(JSTracer* trc)
{
TraceNullableEdge(trc, &sharedRef(), "RegExpObject shared");
}
-static JSObject*
-CreateRegExpPrototype(JSContext* cx, JSProtoKey key)
-{
- return GlobalObject::createBlankPrototype(cx, cx->global(), &RegExpObject::protoClass_);
-}
-
static const ClassOps RegExpObjectClassOps = {
nullptr, /* addProperty */
nullptr, /* delProperty */
nullptr, /* enumerate */
nullptr, /* newEnumerate */
nullptr, /* resolve */
nullptr, /* mayResolve */
nullptr, /* finalize */
nullptr, /* call */
nullptr, /* hasInstance */
nullptr, /* construct */
RegExpObject::trace,
};
static const ClassSpec RegExpObjectClassSpec = {
GenericCreateConstructor<js::regexp_construct, 2, gc::AllocKind::FUNCTION>,
- CreateRegExpPrototype,
+ GenericCreatePrototype<RegExpObject>,
nullptr,
js::regexp_static_props,
js::regexp_methods,
js::regexp_properties
};
const Class RegExpObject::class_ = {
js_RegExp_str,
--- a/js/src/vm/SavedFrame.h
+++ b/js/src/vm/SavedFrame.h
@@ -18,16 +18,17 @@ namespace js {
class SavedFrame : public NativeObject {
friend class SavedStacks;
friend struct ::JSStructuredCloneReader;
static const ClassSpec classSpec_;
public:
static const Class class_;
+ static const Class protoClass_;
static const JSPropertySpec protoAccessors[];
static const JSFunctionSpec protoFunctions[];
static const JSFunctionSpec staticFunctions[];
// Prototype methods and properties to be exposed to JS.
static bool construct(JSContext* cx, unsigned argc, Value* vp);
static bool sourceProperty(JSContext* cx, unsigned argc, Value* vp);
static bool lineProperty(JSContext* cx, unsigned argc, Value* vp);
@@ -91,27 +92,22 @@ class SavedFrame : public NativeObject {
RootedSavedFrame frame_;
public:
RootedRange(JSContext* cx, HandleSavedFrame frame) : frame_(cx, frame) { }
RootedIterator begin() { return RootedIterator(*this); }
RootedIterator end() { return RootedIterator(); }
};
- static bool isSavedFrameAndNotProto(JSObject& obj) {
- return obj.is<SavedFrame>() &&
- !obj.as<SavedFrame>().getReservedSlot(JSSLOT_SOURCE).isNull();
- }
-
- static bool isSavedFrameOrWrapperAndNotProto(JSObject& obj) {
+ static bool isSavedFrameOrWrapper(JSObject& obj) {
auto unwrapped = CheckedUnwrap(&obj);
if (!unwrapped) {
return false;
}
- return isSavedFrameAndNotProto(*unwrapped);
+ return unwrapped->is<SavedFrame>();
}
struct Lookup;
struct HashPolicy;
typedef JS::GCHashSet<ReadBarriered<SavedFrame*>,
HashPolicy,
SystemAllocPolicy> Set;
--- a/js/src/vm/SavedStacks-inl.h
+++ b/js/src/vm/SavedStacks-inl.h
@@ -18,13 +18,13 @@
// and use the original caller's compartment's principals to determine what
// level of data to present. Unwrapping and entering the referent's compartment
// would mess that up. See the module level documentation in
// `js/src/vm/SavedStacks.h` as well as the comments in `js/src/jsapi.h`.
inline void
js::AssertObjectIsSavedFrameOrWrapper(JSContext* cx, HandleObject stack)
{
if (stack) {
- MOZ_RELEASE_ASSERT(js::SavedFrame::isSavedFrameOrWrapperAndNotProto(*stack));
+ MOZ_RELEASE_ASSERT(js::SavedFrame::isSavedFrameOrWrapper(*stack));
}
}
#endif // vm_SavedStacksInl_h
--- a/js/src/vm/SavedStacks.cpp
+++ b/js/src/vm/SavedStacks.cpp
@@ -342,20 +342,16 @@ SavedFrame::HashPolicy::match(SavedFrame
SavedFrame::HashPolicy::rekey(Key& key, const Key& newKey)
{
key = newKey;
}
/* static */ bool
SavedFrame::finishSavedFrameInit(JSContext* cx, HandleObject ctor, HandleObject proto)
{
- // The only object with the SavedFrame::class_ that doesn't have a source
- // should be the prototype.
- proto->as<NativeObject>().setReservedSlot(SavedFrame::JSSLOT_SOURCE, NullValue());
-
return FreezeObject(cx, proto);
}
static const ClassOps SavedFrameClassOps = {
nullptr, // addProperty
nullptr, // delProperty
nullptr, // enumerate
nullptr, // newEnumerate
@@ -365,17 +361,17 @@ static const ClassOps SavedFrameClassOps
nullptr, // call
nullptr, // hasInstance
nullptr, // construct
nullptr, // trace
};
const ClassSpec SavedFrame::classSpec_ = {
GenericCreateConstructor<SavedFrame::construct, 0, gc::AllocKind::FUNCTION>,
- GenericCreatePrototype,
+ GenericCreatePrototype<SavedFrame>,
SavedFrame::staticFunctions,
nullptr,
SavedFrame::protoFunctions,
SavedFrame::protoAccessors,
SavedFrame::finishSavedFrameInit,
ClassSpec::DontDefineConstructor
};
@@ -385,16 +381,23 @@ const ClassSpec SavedFrame::classSpec_ =
JSCLASS_HAS_RESERVED_SLOTS(SavedFrame::JSSLOT_COUNT) |
JSCLASS_HAS_CACHED_PROTO(JSProto_SavedFrame) |
JSCLASS_IS_ANONYMOUS |
JSCLASS_FOREGROUND_FINALIZE,
&SavedFrameClassOps,
&SavedFrame::classSpec_
};
+const Class SavedFrame::protoClass_ = {
+ js_Object_str,
+ JSCLASS_HAS_CACHED_PROTO(JSProto_SavedFrame),
+ JS_NULL_CLASS_OPS,
+ &SavedFrame::classSpec_
+};
+
/* static */ const JSFunctionSpec
SavedFrame::staticFunctions[] = {
JS_FS_END
};
/* static */ const JSFunctionSpec
SavedFrame::protoFunctions[] = {
JS_FN("constructor", SavedFrame::construct, 0, 0),
@@ -734,25 +737,16 @@ SavedFrame_checkThis(JSContext* cx, Call
JSObject* thisObject = CheckedUnwrap(&thisValue.toObject());
if (!thisObject || !thisObject->is<SavedFrame>()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
SavedFrame::class_.name, fnName,
thisObject ? thisObject->getClass()->name : "object");
return false;
}
- // Check for SavedFrame.prototype, which has the same class as SavedFrame
- // instances, however doesn't actually represent a captured stack frame. It
- // is the only object that is<SavedFrame>() but doesn't have a source.
- if (!SavedFrame::isSavedFrameAndNotProto(*thisObject)) {
- JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
- SavedFrame::class_.name, fnName, "prototype object");
- return false;
- }
-
// Now set "frame" to the actual object we were invoked in (which may be a
// wrapper), not the unwrapped version. Consumers will need to know what
// that original object was, and will do principal checks as needed.
frame.set(&thisValue.toObject());
return true;
}
// Get the SavedFrame * from the current this value and handle any errors that
@@ -784,17 +778,17 @@ UnwrapSavedFrame(JSContext* cx, JSPrinci
return nullptr;
}
RootedObject savedFrameObj(cx, CheckedUnwrap(obj));
if (!savedFrameObj) {
return nullptr;
}
- MOZ_RELEASE_ASSERT(js::SavedFrame::isSavedFrameAndNotProto(*savedFrameObj));
+ MOZ_RELEASE_ASSERT(savedFrameObj->is<js::SavedFrame>());
js::RootedSavedFrame frame(cx, &savedFrameObj->as<js::SavedFrame>());
return GetFirstSubsumedFrame(cx, principals, frame, selfHosted, skippedAsync);
}
JS_PUBLIC_API(SavedFrameResult)
GetSavedFrameSource(JSContext* cx, JSPrincipals* principals, HandleObject savedFrame,
MutableHandleString sourcep,
SavedFrameSelfHosted selfHosted /* = SavedFrameSelfHosted::Include */)
@@ -1133,24 +1127,24 @@ BuildStackString(JSContext* cx, JSPrinci
stringp.set(str);
return true;
}
JS_PUBLIC_API(bool)
IsMaybeWrappedSavedFrame(JSObject* obj)
{
MOZ_ASSERT(obj);
- return js::SavedFrame::isSavedFrameOrWrapperAndNotProto(*obj);
+ return js::SavedFrame::isSavedFrameOrWrapper(*obj);
}
JS_PUBLIC_API(bool)
IsUnwrappedSavedFrame(JSObject* obj)
{
MOZ_ASSERT(obj);
- return js::SavedFrame::isSavedFrameAndNotProto(*obj);
+ return obj->is<js::SavedFrame>();
}
} /* namespace JS */
namespace js {
/* static */ bool
SavedFrame::sourceProperty(JSContext* cx, unsigned argc, Value* vp)
@@ -1305,17 +1299,17 @@ SavedStacks::copyAsyncStack(JSContext* c
RootedAtom asyncCauseAtom(cx, AtomizeString(cx, asyncCause));
if (!asyncCauseAtom) {
return false;
}
RootedObject asyncStackObj(cx, CheckedUnwrap(asyncStack));
MOZ_RELEASE_ASSERT(asyncStackObj);
- MOZ_RELEASE_ASSERT(js::SavedFrame::isSavedFrameAndNotProto(*asyncStackObj));
+ MOZ_RELEASE_ASSERT(asyncStackObj->is<js::SavedFrame>());
adoptedStack.set(&asyncStackObj->as<js::SavedFrame>());
if (!adoptAsyncStack(cx, adoptedStack, asyncCauseAtom, maxFrameCount)) {
return false;
}
return true;
}
--- a/js/src/vm/SharedArrayObject.cpp
+++ b/js/src/vm/SharedArrayObject.cpp
@@ -362,23 +362,16 @@ SharedArrayBufferObject::createFromNewRa
return nullptr;
}
obj->acceptRawBuffer(buffer, initialSize);
return obj;
}
-static JSObject*
-CreateSharedArrayBufferPrototype(JSContext* cx, JSProtoKey key)
-{
- return GlobalObject::createBlankPrototype(cx, cx->global(),
- &SharedArrayBufferObject::protoClass_);
-}
-
static const ClassOps SharedArrayBufferObjectClassOps = {
nullptr, /* addProperty */
nullptr, /* delProperty */
nullptr, /* enumerate */
nullptr, /* newEnumerate */
nullptr, /* resolve */
nullptr, /* mayResolve */
SharedArrayBufferObject::Finalize,
@@ -405,17 +398,17 @@ static const JSFunctionSpec sharedarray_
static const JSPropertySpec sharedarray_proto_properties[] = {
JS_PSG("byteLength", SharedArrayBufferObject::byteLengthGetter, 0),
JS_STRING_SYM_PS(toStringTag, "SharedArrayBuffer", JSPROP_READONLY),
JS_PS_END
};
static const ClassSpec SharedArrayBufferObjectClassSpec = {
GenericCreateConstructor<SharedArrayBufferObject::class_constructor, 1, gc::AllocKind::FUNCTION>,
- CreateSharedArrayBufferPrototype,
+ GenericCreatePrototype<SharedArrayBufferObject>,
sharedarrray_functions,
sharedarrray_properties,
sharedarray_proto_functions,
sharedarray_proto_properties
};
const Class SharedArrayBufferObject::class_ = {
"SharedArrayBuffer",
--- a/js/src/vm/StructuredClone.cpp
+++ b/js/src/vm/StructuredClone.cpp
@@ -1844,17 +1844,17 @@ JSStructuredCloneWriter::startWrite(Hand
return writeTypedArray(obj);
}
if (JS_IsDataViewObject(obj)) {
return writeDataView(obj);
}
if (wasm::IsSharedWasmMemoryObject(obj)) {
return writeSharedWasmMemory(obj);
}
- if (SavedFrame::isSavedFrameOrWrapperAndNotProto(*obj)) {
+ if (SavedFrame::isSavedFrameOrWrapper(*obj)) {
return traverseSavedFrame(obj);
}
break;
}
}
if (out.buf.callbacks_ && out.buf.callbacks_->write) {
return out.buf.callbacks_->write(context(), this, obj, out.buf.closure_);
@@ -2080,17 +2080,17 @@ JSStructuredCloneWriter::write(HandleVal
counts.back()--;
val = otherEntries.popCopy();
checkStack();
if (!startWrite(key) || !startWrite(val)) {
return false;
}
- } else if (cls == ESClass::Set || SavedFrame::isSavedFrameOrWrapperAndNotProto(*obj)) {
+ } else if (cls == ESClass::Set || SavedFrame::isSavedFrameOrWrapper(*obj)) {
key = otherEntries.popCopy();
checkStack();
if (!startWrite(key)) {
return false;
}
} else {
id = objectEntries.popCopy();
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -1762,37 +1762,39 @@ TypedArrayObject::staticFunctions[] = {
};
/* static */ const JSPropertySpec
TypedArrayObject::staticProperties[] = {
JS_SELF_HOSTED_SYM_GET(species, "TypedArraySpecies", 0),
JS_PS_END
};
+static JSObject*
+CreateSharedTypedArrayPrototype(JSContext* cx, JSProtoKey key)
+{
+ return GlobalObject::createBlankPrototype(cx,
+ cx->global(),
+ &TypedArrayObject::sharedTypedArrayPrototypeClass);
+}
+
static const ClassSpec
TypedArrayObjectSharedTypedArrayPrototypeClassSpec = {
GenericCreateConstructor<TypedArrayConstructor, 0, gc::AllocKind::FUNCTION>,
- GenericCreatePrototype,
+ CreateSharedTypedArrayPrototype,
TypedArrayObject::staticFunctions,
TypedArrayObject::staticProperties,
TypedArrayObject::protoFunctions,
TypedArrayObject::protoAccessors,
nullptr,
ClassSpec::DontDefineConstructor
};
/* static */ const Class
TypedArrayObject::sharedTypedArrayPrototypeClass = {
- // Actually ({}).toString.call(%TypedArray%.prototype) should throw,
- // because %TypedArray%.prototype lacks the the typed array internal
- // slots. (It's not clear this is desirable -- particularly applied to
- // the actual typed array prototypes, see below -- but it's what ES6
- // draft 20140824 requires.) But this is about as much as we can do
- // until we implement @@toStringTag.
- "???",
+ "TypedArrayPrototype",
JSCLASS_HAS_CACHED_PROTO(JSProto_TypedArray),
JS_NULL_CLASS_OPS,
&TypedArrayObjectSharedTypedArrayPrototypeClassSpec
};
// this default implementation is only valid for integer types
// less than 32-bits in size.
template<typename NativeType>
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -783,29 +783,29 @@ nsIPresShell::nsIPresShell()
, mFontSizeInflationMinTwips(0)
, mFontSizeInflationLineThreshold(0)
, mFontSizeInflationForceEnabled(false)
, mFontSizeInflationDisabledInMasterProcess(false)
, mFontSizeInflationEnabled(false)
, mPaintingIsFrozen(false)
, mIsNeverPainting(false)
, mInFlush(false)
+ , mCurrentEventFrame(nullptr)
{}
PresShell::PresShell()
: mCaretEnabled(false)
#ifdef DEBUG
, mInVerifyReflow(false)
, mCurrentReflowRoot(nullptr)
#endif
#ifdef MOZ_REFLOW_PERF
, mReflowCountMgr(nullptr)
#endif
, mMouseLocation(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE)
- , mCurrentEventFrame(nullptr)
, mFirstCallbackEventRequest(nullptr)
, mLastCallbackEventRequest(nullptr)
, mLastReflowStart(0.0)
, mLastAnchorScrollPositionY(0)
, mActiveSuppressDisplayport(0)
, mAPZFocusSequenceNumber(0)
, mDocumentLoading(false)
, mIgnoreFrameDestruction(false)
@@ -2090,16 +2090,32 @@ PresShell::FireResizeEvent()
nsEventStatus status = nsEventStatus_eIgnore;
if (nsPIDOMWindowOuter* window = mDocument->GetWindow()) {
EventDispatcher::Dispatch(window, mPresContext, &event, nullptr, &status);
}
}
void
+nsIPresShell::NativeAnonymousContentRemoved(nsIContent* aAnonContent)
+{
+ if (aAnonContent == mCurrentEventContent) {
+ mCurrentEventContent = aAnonContent->GetFlattenedTreeParent();
+ mCurrentEventFrame = nullptr;
+ }
+
+ for (unsigned int i = 0; i < mCurrentEventContentStack.Length(); i++) {
+ if (aAnonContent == mCurrentEventContentStack.ElementAt(i)) {
+ mCurrentEventContentStack.ReplaceObjectAt(aAnonContent->GetFlattenedTreeParent(), i);
+ mCurrentEventFrameStack[i] = nullptr;
+ }
+ }
+}
+
+void
PresShell::SetIgnoreFrameDestruction(bool aIgnore)
{
if (mDocument) {
// We need to tell the ImageLoader to drop all its references to frames
// because they're about to go away and it won't get notifications of that.
mDocument->StyleImageLoader()->ClearFrames(mPresContext);
}
mIgnoreFrameDestruction = aIgnore;
@@ -6404,28 +6420,28 @@ nsIPresShell::SetCapturingContent(nsICon
gCaptureInfo.mRetargetToElement = ((aFlags & CAPTURE_RETARGETTOELEMENT) != 0) ||
((aFlags & CAPTURE_POINTERLOCK) != 0);
gCaptureInfo.mPreventDrag = (aFlags & CAPTURE_PREVENTDRAG) != 0;
gCaptureInfo.mPointerLock = (aFlags & CAPTURE_POINTERLOCK) != 0;
}
}
nsIContent*
-PresShell::GetCurrentEventContent()
+nsIPresShell::GetCurrentEventContent()
{
if (mCurrentEventContent &&
mCurrentEventContent->GetComposedDoc() != mDocument) {
mCurrentEventContent = nullptr;
mCurrentEventFrame = nullptr;
}
return mCurrentEventContent;
}
nsIFrame*
-PresShell::GetCurrentEventFrame()
+nsIPresShell::GetCurrentEventFrame()
{
if (MOZ_UNLIKELY(mIsDestroying)) {
return nullptr;
}
// GetCurrentEventContent() makes sure the content is still in the
// same document that this pres shell belongs to. If not, then the
// frame shouldn't get an event, nor should we even assume its safe
@@ -6435,43 +6451,43 @@ PresShell::GetCurrentEventFrame()
mCurrentEventFrame = content->GetPrimaryFrame();
MOZ_ASSERT(!mCurrentEventFrame ||
mCurrentEventFrame->PresContext()->GetPresShell() == this);
}
return mCurrentEventFrame;
}
already_AddRefed<nsIContent>
-PresShell::GetEventTargetContent(WidgetEvent* aEvent)
+nsIPresShell::GetEventTargetContent(WidgetEvent* aEvent)
{
nsCOMPtr<nsIContent> content = GetCurrentEventContent();
if (!content) {
nsIFrame* currentEventFrame = GetCurrentEventFrame();
if (currentEventFrame) {
currentEventFrame->GetContentForEvent(aEvent, getter_AddRefs(content));
NS_ASSERTION(!content || content->GetComposedDoc() == mDocument,
"handing out content from a different doc");
}
}
return content.forget();
}
void
-PresShell::PushCurrentEventInfo(nsIFrame* aFrame, nsIContent* aContent)
+nsIPresShell::PushCurrentEventInfo(nsIFrame* aFrame, nsIContent* aContent)
{
if (mCurrentEventFrame || mCurrentEventContent) {
mCurrentEventFrameStack.InsertElementAt(0, mCurrentEventFrame);
mCurrentEventContentStack.InsertObjectAt(mCurrentEventContent, 0);
}
mCurrentEventFrame = aFrame;
mCurrentEventContent = aContent;
}
void
-PresShell::PopCurrentEventInfo()
+nsIPresShell::PopCurrentEventInfo()
{
mCurrentEventFrame = nullptr;
mCurrentEventContent = nullptr;
if (0 != mCurrentEventFrameStack.Length()) {
mCurrentEventFrame = mCurrentEventFrameStack.ElementAt(0);
mCurrentEventFrameStack.RemoveElementAt(0);
mCurrentEventContent = mCurrentEventContentStack.ObjectAt(0);
--- a/layout/base/PresShell.h
+++ b/layout/base/PresShell.h
@@ -168,18 +168,16 @@ public:
nsresult HandleEventWithTarget(WidgetEvent* aEvent,
nsIFrame* aFrame,
nsIContent* aContent,
nsEventStatus* aStatus,
bool aIsHandlingNativeEvent = false,
nsIContent** aTargetContent = nullptr,
nsIContent* aOverrideClickTarget = nullptr)
override;
- already_AddRefed<nsIContent>
- GetEventTargetContent(WidgetEvent* aEvent) override;
void NotifyCounterStylesAreDirty() override;
void ReconstructFrames(void) override;
void Freeze() override;
void Thaw() override;
void FireOrClearDelayedEvents(bool aFireEvents) override;
@@ -652,22 +650,19 @@ private:
};
void ProcessSynthMouseMoveEvent(bool aFromScroll);
void QueryIsActive();
nsresult UpdateImageLockingState();
bool InZombieDocument(nsIContent *aContent);
already_AddRefed<nsIPresShell> GetParentPresShellForEventHandling();
- nsIContent* GetCurrentEventContent();
- nsIFrame* GetCurrentEventFrame() override;
MOZ_CAN_RUN_SCRIPT nsresult
RetargetEventToParent(WidgetGUIEvent* aEvent, nsEventStatus* aEventStatus);
- void PushCurrentEventInfo(nsIFrame* aFrame, nsIContent* aContent);
- void PopCurrentEventInfo();
+
/**
* @param aIsHandlingNativeEvent true when the caller (perhaps) handles
* an event which is caused by native
* event. Otherwise, false.
*/
nsresult HandleEventInternal(WidgetEvent* aEvent,
nsEventStatus* aStatus,
bool aIsHandlingNativeEvent,
@@ -782,20 +777,16 @@ private:
RefPtr<StyleSheet> mPrefStyleSheet;
// Set of frames that we should mark with NS_FRAME_HAS_DIRTY_CHILDREN after
// we finish reflowing mCurrentReflowRoot.
nsTHashtable<nsPtrHashKey<nsIFrame> > mFramesToDirty;
nsTArray<nsAutoPtr<DelayedEvent> > mDelayedEvents;
private:
- nsIFrame* mCurrentEventFrame;
- nsCOMPtr<nsIContent> mCurrentEventContent;
- nsTArray<nsIFrame*> mCurrentEventFrameStack;
- nsCOMArray<nsIContent> mCurrentEventContentStack;
nsRevocableEventPtr<nsSynthMouseMoveEvent> mSynthMouseMoveEvent;
nsCOMPtr<nsIContent> mLastAnchorScrolledTo;
RefPtr<nsCaret> mCaret;
RefPtr<nsCaret> mOriginalCaret;
nsCallbackEventRequest* mFirstCallbackEventRequest;
nsCallbackEventRequest* mLastCallbackEventRequest;
TouchManager mTouchManager;
--- a/layout/base/nsIPresShell.h
+++ b/layout/base/nsIPresShell.h
@@ -37,16 +37,17 @@
#include "nsChangeHint.h"
#include "nsRefPtrHashtable.h"
#include "nsClassHashtable.h"
#include "nsPresArena.h"
#include "nsIImageLoadingContent.h"
#include "nsMargin.h"
#include "nsFrameState.h"
#include "nsStubDocumentObserver.h"
+#include "nsCOMArray.h"
#include "Units.h"
class gfxContext;
class nsDocShell;
class nsIDocument;
class nsIFrame;
class nsPresContext;
class nsWindowSizes;
@@ -949,23 +950,22 @@ public:
* Return whether or not the event is valid to be dispatched
*/
virtual bool CanDispatchEvent(
const mozilla::WidgetGUIEvent* aEvent = nullptr) const = 0;
/**
* Gets the current target event frame from the PresShell
*/
- virtual nsIFrame* GetCurrentEventFrame() = 0;
+ nsIFrame* GetCurrentEventFrame();
/**
* Gets the current target event frame from the PresShell
*/
- virtual already_AddRefed<nsIContent> GetEventTargetContent(
- mozilla::WidgetEvent* aEvent) = 0;
+ already_AddRefed<nsIContent> GetEventTargetContent(mozilla::WidgetEvent* aEvent);
/**
* Get and set the history state for the current document
*/
virtual nsresult CaptureHistoryState(nsILayoutHistoryState** aLayoutHistoryState) = 0;
/**
@@ -1621,16 +1621,18 @@ public:
/**
* Returns whether or not the document has ever handled user input
*/
virtual bool HasHandledUserInput() const = 0;
virtual void FireResizeEvent() = 0;
+ void NativeAnonymousContentRemoved(nsIContent* aAnonContent);
+
protected:
/**
* Refresh observer management.
*/
void DoObserveStyleFlushes();
void DoObserveLayoutFlushes();
/**
@@ -1648,16 +1650,20 @@ protected:
void RecordFree(void* aPtr) {
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
MOZ_DIAGNOSTIC_ASSERT(mAllocatedPointers.Contains(aPtr));
mAllocatedPointers.RemoveEntry(aPtr);
#endif
}
+ void PushCurrentEventInfo(nsIFrame* aFrame, nsIContent* aContent);
+ void PopCurrentEventInfo();
+ nsIContent* GetCurrentEventContent();
+
public:
bool AddRefreshObserver(nsARefreshObserver* aObserver,
mozilla::FlushType aFlushType);
bool RemoveRefreshObserver(nsARefreshObserver* aObserver,
mozilla::FlushType aFlushType);
bool AddPostRefreshObserver(nsAPostRefreshObserver* aObserver);
bool RemovePostRefreshObserver(nsAPostRefreshObserver* aObserver);
@@ -1862,13 +1868,18 @@ protected:
// If a document belongs to an invisible DocShell, this flag must be set
// to true, so we can avoid any paint calls for widget related to this
// presshell.
bool mIsNeverPainting;
// Whether we're currently under a FlushPendingNotifications.
// This is used to handle flush reentry correctly.
bool mInFlush;
+
+ nsIFrame* mCurrentEventFrame;
+ nsCOMPtr<nsIContent> mCurrentEventContent;
+ nsTArray<nsIFrame*> mCurrentEventFrameStack;
+ nsCOMArray<nsIContent> mCurrentEventContentStack;
};
NS_DEFINE_STATIC_IID_ACCESSOR(nsIPresShell, NS_IPRESSHELL_IID)
#endif /* nsIPresShell_h___ */
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -10120,110 +10120,16 @@ nsLayoutUtils::ComputeSystemFont(nsFont*
aSystemFont->size =
std::max(aDefaultVariableFont->size -
nsPresContext::CSSPointsToAppUnits(2), 0);
}
#endif
}
}
-static inline void
-AssertValidFontTag(const nsString& aString)
-{
- // To be valid as a font feature tag, a string MUST be:
- MOZ_ASSERT(aString.Length() == 4 && // (1) exactly 4 chars long
- NS_IsAscii(aString.BeginReading()) && // (2) entirely ASCII
- isprint(aString[0]) && // (3) all printable chars
- isprint(aString[1]) &&
- isprint(aString[2]) &&
- isprint(aString[3]));
-}
-
-/* static */ void
-nsLayoutUtils::ComputeFontFeatures(const nsCSSValuePairList *aFeaturesList,
- nsTArray<gfxFontFeature>& aFeatureSettings)
-{
- aFeatureSettings.Clear();
- for (const nsCSSValuePairList* p = aFeaturesList; p; p = p->mNext) {
- gfxFontFeature feat;
-
- MOZ_ASSERT(aFeaturesList->mXValue.GetUnit() == eCSSUnit_String,
- "unexpected value unit");
-
- // tag is a 4-byte ASCII sequence
- nsAutoString tag;
- p->mXValue.GetStringValue(tag);
- AssertValidFontTag(tag);
- if (tag.Length() != 4) {
- continue;
- }
- // parsing validates that these are ASCII chars
- // tags are always big-endian
- feat.mTag = (tag[0] << 24) | (tag[1] << 16) | (tag[2] << 8) | tag[3];
-
- // value
- NS_ASSERTION(p->mYValue.GetUnit() == eCSSUnit_Integer,
- "should have found an integer unit");
- feat.mValue = p->mYValue.GetIntValue();
-
- aFeatureSettings.AppendElement(feat);
- }
-}
-
-/* static */ void
-nsLayoutUtils::ComputeFontVariations(const nsCSSValuePairList* aVariationsList,
- nsTArray<gfxFontVariation>& aVariationSettings)
-{
- aVariationSettings.Clear();
- for (const nsCSSValuePairList* p = aVariationsList; p; p = p->mNext) {
- gfxFontVariation var;
-
- MOZ_ASSERT(aVariationsList->mXValue.GetUnit() == eCSSUnit_String,
- "unexpected value unit");
-
- // tag is a 4-byte ASCII sequence
- nsAutoString tag;
- p->mXValue.GetStringValue(tag);
- AssertValidFontTag(tag);
- if (tag.Length() != 4) {
- continue;
- }
- // parsing validates that these are ASCII chars
- // tags are always big-endian
- var.mTag = (tag[0] << 24) | (tag[1] << 16) | (tag[2] << 8) | tag[3];
-
- // value
- NS_ASSERTION(p->mYValue.GetUnit() == eCSSUnit_Number,
- "should have found a number unit");
- var.mValue = p->mYValue.GetFloatValue();
-
- aVariationSettings.AppendElement(var);
- }
-}
-
-/* static */ uint32_t
-nsLayoutUtils::ParseFontLanguageOverride(const nsAString& aLangTag)
-{
- if (!aLangTag.Length() || aLangTag.Length() > 4) {
- return NO_FONT_LANGUAGE_OVERRIDE;
- }
- uint32_t index, result = 0;
- for (index = 0; index < aLangTag.Length(); ++index) {
- char16_t ch = aLangTag[index];
- if (!nsCRT::IsAscii(ch)) { // valid tags are pure ASCII
- return NO_FONT_LANGUAGE_OVERRIDE;
- }
- result = (result << 8) + ch;
- }
- while (index++ < 4) {
- result = (result << 8) + 0x20;
- }
- return result;
-}
-
/* static */ bool
nsLayoutUtils::ShouldHandleMetaViewport(nsIDocument* aDocument)
{
uint32_t metaViewportOverride = nsIDocShell::META_VIEWPORT_OVERRIDE_NONE;
if (aDocument) {
if (nsIDocShell* docShell = aDocument->GetDocShell()) {
docShell->GetMetaViewportOverride(&metaViewportOverride);
}
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -246,16 +246,17 @@ nsReflowStatus::UpdateTruncated(const Re
}
/* static */ void
nsIFrame::DestroyAnonymousContent(nsPresContext* aPresContext,
already_AddRefed<nsIContent>&& aContent)
{
if (nsCOMPtr<nsIContent> content = aContent) {
aPresContext->EventStateManager()->NativeAnonymousContentRemoved(content);
+ aPresContext->PresShell()->NativeAnonymousContentRemoved(content);
content->UnbindFromTree();
}
}
// Formerly the nsIFrameDebug interface
std::ostream& operator<<(std::ostream& aStream,
const nsReflowStatus& aStatus)
--- a/layout/painting/FrameLayerBuilder.cpp
+++ b/layout/painting/FrameLayerBuilder.cpp
@@ -5514,22 +5514,16 @@ FrameLayerBuilder::ComputeGeometryChange
}
PaintedDisplayItemLayerUserData* layerData =
static_cast<PaintedDisplayItemLayerUserData*>(
aData->mLayer->GetUserData(&gPaintedDisplayItemLayerUserData));
nsPoint shift = layerData->mAnimatedGeometryRootOrigin -
layerData->mLastAnimatedGeometryRootOrigin;
- if (aData->mTransform) {
- // If this display item is inside a flattened transform, the shift is
- // already included in the root transform.
- shift = nsPoint();
- }
-
const DisplayItemClip& clip = item->GetClip();
const int32_t appUnitsPerDevPixel = layerData->mAppUnitsPerDevPixel;
// If the frame is marked as invalidated, and didn't specify a rect to
// invalidate then we want to invalidate both the old and new bounds,
// otherwise we only want to invalidate the changed areas. If we do get an
// invalid rect, then we want to add this on top of the change areas.
nsRect invalid;
@@ -5581,16 +5575,22 @@ FrameLayerBuilder::ComputeGeometryChange
}
#endif
} else {
// Let the display item check for geometry changes and decide what needs to
// be repainted.
const nsRegion& changedFrameInvalidations =
aData->GetChangedFrameInvalidations();
+ if (aData->mTransform) {
+ // If this display item is inside a flattened transform the offset is
+ // already included in the root transform, so there is no need to shift.
+ shift = nsPoint();
+ }
+
aData->mGeometry->MoveBy(shift);
nsRegion combined;
item->ComputeInvalidationRegion(
mDisplayListBuilder, aData->mGeometry, &combined);
// Only allocate a new geometry object if something actually changed,
// otherwise the existing one should be fine. We always reallocate for
--- a/layout/style/FontFace.cpp
+++ b/layout/style/FontFace.cpp
@@ -218,33 +218,17 @@ FontFace::InitializeSource(const StringO
SetStatus(FontFaceLoadStatus::Loading);
DoLoad();
}
void
FontFace::GetFamily(nsString& aResult)
{
mFontFaceSet->FlushUserFontSet();
-
- // Serialize the same way as in nsCSSFontFaceStyleDecl::GetPropertyValue.
- nsCSSValue value;
- GetDesc(eCSSFontDesc_Family, value);
-
- aResult.Truncate();
-
- if (value.GetUnit() == eCSSUnit_Null) {
- return;
- }
-
- nsDependentString family(value.GetStringBufferValue());
- if (!family.IsEmpty()) {
- // The string length can be zero when the author passed an invalid
- // family name or an invalid descriptor to the JS FontFace constructor.
- nsStyleUtil::AppendEscapedCSSString(family, aResult);
- }
+ GetDesc(eCSSFontDesc_Family, aResult);
}
void
FontFace::SetFamily(const nsAString& aValue, ErrorResult& aRv)
{
mFontFaceSet->FlushUserFontSet();
SetDescriptor(eCSSFontDesc_Family, aValue, aRv);
}
@@ -290,20 +274,16 @@ FontFace::SetStretch(const nsAString& aV
mFontFaceSet->FlushUserFontSet();
SetDescriptor(eCSSFontDesc_Stretch, aValue, aRv);
}
void
FontFace::GetUnicodeRange(nsString& aResult)
{
mFontFaceSet->FlushUserFontSet();
-
- // There is no eCSSProperty_unicode_range for us to pass in to GetDesc
- // to get a serialized (possibly defaulted) value, but that function
- // doesn't use the property ID for this descriptor anyway.
GetDesc(eCSSFontDesc_UnicodeRange, aResult);
}
void
FontFace::SetUnicodeRange(const nsAString& aValue, ErrorResult& aRv)
{
mFontFaceSet->FlushUserFontSet();
SetDescriptor(eCSSFontDesc_UnicodeRange, aValue, aRv);
@@ -602,23 +582,16 @@ FontFace::SetDescriptors(const nsAString
SetStatus(FontFaceLoadStatus::Error);
return false;
}
return true;
}
void
-FontFace::GetDesc(nsCSSFontDesc aDescID, nsCSSValue& aResult) const
-{
- aResult.Reset();
- Servo_FontFaceRule_GetDescriptor(GetData(), aDescID, &aResult);
-}
-
-void
FontFace::GetDesc(nsCSSFontDesc aDescID, nsString& aResult) const
{
aResult.Truncate();
Servo_FontFaceRule_GetDescriptorCssText(GetData(), aDescID, &aResult);
// Fill in a default value for missing descriptors.
if (aResult.IsEmpty()) {
if (aDescID == eCSSFontDesc_UnicodeRange) {
@@ -661,29 +634,101 @@ FontFace::SetUserFontEntry(gfxUserFontEn
FontFaceLoadStatus newStatus =
LoadStateToStatus(mUserFontEntry->LoadState());
if (newStatus > mStatus) {
SetStatus(newStatus);
}
}
}
-bool
-FontFace::GetFamilyName(nsCString& aResult)
+Maybe<StyleComputedFontWeightRange>
+FontFace::GetFontWeight() const
+{
+ StyleComputedFontWeightRange range;
+ if (!Servo_FontFaceRule_GetFontWeight(GetData(), &range)) {
+ return Nothing();
+ }
+ return Some(range);
+}
+
+Maybe<StyleComputedFontStretchRange>
+FontFace::GetFontStretch() const
{
- nsCSSValue value;
- GetDesc(eCSSFontDesc_Family, value);
+ StyleComputedFontStretchRange range;
+ if (!Servo_FontFaceRule_GetFontStretch(GetData(), &range)) {
+ return Nothing();
+ }
+ return Some(range);
+}
+
+Maybe<StyleComputedFontStyleDescriptor>
+FontFace::GetFontStyle() const
+{
+ StyleComputedFontStyleDescriptor descriptor;
+ if (!Servo_FontFaceRule_GetFontStyle(GetData(), &descriptor)) {
+ return Nothing();
+ }
+ return Some(descriptor);
+}
+
+Maybe<StyleFontDisplay>
+FontFace::GetFontDisplay() const
+{
+ StyleFontDisplay display;
+ if (!Servo_FontFaceRule_GetFontDisplay(GetData(), &display)) {
+ return Nothing();
+ }
+ return Some(display);
+}
- if (value.GetUnit() == eCSSUnit_String) {
- nsString familyname;
- value.GetStringValue(familyname);
- AppendUTF16toUTF8(familyname, aResult);
+Maybe<StyleFontLanguageOverride>
+FontFace::GetFontLanguageOverride() const
+{
+ StyleFontLanguageOverride langOverride;
+ if (!Servo_FontFaceRule_GetFontLanguageOverride(GetData(), &langOverride)) {
+ return Nothing();
+ }
+ return Some(langOverride);
+}
+
+bool
+FontFace::HasLocalSrc() const
+{
+ AutoTArray<StyleFontFaceSourceListComponent, 8> components;
+ GetSources(components);
+ for (auto& component : components) {
+ if (component.tag == StyleFontFaceSourceListComponent::Tag::Local) {
+ return true;
+ }
}
+ return false;
+}
- return !aResult.IsEmpty();
+void
+FontFace::GetFontFeatureSettings(nsTArray<gfxFontFeature>& aFeatures) const
+{
+ Servo_FontFaceRule_GetFeatureSettings(GetData(), &aFeatures);
+}
+
+void
+FontFace::GetFontVariationSettings(nsTArray<gfxFontVariation>& aVariations) const
+{
+ Servo_FontFaceRule_GetVariationSettings(GetData(), &aVariations);
+}
+
+void
+FontFace::GetSources(nsTArray<StyleFontFaceSourceListComponent>& aSources) const
+{
+ Servo_FontFaceRule_GetSources(GetData(), &aSources);
+}
+
+nsAtom*
+FontFace::GetFamilyName() const
+{
+ return Servo_FontFaceRule_GetFamilyName(GetData());
}
void
FontFace::DisconnectFromRule()
{
MOZ_ASSERT(HasRule());
// Make a copy of the descriptors.
@@ -790,29 +835,25 @@ FontFace::EnsurePromise()
gfxCharacterMap*
FontFace::GetUnicodeRangeAsCharacterMap()
{
if (!mUnicodeRangeDirty) {
return mUnicodeRange;
}
- nsCSSValue val;
- GetDesc(eCSSFontDesc_UnicodeRange, val);
+ size_t len;
+ const StyleUnicodeRange* rangesPtr =
+ Servo_FontFaceRule_GetUnicodeRanges(GetData(), &len);
- if (val.GetUnit() == eCSSUnit_Array) {
+ Span<const StyleUnicodeRange> ranges(rangesPtr, len);
+ if (!ranges.IsEmpty()) {
mUnicodeRange = new gfxCharacterMap();
- const nsCSSValue::Array& sources = *val.GetArrayValue();
- MOZ_ASSERT(sources.Count() % 2 == 0,
- "odd number of entries in a unicode-range: array");
-
- for (uint32_t i = 0; i < sources.Count(); i += 2) {
- uint32_t min = sources[i].GetIntValue();
- uint32_t max = sources[i+1].GetIntValue();
- mUnicodeRange->SetRange(min, max);
+ for (auto& range : ranges) {
+ mUnicodeRange->SetRange(range.start, range.end);
}
} else {
mUnicodeRange = nullptr;
}
mUnicodeRangeDirty = false;
return mUnicodeRange;
}
--- a/layout/style/FontFace.h
+++ b/layout/style/FontFace.h
@@ -4,16 +4,19 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_FontFace_h
#define mozilla_dom_FontFace_h
#include "mozilla/dom/FontFaceBinding.h"
#include "mozilla/FontPropertyTypes.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Pair.h"
+#include "mozilla/ServoStyleConsts.h"
#include "gfxUserFontSet.h"
#include "nsAutoPtr.h"
#include "nsCSSPropertyID.h"
#include "nsCSSValue.h"
#include "nsWrapperCache.h"
class gfxFontFaceBufferSource;
struct RawServoFontFaceRule;
@@ -50,17 +53,17 @@ public:
const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
WeightRange aWeight,
StretchRange aStretch,
SlantStyleRange aStyle,
const nsTArray<gfxFontFeature>& aFeatureSettings,
const nsTArray<gfxFontVariation>& aVariationSettings,
uint32_t aLanguageOverride,
gfxCharacterMap* aUnicodeRanges,
- uint8_t aFontDisplay,
+ StyleFontDisplay aFontDisplay,
RangeFlags aRangeFlags)
: gfxUserFontEntry(aFontSet, aFontFaceSrcList, aWeight, aStretch,
aStyle, aFeatureSettings, aVariationSettings,
aLanguageOverride,
aUnicodeRanges, aFontDisplay,
aRangeFlags) {}
virtual void SetLoadState(UserFontLoadState aLoadState) override;
@@ -82,17 +85,25 @@ public:
virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
static already_AddRefed<FontFace>
CreateForRule(nsISupports* aGlobal, FontFaceSet* aFontFaceSet,
RawServoFontFaceRule* aRule);
RawServoFontFaceRule* GetRule() { return mRule; }
- void GetDesc(nsCSSFontDesc aDescID, nsCSSValue& aResult) const;
+ bool HasLocalSrc() const;
+ Maybe<StyleComputedFontWeightRange> GetFontWeight() const;
+ Maybe<StyleComputedFontStretchRange> GetFontStretch() const;
+ Maybe<StyleComputedFontStyleDescriptor> GetFontStyle() const;
+ Maybe<StyleFontDisplay> GetFontDisplay() const;
+ void GetFontFeatureSettings(nsTArray<gfxFontFeature>&) const;
+ void GetFontVariationSettings(nsTArray<gfxFontVariation>&) const;
+ void GetSources(nsTArray<StyleFontFaceSourceListComponent>&) const;
+ Maybe<StyleFontLanguageOverride> GetFontLanguageOverride() const;
gfxUserFontEntry* CreateUserFontEntry();
gfxUserFontEntry* GetUserFontEntry() const { return mUserFontEntry; }
void SetUserFontEntry(gfxUserFontEntry* aEntry);
/**
* Returns whether this object is in the specified FontFaceSet.
*/
@@ -101,19 +112,19 @@ public:
void AddFontFaceSet(FontFaceSet* aFontFaceSet);
void RemoveFontFaceSet(FontFaceSet* aFontFaceSet);
FontFaceSet* GetPrimaryFontFaceSet() const { return mFontFaceSet; }
/**
* Gets the family name of the FontFace as a raw string (such as 'Times', as
* opposed to GetFamily, which returns a CSS-escaped string, such as
- * '"Times"'). Returns whether a valid family name was available.
+ * '"Times"'). Returns null if a valid family name was not available.
*/
- bool GetFamilyName(nsCString& aResult);
+ nsAtom* GetFamilyName() const;
/**
* Returns whether this object is CSS-connected, i.e. reflecting an
* @font-face rule.
*/
bool HasRule() const { return mRule; }
/**
--- a/layout/style/FontFaceSet.cpp
+++ b/layout/style/FontFaceSet.cpp
@@ -206,53 +206,46 @@ void
FontFaceSet::ParseFontShorthandForMatching(
const nsAString& aFont,
RefPtr<SharedFontList>& aFamilyList,
FontWeight& aWeight,
FontStretch& aStretch,
FontSlantStyle& aStyle,
ErrorResult& aRv)
{
- nsCSSValue style;
- nsCSSValue stretch;
- nsCSSValue weight;
+ StyleComputedFontStyleDescriptor style;
+ float stretch;
+ float weight;
// FIXME(emilio): This Servo -> nsCSSValue -> Gecko conversion is stupid,
// Servo understands the font types.
RefPtr<URLExtraData> url = ServoCSSParser::GetURLExtraData(mDocument);
if (!ServoCSSParser::ParseFontShorthandForMatching(
aFont, url, aFamilyList, style, stretch, weight)) {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
return;
}
- switch (style.GetUnit()) {
- case eCSSUnit_Normal:
+ switch (style.tag) {
+ case StyleComputedFontStyleDescriptor::Tag::Normal:
aStyle = FontSlantStyle::Normal();
break;
- case eCSSUnit_Enumerated:
- MOZ_ASSERT(style.GetIntValue() == NS_FONT_STYLE_ITALIC);
+ case StyleComputedFontStyleDescriptor::Tag::Italic:
aStyle = FontSlantStyle::Italic();
break;
- case eCSSUnit_FontSlantStyle:
- aStyle = style.GetFontSlantStyle();
+ case StyleComputedFontStyleDescriptor::Tag::Oblique:
+ MOZ_ASSERT(style.oblique._0 == style.oblique._1,
+ "We use ComputedFontStyleDescriptor just for convenience, "
+ "the two values should always match");
+ aStyle = FontSlantStyle::Oblique(style.oblique._0);
break;
- default:
- MOZ_ASSERT_UNREACHABLE("Unknown unit for font-style");
}
- if (weight.GetUnit() == eCSSUnit_FontWeight) {
- aWeight = weight.GetFontWeight();
- } else {
- MOZ_ASSERT(weight.GetUnit() == eCSSUnit_Enumerated);
- aWeight = FontWeight(weight.GetIntValue());
- }
-
- MOZ_ASSERT(stretch.GetUnit() == eCSSUnit_FontStretch);
- aStretch = stretch.GetFontStretch();
+ aWeight = FontWeight(weight);
+ aStretch = FontStretch::FromStyle(stretch);
}
static bool
HasAnyCharacterInUnicodeRange(gfxUserFontEntry* aEntry,
const nsAString& aInput)
{
const char16_t* p = aInput.Data();
const char16_t* end = p + aInput.Length();
@@ -828,107 +821,94 @@ FontFaceSet::UpdateRules(const nsTArray<
mUserFontSet.get(),
(modified ? "modified" : "not modified"),
(int)(mRuleFaces.Length())));
}
return modified;
}
-static bool
-HasLocalSrc(const nsCSSValue::Array *aSrcArr)
-{
- size_t numSrc = aSrcArr->Count();
- for (size_t i = 0; i < numSrc; i++) {
- if (aSrcArr->Item(i).GetUnit() == eCSSUnit_Local_Font) {
- return true;
- }
- }
- return false;
-}
-
void
FontFaceSet::IncrementGeneration(bool aIsRebuild)
{
MOZ_ASSERT(mUserFontSet);
mUserFontSet->IncrementGeneration(aIsRebuild);
}
void
FontFaceSet::InsertNonRuleFontFace(FontFace* aFontFace,
bool& aFontSetModified)
{
- nsAutoCString fontfamily;
- if (!aFontFace->GetFamilyName(fontfamily)) {
+ nsAtom* fontFamily = aFontFace->GetFamilyName();
+ if (!fontFamily) {
// If there is no family name, this rule cannot contribute a
// usable font, so there is no point in processing it further.
return;
}
+ nsAtomCString family(fontFamily);
+
// Just create a new font entry if we haven't got one already.
if (!aFontFace->GetUserFontEntry()) {
// XXX Should we be checking mUserFontSet->mLocalRulesUsed like
// InsertRuleFontFace does?
RefPtr<gfxUserFontEntry> entry =
- FindOrCreateUserFontEntryFromFontFace(fontfamily, aFontFace,
- SheetType::Doc);
+ FindOrCreateUserFontEntryFromFontFace(family, aFontFace, SheetType::Doc);
if (!entry) {
return;
}
aFontFace->SetUserFontEntry(entry);
}
aFontSetModified = true;
- mUserFontSet->AddUserFontEntry(fontfamily, aFontFace->GetUserFontEntry());
+ mUserFontSet->AddUserFontEntry(family, aFontFace->GetUserFontEntry());
}
void
FontFaceSet::InsertRuleFontFace(FontFace* aFontFace, SheetType aSheetType,
nsTArray<FontFaceRecord>& aOldRecords,
bool& aFontSetModified)
{
- nsAutoCString fontfamily;
- if (!aFontFace->GetFamilyName(fontfamily)) {
+ nsAtom* fontFamily = aFontFace->GetFamilyName();
+ if (!fontFamily) {
// If there is no family name, this rule cannot contribute a
// usable font, so there is no point in processing it further.
return;
}
bool remove = false;
size_t removeIndex;
+ nsAtomCString family(fontFamily);
+
// This is a rule backed FontFace. First, we check in aOldRecords; if
// the FontFace for the rule exists there, just move it to the new record
// list, and put the entry into the appropriate family.
for (size_t i = 0; i < aOldRecords.Length(); ++i) {
FontFaceRecord& rec = aOldRecords[i];
if (rec.mFontFace == aFontFace &&
rec.mSheetType == aSheetType) {
// if local rules were used, don't use the old font entry
// for rules containing src local usage
- if (mUserFontSet->mLocalRulesUsed &&
- mUserFontSet->mRebuildLocalRules) {
- nsCSSValue val;
- aFontFace->GetDesc(eCSSFontDesc_Src, val);
- nsCSSUnit unit = val.GetUnit();
- if (unit == eCSSUnit_Array && HasLocalSrc(val.GetArrayValue())) {
+ if (mUserFontSet->mLocalRulesUsed && mUserFontSet->mRebuildLocalRules) {
+ if (aFontFace->HasLocalSrc()) {
// Remove the old record, but wait to see if we successfully create a
// new user font entry below.
remove = true;
removeIndex = i;
break;
}
}
gfxUserFontEntry* entry = rec.mFontFace->GetUserFontEntry();
MOZ_ASSERT(entry, "FontFace should have a gfxUserFontEntry by now");
- mUserFontSet->AddUserFontEntry(fontfamily, entry);
+ mUserFontSet->AddUserFontEntry(family, entry);
MOZ_ASSERT(!HasRuleFontFace(rec.mFontFace),
"FontFace should not occur in mRuleFaces twice");
mRuleFaces.AppendElement(rec);
aOldRecords.RemoveElementAt(i);
// note the set has been modified if an old rule was skipped to find
// this one - something has been dropped, or ordering changed
@@ -936,17 +916,17 @@ FontFaceSet::InsertRuleFontFace(FontFace
aFontSetModified = true;
}
return;
}
}
// this is a new rule:
RefPtr<gfxUserFontEntry> entry =
- FindOrCreateUserFontEntryFromFontFace(fontfamily, aFontFace, aSheetType);
+ FindOrCreateUserFontEntryFromFontFace(family, aFontFace, aSheetType);
if (!entry) {
return;
}
if (remove) {
// Although we broke out of the aOldRecords loop above, since we found
// src local usage, and we're not using the old user font entry, we still
@@ -974,202 +954,121 @@ FontFaceSet::InsertRuleFontFace(FontFace
// this was a new rule and font entry, so note that the set was modified
aFontSetModified = true;
// Add the entry to the end of the list. If an existing userfont entry was
// returned by FindOrCreateUserFontEntryFromFontFace that was already stored
// on the family, gfxUserFontFamily::AddFontEntry(), which AddUserFontEntry
// calls, will automatically remove the earlier occurrence of the same
// userfont entry.
- mUserFontSet->AddUserFontEntry(fontfamily, entry);
+ mUserFontSet->AddUserFontEntry(family, entry);
}
/* static */ already_AddRefed<gfxUserFontEntry>
FontFaceSet::FindOrCreateUserFontEntryFromFontFace(FontFace* aFontFace)
{
- nsAutoCString fontfamily;
- if (!aFontFace->GetFamilyName(fontfamily)) {
+ nsAtom* fontFamily = aFontFace->GetFamilyName();
+ if (!fontFamily) {
// If there is no family name, this rule cannot contribute a
// usable font, so there is no point in processing it further.
return nullptr;
}
- return FindOrCreateUserFontEntryFromFontFace(fontfamily, aFontFace,
+ return FindOrCreateUserFontEntryFromFontFace(nsAtomCString(fontFamily), aFontFace,
SheetType::Doc);
}
-static FontWeight
-GetWeightForDescriptor(const nsCSSValue& aVal)
-{
- switch (aVal.GetUnit()) {
- case eCSSUnit_FontWeight:
- return aVal.GetFontWeight();
- case eCSSUnit_Enumerated:
- return FontWeight(aVal.GetIntValue());
- case eCSSUnit_Normal:
- case eCSSUnit_Null:
- return FontWeight::Normal();
- default:
- MOZ_ASSERT_UNREACHABLE("Unknown font-weight descriptor value");
- return FontWeight::Normal();
- }
-}
-
static WeightRange
-GetWeightRangeForDescriptor(const nsCSSValue& aVal,
+GetWeightRangeForDescriptor(const Maybe<StyleComputedFontWeightRange>& aVal,
gfxFontEntry::RangeFlags& aRangeFlags)
{
- if (aVal.GetUnit() == eCSSUnit_Null) {
+ if (!aVal) {
aRangeFlags |= gfxFontEntry::RangeFlags::eAutoWeight;
return WeightRange(FontWeight::Normal());
}
- if (aVal.GetUnit() == eCSSUnit_Pair) {
- return WeightRange(GetWeightForDescriptor(aVal.GetPairValue().mXValue),
- GetWeightForDescriptor(aVal.GetPairValue().mYValue));
- }
- return WeightRange(GetWeightForDescriptor(aVal));
-}
-
-static FontSlantStyle
-GetStyleForDescriptor(const nsCSSValue& aVal)
-{
- switch (aVal.GetUnit()) {
- case eCSSUnit_Normal:
- case eCSSUnit_Null:
- return FontSlantStyle::Normal();
- case eCSSUnit_Enumerated:
- MOZ_ASSERT(aVal.GetIntValue() == NS_FONT_STYLE_ITALIC);
- return FontSlantStyle::Italic();
- case eCSSUnit_FontSlantStyle:
- return aVal.GetFontSlantStyle();
- default:
- MOZ_ASSERT_UNREACHABLE("Unknown font-style descriptor value");
- return FontSlantStyle::Normal();
- }
+ return WeightRange(FontWeight(aVal->_0), FontWeight(aVal->_1));
}
static SlantStyleRange
-GetStyleRangeForDescriptor(const nsCSSValue& aVal,
+GetStyleRangeForDescriptor(const Maybe<StyleComputedFontStyleDescriptor>& aVal,
gfxFontEntry::RangeFlags& aRangeFlags)
{
- if (aVal.GetUnit() == eCSSUnit_Null) {
+ if (!aVal) {
aRangeFlags |= gfxFontEntry::RangeFlags::eAutoSlantStyle;
return SlantStyleRange(FontSlantStyle::Normal());
}
- if (aVal.GetUnit() == eCSSUnit_Pair) {
- return SlantStyleRange(GetStyleForDescriptor(aVal.GetPairValue().mXValue),
- GetStyleForDescriptor(aVal.GetPairValue().mYValue));
+ auto& val = *aVal;
+ switch (val.tag) {
+ case StyleComputedFontStyleDescriptor::Tag::Normal:
+ return SlantStyleRange(FontSlantStyle::Normal());
+ case StyleComputedFontStyleDescriptor::Tag::Italic:
+ return SlantStyleRange(FontSlantStyle::Italic());
+ case StyleComputedFontStyleDescriptor::Tag::Oblique:
+ return SlantStyleRange(FontSlantStyle::Oblique(val.oblique._0),
+ FontSlantStyle::Oblique(val.oblique._1));
}
- return SlantStyleRange(GetStyleForDescriptor(aVal));
-}
-
-static FontStretch
-GetStretchForDescriptor(const nsCSSValue& aVal)
-{
- switch (aVal.GetUnit()) {
- case eCSSUnit_Null:
- return FontStretch::Normal();
- case eCSSUnit_FontStretch:
- return aVal.GetFontStretch();
- default:
- MOZ_ASSERT_UNREACHABLE("Unknown font-style descriptor value");
- return FontStretch::Normal();
- }
+ MOZ_ASSERT_UNREACHABLE("How?");
+ return SlantStyleRange(FontSlantStyle::Normal());
}
static StretchRange
-GetStretchRangeForDescriptor(const nsCSSValue& aVal,
+GetStretchRangeForDescriptor(const Maybe<StyleComputedFontStretchRange>& aVal,
gfxFontEntry::RangeFlags& aRangeFlags)
{
- if (aVal.GetUnit() == eCSSUnit_Null) {
+ if (!aVal) {
aRangeFlags |= gfxFontEntry::RangeFlags::eAutoStretch;
return StretchRange(FontStretch::Normal());
}
- if (aVal.GetUnit() == eCSSUnit_Pair) {
- return StretchRange(GetStretchForDescriptor(aVal.GetPairValue().mXValue),
- GetStretchForDescriptor(aVal.GetPairValue().mYValue));
- }
- return StretchRange(GetStretchForDescriptor(aVal));
+ return StretchRange(FontStretch::FromStyle(aVal->_0),
+ FontStretch::FromStyle(aVal->_1));
}
+// TODO(emilio): Should this take an nsAtom* aFamilyName instead?
+//
+// All callers have one handy.
/* static */ already_AddRefed<gfxUserFontEntry>
FontFaceSet::FindOrCreateUserFontEntryFromFontFace(const nsACString& aFamilyName,
FontFace* aFontFace,
SheetType aSheetType)
{
FontFaceSet* set = aFontFace->GetPrimaryFontFaceSet();
- nsCSSValue val;
- nsCSSUnit unit;
-
uint32_t languageOverride = NO_FONT_LANGUAGE_OVERRIDE;
- uint8_t fontDisplay = NS_FONT_DISPLAY_AUTO;
+ StyleFontDisplay fontDisplay = StyleFontDisplay::Auto;
gfxFontEntry::RangeFlags rangeFlags = gfxFontEntry::RangeFlags::eNoFlags;
// set up weight
- aFontFace->GetDesc(eCSSFontDesc_Weight, val);
- WeightRange weight = GetWeightRangeForDescriptor(val, rangeFlags);
+ WeightRange weight =
+ GetWeightRangeForDescriptor(aFontFace->GetFontWeight(), rangeFlags);
// set up stretch
- aFontFace->GetDesc(eCSSFontDesc_Stretch, val);
- StretchRange stretch = GetStretchRangeForDescriptor(val, rangeFlags);
+ StretchRange stretch =
+ GetStretchRangeForDescriptor(aFontFace->GetFontStretch(), rangeFlags);
// set up font style
- aFontFace->GetDesc(eCSSFontDesc_Style, val);
- SlantStyleRange italicStyle = GetStyleRangeForDescriptor(val, rangeFlags);
+ SlantStyleRange italicStyle =
+ GetStyleRangeForDescriptor(aFontFace->GetFontStyle(), rangeFlags);
// set up font display
- aFontFace->GetDesc(eCSSFontDesc_Display, val);
- unit = val.GetUnit();
- if (unit == eCSSUnit_Enumerated) {
- fontDisplay = val.GetIntValue();
- } else {
- NS_ASSERTION(unit == eCSSUnit_Null,
- "@font-face style has unexpected unit");
+ if (Maybe<StyleFontDisplay> display = aFontFace->GetFontDisplay()) {
+ fontDisplay = *display;
}
// set up font features
nsTArray<gfxFontFeature> featureSettings;
- aFontFace->GetDesc(eCSSFontDesc_FontFeatureSettings, val);
- unit = val.GetUnit();
- if (unit == eCSSUnit_Normal) {
- // empty list of features
- } else if (unit == eCSSUnit_PairList || unit == eCSSUnit_PairListDep) {
- nsLayoutUtils::ComputeFontFeatures(val.GetPairListValue(), featureSettings);
- } else {
- NS_ASSERTION(unit == eCSSUnit_Null,
- "@font-face font-feature-settings has unexpected unit");
- }
+ aFontFace->GetFontFeatureSettings(featureSettings);
// set up font variations
nsTArray<gfxFontVariation> variationSettings;
- aFontFace->GetDesc(eCSSFontDesc_FontVariationSettings, val);
- unit = val.GetUnit();
- if (unit == eCSSUnit_Normal) {
- // empty list of variations
- } else if (unit == eCSSUnit_PairList || unit == eCSSUnit_PairListDep) {
- nsLayoutUtils::ComputeFontVariations(val.GetPairListValue(), variationSettings);
- } else {
- NS_ASSERTION(unit == eCSSUnit_Null,
- "@font-face font-variation-settings has unexpected unit");
- }
+ aFontFace->GetFontVariationSettings(variationSettings);
// set up font language override
- aFontFace->GetDesc(eCSSFontDesc_FontLanguageOverride, val);
- unit = val.GetUnit();
- if (unit == eCSSUnit_Normal) {
- // empty feature string
- } else if (unit == eCSSUnit_String) {
- nsString stringValue;
- val.GetStringValue(stringValue);
- languageOverride = nsLayoutUtils::ParseFontLanguageOverride(stringValue);
- } else {
- NS_ASSERTION(unit == eCSSUnit_Null,
- "@font-face font-language-override has unexpected unit");
+ if (Maybe<StyleFontLanguageOverride> descriptor = aFontFace->GetFontLanguageOverride()) {
+ languageOverride = descriptor->_0;
}
// set up unicode-range
gfxCharacterMap* unicodeRanges = aFontFace->GetUnicodeRangeAsCharacterMap();
// set up src array
nsTArray<gfxFontFaceSrc> srcArray;
@@ -1177,70 +1076,63 @@ FontFaceSet::FindOrCreateUserFontEntryFr
gfxFontFaceSrc* face = srcArray.AppendElement();
if (!face)
return nullptr;
face->mSourceType = gfxFontFaceSrc::eSourceType_Buffer;
face->mBuffer = aFontFace->CreateBufferSource();
face->mReferrerPolicy = mozilla::net::RP_Unset;
} else {
- aFontFace->GetDesc(eCSSFontDesc_Src, val);
- unit = val.GetUnit();
- if (unit == eCSSUnit_Array) {
- // Hold a strong reference because content of val is going away