зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-c to autoland. a=merge
This commit is contained in:
Коммит
44c10c107a
|
@ -5,505 +5,19 @@
|
|||
|
||||
#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() {}
|
||||
|
||||
nsresult
|
||||
AccessibleWrap::HandleAccEvent(AccEvent* aEvent)
|
||||
AccessibleWrap::~AccessibleWrap()
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -6,74 +6,19 @@
|
|||
#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:
|
||||
public: // construction, destruction
|
||||
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
|
||||
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
/* -*- 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;
|
||||
}
|
|
@ -11,28 +11,7 @@
|
|||
namespace mozilla {
|
||||
namespace a11y {
|
||||
|
||||
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;
|
||||
};
|
||||
typedef DocAccessible DocAccessibleWrap;
|
||||
|
||||
} // namespace a11y
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -5,11 +5,6 @@
|
|||
* 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;
|
||||
|
@ -25,117 +20,43 @@ a11y::PlatformShutdown()
|
|||
}
|
||||
|
||||
void
|
||||
a11y::ProxyCreated(ProxyAccessible* aProxy, uint32_t aInterfaces)
|
||||
a11y::ProxyCreated(ProxyAccessible*, uint32_t)
|
||||
{
|
||||
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)
|
||||
a11y::ProxyDestroyed(ProxyAccessible*)
|
||||
{
|
||||
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::ProxyEvent(ProxyAccessible* aTarget, uint32_t aEventType)
|
||||
a11y::ProxyEvent(ProxyAccessible*, uint32_t)
|
||||
{
|
||||
SessionAccessibility* sessionAcc =
|
||||
SessionAccessibility::GetInstanceFor(aTarget);
|
||||
if (!sessionAcc) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (aEventType) {
|
||||
case nsIAccessibleEvent::EVENT_FOCUS:
|
||||
sessionAcc->SendFocusEvent(WrapperFor(aTarget));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
a11y::ProxyStateChangeEvent(ProxyAccessible* aTarget,
|
||||
uint64_t aState,
|
||||
bool aEnabled)
|
||||
a11y::ProxyStateChangeEvent(ProxyAccessible*, uint64_t, bool)
|
||||
{
|
||||
SessionAccessibility* sessionAcc =
|
||||
SessionAccessibility::GetInstanceFor(aTarget);
|
||||
|
||||
if (!sessionAcc) {
|
||||
return;
|
||||
}
|
||||
|
||||
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* aTarget,
|
||||
const nsString& aStr,
|
||||
int32_t aStart,
|
||||
uint32_t aLen,
|
||||
bool aIsInsert,
|
||||
bool aFromUser)
|
||||
a11y::ProxyTextChangeEvent(ProxyAccessible*,
|
||||
const nsString&,
|
||||
int32_t,
|
||||
uint32_t,
|
||||
bool,
|
||||
bool)
|
||||
{
|
||||
SessionAccessibility* sessionAcc =
|
||||
SessionAccessibility::GetInstanceFor(aTarget);
|
||||
|
||||
if (sessionAcc) {
|
||||
sessionAcc->SendTextChangedEvent(
|
||||
WrapperFor(aTarget), aStr, aStart, aLen, aIsInsert, aFromUser);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
a11y::ProxyShowHideEvent(ProxyAccessible* aTarget,
|
||||
ProxyAccessible* aParent,
|
||||
bool aInsert,
|
||||
bool aFromUser)
|
||||
a11y::ProxyShowHideEvent(ProxyAccessible*, ProxyAccessible*, bool, bool)
|
||||
{
|
||||
SessionAccessibility* sessionAcc =
|
||||
SessionAccessibility::GetInstanceFor(aTarget);
|
||||
if (sessionAcc) {
|
||||
sessionAcc->SendWindowContentChangedEvent(WrapperFor(aParent));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -144,57 +65,25 @@ a11y::ProxySelectionEvent(ProxyAccessible*, ProxyAccessible*, uint32_t)
|
|||
}
|
||||
|
||||
void
|
||||
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)
|
||||
a11y::ProxyVirtualCursorChangeEvent(ProxyAccessible*,
|
||||
ProxyAccessible*,
|
||||
int32_t,
|
||||
int32_t,
|
||||
ProxyAccessible*,
|
||||
int32_t,
|
||||
int32_t,
|
||||
int16_t,
|
||||
int16_t,
|
||||
bool)
|
||||
{
|
||||
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* aTarget,
|
||||
uint32_t aEventType,
|
||||
uint32_t aScrollX,
|
||||
uint32_t aScrollY,
|
||||
uint32_t aMaxScrollX,
|
||||
uint32_t aMaxScrollY)
|
||||
a11y::ProxyScrollingEvent(ProxyAccessible*,
|
||||
uint32_t,
|
||||
uint32_t,
|
||||
uint32_t,
|
||||
uint32_t,
|
||||
uint32_t)
|
||||
{
|
||||
if (aEventType == nsIAccessibleEvent::EVENT_SCROLLING) {
|
||||
SessionAccessibility* sessionAcc =
|
||||
SessionAccessibility::GetInstanceFor(aTarget);
|
||||
|
||||
if (sessionAcc) {
|
||||
sessionAcc->SendScrollingEvent(
|
||||
WrapperFor(aTarget), aScrollX, aScrollY, aMaxScrollX, aMaxScrollY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,147 +0,0 @@
|
|||
/* -*- 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);
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
/* -*- 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
|
|
@ -1,92 +0,0 @@
|
|||
/* -*- 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;
|
||||
}
|
|
@ -11,25 +11,7 @@
|
|||
namespace mozilla {
|
||||
namespace a11y {
|
||||
|
||||
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);
|
||||
};
|
||||
typedef RootAccessible RootAccessibleWrap;
|
||||
|
||||
} // namespace a11y
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -6,12 +6,6 @@
|
|||
#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>
|
||||
|
@ -29,20 +23,6 @@ const char nsWindow::NativePtr<mozilla::a11y::SessionAccessibility>::sName[] =
|
|||
|
||||
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)
|
||||
|
@ -61,259 +41,3 @@ SessionAccessibility::SetAttached(bool aAttached,
|
|||
}));
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
|
|
@ -7,41 +7,11 @@
|
|||
#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>
|
||||
{
|
||||
|
@ -68,49 +38,16 @@ public:
|
|||
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();
|
||||
|
||||
// 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)
|
||||
NS_INLINE_DECL_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;
|
||||
|
|
|
@ -11,10 +11,7 @@ EXPORTS.mozilla.a11y += ['AccessibleWrap.h',
|
|||
|
||||
SOURCES += [
|
||||
'AccessibleWrap.cpp',
|
||||
'DocAccessibleWrap.cpp',
|
||||
'Platform.cpp',
|
||||
'ProxyAccessibleWrap.cpp',
|
||||
'RootAccessibleWrap.cpp',
|
||||
'SessionAccessibility.cpp',
|
||||
]
|
||||
|
||||
|
|
|
@ -555,7 +555,7 @@ public:
|
|||
/**
|
||||
* Scroll the accessible into view.
|
||||
*/
|
||||
virtual void ScrollTo(uint32_t aHow) const;
|
||||
void ScrollTo(uint32_t aHow) const;
|
||||
|
||||
/**
|
||||
* Scroll the accessible to the given point.
|
||||
|
|
|
@ -168,14 +168,13 @@ protected:
|
|||
, 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), mIsSelection(false)
|
||||
mIsHyperLink(false), mIsHyperText(false)
|
||||
{}
|
||||
|
||||
protected:
|
||||
|
@ -205,7 +204,6 @@ public:
|
|||
const bool mHasValue: 1;
|
||||
const bool mIsHyperLink: 1;
|
||||
const bool mIsHyperText: 1;
|
||||
const bool mIsSelection: 1;
|
||||
};
|
||||
|
||||
extern template class ProxyAccessibleBase<ProxyAccessible>;
|
||||
|
|
|
@ -19,7 +19,6 @@ 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",
|
||||
|
@ -32,6 +31,7 @@ const GECKOVIEW_MESSAGE = {
|
|||
};
|
||||
|
||||
const ACCESSFU_MESSAGE = {
|
||||
PRESENT: "AccessFu:Present",
|
||||
DOSCROLL: "AccessFu:DoScroll",
|
||||
};
|
||||
|
||||
|
@ -57,6 +57,11 @@ var AccessFu = {
|
|||
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");
|
||||
|
@ -87,6 +92,8 @@ var AccessFu = {
|
|||
this._detachWindow(win);
|
||||
}
|
||||
|
||||
delete this._notifyOutputPref;
|
||||
|
||||
if (this.doneCallback) {
|
||||
this.doneCallback();
|
||||
delete this.doneCallback;
|
||||
|
@ -101,6 +108,9 @@ var AccessFu = {
|
|||
});
|
||||
|
||||
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;
|
||||
|
@ -145,6 +155,38 @@ var AccessFu = {
|
|||
}
|
||||
},
|
||||
|
||||
_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:
|
||||
|
@ -177,8 +219,11 @@ var AccessFu = {
|
|||
case GECKOVIEW_MESSAGE.SCROLL_BACKWARD:
|
||||
this.Input.androidScroll("backward");
|
||||
break;
|
||||
case GECKOVIEW_MESSAGE.CURSOR_TO_FOCUSED:
|
||||
this.autoMove({ moveToFocused: true });
|
||||
case GECKOVIEW_MESSAGE.VIEW_FOCUSED:
|
||||
this._focused = data.gainFocus;
|
||||
if (this._focused) {
|
||||
this.autoMove({ forcePresent: true, noOpIfOnScreen: true });
|
||||
}
|
||||
break;
|
||||
case GECKOVIEW_MESSAGE.BY_GRANULARITY:
|
||||
this.Input.moveByGranularity(data);
|
||||
|
@ -236,6 +281,10 @@ var AccessFu = {
|
|||
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,
|
||||
|
||||
|
|
|
@ -14,6 +14,8 @@ 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"];
|
||||
|
||||
|
@ -153,6 +155,9 @@ this.ContentControl.prototype = {
|
|||
// 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));
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -223,6 +228,13 @@ this.ContentControl.prototype = {
|
|||
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(
|
||||
|
@ -230,11 +242,15 @@ this.ContentControl.prototype = {
|
|||
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;
|
||||
}
|
||||
|
||||
|
@ -383,6 +399,15 @@ this.ContentControl.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
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) {
|
||||
|
@ -431,12 +456,13 @@ this.ContentControl.prototype = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Move cursor.
|
||||
* Move cursor and/or present its location.
|
||||
* 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',
|
||||
|
@ -449,9 +475,19 @@ this.ContentControl.prototype = {
|
|||
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;
|
||||
}
|
||||
|
||||
|
@ -473,14 +509,19 @@ this.ContentControl.prototype = {
|
|||
moved = vc[moveMethod](rule, true);
|
||||
}
|
||||
|
||||
this.sendToChild(vc, {
|
||||
let sentToChild = 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) {
|
||||
|
|
|
@ -10,12 +10,18 @@ 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"];
|
||||
|
||||
|
@ -27,6 +33,9 @@ function EventManager(aContentScope) {
|
|||
this.contentScope);
|
||||
this.sendMsgFunc = this.contentScope.sendAsyncMessage.bind(
|
||||
this.contentScope);
|
||||
this.webProgress = this.contentScope.docShell.
|
||||
QueryInterface(Ci.nsIInterfaceRequestor).
|
||||
getInterface(Ci.nsIWebProgress);
|
||||
}
|
||||
|
||||
this.EventManager.prototype = {
|
||||
|
@ -39,8 +48,16 @@ this.EventManager.prototype = {
|
|||
|
||||
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");
|
||||
}
|
||||
|
@ -56,6 +73,10 @@ this.EventManager.prototype = {
|
|||
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 {
|
||||
|
@ -67,6 +88,37 @@ this.EventManager.prototype = {
|
|||
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),
|
||||
|
@ -104,11 +156,37 @@ this.EventManager.prototype = {
|
|||
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:
|
||||
{
|
||||
// XXX: Port to Android
|
||||
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));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Events.SCROLLING_START:
|
||||
|
@ -116,25 +194,310 @@ this.EventManager.prototype = {
|
|||
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:
|
||||
{
|
||||
// XXX: Port to Android
|
||||
this._handleShow(aEvent);
|
||||
break;
|
||||
}
|
||||
case Events.HIDE:
|
||||
{
|
||||
// XXX: Port to Android
|
||||
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);
|
||||
}
|
||||
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:
|
||||
{
|
||||
// XXX: Port to Android
|
||||
break;
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
QueryInterface: ChromeUtils.generateQI([Ci.nsISupportsWeakReference, Ci.nsIObserver])
|
||||
_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 });
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_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])
|
||||
};
|
||||
|
||||
const AccessibilityEventObserver = {
|
||||
|
|
|
@ -0,0 +1,824 @@
|
|||
/* 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;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,332 @@
|
|||
/* 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();
|
|
@ -9,6 +9,8 @@ EXTRA_JS_MODULES.accessibility += [
|
|||
'Constants.jsm',
|
||||
'ContentControl.jsm',
|
||||
'EventManager.jsm',
|
||||
'OutputGenerator.jsm',
|
||||
'Presentation.jsm',
|
||||
'Traversal.jsm',
|
||||
'Utils.jsm'
|
||||
]
|
||||
|
|
|
@ -10,28 +10,19 @@ support-files =
|
|||
|
||||
[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 = true
|
||||
skip-if = (verify && !debug && (os == 'linux'))
|
||||
[test_text_editing.html]
|
||||
skip-if = true
|
||||
skip-if = (verify && !debug && (os == 'linux'))
|
||||
[test_text_navigation_focus.html]
|
||||
skip-if = true
|
||||
skip-if = (verify && !debug && (os == 'linux'))
|
||||
[test_text_navigation.html]
|
||||
skip-if = true
|
||||
[test_traversal.html]
|
||||
[test_traversal_helper.html]
|
||||
|
|
|
@ -4,40 +4,36 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const { ADBScanner } = require("devtools/shared/adb/adb-scanner");
|
||||
loader.lazyRequireGetter(this, "adbAddon", "devtools/shared/adb/adb-addon", true);
|
||||
loader.lazyRequireGetter(this, "ADB_ADDON_STATES", "devtools/shared/adb/adb-addon", true);
|
||||
loader.lazyGetter(this, "adbScanner", () => {
|
||||
const { AddonAwareADBScanner } = require("devtools/shared/adb/addon-aware-adb-scanner");
|
||||
return new AddonAwareADBScanner();
|
||||
});
|
||||
|
||||
/**
|
||||
* This module provides a collection of helper methods to detect USB runtimes whom Firefox
|
||||
* is running on.
|
||||
*/
|
||||
function addUSBRuntimesObserver(listener) {
|
||||
ADBScanner.on("runtime-list-updated", listener);
|
||||
adbScanner.on("runtime-list-updated", listener);
|
||||
}
|
||||
exports.addUSBRuntimesObserver = addUSBRuntimesObserver;
|
||||
|
||||
function disableUSBRuntimes() {
|
||||
ADBScanner.disable();
|
||||
adbScanner.disable();
|
||||
}
|
||||
exports.disableUSBRuntimes = disableUSBRuntimes;
|
||||
|
||||
async function enableUSBRuntimes() {
|
||||
if (adbAddon.status !== ADB_ADDON_STATES.INSTALLED) {
|
||||
console.error("ADB extension is not installed");
|
||||
return;
|
||||
}
|
||||
|
||||
ADBScanner.enable();
|
||||
adbScanner.enable();
|
||||
}
|
||||
exports.enableUSBRuntimes = enableUSBRuntimes;
|
||||
|
||||
function getUSBRuntimes() {
|
||||
return ADBScanner.listRuntimes();
|
||||
return adbScanner.listRuntimes();
|
||||
}
|
||||
exports.getUSBRuntimes = getUSBRuntimes;
|
||||
|
||||
function removeUSBRuntimesObserver(listener) {
|
||||
ADBScanner.off("runtime-list-updated", listener);
|
||||
adbScanner.off("runtime-list-updated", listener);
|
||||
}
|
||||
exports.removeUSBRuntimesObserver = removeUSBRuntimesObserver;
|
||||
|
|
|
@ -8,11 +8,8 @@ const Services = require("Services");
|
|||
const Strings = Services.strings.createBundle("chrome://devtools/locale/webide.properties");
|
||||
|
||||
const {gDevTools} = require("devtools/client/framework/devtools");
|
||||
const {ADBScanner} = require("devtools/shared/adb/adb-scanner");
|
||||
const {RuntimeScanners} = require("devtools/client/webide/modules/runtimes");
|
||||
|
||||
loader.lazyRequireGetter(this, "adbAddon", "devtools/shared/adb/adb-addon", true);
|
||||
loader.lazyRequireGetter(this, "ADB_ADDON_STATES", "devtools/shared/adb/adb-addon", true);
|
||||
|
||||
window.addEventListener("load", function() {
|
||||
document.querySelector("#aboutaddons").onclick = function() {
|
||||
|
@ -34,11 +31,6 @@ function BuildUI() {
|
|||
progress.removeAttribute("value");
|
||||
li.setAttribute("status", adbAddon.status);
|
||||
status.textContent = Strings.GetStringFromName("addons_status_" + adbAddon.status);
|
||||
if (adbAddon.status == ADB_ADDON_STATES.INSTALLED) {
|
||||
RuntimeScanners.add(ADBScanner);
|
||||
} else if (adbAddon.status == ADB_ADDON_STATES.UNINSTALLED) {
|
||||
RuntimeScanners.remove(ADBScanner);
|
||||
}
|
||||
}
|
||||
|
||||
function onAddonFailure(arg) {
|
||||
|
|
|
@ -10,6 +10,12 @@ const discovery = require("devtools/shared/discovery/discovery");
|
|||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const {RuntimeTypes} = require("devtools/client/webide/modules/runtime-types");
|
||||
const promise = require("promise");
|
||||
|
||||
loader.lazyGetter(this, "adbScanner", () => {
|
||||
const { AddonAwareADBScanner } = require("devtools/shared/adb/addon-aware-adb-scanner");
|
||||
return new AddonAwareADBScanner();
|
||||
});
|
||||
|
||||
loader.lazyRequireGetter(this, "AuthenticationResult",
|
||||
"devtools/shared/security/auth", true);
|
||||
loader.lazyRequireGetter(this, "DevToolsUtils",
|
||||
|
@ -192,6 +198,10 @@ exports.RuntimeScanners = RuntimeScanners;
|
|||
|
||||
/* SCANNERS */
|
||||
|
||||
// The adb-scanner will automatically start and stop when the ADB extension is installed
|
||||
// and uninstalled, so the scanner itself can always be used.
|
||||
RuntimeScanners.add(adbScanner);
|
||||
|
||||
var WiFiScanner = {
|
||||
|
||||
_runtimes: [],
|
||||
|
|
|
@ -12,17 +12,20 @@ const { RuntimeTypes } =
|
|||
const { ADB } = require("devtools/shared/adb/adb");
|
||||
loader.lazyRequireGetter(this, "Device", "devtools/shared/adb/adb-device");
|
||||
|
||||
const ADBScanner = {
|
||||
class ADBScanner extends EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
this._runtimes = [];
|
||||
|
||||
_runtimes: [],
|
||||
|
||||
enable() {
|
||||
this._onDeviceConnected = this._onDeviceConnected.bind(this);
|
||||
this._onDeviceDisconnected = this._onDeviceDisconnected.bind(this);
|
||||
this._updateRuntimes = this._updateRuntimes.bind(this);
|
||||
}
|
||||
|
||||
enable() {
|
||||
EventEmitter.on(ADB, "device-connected", this._onDeviceConnected);
|
||||
EventEmitter.on(ADB, "device-disconnected", this._onDeviceDisconnected);
|
||||
|
||||
this._updateRuntimes = this._updateRuntimes.bind(this);
|
||||
Devices.on("register", this._updateRuntimes);
|
||||
Devices.on("unregister", this._updateRuntimes);
|
||||
Devices.on("addon-status-updated", this._updateRuntimes);
|
||||
|
@ -31,7 +34,7 @@ const ADBScanner = {
|
|||
ADB.trackDevices();
|
||||
});
|
||||
this._updateRuntimes();
|
||||
},
|
||||
}
|
||||
|
||||
disable() {
|
||||
EventEmitter.off(ADB, "device-connected", this._onDeviceConnected);
|
||||
|
@ -39,20 +42,20 @@ const ADBScanner = {
|
|||
Devices.off("register", this._updateRuntimes);
|
||||
Devices.off("unregister", this._updateRuntimes);
|
||||
Devices.off("addon-status-updated", this._updateRuntimes);
|
||||
},
|
||||
}
|
||||
|
||||
_emitUpdated() {
|
||||
this.emit("runtime-list-updated");
|
||||
},
|
||||
}
|
||||
|
||||
_onDeviceConnected(deviceId) {
|
||||
const device = new Device(deviceId);
|
||||
Devices.register(deviceId, device);
|
||||
},
|
||||
}
|
||||
|
||||
_onDeviceDisconnected(deviceId) {
|
||||
Devices.unregister(deviceId);
|
||||
},
|
||||
}
|
||||
|
||||
_updateRuntimes() {
|
||||
if (this._updatingPromise) {
|
||||
|
@ -72,26 +75,23 @@ const ADBScanner = {
|
|||
this._updatingPromise = null;
|
||||
});
|
||||
return this._updatingPromise;
|
||||
},
|
||||
}
|
||||
|
||||
_detectRuntimes: async function(device) {
|
||||
async _detectRuntimes(device) {
|
||||
const model = await device.getModel();
|
||||
const detectedRuntimes =
|
||||
await FirefoxOnAndroidRuntime.detect(device, model);
|
||||
this._runtimes.push(...detectedRuntimes);
|
||||
},
|
||||
}
|
||||
|
||||
scan() {
|
||||
return this._updateRuntimes();
|
||||
},
|
||||
}
|
||||
|
||||
listRuntimes() {
|
||||
return this._runtimes;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
EventEmitter.decorate(ADBScanner);
|
||||
}
|
||||
|
||||
function Runtime(device, model, socketPath) {
|
||||
this.device = device;
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
|
||||
loader.lazyRequireGetter(this, "adbAddon", "devtools/shared/adb/adb-addon", true);
|
||||
loader.lazyRequireGetter(this, "ADBScanner", "devtools/shared/adb/adb-scanner", true);
|
||||
|
||||
/**
|
||||
* The AddonAwareADBScanner decorates an instance of ADBScanner. It will wait until the
|
||||
* ADB addon is installed to enable() the real scanner, and will automatically disable
|
||||
* it if the addon is uninstalled.
|
||||
*
|
||||
* It implements the following public API of ADBScanner:
|
||||
* - enable
|
||||
* - disable
|
||||
* - scan
|
||||
* - listRuntimes
|
||||
* - event "runtime-list-updated"
|
||||
*/
|
||||
class AddonAwareADBScanner extends EventEmitter {
|
||||
/**
|
||||
* Parameters are provided only to allow tests to replace actual implementations with
|
||||
* mocks.
|
||||
*
|
||||
* @param {ADBScanner} scanner
|
||||
* Only provided in tests for mocks
|
||||
* @param {ADBAddon} addon
|
||||
* Only provided in tests for mocks
|
||||
*/
|
||||
constructor(scanner = new ADBScanner(), addon = adbAddon) {
|
||||
super();
|
||||
|
||||
this._onScannerListUpdated = this._onScannerListUpdated.bind(this);
|
||||
this._onAddonUpdate = this._onAddonUpdate.bind(this);
|
||||
|
||||
this._scanner = scanner;
|
||||
this._scanner.on("runtime-list-updated", this._onScannerListUpdated);
|
||||
|
||||
this._addon = addon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only forward the enable() call if the addon is installed, because ADBScanner::enable
|
||||
* only works if the addon is installed.
|
||||
*/
|
||||
enable() {
|
||||
if (this._addon.status === "installed") {
|
||||
this._scanner.enable();
|
||||
}
|
||||
|
||||
// Remove any previous listener, to make sure we only add one listener if enable() is
|
||||
// called several times.
|
||||
this._addon.off("update", this._onAddonUpdate);
|
||||
|
||||
this._addon.on("update", this._onAddonUpdate);
|
||||
}
|
||||
|
||||
disable() {
|
||||
this._scanner.disable();
|
||||
|
||||
this._addon.off("update", this._onAddonUpdate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan for USB devices.
|
||||
*
|
||||
* @return {Promise} Promise that will resolve when the scan is completed.
|
||||
*/
|
||||
scan() {
|
||||
return this._scanner.scan();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of currently detected runtimes.
|
||||
*
|
||||
* @return {Array} Array of currently detected runtimes.
|
||||
*/
|
||||
listRuntimes() {
|
||||
return this._scanner.listRuntimes();
|
||||
}
|
||||
|
||||
_onAddonUpdate() {
|
||||
if (this._addon.status === "installed") {
|
||||
this._scanner.enable();
|
||||
} else {
|
||||
this._scanner.disable();
|
||||
}
|
||||
}
|
||||
|
||||
_onScannerListUpdated() {
|
||||
this.emit("runtime-list-updated");
|
||||
}
|
||||
}
|
||||
exports.AddonAwareADBScanner = AddonAwareADBScanner;
|
|
@ -11,6 +11,7 @@ DevToolsModules(
|
|||
'adb-scanner.js',
|
||||
'adb-socket.js',
|
||||
'adb.js',
|
||||
'addon-aware-adb-scanner.js',
|
||||
)
|
||||
|
||||
with Files('**'):
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
|
|
|
@ -0,0 +1,221 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const { AddonAwareADBScanner } = require("devtools/shared/adb/addon-aware-adb-scanner");
|
||||
|
||||
/**
|
||||
* For the scanner mock, we create an object with spies for each of the public methods
|
||||
* used by the AddonAwareADBScanner, and the ability to emit events.
|
||||
*/
|
||||
function prepareMockScanner() {
|
||||
const mockScanner = {
|
||||
enable: sinon.spy(),
|
||||
disable: sinon.spy(),
|
||||
scan: sinon.spy(),
|
||||
listRuntimes: sinon.spy()
|
||||
};
|
||||
EventEmitter.decorate(mockScanner);
|
||||
return mockScanner;
|
||||
}
|
||||
|
||||
/**
|
||||
* For the addon mock, we simply need an object that is able to emit events and has a
|
||||
* status.
|
||||
*/
|
||||
function prepareMockAddon() {
|
||||
const mockAddon = {
|
||||
status: "unknown",
|
||||
};
|
||||
EventEmitter.decorate(mockAddon);
|
||||
return mockAddon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare all mocks needed for the scanner tests.
|
||||
*/
|
||||
function prepareMocks() {
|
||||
const mockScanner = prepareMockScanner();
|
||||
const mockAddon = prepareMockAddon();
|
||||
const addonAwareAdbScanner = new AddonAwareADBScanner(mockScanner, mockAddon);
|
||||
return { addonAwareAdbScanner, mockAddon, mockScanner };
|
||||
}
|
||||
|
||||
/**
|
||||
* This test covers basic usage of enable() on the AddonAwareADBScanner, and checks the
|
||||
* different behaviors based on the addon status.
|
||||
*/
|
||||
add_task(async function testCallingEnable() {
|
||||
const { mockScanner, mockAddon, addonAwareAdbScanner } = prepareMocks();
|
||||
|
||||
// Check that enable() is not called if the addon is uninstalled
|
||||
mockAddon.status = "uninstalled";
|
||||
addonAwareAdbScanner.enable();
|
||||
ok(mockScanner.enable.notCalled, "enable() was not called");
|
||||
mockScanner.enable.reset();
|
||||
|
||||
// Check that enable() is called if the addon is installed
|
||||
mockAddon.status = "installed";
|
||||
addonAwareAdbScanner.enable();
|
||||
ok(mockScanner.enable.called, "enable() was called");
|
||||
mockScanner.enable.reset();
|
||||
});
|
||||
|
||||
/**
|
||||
* This test checks that enable()/disable() methods from the internal ADBScanner are
|
||||
* called when the addon is installed or uninstalled.
|
||||
*/
|
||||
add_task(async function testUpdatingAddonEnablesDisablesScanner() {
|
||||
const { mockScanner, mockAddon, addonAwareAdbScanner } = prepareMocks();
|
||||
|
||||
// Enable the addon aware scanner
|
||||
addonAwareAdbScanner.enable();
|
||||
ok(mockScanner.enable.notCalled, "enable() was not called initially");
|
||||
|
||||
// Check that enable() is called automatically when the addon is installed
|
||||
mockAddon.status = "installed";
|
||||
mockAddon.emit("update");
|
||||
ok(mockScanner.enable.called, "enable() was called when installing the addon");
|
||||
ok(mockScanner.disable.notCalled, "disable() was not called when installing the addon");
|
||||
mockScanner.enable.reset();
|
||||
mockScanner.disable.reset();
|
||||
|
||||
// Check that disabled() is called automatically when the addon is uninstalled
|
||||
mockAddon.status = "uninstalled";
|
||||
mockAddon.emit("update");
|
||||
ok(mockScanner.enable.notCalled, "enable() was not called when uninstalling the addon");
|
||||
ok(mockScanner.disable.called, "disable() was called when uninstalling the addon");
|
||||
mockScanner.enable.reset();
|
||||
mockScanner.disable.reset();
|
||||
|
||||
// Check that enable() is called again when the addon is reinstalled
|
||||
mockAddon.status = "installed";
|
||||
mockAddon.emit("update");
|
||||
ok(mockScanner.enable.called, "enable() was called when installing the addon");
|
||||
ok(mockScanner.disable.notCalled, "disable() was not called when installing the addon");
|
||||
mockScanner.enable.reset();
|
||||
mockScanner.disable.reset();
|
||||
});
|
||||
|
||||
/**
|
||||
* This test checks that disable() is forwarded from the AddonAwareADBScanner to the real
|
||||
* scanner even if the addon is uninstalled. We might miss the addon uninstall
|
||||
* notification, so it is safer to always proceed with disabling.
|
||||
*/
|
||||
add_task(async function testScannerIsDisabledWhenMissingAddonUpdate() {
|
||||
const { mockScanner, mockAddon, addonAwareAdbScanner } = prepareMocks();
|
||||
|
||||
// Enable the addon aware scanner
|
||||
mockAddon.status = "installed";
|
||||
addonAwareAdbScanner.enable();
|
||||
ok(mockScanner.enable.called, "enable() was called initially");
|
||||
mockScanner.enable.reset();
|
||||
|
||||
// Uninstall the addon without firing any event
|
||||
mockAddon.status = "uninstalled";
|
||||
|
||||
// Programmatically call disable, check that the scanner's disable is called even though
|
||||
// the addon was uninstalled.
|
||||
addonAwareAdbScanner.disable();
|
||||
ok(mockScanner.disable.called, "disable() was called when uninstalling the addon");
|
||||
mockScanner.disable.reset();
|
||||
});
|
||||
|
||||
/**
|
||||
* This test checks that when the AddonAwareADBScanner is disabled, then enable/disable
|
||||
* are not called on the inner scanner when the addon status changes.
|
||||
*/
|
||||
add_task(async function testInnerEnableIsNotCalledIfNotStarted() {
|
||||
const { mockScanner, mockAddon, addonAwareAdbScanner } = prepareMocks();
|
||||
|
||||
// Check that enable() is not called on the inner scanner when the addon is installed
|
||||
// if the AddonAwareADBScanner was not enabled
|
||||
mockAddon.status = "installed";
|
||||
mockAddon.emit("update");
|
||||
ok(mockScanner.enable.notCalled, "enable() was not called");
|
||||
|
||||
// Same for disable() and "uninstall"
|
||||
mockAddon.status = "uninstalled";
|
||||
mockAddon.emit("update");
|
||||
ok(mockScanner.disable.notCalled, "disable() was not called");
|
||||
|
||||
// Enable the addon aware scanner
|
||||
addonAwareAdbScanner.enable();
|
||||
ok(mockScanner.enable.notCalled, "enable() was not called");
|
||||
ok(mockScanner.disable.notCalled, "disable() was not called");
|
||||
});
|
||||
|
||||
/**
|
||||
* This test checks that when the AddonAwareADBScanner is disabled, installing the addon
|
||||
* no longer enables the internal ADBScanner.
|
||||
*/
|
||||
add_task(async function testEnableIsNoLongerCalledAfterDisabling() {
|
||||
const { mockScanner, mockAddon, addonAwareAdbScanner } = prepareMocks();
|
||||
|
||||
// Start with the addon installed
|
||||
mockAddon.status = "installed";
|
||||
addonAwareAdbScanner.enable();
|
||||
ok(mockScanner.enable.called, "enable() was called since addon was already installed");
|
||||
mockScanner.enable.reset();
|
||||
|
||||
// Here we call enable again to check that we will not add too many events.
|
||||
// A single call to disable() should stop all listeners, even if we called enable()
|
||||
// several times.
|
||||
addonAwareAdbScanner.enable();
|
||||
ok(mockScanner.enable.called, "enable() was called again");
|
||||
mockScanner.enable.reset();
|
||||
|
||||
// Disable the scanner
|
||||
addonAwareAdbScanner.disable();
|
||||
ok(mockScanner.disable.called, "disable() was called");
|
||||
mockScanner.disable.reset();
|
||||
|
||||
// Emit an addon update event
|
||||
mockAddon.emit("update");
|
||||
ok(mockScanner.enable.notCalled,
|
||||
"enable() is not called since the main scanner is disabled");
|
||||
});
|
||||
|
||||
/**
|
||||
* Basic check that the "runtime-list-updated" event is forwarded.
|
||||
*/
|
||||
add_task(async function testListUpdatedEventForwarding() {
|
||||
const { mockScanner, addonAwareAdbScanner } = prepareMocks();
|
||||
|
||||
const spy = sinon.spy();
|
||||
addonAwareAdbScanner.on("runtime-list-updated", spy);
|
||||
mockScanner.emit("runtime-list-updated");
|
||||
ok(spy.called, "The runtime-list-updated event was forwarded from ADBScanner");
|
||||
addonAwareAdbScanner.off("runtime-list-updated", spy);
|
||||
});
|
||||
|
||||
/**
|
||||
* Basic check that calls to scan() are forwarded.
|
||||
*/
|
||||
add_task(async function testScanCallForwarding() {
|
||||
const { mockScanner, addonAwareAdbScanner } = prepareMocks();
|
||||
|
||||
ok(mockScanner.scan.notCalled, "ADBScanner scan() is not called initially");
|
||||
|
||||
addonAwareAdbScanner.scan();
|
||||
mockScanner.emit("runtime-list-updated");
|
||||
ok(mockScanner.scan.called, "ADBScanner scan() was called");
|
||||
mockScanner.scan.reset();
|
||||
});
|
||||
|
||||
/**
|
||||
* Basic check that calls to scan() are forwarded.
|
||||
*/
|
||||
add_task(async function testListRuntimesCallForwarding() {
|
||||
const { mockScanner, addonAwareAdbScanner } = prepareMocks();
|
||||
|
||||
ok(mockScanner.listRuntimes.notCalled,
|
||||
"ADBScanner listRuntimes() is not called initially");
|
||||
|
||||
addonAwareAdbScanner.listRuntimes();
|
||||
mockScanner.emit("runtime-list-updated");
|
||||
ok(mockScanner.listRuntimes.called, "ADBScanner listRuntimes() was called");
|
||||
mockScanner.scan.reset();
|
||||
});
|
|
@ -6,3 +6,12 @@
|
|||
/* eslint no-unused-vars: [2, {"vars": "local"}] */
|
||||
|
||||
const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
|
||||
const Services = require("Services");
|
||||
|
||||
// ================================================
|
||||
// Load mocking/stubbing library, sinon
|
||||
// docs: http://sinonjs.org/releases/v2.3.2/
|
||||
ChromeUtils.import("resource://gre/modules/Timer.jsm");
|
||||
Services.scriptloader.loadSubScript("resource://testing-common/sinon-2.3.2.js", this);
|
||||
/* globals sinon */
|
||||
// ================================================
|
||||
|
|
|
@ -8,3 +8,4 @@ support-files =
|
|||
|
||||
[test_adb.js]
|
||||
run-sequentially = An extension having the same id is installed/uninstalled in different tests
|
||||
[test_addon-aware-adb-scanner.js]
|
||||
|
|
|
@ -5340,7 +5340,6 @@ fail-if = 1
|
|||
subsuite = webgl2-core
|
||||
[generated/test_2_conformance2__rendering__instanced-rendering-large-divisor.html]
|
||||
subsuite = webgl2-core
|
||||
skip-if = (os == 'win')
|
||||
[generated/test_2_conformance2__rendering__line-rendering-quality.html]
|
||||
subsuite = webgl2-core
|
||||
[generated/test_2_conformance2__rendering__multisampling-fragment-evaluation.html]
|
||||
|
|
|
@ -1172,6 +1172,3 @@ skip-if = (os == 'win')
|
|||
skip-if = (os == 'win')
|
||||
[generated/test_2_conformance__textures__misc__tex-video-using-tex-unit-non-zero.html]
|
||||
skip-if = (os == 'win')
|
||||
[generated/test_2_conformance2__rendering__instanced-rendering-large-divisor.html]
|
||||
# Assertion failed: static_cast<unsigned int>(packedAttrib.divisor) == divisor, file z:/build/build/src/gfx/angle/checkout/src/libANGLE/renderer/d3d/d3d11/InputLayoutCache.cpp, line 89
|
||||
skip-if = (os == 'win')
|
||||
|
|
|
@ -2102,21 +2102,6 @@ ContentParent::RecvCreateReplayingProcess(const uint32_t& aChannelId)
|
|||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
ContentParent::RecvTerminateReplayingProcess(const uint32_t& aChannelId)
|
||||
{
|
||||
// We should only get this message from the child if it is recording or replaying.
|
||||
if (!this->IsRecordingOrReplaying()) {
|
||||
return IPC_FAIL_NO_REASON(this);
|
||||
}
|
||||
|
||||
if (aChannelId < mReplayingChildren.length() && mReplayingChildren[aChannelId]) {
|
||||
DelayedDeleteSubprocess(mReplayingChildren[aChannelId]);
|
||||
mReplayingChildren[aChannelId] = nullptr;
|
||||
}
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
jsipc::CPOWManager*
|
||||
ContentParent::GetCPOWManager()
|
||||
{
|
||||
|
|
|
@ -307,7 +307,6 @@ public:
|
|||
virtual mozilla::ipc::IPCResult RecvOpenRecordReplayChannel(const uint32_t& channelId,
|
||||
FileDescriptor* connection) override;
|
||||
virtual mozilla::ipc::IPCResult RecvCreateReplayingProcess(const uint32_t& aChannelId) override;
|
||||
virtual mozilla::ipc::IPCResult RecvTerminateReplayingProcess(const uint32_t& aChannelId) override;
|
||||
|
||||
virtual mozilla::ipc::IPCResult RecvCreateGMPService() override;
|
||||
|
||||
|
|
|
@ -742,7 +742,6 @@ parent:
|
|||
sync OpenRecordReplayChannel(uint32_t channelId)
|
||||
returns (FileDescriptor connection);
|
||||
async CreateReplayingProcess(uint32_t channelId);
|
||||
async TerminateReplayingProcess(uint32_t channelId);
|
||||
|
||||
async CreateGMPService();
|
||||
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
#define ANGLE_COMMIT_HASH "598f2502e3a4"
|
||||
#define ANGLE_COMMIT_HASH "790e8e6b4179"
|
||||
#define ANGLE_COMMIT_HASH_SIZE 12
|
||||
#define ANGLE_COMMIT_DATE "2018-10-09 13:54:05 -0700"
|
||||
#define ANGLE_COMMIT_DATE "2018-10-09 17:41:46 -0700"
|
||||
|
|
|
@ -59,7 +59,8 @@ struct PackedAttribute
|
|||
uint8_t attribType;
|
||||
uint8_t semanticIndex;
|
||||
uint8_t vertexFormatType;
|
||||
uint8_t divisor;
|
||||
uint8_t dummyPadding;
|
||||
uint32_t divisor;
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
@ -81,17 +82,18 @@ void PackedAttributeLayout::addAttributeData(GLenum glType,
|
|||
packedAttrib.attribType = static_cast<uint8_t>(attribType);
|
||||
packedAttrib.semanticIndex = static_cast<uint8_t>(semanticIndex);
|
||||
packedAttrib.vertexFormatType = static_cast<uint8_t>(vertexFormatType);
|
||||
packedAttrib.divisor = static_cast<uint8_t>(divisor);
|
||||
packedAttrib.dummyPadding = 0u;
|
||||
packedAttrib.divisor = static_cast<uint32_t>(divisor);
|
||||
|
||||
ASSERT(static_cast<gl::AttributeType>(packedAttrib.attribType) == attribType);
|
||||
ASSERT(static_cast<UINT>(packedAttrib.semanticIndex) == semanticIndex);
|
||||
ASSERT(static_cast<gl::VertexFormatType>(packedAttrib.vertexFormatType) == vertexFormatType);
|
||||
ASSERT(static_cast<unsigned int>(packedAttrib.divisor) == divisor);
|
||||
|
||||
static_assert(sizeof(uint32_t) == sizeof(PackedAttribute),
|
||||
"PackedAttributes must be 32-bits exactly.");
|
||||
static_assert(sizeof(uint64_t) == sizeof(PackedAttribute),
|
||||
"PackedAttributes must be 64-bits exactly.");
|
||||
|
||||
attributeData[numAttributes++] = gl::bitCast<uint32_t>(packedAttrib);
|
||||
attributeData[numAttributes++] = gl::bitCast<uint64_t>(packedAttrib);
|
||||
}
|
||||
|
||||
bool PackedAttributeLayout::operator==(const PackedAttributeLayout &other) const
|
||||
|
|
|
@ -48,7 +48,7 @@ struct PackedAttributeLayout
|
|||
|
||||
uint32_t numAttributes;
|
||||
uint32_t flags;
|
||||
gl::AttribArray<uint32_t> attributeData;
|
||||
gl::AttribArray<uint64_t> attributeData;
|
||||
};
|
||||
} // namespace rx
|
||||
|
||||
|
|
|
@ -1,3 +1,23 @@
|
|||
commit 790e8e6b417905eca335d06c16ec54c977188110
|
||||
Author: Olli Etuaho <oetuaho@nvidia.com>
|
||||
Date: Thu Sep 20 13:20:50 2018 +0300
|
||||
|
||||
Fix using a large vertex attrib divisor on D3D11
|
||||
|
||||
A divisor >= 256 used to trigger an assert on the D3D11 backend since
|
||||
it couldn't fit into the input layout cache. Increase the space
|
||||
reserved for the divisor in the input layout cache to make sure that
|
||||
the correct input layout will get used and to fix the assert.
|
||||
|
||||
BUG=angleproject:2832
|
||||
TEST=angle_end2end_tests
|
||||
|
||||
Change-Id: I34eead6c4e8c4fea379bbafc8670b4e32a5b633b
|
||||
Reviewed-on: https://chromium-review.googlesource.com/1236293
|
||||
Reviewed-by: Jamie Madill <jmadill@chromium.org>
|
||||
Reviewed-by: Geoff Lang <geofflang@chromium.org>
|
||||
Commit-Queue: Olli Etuaho <oetuaho@nvidia.com>
|
||||
|
||||
commit 598f2502e3a41b76d90037e7858c43c18e66399d
|
||||
Merge: 8212058a6 e15a25c6f
|
||||
Author: Jeff Gilbert <jdashg@gmail.com>
|
||||
|
|
|
@ -534,6 +534,8 @@ GLContextEGL::HoldSurface(gfxASurface* aSurf) {
|
|||
mThebesSurface = aSurf;
|
||||
}
|
||||
|
||||
#define LOCAL_EGL_CONTEXT_PROVOKING_VERTEX_DONT_CARE_MOZ 0x6000
|
||||
|
||||
already_AddRefed<GLContextEGL>
|
||||
GLContextEGL::CreateGLContext(CreateContextFlags flags,
|
||||
const SurfaceCaps& caps,
|
||||
|
@ -567,6 +569,13 @@ GLContextEGL::CreateGLContext(CreateContextFlags flags,
|
|||
required_attribs.push_back(LOCAL_EGL_TRUE);
|
||||
}
|
||||
|
||||
if (flags & CreateContextFlags::PROVOKING_VERTEX_DONT_CARE &&
|
||||
egl->IsExtensionSupported(GLLibraryEGL::MOZ_create_context_provoking_vertex_dont_care))
|
||||
{
|
||||
required_attribs.push_back(LOCAL_EGL_CONTEXT_PROVOKING_VERTEX_DONT_CARE_MOZ);
|
||||
required_attribs.push_back(LOCAL_EGL_TRUE);
|
||||
}
|
||||
|
||||
std::vector<EGLint> robustness_attribs;
|
||||
std::vector<EGLint> rbab_attribs; // RBAB: Robust Buffer Access Behavior
|
||||
if (flags & CreateContextFlags::PREFER_ROBUSTNESS) {
|
||||
|
|
|
@ -59,6 +59,8 @@ enum class CreateContextFlags : uint8_t {
|
|||
PREFER_ROBUSTNESS = 1 << 5,
|
||||
|
||||
HIGH_POWER = 1 << 6,
|
||||
|
||||
PROVOKING_VERTEX_DONT_CARE = 1 << 7,
|
||||
};
|
||||
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(CreateContextFlags)
|
||||
|
||||
|
|
|
@ -69,7 +69,8 @@ static const char* sEGLExtensionNames[] = {
|
|||
"EGL_ANGLE_device_creation",
|
||||
"EGL_ANGLE_device_creation_d3d11",
|
||||
"EGL_KHR_surfaceless_context",
|
||||
"EGL_KHR_create_context_no_error"
|
||||
"EGL_KHR_create_context_no_error",
|
||||
"EGL_MOZ_create_context_provoking_vertex_dont_care"
|
||||
};
|
||||
|
||||
#if defined(ANDROID)
|
||||
|
|
|
@ -97,6 +97,7 @@ public:
|
|||
ANGLE_device_creation_d3d11,
|
||||
KHR_surfaceless_context,
|
||||
KHR_create_context_no_error,
|
||||
MOZ_create_context_provoking_vertex_dont_care,
|
||||
Extensions_Max
|
||||
};
|
||||
|
||||
|
|
|
@ -280,6 +280,8 @@ AsyncImagePipelineManager::UpdateImageKeys(const wr::Epoch& aEpoch,
|
|||
}
|
||||
|
||||
if (aPipeline->mWrTextureWrapper) {
|
||||
// Force frame rendering, since WebRenderTextureHost update its data outside of WebRender.
|
||||
aMaybeFastTxn.InvalidateRenderedFrame();
|
||||
HoldExternalImage(aPipelineId, aEpoch, aPipeline->mWrTextureWrapper);
|
||||
}
|
||||
|
||||
|
|
|
@ -1393,6 +1393,12 @@ WebRenderBridgeParent::RecvScheduleComposite()
|
|||
if (mDestroyed) {
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
// Force frame rendering during next frame generation.
|
||||
wr::TransactionBuilder fastTxn(/* aUseSceneBuilderThread */ false);
|
||||
fastTxn.InvalidateRenderedFrame();
|
||||
mApi->SendTransaction(fastTxn);
|
||||
|
||||
ScheduleGenerateFrame();
|
||||
return IPC_OK();
|
||||
}
|
||||
|
|
|
@ -2680,6 +2680,12 @@ gfxPlatform::InitWebRenderConfig()
|
|||
// In all cases WR- means WR was not enabled, for one of many possible reasons.
|
||||
ScopedGfxFeatureReporter reporter("WR", prefEnabled || envvarEnabled);
|
||||
if (!XRE_IsParentProcess()) {
|
||||
// Force-disable WebRender in recording/replaying child processes, which
|
||||
// have their own compositor.
|
||||
if (recordreplay::IsRecordingOrReplaying()) {
|
||||
gfxVars::SetUseWebRender(false);
|
||||
}
|
||||
|
||||
// The parent process runs through all the real decision-making code
|
||||
// later in this function. For other processes we still want to report
|
||||
// the state of the feature for crash reports.
|
||||
|
|
|
@ -19,7 +19,7 @@ use picture::{PictureCompositeMode, PicturePrimitive, PictureSurface};
|
|||
use plane_split::{BspSplitter, Clipper, Polygon, Splitter};
|
||||
use prim_store::{BrushKind, BrushPrimitive, BrushSegmentTaskId, DeferredResolve};
|
||||
use prim_store::{EdgeAaSegmentMask, ImageSource};
|
||||
use prim_store::{PrimitiveMetadata, VisibleGradientTile, PrimitiveInstance};
|
||||
use prim_store::{VisibleGradientTile, PrimitiveInstance};
|
||||
use prim_store::{BorderSource, Primitive, PrimitiveDetails};
|
||||
use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskTree};
|
||||
use renderer::{BlendMode, ImageBufferKind, ShaderColorMode};
|
||||
|
@ -476,14 +476,14 @@ impl AlphaBatchBuilder {
|
|||
if cfg!(debug_assertions) && ctx.prim_store.chase_id == Some(prim_index) {
|
||||
println!("\t\tsplit polygon {:?}", poly.points);
|
||||
}
|
||||
let transform = transforms.get_world_transform(pic_metadata.spatial_node_index).inverse().unwrap();
|
||||
let transform = transforms.get_world_transform(prim_instance.spatial_node_index).inverse().unwrap();
|
||||
let transform_id = transforms.get_id(
|
||||
pic_metadata.spatial_node_index,
|
||||
prim_instance.spatial_node_index,
|
||||
ROOT_SPATIAL_NODE_INDEX,
|
||||
ctx.clip_scroll_tree,
|
||||
);
|
||||
|
||||
let clip_task_address = pic_metadata
|
||||
let clip_task_address = prim_instance
|
||||
.clip_task_id
|
||||
.map_or(OPAQUE_TASK_ADDRESS, |id| render_tasks.get_task_address(id));
|
||||
|
||||
|
@ -538,7 +538,7 @@ impl AlphaBatchBuilder {
|
|||
let batch = self.batch_list
|
||||
.get_suitable_batch(
|
||||
key,
|
||||
&pic_metadata.clipped_world_rect.as_ref().expect("bug"),
|
||||
&prim_instance.clipped_world_rect.as_ref().expect("bug"),
|
||||
);
|
||||
|
||||
let gpu_address = gpu_cache.get_address(&gpu_handle);
|
||||
|
@ -575,16 +575,16 @@ impl AlphaBatchBuilder {
|
|||
let prim = &ctx.prim_store.primitives[prim_instance.prim_index.0];
|
||||
let prim_metadata = &prim.metadata;
|
||||
|
||||
if prim_metadata.clipped_world_rect.is_none() {
|
||||
if prim_instance.clipped_world_rect.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)] //TODO: why is this needed?
|
||||
debug_assert_eq!(prim_metadata.prepared_frame_id, render_tasks.frame_id());
|
||||
debug_assert_eq!(prim_instance.prepared_frame_id, render_tasks.frame_id());
|
||||
|
||||
let transform_id = transforms
|
||||
.get_id(
|
||||
prim_metadata.spatial_node_index,
|
||||
prim_instance.spatial_node_index,
|
||||
root_spatial_node_index,
|
||||
ctx.clip_scroll_tree,
|
||||
);
|
||||
|
@ -593,7 +593,7 @@ impl AlphaBatchBuilder {
|
|||
// wasteful. We should probably cache this in
|
||||
// the scroll node...
|
||||
let transform_kind = transform_id.transform_kind();
|
||||
let bounding_rect = prim_metadata.clipped_world_rect
|
||||
let bounding_rect = prim_instance.clipped_world_rect
|
||||
.as_ref()
|
||||
.expect("bug");
|
||||
|
||||
|
@ -615,17 +615,17 @@ impl AlphaBatchBuilder {
|
|||
let prim_cache_address = if is_multiple_primitives {
|
||||
GpuCacheAddress::invalid()
|
||||
} else {
|
||||
gpu_cache.get_address(&prim_metadata.gpu_location)
|
||||
gpu_cache.get_address(&prim_instance.gpu_location)
|
||||
};
|
||||
|
||||
let clip_task_address = prim_metadata
|
||||
let clip_task_address = prim_instance
|
||||
.clip_task_id
|
||||
.map_or(OPAQUE_TASK_ADDRESS, |id| render_tasks.get_task_address(id));
|
||||
|
||||
let specified_blend_mode = prim.get_blend_mode();
|
||||
|
||||
let non_segmented_blend_mode = if !prim_metadata.opacity.is_opaque ||
|
||||
prim_metadata.clip_task_id.is_some() ||
|
||||
let non_segmented_blend_mode = if !prim_instance.opacity.is_opaque ||
|
||||
prim_instance.clip_task_id.is_some() ||
|
||||
transform_kind == TransformedRectKind::Complex {
|
||||
specified_blend_mode
|
||||
} else {
|
||||
|
@ -656,7 +656,7 @@ impl AlphaBatchBuilder {
|
|||
if picture.is_in_3d_context {
|
||||
// Push into parent plane splitter.
|
||||
debug_assert!(picture.raster_config.is_some());
|
||||
let transform = transforms.get_world_transform(prim_metadata.spatial_node_index);
|
||||
let transform = transforms.get_world_transform(prim_instance.spatial_node_index);
|
||||
|
||||
// Apply the local clip rect here, before splitting. This is
|
||||
// because the local clip rect can't be applied in the vertex
|
||||
|
@ -670,7 +670,7 @@ impl AlphaBatchBuilder {
|
|||
if let Some(local_rect) = local_rect {
|
||||
match transform.transform_kind() {
|
||||
TransformedRectKind::AxisAligned => {
|
||||
let inv_transform = transforms.get_world_inv_transform(prim_metadata.spatial_node_index);
|
||||
let inv_transform = transforms.get_world_inv_transform(prim_instance.spatial_node_index);
|
||||
let polygon = Polygon::from_transformed_rect_with_inverse(
|
||||
local_rect.cast(),
|
||||
&transform.cast(),
|
||||
|
@ -1062,7 +1062,7 @@ impl AlphaBatchBuilder {
|
|||
|
||||
self.add_brush_to_batch(
|
||||
brush,
|
||||
prim_metadata,
|
||||
prim_instance,
|
||||
batch_kind,
|
||||
specified_blend_mode,
|
||||
non_segmented_blend_mode,
|
||||
|
@ -1200,7 +1200,7 @@ impl AlphaBatchBuilder {
|
|||
fn add_brush_to_batch(
|
||||
&mut self,
|
||||
brush: &BrushPrimitive,
|
||||
prim_metadata: &PrimitiveMetadata,
|
||||
prim_instance: &PrimitiveInstance,
|
||||
batch_kind: BrushBatchKind,
|
||||
alpha_blend_mode: BlendMode,
|
||||
non_segmented_blend_mode: BlendMode,
|
||||
|
@ -1245,7 +1245,7 @@ impl AlphaBatchBuilder {
|
|||
|
||||
for (i, segment) in segment_desc.segments.iter().enumerate() {
|
||||
let is_inner = segment.edge_flags.is_empty();
|
||||
let needs_blending = !prim_metadata.opacity.is_opaque ||
|
||||
let needs_blending = !prim_instance.opacity.is_opaque ||
|
||||
segment.clip_task_id.needs_blending() ||
|
||||
(!is_inner && transform_kind == TransformedRectKind::Complex);
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use api::{BorderRadius, ClipMode, ComplexClipRegion, DeviceIntRect, DevicePixelScale, ImageMask};
|
||||
use api::{ImageRendering, LayoutRect, LayoutSize, LayoutPoint, LayoutVector2D, LocalClip};
|
||||
use api::{ImageRendering, LayoutRect, LayoutSize, LayoutPoint, LayoutVector2D};
|
||||
use api::{BoxShadowClipMode, LayoutToWorldScale, LineOrientation, LineStyle, PicturePixel, WorldPixel};
|
||||
use api::{PictureRect, LayoutPixel, WorldPoint, WorldSize, WorldRect, LayoutToWorldTransform};
|
||||
use api::{VoidPtrToSizeFn, LayoutRectAu, ImageKey, AuHelpers};
|
||||
|
@ -713,24 +713,13 @@ impl<J> ClipRegion<ComplexTranslateIter<J>> {
|
|||
|
||||
impl ClipRegion<Option<ComplexClipRegion>> {
|
||||
pub fn create_for_clip_node_with_local_clip(
|
||||
local_clip: &LocalClip,
|
||||
local_clip: &LayoutRect,
|
||||
reference_frame_relative_offset: &LayoutVector2D
|
||||
) -> Self {
|
||||
ClipRegion {
|
||||
main: local_clip
|
||||
.clip_rect()
|
||||
.translate(reference_frame_relative_offset),
|
||||
main: local_clip.translate(reference_frame_relative_offset),
|
||||
image_mask: None,
|
||||
complex_clips: match *local_clip {
|
||||
LocalClip::Rect(_) => None,
|
||||
LocalClip::RoundedRect(_, ref region) => {
|
||||
Some(ComplexClipRegion {
|
||||
rect: region.rect.translate(reference_frame_relative_offset),
|
||||
radii: region.radii,
|
||||
mode: region.mode,
|
||||
})
|
||||
},
|
||||
}
|
||||
complex_clips: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -762,7 +762,6 @@ impl Device {
|
|||
gl: Rc<gl::Gl>,
|
||||
resource_override_path: Option<PathBuf>,
|
||||
upload_method: UploadMethod,
|
||||
_file_changed_handler: Box<FileWatcherHandler>,
|
||||
cached_programs: Option<Rc<ProgramCache>>,
|
||||
) -> Device {
|
||||
let mut max_texture_size = [0];
|
||||
|
|
|
@ -9,7 +9,7 @@ use api::{DevicePixelScale, DeviceUintRect, DisplayItemRef, ExtendMode, External
|
|||
use api::{FilterOp, FontInstanceKey, GlyphInstance, GlyphOptions, RasterSpace, GradientStop};
|
||||
use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, LayoutPoint, ColorDepth};
|
||||
use api::{LayoutPrimitiveInfo, LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D};
|
||||
use api::{LineOrientation, LineStyle, LocalClip, NinePatchBorderSource, PipelineId};
|
||||
use api::{LineOrientation, LineStyle, NinePatchBorderSource, PipelineId};
|
||||
use api::{PropertyBinding, ReferenceFrame, RepeatMode, ScrollFrameDisplayItem, ScrollSensitivity};
|
||||
use api::{Shadow, SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, TexelRect};
|
||||
use api::{ClipMode, TransformStyle, YuvColorSpace, YuvData};
|
||||
|
@ -24,8 +24,8 @@ use hit_test::{HitTestingItem, HitTestingRun};
|
|||
use image::simplify_repeated_primitive;
|
||||
use internal_types::{FastHashMap, FastHashSet};
|
||||
use picture::{PictureCompositeMode, PictureIdGenerator, PicturePrimitive};
|
||||
use prim_store::{BrushKind, BrushPrimitive, BrushSegmentDescriptor};
|
||||
use prim_store::{EdgeAaSegmentMask, ImageSource, PrimitiveOpacity};
|
||||
use prim_store::{BrushKind, BrushPrimitive, BrushSegmentDescriptor, PrimitiveInstance};
|
||||
use prim_store::{EdgeAaSegmentMask, ImageSource, PrimitiveOpacity, PrimitiveKey};
|
||||
use prim_store::{BorderSource, BrushSegment, BrushSegmentVec, PrimitiveContainer, PrimitiveIndex, PrimitiveStore};
|
||||
use prim_store::{OpacityBinding, ScrollNodeAndClipChain, TextRunPrimitive};
|
||||
use render_backend::{DocumentView};
|
||||
|
@ -34,6 +34,7 @@ use scene::{Scene, ScenePipeline, StackingContextHelpers};
|
|||
use scene_builder::DocumentResources;
|
||||
use spatial_node::{SpatialNodeType, StickyFrameInfo};
|
||||
use std::{f32, mem};
|
||||
use std::collections::vec_deque::VecDeque;
|
||||
use tiling::{CompositeOps};
|
||||
use util::{MaxRect, RectHelpers};
|
||||
|
||||
|
@ -132,8 +133,8 @@ pub struct DisplayListFlattener<'a> {
|
|||
/// A stack of stacking context properties.
|
||||
sc_stack: Vec<FlattenedStackingContext>,
|
||||
|
||||
/// A stack of the currently active shadows
|
||||
shadow_stack: Vec<(Shadow, PrimitiveIndex)>,
|
||||
/// Maintains state for any currently active shadows
|
||||
pending_shadow_items: VecDeque<ShadowItem>,
|
||||
|
||||
/// The stack keeping track of the root clip chains associated with pipelines.
|
||||
pipeline_clip_chain_stack: Vec<ClipChainId>,
|
||||
|
@ -157,6 +158,10 @@ pub struct DisplayListFlattener<'a> {
|
|||
|
||||
/// The estimated count of primtives we expect to encounter during flattening.
|
||||
prim_count_estimate: usize,
|
||||
|
||||
/// The root primitive index for this flattener. This is the primitive
|
||||
/// to start the culling phase from.
|
||||
pub root_prim_index: PrimitiveIndex,
|
||||
}
|
||||
|
||||
impl<'a> DisplayListFlattener<'a> {
|
||||
|
@ -188,7 +193,7 @@ impl<'a> DisplayListFlattener<'a> {
|
|||
output_pipelines,
|
||||
id_to_index_mapper: ClipIdToIndexMapper::default(),
|
||||
hit_testing_runs: Vec::new(),
|
||||
shadow_stack: Vec::new(),
|
||||
pending_shadow_items: VecDeque::new(),
|
||||
sc_stack: Vec::new(),
|
||||
pipeline_clip_chain_stack: vec![ClipChainId::NONE],
|
||||
prim_store: PrimitiveStore::new(),
|
||||
|
@ -196,6 +201,7 @@ impl<'a> DisplayListFlattener<'a> {
|
|||
picture_id_generator,
|
||||
resources,
|
||||
prim_count_estimate: 0,
|
||||
root_prim_index: PrimitiveIndex(0),
|
||||
};
|
||||
|
||||
flattener.push_root(
|
||||
|
@ -474,7 +480,7 @@ impl<'a> DisplayListFlattener<'a> {
|
|||
info.clip_id,
|
||||
clip_and_scroll_ids.scroll_node_id,
|
||||
ClipRegion::create_for_clip_node_with_local_clip(
|
||||
&LocalClip::from(*item.clip_rect()),
|
||||
item.clip_rect(),
|
||||
reference_frame_relative_offset
|
||||
),
|
||||
);
|
||||
|
@ -821,17 +827,26 @@ impl<'a> DisplayListFlattener<'a> {
|
|||
clip_chain_id: ClipChainId,
|
||||
spatial_node_index: SpatialNodeIndex,
|
||||
container: PrimitiveContainer,
|
||||
) -> PrimitiveIndex {
|
||||
) -> PrimitiveInstance {
|
||||
let stacking_context = self.sc_stack.last().expect("bug: no stacking context!");
|
||||
|
||||
self.prim_store.add_primitive(
|
||||
let prim_key = PrimitiveKey::new(
|
||||
info.is_backface_visible && stacking_context.is_backface_visible,
|
||||
);
|
||||
|
||||
let prim_data_handle = self.resources.prim_interner.intern(&prim_key);
|
||||
|
||||
let prim_index = self.prim_store.add_primitive(
|
||||
&info.rect,
|
||||
&info.clip_rect,
|
||||
info.is_backface_visible && stacking_context.is_backface_visible,
|
||||
container,
|
||||
);
|
||||
|
||||
PrimitiveInstance::new(
|
||||
prim_index,
|
||||
prim_data_handle,
|
||||
clip_chain_id,
|
||||
spatial_node_index,
|
||||
info.tag,
|
||||
container,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -861,15 +876,14 @@ impl<'a> DisplayListFlattener<'a> {
|
|||
/// Add an already created primitive to the draw lists.
|
||||
pub fn add_primitive_to_draw_list(
|
||||
&mut self,
|
||||
prim_index: PrimitiveIndex,
|
||||
prim_instance: PrimitiveInstance,
|
||||
) {
|
||||
// Add primitive to the top-most Picture on the stack.
|
||||
let pic_prim_index = self.sc_stack.last().unwrap().leaf_prim_index;
|
||||
if cfg!(debug_assertions) && self.prim_store.chase_id == Some(prim_index) {
|
||||
println!("\tadded to picture primitive {:?}", pic_prim_index);
|
||||
// Add primitive to the top-most stacking context on the stack.
|
||||
if cfg!(debug_assertions) && self.prim_store.chase_id == Some(prim_instance.prim_index) {
|
||||
println!("\tadded to stacking context at {}", self.sc_stack.len());
|
||||
}
|
||||
let pic = self.prim_store.get_pic_mut(pic_prim_index);
|
||||
pic.add_primitive(prim_index);
|
||||
let stacking_context = self.sc_stack.last_mut().unwrap();
|
||||
stacking_context.normal_primitives.push(prim_instance);
|
||||
}
|
||||
|
||||
/// Convenience interface that creates a primitive entry and adds it
|
||||
|
@ -881,62 +895,37 @@ impl<'a> DisplayListFlattener<'a> {
|
|||
clip_items: Vec<ClipItemKey>,
|
||||
container: PrimitiveContainer,
|
||||
) {
|
||||
if !self.shadow_stack.is_empty() {
|
||||
// TODO(gw): Restructure this so we don't need to move the shadow
|
||||
// stack out (borrowck due to create_primitive below).
|
||||
let shadow_stack = mem::replace(&mut self.shadow_stack, Vec::new());
|
||||
for &(ref shadow, shadow_pic_prim_index) in &shadow_stack {
|
||||
// Offset the local rect and clip rect by the shadow offset.
|
||||
let mut info = info.clone();
|
||||
info.rect = info.rect.translate(&shadow.offset);
|
||||
info.clip_rect = info.clip_rect.translate(&shadow.offset);
|
||||
|
||||
// Offset any local clip sources by the shadow offset.
|
||||
let clip_items: Vec<ClipItemKey> = clip_items
|
||||
.iter()
|
||||
.map(|cs| cs.offset(&shadow.offset))
|
||||
.collect();
|
||||
// If a shadow context is not active, then add the primitive
|
||||
// directly to the parent picture.
|
||||
if self.pending_shadow_items.is_empty() {
|
||||
if container.is_visible() {
|
||||
let clip_chain_id = self.build_clip_chain(
|
||||
clip_items,
|
||||
clip_and_scroll.spatial_node_index,
|
||||
clip_and_scroll.clip_chain_id,
|
||||
);
|
||||
|
||||
// Construct and add a primitive for the given shadow.
|
||||
let shadow_prim_index = self.create_primitive(
|
||||
&info,
|
||||
let prim_instance = self.create_primitive(
|
||||
info,
|
||||
clip_chain_id,
|
||||
clip_and_scroll.spatial_node_index,
|
||||
container.create_shadow(shadow),
|
||||
container,
|
||||
);
|
||||
|
||||
// Add the new primitive to the shadow picture.
|
||||
let shadow_pic = self.prim_store.get_pic_mut(shadow_pic_prim_index);
|
||||
shadow_pic.add_primitive(shadow_prim_index);
|
||||
if cfg!(debug_assertions) && ChasePrimitive::LocalRect(info.rect) == self.config.chase_primitive {
|
||||
println!("Chasing {:?} by local rect", prim_instance.prim_index);
|
||||
self.prim_store.chase_id = Some(prim_instance.prim_index);
|
||||
}
|
||||
self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
|
||||
self.add_primitive_to_draw_list(prim_instance);
|
||||
}
|
||||
self.shadow_stack = shadow_stack;
|
||||
}
|
||||
|
||||
if container.is_visible() {
|
||||
let clip_chain_id = self.build_clip_chain(
|
||||
} else {
|
||||
// There is an active shadow context. Store as a pending primitive
|
||||
// for processing during pop_all_shadows.
|
||||
self.pending_shadow_items.push_back(ShadowItem::Primitive(PendingPrimitive {
|
||||
clip_and_scroll,
|
||||
info: *info,
|
||||
clip_items,
|
||||
clip_and_scroll.spatial_node_index,
|
||||
clip_and_scroll.clip_chain_id,
|
||||
);
|
||||
let prim_index = self.create_primitive(
|
||||
info,
|
||||
clip_chain_id,
|
||||
clip_and_scroll.spatial_node_index,
|
||||
container,
|
||||
);
|
||||
if cfg!(debug_assertions) && ChasePrimitive::LocalRect(info.rect) == self.config.chase_primitive {
|
||||
println!("Chasing {:?} by local rect", prim_index);
|
||||
self.prim_store.chase_id = Some(prim_index);
|
||||
}
|
||||
self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
|
||||
self.add_primitive_to_draw_list(
|
||||
prim_index,
|
||||
);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -957,13 +946,13 @@ impl<'a> DisplayListFlattener<'a> {
|
|||
None => ClipChainId::NONE,
|
||||
};
|
||||
|
||||
// An arbitrary large clip rect. For now, we don't
|
||||
// specify a clip specific to the stacking context.
|
||||
// However, now that they are represented as Picture
|
||||
// primitives, we can apply any kind of clip mask
|
||||
// to them, as for a normal primitive. This is needed
|
||||
// to correctly handle some CSS cases (see #1957).
|
||||
let max_clip = LayoutRect::max_rect();
|
||||
// Check if this stacking context is the root of a pipeline, and the caller
|
||||
// has requested it as an output frame.
|
||||
let frame_output_pipeline_id = if is_pipeline_root && self.output_pipelines.contains(&pipeline_id) {
|
||||
Some(pipeline_id)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Get the transform-style of the parent stacking context,
|
||||
// which determines if we *might* need to draw this on
|
||||
|
@ -990,143 +979,11 @@ impl<'a> DisplayListFlattener<'a> {
|
|||
participating_in_3d_context &&
|
||||
parent_transform_style == TransformStyle::Flat;
|
||||
|
||||
// By default, this picture will be collapsed into
|
||||
// the owning target.
|
||||
let mut composite_mode = None;
|
||||
|
||||
// If this stacking context is the root of a pipeline, and the caller
|
||||
// has requested it as an output frame, create a render task to isolate it.
|
||||
let mut frame_output_pipeline_id = None;
|
||||
if is_pipeline_root && self.output_pipelines.contains(&pipeline_id) {
|
||||
composite_mode = Some(PictureCompositeMode::Blit);
|
||||
frame_output_pipeline_id = Some(pipeline_id);
|
||||
}
|
||||
|
||||
// Force an intermediate surface if the stacking context
|
||||
// has a clip node. In the future, we may decide during
|
||||
// prepare step to skip the intermediate surface if the
|
||||
// clip node doesn't affect the stacking context rect.
|
||||
if participating_in_3d_context || clipping_node.is_some() {
|
||||
// TODO(gw): For now, as soon as this picture is in
|
||||
// a 3D context, we draw it to an intermediate
|
||||
// surface and apply plane splitting. However,
|
||||
// there is a large optimization opportunity here.
|
||||
// During culling, we can check if there is actually
|
||||
// perspective present, and skip the plane splitting
|
||||
// completely when that is not the case.
|
||||
composite_mode = Some(PictureCompositeMode::Blit);
|
||||
}
|
||||
|
||||
// Add picture for this actual stacking context contents to render into.
|
||||
let leaf_picture = PicturePrimitive::new_image(
|
||||
self.picture_id_generator.next(),
|
||||
composite_mode,
|
||||
participating_in_3d_context,
|
||||
pipeline_id,
|
||||
frame_output_pipeline_id,
|
||||
true,
|
||||
requested_raster_space,
|
||||
);
|
||||
|
||||
// Create a brush primitive that draws this picture.
|
||||
let leaf_prim = BrushPrimitive::new_picture(leaf_picture);
|
||||
|
||||
// Add the brush to the parent picture.
|
||||
let leaf_prim_index = self.prim_store.add_primitive(
|
||||
&LayoutRect::zero(),
|
||||
&max_clip,
|
||||
true,
|
||||
clip_chain_id,
|
||||
spatial_node_index,
|
||||
None,
|
||||
PrimitiveContainer::Brush(leaf_prim),
|
||||
);
|
||||
|
||||
// Create a chain of pictures based on presence of filters,
|
||||
// mix-blend-mode and/or 3d rendering context containers.
|
||||
let mut current_prim_index = leaf_prim_index;
|
||||
|
||||
// For each filter, create a new image with that composite mode.
|
||||
for filter in &composite_ops.filters {
|
||||
let filter = filter.sanitize();
|
||||
|
||||
let mut filter_picture = PicturePrimitive::new_image(
|
||||
self.picture_id_generator.next(),
|
||||
Some(PictureCompositeMode::Filter(filter)),
|
||||
false,
|
||||
pipeline_id,
|
||||
None,
|
||||
true,
|
||||
requested_raster_space,
|
||||
);
|
||||
|
||||
filter_picture.add_primitive(current_prim_index);
|
||||
let filter_prim = BrushPrimitive::new_picture(filter_picture);
|
||||
|
||||
current_prim_index = self.prim_store.add_primitive(
|
||||
&LayoutRect::zero(),
|
||||
&max_clip,
|
||||
true,
|
||||
clip_chain_id,
|
||||
spatial_node_index,
|
||||
None,
|
||||
PrimitiveContainer::Brush(filter_prim),
|
||||
);
|
||||
}
|
||||
|
||||
// Same for mix-blend-mode.
|
||||
if let Some(mix_blend_mode) = composite_ops.mix_blend_mode {
|
||||
let mut blend_picture = PicturePrimitive::new_image(
|
||||
self.picture_id_generator.next(),
|
||||
Some(PictureCompositeMode::MixBlend(mix_blend_mode)),
|
||||
false,
|
||||
pipeline_id,
|
||||
None,
|
||||
true,
|
||||
requested_raster_space,
|
||||
);
|
||||
|
||||
blend_picture.add_primitive(current_prim_index);
|
||||
let blend_prim = BrushPrimitive::new_picture(blend_picture);
|
||||
|
||||
current_prim_index = self.prim_store.add_primitive(
|
||||
&LayoutRect::zero(),
|
||||
&max_clip,
|
||||
true,
|
||||
clip_chain_id,
|
||||
spatial_node_index,
|
||||
None,
|
||||
PrimitiveContainer::Brush(blend_prim),
|
||||
);
|
||||
}
|
||||
|
||||
if establishes_3d_context {
|
||||
// If establishing a 3d context, we need to add a picture
|
||||
// that will be the container for all the planes and any
|
||||
// un-transformed content.
|
||||
let mut container_picture = PicturePrimitive::new_image(
|
||||
self.picture_id_generator.next(),
|
||||
None,
|
||||
false,
|
||||
pipeline_id,
|
||||
None,
|
||||
true,
|
||||
requested_raster_space,
|
||||
);
|
||||
|
||||
container_picture.add_primitive(current_prim_index);
|
||||
let container_prim = BrushPrimitive::new_picture(container_picture);
|
||||
|
||||
current_prim_index = self.prim_store.add_primitive(
|
||||
&LayoutRect::zero(),
|
||||
&max_clip,
|
||||
true,
|
||||
clip_chain_id,
|
||||
spatial_node_index,
|
||||
None,
|
||||
PrimitiveContainer::Brush(container_prim),
|
||||
);
|
||||
}
|
||||
let should_isolate = clipping_node.is_some();
|
||||
|
||||
// preserve-3d's semantics are to hoist all your children to be your siblings
|
||||
// when doing backface-visibility checking, so we need to grab the backface-visibility
|
||||
|
@ -1146,69 +1003,233 @@ impl<'a> DisplayListFlattener<'a> {
|
|||
|
||||
// Push the SC onto the stack, so we know how to handle things in
|
||||
// pop_stacking_context.
|
||||
let sc = FlattenedStackingContext {
|
||||
is_backface_visible,
|
||||
self.sc_stack.push(FlattenedStackingContext {
|
||||
preserve3d_primitives: Vec::new(),
|
||||
normal_primitives: Vec::new(),
|
||||
pipeline_id,
|
||||
is_backface_visible,
|
||||
requested_raster_space,
|
||||
spatial_node_index,
|
||||
clip_chain_id,
|
||||
frame_output_pipeline_id,
|
||||
composite_ops,
|
||||
should_isolate,
|
||||
transform_style,
|
||||
establishes_3d_context,
|
||||
participating_in_3d_context,
|
||||
leaf_prim_index,
|
||||
root_prim_index: current_prim_index,
|
||||
has_mix_blend_mode: composite_ops.mix_blend_mode.is_some(),
|
||||
};
|
||||
|
||||
self.sc_stack.push(sc);
|
||||
establishes_3d_context,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn pop_stacking_context(&mut self) {
|
||||
let sc = self.sc_stack.pop().unwrap();
|
||||
let stacking_context = self.sc_stack.pop().unwrap();
|
||||
|
||||
// Run the optimize pass on each picture in the chain,
|
||||
// to see if we can collapse opacity and avoid drawing
|
||||
// to an off-screen surface.
|
||||
for i in sc.leaf_prim_index.0 .. sc.root_prim_index.0 + 1 {
|
||||
let prim_index = PrimitiveIndex(i);
|
||||
self.prim_store.optimize_picture_if_possible(prim_index);
|
||||
// An arbitrary large clip rect. For now, we don't
|
||||
// specify a clip specific to the stacking context.
|
||||
// However, now that they are represented as Picture
|
||||
// primitives, we can apply any kind of clip mask
|
||||
// to them, as for a normal primitive. This is needed
|
||||
// to correctly handle some CSS cases (see #1957).
|
||||
let max_clip = LayoutRect::max_rect();
|
||||
|
||||
// By default, this picture will be collapsed into
|
||||
// the owning target.
|
||||
let mut composite_mode = if stacking_context.should_isolate {
|
||||
Some(PictureCompositeMode::Blit)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Force an intermediate surface if the stacking context
|
||||
// has a clip node. In the future, we may decide during
|
||||
// prepare step to skip the intermediate surface if the
|
||||
// clip node doesn't affect the stacking context rect.
|
||||
if stacking_context.participating_in_3d_context {
|
||||
// TODO(gw): For now, as soon as this picture is in
|
||||
// a 3D context, we draw it to an intermediate
|
||||
// surface and apply plane splitting. However,
|
||||
// there is a large optimization opportunity here.
|
||||
// During culling, we can check if there is actually
|
||||
// perspective present, and skip the plane splitting
|
||||
// completely when that is not the case.
|
||||
composite_mode = Some(PictureCompositeMode::Blit);
|
||||
}
|
||||
|
||||
let prim_key = PrimitiveKey::new(true);
|
||||
let prim_data_handle = self.resources.prim_interner.intern(&prim_key);
|
||||
|
||||
// Add picture for this actual stacking context contents to render into.
|
||||
let leaf_picture = PicturePrimitive::new_image(
|
||||
self.picture_id_generator.next(),
|
||||
composite_mode,
|
||||
stacking_context.participating_in_3d_context,
|
||||
stacking_context.pipeline_id,
|
||||
stacking_context.frame_output_pipeline_id,
|
||||
true,
|
||||
stacking_context.requested_raster_space,
|
||||
stacking_context.normal_primitives,
|
||||
);
|
||||
|
||||
// Create a brush primitive that draws this picture.
|
||||
let leaf_prim = BrushPrimitive::new_picture(leaf_picture);
|
||||
|
||||
// Add the brush to the parent picture.
|
||||
let leaf_prim_index = self.prim_store.add_primitive(
|
||||
&LayoutRect::zero(),
|
||||
&max_clip,
|
||||
PrimitiveContainer::Brush(leaf_prim),
|
||||
);
|
||||
|
||||
// Create a chain of pictures based on presence of filters,
|
||||
// mix-blend-mode and/or 3d rendering context containers.
|
||||
let mut current_prim_index = leaf_prim_index;
|
||||
|
||||
// For each filter, create a new image with that composite mode.
|
||||
for filter in &stacking_context.composite_ops.filters {
|
||||
let filter = filter.sanitize();
|
||||
|
||||
let filter_picture = PicturePrimitive::new_image(
|
||||
self.picture_id_generator.next(),
|
||||
Some(PictureCompositeMode::Filter(filter)),
|
||||
false,
|
||||
stacking_context.pipeline_id,
|
||||
None,
|
||||
true,
|
||||
stacking_context.requested_raster_space,
|
||||
vec![
|
||||
PrimitiveInstance::new(
|
||||
current_prim_index,
|
||||
prim_data_handle,
|
||||
stacking_context.clip_chain_id,
|
||||
stacking_context.spatial_node_index,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
let filter_prim = BrushPrimitive::new_picture(filter_picture);
|
||||
|
||||
current_prim_index = self.prim_store.add_primitive(
|
||||
&LayoutRect::zero(),
|
||||
&max_clip,
|
||||
PrimitiveContainer::Brush(filter_prim),
|
||||
);
|
||||
|
||||
// Run the optimize pass on this picture, to see if we can
|
||||
// collapse opacity and avoid drawing to an off-screen surface.
|
||||
self.prim_store.optimize_picture_if_possible(current_prim_index);
|
||||
}
|
||||
|
||||
// Same for mix-blend-mode.
|
||||
if let Some(mix_blend_mode) = stacking_context.composite_ops.mix_blend_mode {
|
||||
let blend_picture = PicturePrimitive::new_image(
|
||||
self.picture_id_generator.next(),
|
||||
Some(PictureCompositeMode::MixBlend(mix_blend_mode)),
|
||||
false,
|
||||
stacking_context.pipeline_id,
|
||||
None,
|
||||
true,
|
||||
stacking_context.requested_raster_space,
|
||||
vec![
|
||||
PrimitiveInstance::new(
|
||||
current_prim_index,
|
||||
prim_data_handle,
|
||||
stacking_context.clip_chain_id,
|
||||
stacking_context.spatial_node_index,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
let blend_prim = BrushPrimitive::new_picture(blend_picture);
|
||||
|
||||
current_prim_index = self.prim_store.add_primitive(
|
||||
&LayoutRect::zero(),
|
||||
&max_clip,
|
||||
PrimitiveContainer::Brush(blend_prim),
|
||||
);
|
||||
}
|
||||
|
||||
if stacking_context.establishes_3d_context {
|
||||
// If establishing a 3d context, we need to add a picture
|
||||
// that will be the container for all the planes and any
|
||||
// un-transformed content.
|
||||
let mut prims = vec![
|
||||
PrimitiveInstance::new(
|
||||
current_prim_index,
|
||||
prim_data_handle,
|
||||
stacking_context.clip_chain_id,
|
||||
stacking_context.spatial_node_index,
|
||||
),
|
||||
];
|
||||
prims.extend(stacking_context.preserve3d_primitives);
|
||||
|
||||
let container_picture = PicturePrimitive::new_image(
|
||||
self.picture_id_generator.next(),
|
||||
None,
|
||||
false,
|
||||
stacking_context.pipeline_id,
|
||||
None,
|
||||
true,
|
||||
stacking_context.requested_raster_space,
|
||||
prims,
|
||||
);
|
||||
|
||||
let container_prim = BrushPrimitive::new_picture(container_picture);
|
||||
|
||||
current_prim_index = self.prim_store.add_primitive(
|
||||
&LayoutRect::zero(),
|
||||
&max_clip,
|
||||
PrimitiveContainer::Brush(container_prim),
|
||||
);
|
||||
} else {
|
||||
debug_assert!(stacking_context.preserve3d_primitives.is_empty());
|
||||
}
|
||||
|
||||
if self.sc_stack.is_empty() {
|
||||
// This must be the root stacking context
|
||||
self.root_prim_index = current_prim_index;
|
||||
return;
|
||||
}
|
||||
|
||||
let parent_prim_index = if !sc.establishes_3d_context && sc.participating_in_3d_context {
|
||||
let sc_count = self.sc_stack.len();
|
||||
let prim_instance = PrimitiveInstance::new(
|
||||
current_prim_index,
|
||||
prim_data_handle,
|
||||
stacking_context.clip_chain_id,
|
||||
stacking_context.spatial_node_index,
|
||||
);
|
||||
|
||||
if !stacking_context.establishes_3d_context && stacking_context.participating_in_3d_context {
|
||||
// If we're in a 3D context, we will parent the picture
|
||||
// to the first stacking context we find that is a
|
||||
// 3D rendering context container. This follows the spec
|
||||
// by hoisting these items out into the same 3D context
|
||||
// for plane splitting.
|
||||
self.sc_stack
|
||||
let parent_index = self.sc_stack
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|sc| sc.establishes_3d_context)
|
||||
.map(|sc| sc.root_prim_index)
|
||||
.unwrap()
|
||||
.rposition(|sc| sc.establishes_3d_context)
|
||||
.unwrap();
|
||||
|
||||
let parent_stacking_context = &mut self.sc_stack[parent_index];
|
||||
parent_stacking_context.preserve3d_primitives.push(prim_instance);
|
||||
|
||||
} else {
|
||||
self.sc_stack.last().unwrap().leaf_prim_index
|
||||
let parent_stacking_context = self.sc_stack.last_mut().unwrap();
|
||||
|
||||
// If we have a mix-blend-mode, and we aren't the primary framebuffer,
|
||||
// the stacking context needs to be isolated to blend correctly as per
|
||||
// the CSS spec.
|
||||
// If not already isolated for some other reason,
|
||||
// make this picture as isolated.
|
||||
if stacking_context.composite_ops.mix_blend_mode.is_some() &&
|
||||
sc_count > 2 {
|
||||
parent_stacking_context.should_isolate = true;
|
||||
}
|
||||
|
||||
parent_stacking_context.normal_primitives.push(prim_instance);
|
||||
};
|
||||
|
||||
let parent_pic = self.prim_store.get_pic_mut(parent_prim_index);
|
||||
parent_pic.add_primitive(sc.root_prim_index);
|
||||
|
||||
// If we have a mix-blend-mode, and we aren't the primary framebuffer,
|
||||
// the stacking context needs to be isolated to blend correctly as per
|
||||
// the CSS spec.
|
||||
// If not already isolated for some other reason,
|
||||
// make this picture as isolated.
|
||||
if sc.has_mix_blend_mode &&
|
||||
self.sc_stack.len() > 2 &&
|
||||
parent_pic.requested_composite_mode.is_none() {
|
||||
parent_pic.requested_composite_mode = Some(PictureCompositeMode::Blit);
|
||||
}
|
||||
|
||||
assert!(
|
||||
self.shadow_stack.is_empty(),
|
||||
"Found unpopped text shadows when popping stacking context!"
|
||||
self.pending_shadow_items.is_empty(),
|
||||
"Found unpopped shadows when popping stacking context!"
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1400,69 +1421,162 @@ impl<'a> DisplayListFlattener<'a> {
|
|||
shadow: Shadow,
|
||||
clip_and_scroll: ScrollNodeAndClipChain,
|
||||
) {
|
||||
let pipeline_id = self.sc_stack.last().unwrap().pipeline_id;
|
||||
let max_clip = LayoutRect::max_rect();
|
||||
|
||||
// Quote from https://drafts.csswg.org/css-backgrounds-3/#shadow-blur
|
||||
// "the image that would be generated by applying to the shadow a
|
||||
// Gaussian blur with a standard deviation equal to half the blur radius."
|
||||
let std_deviation = shadow.blur_radius * 0.5;
|
||||
|
||||
// If the shadow has no blur, any elements will get directly rendered
|
||||
// into the parent picture surface, instead of allocating and drawing
|
||||
// into an intermediate surface. In this case, we will need to apply
|
||||
// the local clip rect to primitives.
|
||||
let is_passthrough = shadow.blur_radius == 0.0;
|
||||
|
||||
// shadows always rasterize in local space.
|
||||
// TODO(gw): expose API for clients to specify a raster scale
|
||||
let raster_space = if is_passthrough {
|
||||
let parent_pic_prim_index = self.sc_stack.last().unwrap().leaf_prim_index;
|
||||
self.prim_store
|
||||
.get_pic(parent_pic_prim_index)
|
||||
.requested_raster_space
|
||||
} else {
|
||||
RasterSpace::Local(1.0)
|
||||
};
|
||||
|
||||
// Create a picture that the shadow primitives will be added to. If the
|
||||
// blur radius is 0, the code in Picture::prepare_for_render will
|
||||
// detect this and mark the picture to be drawn directly into the
|
||||
// parent picture, which avoids an intermediate surface and blur.
|
||||
let blur_filter = FilterOp::Blur(std_deviation).sanitize();
|
||||
let shadow_pic = PicturePrimitive::new_image(
|
||||
self.picture_id_generator.next(),
|
||||
Some(PictureCompositeMode::Filter(blur_filter)),
|
||||
false,
|
||||
pipeline_id,
|
||||
None,
|
||||
is_passthrough,
|
||||
raster_space,
|
||||
);
|
||||
|
||||
// Create the primitive to draw the shadow picture into the scene.
|
||||
let shadow_prim = BrushPrimitive::new_picture(shadow_pic);
|
||||
let shadow_prim_index = self.prim_store.add_primitive(
|
||||
&LayoutRect::zero(),
|
||||
&max_clip,
|
||||
true,
|
||||
clip_and_scroll.clip_chain_id,
|
||||
clip_and_scroll.spatial_node_index,
|
||||
None,
|
||||
PrimitiveContainer::Brush(shadow_prim),
|
||||
);
|
||||
|
||||
// Add the shadow primitive. This must be done before pushing this
|
||||
// picture on to the shadow stack, to avoid infinite recursion!
|
||||
self.add_primitive_to_draw_list(
|
||||
shadow_prim_index,
|
||||
);
|
||||
self.shadow_stack.push((shadow, shadow_prim_index));
|
||||
// Store this shadow in the pending list, for processing
|
||||
// during pop_all_shadows.
|
||||
self.pending_shadow_items.push_back(ShadowItem::Shadow(PendingShadow {
|
||||
shadow,
|
||||
clip_and_scroll,
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn pop_all_shadows(&mut self) {
|
||||
assert!(self.shadow_stack.len() > 0, "popped shadows, but none were present");
|
||||
self.shadow_stack.clear();
|
||||
assert!(!self.pending_shadow_items.is_empty(), "popped shadows, but none were present");
|
||||
|
||||
let pipeline_id = self.sc_stack.last().unwrap().pipeline_id;
|
||||
let max_clip = LayoutRect::max_rect();
|
||||
let mut items = mem::replace(&mut self.pending_shadow_items, VecDeque::new());
|
||||
|
||||
//
|
||||
// The pending_shadow_items queue contains a list of shadows and primitives
|
||||
// that were pushed during the active shadow context. To process these, we:
|
||||
//
|
||||
// Iterate the list, popping an item from the front each iteration.
|
||||
//
|
||||
// If the item is a shadow:
|
||||
// - Create a shadow picture primitive.
|
||||
// - Add *any* primitives that remain in the item list to this shadow.
|
||||
// If the item is a primitive:
|
||||
// - Add that primitive as a normal item (if alpha > 0)
|
||||
//
|
||||
|
||||
while let Some(item) = items.pop_front() {
|
||||
match item {
|
||||
ShadowItem::Shadow(pending_shadow) => {
|
||||
// Quote from https://drafts.csswg.org/css-backgrounds-3/#shadow-blur
|
||||
// "the image that would be generated by applying to the shadow a
|
||||
// Gaussian blur with a standard deviation equal to half the blur radius."
|
||||
let std_deviation = pending_shadow.shadow.blur_radius * 0.5;
|
||||
|
||||
// If the shadow has no blur, any elements will get directly rendered
|
||||
// into the parent picture surface, instead of allocating and drawing
|
||||
// into an intermediate surface. In this case, we will need to apply
|
||||
// the local clip rect to primitives.
|
||||
let is_passthrough = pending_shadow.shadow.blur_radius == 0.0;
|
||||
|
||||
// shadows always rasterize in local space.
|
||||
// TODO(gw): expose API for clients to specify a raster scale
|
||||
let raster_space = if is_passthrough {
|
||||
self.sc_stack.last().unwrap().requested_raster_space
|
||||
} else {
|
||||
RasterSpace::Local(1.0)
|
||||
};
|
||||
|
||||
// Add any primitives that come after this shadow in the item
|
||||
// list to this shadow.
|
||||
let mut prims = Vec::new();
|
||||
|
||||
for item in &items {
|
||||
if let ShadowItem::Primitive(ref pending_primitive) = item {
|
||||
// Offset the local rect and clip rect by the shadow offset.
|
||||
let mut info = pending_primitive.info.clone();
|
||||
info.rect = info.rect.translate(&pending_shadow.shadow.offset);
|
||||
info.clip_rect = info.clip_rect.translate(&pending_shadow.shadow.offset);
|
||||
|
||||
// Offset any local clip sources by the shadow offset.
|
||||
let clip_items: Vec<ClipItemKey> = pending_primitive
|
||||
.clip_items
|
||||
.iter()
|
||||
.map(|cs| cs.offset(&pending_shadow.shadow.offset))
|
||||
.collect();
|
||||
let clip_chain_id = self.build_clip_chain(
|
||||
clip_items,
|
||||
pending_primitive.clip_and_scroll.spatial_node_index,
|
||||
pending_primitive.clip_and_scroll.clip_chain_id,
|
||||
);
|
||||
|
||||
// Construct and add a primitive for the given shadow.
|
||||
let shadow_prim_instance = self.create_primitive(
|
||||
&info,
|
||||
clip_chain_id,
|
||||
pending_primitive.clip_and_scroll.spatial_node_index,
|
||||
pending_primitive.container.create_shadow(&pending_shadow.shadow),
|
||||
);
|
||||
|
||||
// Add the new primitive to the shadow picture.
|
||||
prims.push(shadow_prim_instance);
|
||||
}
|
||||
}
|
||||
|
||||
// No point in adding a shadow here if there were no primitives
|
||||
// added to the shadow.
|
||||
if !prims.is_empty() {
|
||||
// Create a picture that the shadow primitives will be added to. If the
|
||||
// blur radius is 0, the code in Picture::prepare_for_render will
|
||||
// detect this and mark the picture to be drawn directly into the
|
||||
// parent picture, which avoids an intermediate surface and blur.
|
||||
let blur_filter = FilterOp::Blur(std_deviation).sanitize();
|
||||
let mut shadow_pic = PicturePrimitive::new_image(
|
||||
self.picture_id_generator.next(),
|
||||
Some(PictureCompositeMode::Filter(blur_filter)),
|
||||
false,
|
||||
pipeline_id,
|
||||
None,
|
||||
is_passthrough,
|
||||
raster_space,
|
||||
prims,
|
||||
);
|
||||
|
||||
// Create the primitive to draw the shadow picture into the scene.
|
||||
let shadow_prim = BrushPrimitive::new_picture(shadow_pic);
|
||||
let shadow_prim_index = self.prim_store.add_primitive(
|
||||
&LayoutRect::zero(),
|
||||
&max_clip,
|
||||
PrimitiveContainer::Brush(shadow_prim),
|
||||
);
|
||||
|
||||
let shadow_prim_key = PrimitiveKey::new(true);
|
||||
let shadow_prim_data_handle = self.resources.prim_interner.intern(&shadow_prim_key);
|
||||
|
||||
let shadow_prim_instance = PrimitiveInstance::new(
|
||||
shadow_prim_index,
|
||||
shadow_prim_data_handle,
|
||||
pending_shadow.clip_and_scroll.clip_chain_id,
|
||||
pending_shadow.clip_and_scroll.spatial_node_index,
|
||||
);
|
||||
|
||||
// Add the shadow primitive. This must be done before pushing this
|
||||
// picture on to the shadow stack, to avoid infinite recursion!
|
||||
self.add_primitive_to_draw_list(shadow_prim_instance);
|
||||
}
|
||||
}
|
||||
ShadowItem::Primitive(pending_primitive) => {
|
||||
// For a normal primitive, if it has alpha > 0, then we add this
|
||||
// as a normal primitive to the parent picture.
|
||||
if pending_primitive.container.is_visible() {
|
||||
let clip_chain_id = self.build_clip_chain(
|
||||
pending_primitive.clip_items,
|
||||
pending_primitive.clip_and_scroll.spatial_node_index,
|
||||
pending_primitive.clip_and_scroll.clip_chain_id,
|
||||
);
|
||||
let prim_instance = self.create_primitive(
|
||||
&pending_primitive.info,
|
||||
clip_chain_id,
|
||||
pending_primitive.clip_and_scroll.spatial_node_index,
|
||||
pending_primitive.container,
|
||||
);
|
||||
if cfg!(debug_assertions) && ChasePrimitive::LocalRect(pending_primitive.info.rect) == self.config.chase_primitive {
|
||||
println!("Chasing {:?} by local rect", prim_instance.prim_index);
|
||||
self.prim_store.chase_id = Some(prim_instance.prim_index);
|
||||
}
|
||||
self.add_primitive_to_hit_testing_list(&pending_primitive.info, pending_primitive.clip_and_scroll);
|
||||
self.add_primitive_to_draw_list(prim_instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug_assert!(items.is_empty());
|
||||
self.pending_shadow_items = items;
|
||||
}
|
||||
|
||||
pub fn add_solid_rectangle(
|
||||
|
@ -2048,6 +2162,37 @@ impl<'a> DisplayListFlattener<'a> {
|
|||
/// during creation of the scene. These structures are
|
||||
/// not persisted after the initial scene build.
|
||||
struct FlattenedStackingContext {
|
||||
/// The list of un-transformed content being
|
||||
/// added to this stacking context.
|
||||
normal_primitives: Vec<PrimitiveInstance>,
|
||||
|
||||
/// The list of preserve-3d primitives that
|
||||
/// are being hoisted to this stacking context
|
||||
/// (implies establishes_3d_context).
|
||||
preserve3d_primitives: Vec<PrimitiveInstance>,
|
||||
|
||||
/// Whether or not the caller wants this drawn in
|
||||
/// screen space (quality) or local space (performance)
|
||||
requested_raster_space: RasterSpace,
|
||||
|
||||
/// The positioning node for this stacking context
|
||||
spatial_node_index: SpatialNodeIndex,
|
||||
|
||||
/// The clip chain for this stacking context
|
||||
clip_chain_id: ClipChainId,
|
||||
|
||||
/// If set, this should be provided to caller
|
||||
/// as an output texture.
|
||||
frame_output_pipeline_id: Option<PipelineId>,
|
||||
|
||||
/// The list of filters / mix-blend-mode for this
|
||||
/// stacking context.
|
||||
composite_ops: CompositeOps,
|
||||
|
||||
/// If true, this stacking context should be
|
||||
/// isolated by forcing an off-screen surface.
|
||||
should_isolate: bool,
|
||||
|
||||
/// Pipeline this stacking context belongs to.
|
||||
pipeline_id: PipelineId,
|
||||
|
||||
|
@ -2057,12 +2202,33 @@ struct FlattenedStackingContext {
|
|||
/// CSS transform-style property.
|
||||
transform_style: TransformStyle,
|
||||
|
||||
root_prim_index: PrimitiveIndex,
|
||||
leaf_prim_index: PrimitiveIndex,
|
||||
|
||||
/// If true, this stacking context establishes a new
|
||||
/// 3d rendering context.
|
||||
establishes_3d_context: bool,
|
||||
|
||||
/// If true, this stacking context is part of a
|
||||
/// surrounding 3d rendering context.
|
||||
participating_in_3d_context: bool,
|
||||
has_mix_blend_mode: bool,
|
||||
}
|
||||
|
||||
/// A primitive that is added while a shadow context is
|
||||
/// active is stored as a pending primitive and only
|
||||
/// added to pictures during pop_all_shadows.
|
||||
struct PendingPrimitive {
|
||||
clip_and_scroll: ScrollNodeAndClipChain,
|
||||
info: LayoutPrimitiveInfo,
|
||||
clip_items: Vec<ClipItemKey>,
|
||||
container: PrimitiveContainer,
|
||||
}
|
||||
|
||||
/// As shadows are pushed, they are stored as pending
|
||||
/// shadows, and handled at once during pop_all_shadows.
|
||||
struct PendingShadow {
|
||||
shadow: Shadow,
|
||||
clip_and_scroll: ScrollNodeAndClipChain,
|
||||
}
|
||||
|
||||
enum ShadowItem {
|
||||
Shadow(PendingShadow),
|
||||
Primitive(PendingPrimitive),
|
||||
}
|
||||
|
|
|
@ -58,6 +58,7 @@ pub struct FrameBuilder {
|
|||
background_color: Option<ColorF>,
|
||||
window_size: DeviceUintSize,
|
||||
scene_id: u64,
|
||||
root_prim_index: PrimitiveIndex,
|
||||
pub prim_store: PrimitiveStore,
|
||||
pub clip_store: ClipStore,
|
||||
pub hit_testing_runs: Vec<HitTestingRun>,
|
||||
|
@ -137,6 +138,7 @@ impl FrameBuilder {
|
|||
window_size: DeviceUintSize::zero(),
|
||||
background_color: None,
|
||||
scene_id: 0,
|
||||
root_prim_index: PrimitiveIndex(0),
|
||||
config: FrameBuilderConfig {
|
||||
default_font_render_mode: FontRenderMode::Mono,
|
||||
dual_source_blending_is_enabled: true,
|
||||
|
@ -157,6 +159,7 @@ impl FrameBuilder {
|
|||
hit_testing_runs: flattener.hit_testing_runs,
|
||||
prim_store: flattener.prim_store,
|
||||
clip_store: flattener.clip_store,
|
||||
root_prim_index: flattener.root_prim_index,
|
||||
screen_rect,
|
||||
background_color,
|
||||
window_size,
|
||||
|
@ -186,10 +189,7 @@ impl FrameBuilder {
|
|||
if self.prim_store.primitives.is_empty() {
|
||||
return None
|
||||
}
|
||||
self.prim_store.reset_prim_visibility();
|
||||
|
||||
// The root picture is always the first one added.
|
||||
let root_prim_index = PrimitiveIndex(0);
|
||||
let root_spatial_node_index = clip_scroll_tree.root_reference_frame_index();
|
||||
|
||||
const MAX_CLIP_COORD: f32 = 1.0e9;
|
||||
|
@ -228,7 +228,7 @@ impl FrameBuilder {
|
|||
|
||||
let (pic_context, mut pic_state, mut instances) = self
|
||||
.prim_store
|
||||
.get_pic_mut(root_prim_index)
|
||||
.get_pic_mut(self.root_prim_index)
|
||||
.take_context(
|
||||
&prim_context,
|
||||
root_spatial_node_index,
|
||||
|
@ -253,7 +253,7 @@ impl FrameBuilder {
|
|||
|
||||
let pic = self
|
||||
.prim_store
|
||||
.get_pic_mut(root_prim_index);
|
||||
.get_pic_mut(self.root_prim_index);
|
||||
pic.restore_context(
|
||||
instances,
|
||||
pic_context,
|
||||
|
@ -267,7 +267,7 @@ impl FrameBuilder {
|
|||
let root_render_task = RenderTask::new_picture(
|
||||
RenderTaskLocation::Fixed(self.screen_rect.to_i32()),
|
||||
self.screen_rect.size.to_f32(),
|
||||
root_prim_index,
|
||||
self.root_prim_index,
|
||||
DeviceIntPoint::zero(),
|
||||
pic_state.tasks,
|
||||
UvRectKind::Rect,
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
#[cfg(feature = "pathfinder")]
|
||||
use api::DeviceIntPoint;
|
||||
use glyph_rasterizer::{FontInstance, GlyphFormat, GlyphKey};
|
||||
use glyph_rasterizer::{FontInstance, GlyphFormat, GlyphKey, GlyphRasterizer};
|
||||
use internal_types::FastHashMap;
|
||||
use render_task::RenderTaskCache;
|
||||
#[cfg(feature = "pathfinder")]
|
||||
|
@ -143,10 +143,13 @@ impl GlyphCache {
|
|||
|
||||
// Clear out evicted entries from glyph key caches and, if possible,
|
||||
// also remove entirely any subsequently empty glyph key caches.
|
||||
fn clear_evicted(&mut self,
|
||||
texture_cache: &TextureCache,
|
||||
render_task_cache: &RenderTaskCache) {
|
||||
self.glyph_key_caches.retain(|_, cache| {
|
||||
fn clear_evicted(
|
||||
&mut self,
|
||||
texture_cache: &TextureCache,
|
||||
render_task_cache: &RenderTaskCache,
|
||||
glyph_rasterizer: &mut GlyphRasterizer,
|
||||
) {
|
||||
self.glyph_key_caches.retain(|key, cache| {
|
||||
// Scan for any glyph key caches that have evictions.
|
||||
if cache.eviction_notice().check() {
|
||||
// If there are evictions, filter out any glyphs evicted from the
|
||||
|
@ -157,6 +160,9 @@ impl GlyphCache {
|
|||
keep_cache |= keep_glyph;
|
||||
keep_glyph
|
||||
});
|
||||
if !keep_cache {
|
||||
glyph_rasterizer.delete_font_instance(key);
|
||||
}
|
||||
// Only keep the glyph key cache if it still has valid glyphs.
|
||||
keep_cache
|
||||
} else {
|
||||
|
@ -167,7 +173,8 @@ impl GlyphCache {
|
|||
|
||||
pub fn begin_frame(&mut self,
|
||||
texture_cache: &TextureCache,
|
||||
render_task_cache: &RenderTaskCache) {
|
||||
self.clear_evicted(texture_cache, render_task_cache);
|
||||
render_task_cache: &RenderTaskCache,
|
||||
glyph_rasterizer: &mut GlyphRasterizer) {
|
||||
self.clear_evicted(texture_cache, render_task_cache, glyph_rasterizer);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -545,6 +545,8 @@ pub struct GlyphRasterizer {
|
|||
// - we don't have to worry about the ordering of events if a font is used on
|
||||
// a frame where it is used (although it seems unlikely).
|
||||
fonts_to_remove: Vec<FontKey>,
|
||||
// Defer removal of font instances, as for fonts.
|
||||
font_instances_to_remove: Vec<FontInstance>,
|
||||
|
||||
#[allow(dead_code)]
|
||||
next_gpu_glyph_cache_key: GpuGlyphCacheKey,
|
||||
|
@ -580,6 +582,7 @@ impl GlyphRasterizer {
|
|||
glyph_tx,
|
||||
workers,
|
||||
fonts_to_remove: Vec::new(),
|
||||
font_instances_to_remove: Vec::new(),
|
||||
next_gpu_glyph_cache_key: GpuGlyphCacheKey(0),
|
||||
})
|
||||
}
|
||||
|
@ -597,6 +600,10 @@ impl GlyphRasterizer {
|
|||
self.fonts_to_remove.push(font_key);
|
||||
}
|
||||
|
||||
pub fn delete_font_instance(&mut self, instance: &FontInstance) {
|
||||
self.font_instances_to_remove.push(instance.clone());
|
||||
}
|
||||
|
||||
pub fn prepare_font(&self, font: &mut FontInstance) {
|
||||
FontContext::prepare_font(font);
|
||||
}
|
||||
|
@ -624,15 +631,19 @@ impl GlyphRasterizer {
|
|||
}
|
||||
|
||||
fn remove_dead_fonts(&mut self) {
|
||||
if self.fonts_to_remove.is_empty() {
|
||||
if self.fonts_to_remove.is_empty() && self.font_instances_to_remove.is_empty() {
|
||||
return
|
||||
}
|
||||
|
||||
let fonts_to_remove = mem::replace(&mut self.fonts_to_remove, Vec::new());
|
||||
let font_instances_to_remove = mem::replace(& mut self.font_instances_to_remove, Vec::new());
|
||||
self.font_contexts.for_each(move |mut context| {
|
||||
for font_key in &fonts_to_remove {
|
||||
context.delete_font(font_key);
|
||||
}
|
||||
for instance in &font_instances_to_remove {
|
||||
context.delete_font_instance(instance);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -641,6 +652,7 @@ impl GlyphRasterizer {
|
|||
//TODO: any signals need to be sent to the workers?
|
||||
self.pending_glyphs = 0;
|
||||
self.fonts_to_remove.clear();
|
||||
self.font_instances_to_remove.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -150,6 +150,7 @@ pub enum DebugOutput {
|
|||
LoadCapture(PathBuf, Vec<PlainExternalImage>),
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub enum ResultMsg {
|
||||
DebugCommand(DebugCommand),
|
||||
DebugOutput(DebugOutput),
|
||||
|
|
|
@ -182,11 +182,13 @@ pub extern crate webrender_api;
|
|||
#[doc(hidden)]
|
||||
pub use device::{build_shader_strings, ReadPixelsFormat, UploadMethod, VertexUsageHint};
|
||||
pub use device::{ProgramBinary, ProgramCache, ProgramCacheObserver, ProgramSources};
|
||||
pub use device::{Device};
|
||||
pub use frame_builder::ChasePrimitive;
|
||||
pub use renderer::{AsyncPropertySampler, CpuProfile, DebugFlags, OutputImageHandler, RendererKind};
|
||||
pub use renderer::{ExternalImage, ExternalImageHandler, ExternalImageSource, GpuProfile};
|
||||
pub use renderer::{GraphicsApi, GraphicsApiInfo, PipelineInfo, Renderer, RendererOptions};
|
||||
pub use renderer::{RendererStats, SceneBuilderHooks, ThreadListener, ShaderPrecacheFlags};
|
||||
pub use renderer::MAX_VERTEX_TEXTURE_WIDTH;
|
||||
pub use shade::{Shaders, WrShaders};
|
||||
pub use webrender_api as api;
|
||||
pub use resource_cache::intersect_for_tile;
|
||||
|
|
|
@ -14,7 +14,7 @@ use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState};
|
|||
use frame_builder::{PictureContext, PrimitiveContext};
|
||||
use gpu_cache::{GpuCacheHandle};
|
||||
use gpu_types::UvRectKind;
|
||||
use prim_store::{PrimitiveIndex, PrimitiveInstance, SpaceMapper};
|
||||
use prim_store::{PrimitiveInstance, SpaceMapper};
|
||||
use prim_store::{PrimitiveMetadata, get_raster_rects};
|
||||
use render_task::{ClearMode, RenderTask, RenderTaskCacheEntryHandle};
|
||||
use render_task::{RenderTaskCacheKey, RenderTaskCacheKeyKind, RenderTaskId, RenderTaskLocation};
|
||||
|
@ -222,9 +222,10 @@ impl PicturePrimitive {
|
|||
frame_output_pipeline_id: Option<PipelineId>,
|
||||
apply_local_clip_rect: bool,
|
||||
requested_raster_space: RasterSpace,
|
||||
prim_instances: Vec<PrimitiveInstance>,
|
||||
) -> Self {
|
||||
PicturePrimitive {
|
||||
prim_instances: Vec::new(),
|
||||
prim_instances,
|
||||
state: None,
|
||||
secondary_render_task_id: None,
|
||||
requested_composite_mode,
|
||||
|
@ -378,16 +379,6 @@ impl PicturePrimitive {
|
|||
Some((context, state, instances))
|
||||
}
|
||||
|
||||
pub fn add_primitive(
|
||||
&mut self,
|
||||
prim_index: PrimitiveIndex,
|
||||
) {
|
||||
self.prim_instances.push(PrimitiveInstance {
|
||||
prim_index,
|
||||
combined_local_clip_rect: LayoutRect::zero(),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn restore_context(
|
||||
&mut self,
|
||||
prim_instances: Vec<PrimitiveInstance>,
|
||||
|
@ -448,8 +439,8 @@ impl PicturePrimitive {
|
|||
|
||||
pub fn prepare_for_render(
|
||||
&mut self,
|
||||
prim_index: PrimitiveIndex,
|
||||
prim_metadata: &mut PrimitiveMetadata,
|
||||
prim_instance: &PrimitiveInstance,
|
||||
prim_metadata: &PrimitiveMetadata,
|
||||
pic_state: &mut PictureState,
|
||||
frame_context: &FrameBuildingContext,
|
||||
frame_state: &mut FrameBuildingState,
|
||||
|
@ -459,7 +450,7 @@ impl PicturePrimitive {
|
|||
match self.raster_config {
|
||||
Some(ref mut raster_config) => {
|
||||
let (map_raster_to_world, map_pic_to_raster) = create_raster_mappers(
|
||||
prim_metadata.spatial_node_index,
|
||||
prim_instance.spatial_node_index,
|
||||
raster_config.raster_spatial_node_index,
|
||||
frame_context,
|
||||
);
|
||||
|
@ -470,7 +461,7 @@ impl PicturePrimitive {
|
|||
pic_rect,
|
||||
&map_pic_to_raster,
|
||||
&map_raster_to_world,
|
||||
prim_metadata.clipped_world_rect.expect("bug1"),
|
||||
prim_instance.clipped_world_rect.expect("bug1"),
|
||||
frame_context.device_pixel_scale,
|
||||
) {
|
||||
Some(info) => info,
|
||||
|
@ -520,7 +511,7 @@ impl PicturePrimitive {
|
|||
let picture_task = RenderTask::new_picture(
|
||||
RenderTaskLocation::Dynamic(None, device_rect.size),
|
||||
unclipped.size,
|
||||
prim_index,
|
||||
prim_instance.prim_index,
|
||||
device_rect.origin,
|
||||
pic_state_for_children.tasks,
|
||||
uv_rect_kind,
|
||||
|
@ -578,7 +569,7 @@ impl PicturePrimitive {
|
|||
let picture_task = RenderTask::new_picture(
|
||||
RenderTaskLocation::Dynamic(None, device_rect.size),
|
||||
unclipped.size,
|
||||
prim_index,
|
||||
prim_instance.prim_index,
|
||||
device_rect.origin,
|
||||
child_tasks,
|
||||
uv_rect_kind,
|
||||
|
@ -635,7 +626,7 @@ impl PicturePrimitive {
|
|||
let mut picture_task = RenderTask::new_picture(
|
||||
RenderTaskLocation::Dynamic(None, device_rect.size),
|
||||
unclipped.size,
|
||||
prim_index,
|
||||
prim_instance.prim_index,
|
||||
device_rect.origin,
|
||||
pic_state_for_children.tasks,
|
||||
uv_rect_kind,
|
||||
|
@ -706,7 +697,7 @@ impl PicturePrimitive {
|
|||
let picture_task = RenderTask::new_picture(
|
||||
RenderTaskLocation::Dynamic(None, clipped.size),
|
||||
unclipped.size,
|
||||
prim_index,
|
||||
prim_instance.prim_index,
|
||||
clipped.origin,
|
||||
pic_state_for_children.tasks,
|
||||
uv_rect_kind,
|
||||
|
@ -743,7 +734,7 @@ impl PicturePrimitive {
|
|||
let picture_task = RenderTask::new_picture(
|
||||
RenderTaskLocation::Dynamic(None, clipped.size),
|
||||
unclipped.size,
|
||||
prim_index,
|
||||
prim_instance.prim_index,
|
||||
clipped.origin,
|
||||
pic_state_for_children.tasks,
|
||||
uv_rect_kind,
|
||||
|
@ -765,7 +756,7 @@ impl PicturePrimitive {
|
|||
let picture_task = RenderTask::new_picture(
|
||||
RenderTaskLocation::Dynamic(None, clipped.size),
|
||||
unclipped.size,
|
||||
prim_index,
|
||||
prim_instance.prim_index,
|
||||
clipped.origin,
|
||||
pic_state_for_children.tasks,
|
||||
uv_rect_kind,
|
||||
|
|
|
@ -325,6 +325,11 @@ impl FontContext {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn delete_font_instance(&mut self, instance: &FontInstance) {
|
||||
// Remove the CoreText font corresponding to this instance.
|
||||
self.ct_fonts.remove(&(instance.font_key, instance.size, instance.variations.clone()));
|
||||
}
|
||||
|
||||
fn get_ct_font(
|
||||
&mut self,
|
||||
font_key: FontKey,
|
||||
|
|
|
@ -240,6 +240,10 @@ impl FontContext {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn delete_font_instance(&mut self, _instance: &FontInstance) {
|
||||
// This backend does not yet support variations, so there is nothing to do here.
|
||||
}
|
||||
|
||||
fn load_glyph(&self, font: &FontInstance, glyph: &GlyphKey) -> Option<(FT_GlyphSlot, f32)> {
|
||||
debug_assert!(self.faces.contains_key(&font.font_key));
|
||||
let face = self.faces.get(&font.font_key).unwrap();
|
||||
|
|
|
@ -145,6 +145,18 @@ impl FontContext {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn delete_font_instance(&mut self, instance: &FontInstance) {
|
||||
// Ensure we don't keep around excessive amounts of stale variations.
|
||||
if !instance.variations.is_empty() {
|
||||
let sims = if instance.flags.contains(FontInstanceFlags::SYNTHETIC_BOLD) {
|
||||
dwrote::DWRITE_FONT_SIMULATIONS_BOLD
|
||||
} else {
|
||||
dwrote::DWRITE_FONT_SIMULATIONS_NONE
|
||||
};
|
||||
self.variations.remove(&(instance.font_key, sims, instance.variations.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
// Assumes RGB format from dwrite, which is 3 bytes per pixel as dwrite
|
||||
// doesn't output an alpha value via GlyphRunAnalysis::CreateAlphaTexture
|
||||
#[allow(dead_code)]
|
||||
|
@ -185,7 +197,8 @@ impl FontContext {
|
|||
sims,
|
||||
&font.variations.iter().map(|var| {
|
||||
dwrote::DWRITE_FONT_AXIS_VALUE {
|
||||
axisTag: var.tag,
|
||||
// OpenType tags are big-endian, but DWrite wants little-endian.
|
||||
axisTag: var.tag.swap_bytes(),
|
||||
value: var.value,
|
||||
}
|
||||
}).collect::<Vec<_>>(),
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
use api::{AlphaType, BorderRadius, BuiltDisplayList, ClipMode, ColorF, PictureRect};
|
||||
use api::{DeviceIntRect, DeviceIntSize, DevicePixelScale, ExtendMode, DeviceRect, PictureToRasterTransform};
|
||||
use api::{FilterOp, GlyphInstance, GradientStop, ImageKey, ImageRendering, ItemRange, ItemTag, TileOffset};
|
||||
use api::{FilterOp, GlyphInstance, GradientStop, ImageKey, ImageRendering, ItemRange, TileOffset};
|
||||
use api::{RasterSpace, LayoutPoint, LayoutRect, LayoutSideOffsets, LayoutSize, LayoutToWorldTransform};
|
||||
use api::{LayoutVector2D, PremultipliedColorF, PropertyBinding, Shadow, YuvColorSpace, YuvFormat};
|
||||
use api::{DeviceIntSideOffsets, WorldPixel, BoxShadowClipMode, LayoutToWorldScale, NormalBorder, WorldRect};
|
||||
|
@ -21,6 +21,7 @@ use gpu_cache::{GpuBlockData, GpuCache, GpuCacheAddress, GpuCacheHandle, GpuData
|
|||
ToGpuBlocks};
|
||||
use gpu_types::BrushFlags;
|
||||
use image::{for_each_tile, for_each_repetition};
|
||||
use intern;
|
||||
use picture::{PictureCompositeMode, PicturePrimitive};
|
||||
#[cfg(debug_assertions)]
|
||||
use render_backend::FrameId;
|
||||
|
@ -238,32 +239,53 @@ impl GpuCacheAddress {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "capture", derive(Serialize))]
|
||||
#[cfg_attr(feature = "replay", derive(Deserialize))]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct PrimitiveKey {
|
||||
pub is_backface_visible: bool,
|
||||
}
|
||||
|
||||
impl PrimitiveKey {
|
||||
pub fn new(
|
||||
is_backface_visible: bool,
|
||||
) -> Self {
|
||||
PrimitiveKey {
|
||||
is_backface_visible,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "capture", derive(Serialize))]
|
||||
#[cfg_attr(feature = "replay", derive(Deserialize))]
|
||||
pub struct PrimitiveTemplate {
|
||||
pub is_backface_visible: bool,
|
||||
}
|
||||
|
||||
impl From<PrimitiveKey> for PrimitiveTemplate {
|
||||
fn from(item: PrimitiveKey) -> Self {
|
||||
PrimitiveTemplate {
|
||||
is_backface_visible: item.is_backface_visible,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Type definitions for interning primitives.
|
||||
#[cfg_attr(feature = "capture", derive(Serialize))]
|
||||
#[cfg_attr(feature = "replay", derive(Deserialize))]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct PrimitiveDataMarker;
|
||||
|
||||
pub type PrimitiveDataStore = intern::DataStore<PrimitiveKey, PrimitiveTemplate, PrimitiveDataMarker>;
|
||||
pub type PrimitiveDataHandle = intern::Handle<PrimitiveDataMarker>;
|
||||
pub type PrimitiveDataUpdateList = intern::UpdateList<PrimitiveKey>;
|
||||
pub type PrimitiveDataInterner = intern::Interner<PrimitiveKey, PrimitiveDataMarker>;
|
||||
|
||||
// TODO(gw): Pack the fields here better!
|
||||
#[derive(Debug)]
|
||||
pub struct PrimitiveMetadata {
|
||||
pub opacity: PrimitiveOpacity,
|
||||
pub clip_chain_id: ClipChainId,
|
||||
pub spatial_node_index: SpatialNodeIndex,
|
||||
pub gpu_location: GpuCacheHandle,
|
||||
pub clip_task_id: Option<RenderTaskId>,
|
||||
|
||||
// TODO(gw): In the future, we should just pull these
|
||||
// directly from the DL item, instead of
|
||||
// storing them here.
|
||||
pub local_rect: LayoutRect,
|
||||
pub local_clip_rect: LayoutRect,
|
||||
|
||||
pub is_backface_visible: bool,
|
||||
pub clipped_world_rect: Option<WorldRect>,
|
||||
|
||||
/// A tag used to identify this primitive outside of WebRender. This is
|
||||
/// used for returning useful data during hit testing.
|
||||
pub tag: Option<ItemTag>,
|
||||
|
||||
/// The last frame ID (of the `RenderTaskTree`) this primitive
|
||||
/// was prepared for rendering in.
|
||||
#[cfg(debug_assertions)]
|
||||
pub prepared_frame_id: FrameId,
|
||||
}
|
||||
|
||||
// Maintains a list of opacity bindings that have been collapsed into
|
||||
|
@ -1416,11 +1438,70 @@ impl Primitive {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct PrimitiveInstance {
|
||||
/// Index into the prim store containing information about
|
||||
/// the specific primitive. This will be removed once all
|
||||
/// primitive data is interned.
|
||||
pub prim_index: PrimitiveIndex,
|
||||
|
||||
// The current combined local clip for this primitive, from
|
||||
// the primitive local clip above and the current clip chain.
|
||||
/// Handle to the common interned data for this primitive.
|
||||
pub prim_data_handle: PrimitiveDataHandle,
|
||||
|
||||
/// The current combined local clip for this primitive, from
|
||||
/// the primitive local clip above and the current clip chain.
|
||||
pub combined_local_clip_rect: LayoutRect,
|
||||
|
||||
/// The last frame ID (of the `RenderTaskTree`) this primitive
|
||||
/// was prepared for rendering in.
|
||||
#[cfg(debug_assertions)]
|
||||
pub prepared_frame_id: FrameId,
|
||||
|
||||
/// The current world rect of this primitive, clipped to the
|
||||
/// world rect of the screen. None means the primitive is
|
||||
/// completely off-screen.
|
||||
pub clipped_world_rect: Option<WorldRect>,
|
||||
|
||||
/// If this primitive has a global clip mask, this identifies
|
||||
/// the render task for it.
|
||||
pub clip_task_id: Option<RenderTaskId>,
|
||||
|
||||
/// The main GPU cache handle that this primitive uses to
|
||||
/// store data accessible to shaders. This should be moved
|
||||
/// into the interned data in order to retain this between
|
||||
/// display list changes, but needs to be split into shared
|
||||
/// and per-instance data.
|
||||
pub gpu_location: GpuCacheHandle,
|
||||
|
||||
/// The current opacity of the primitive contents.
|
||||
pub opacity: PrimitiveOpacity,
|
||||
|
||||
/// ID of the clip chain that this primitive is clipped by.
|
||||
pub clip_chain_id: ClipChainId,
|
||||
|
||||
/// ID of the spatial node that this primitive is positioned by.
|
||||
pub spatial_node_index: SpatialNodeIndex,
|
||||
}
|
||||
|
||||
impl PrimitiveInstance {
|
||||
pub fn new(
|
||||
prim_index: PrimitiveIndex,
|
||||
prim_data_handle: PrimitiveDataHandle,
|
||||
clip_chain_id: ClipChainId,
|
||||
spatial_node_index: SpatialNodeIndex,
|
||||
) -> Self {
|
||||
PrimitiveInstance {
|
||||
prim_index,
|
||||
prim_data_handle,
|
||||
combined_local_clip_rect: LayoutRect::zero(),
|
||||
clipped_world_rect: None,
|
||||
#[cfg(debug_assertions)]
|
||||
prepared_frame_id: FrameId(0),
|
||||
clip_task_id: None,
|
||||
gpu_location: GpuCacheHandle::new(),
|
||||
opacity: PrimitiveOpacity::translucent(),
|
||||
clip_chain_id,
|
||||
spatial_node_index,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PrimitiveStore {
|
||||
|
@ -1450,57 +1531,18 @@ impl PrimitiveStore {
|
|||
&mut self,
|
||||
local_rect: &LayoutRect,
|
||||
local_clip_rect: &LayoutRect,
|
||||
is_backface_visible: bool,
|
||||
clip_chain_id: ClipChainId,
|
||||
spatial_node_index: SpatialNodeIndex,
|
||||
tag: Option<ItemTag>,
|
||||
container: PrimitiveContainer,
|
||||
) -> PrimitiveIndex {
|
||||
let prim_index = self.primitives.len();
|
||||
|
||||
let base_metadata = PrimitiveMetadata {
|
||||
clip_chain_id,
|
||||
gpu_location: GpuCacheHandle::new(),
|
||||
clip_task_id: None,
|
||||
spatial_node_index,
|
||||
local_rect: *local_rect,
|
||||
local_clip_rect: *local_clip_rect,
|
||||
is_backface_visible,
|
||||
clipped_world_rect: None,
|
||||
tag,
|
||||
opacity: PrimitiveOpacity::translucent(),
|
||||
#[cfg(debug_assertions)]
|
||||
prepared_frame_id: FrameId(0),
|
||||
};
|
||||
|
||||
let prim = match container {
|
||||
PrimitiveContainer::Brush(brush) => {
|
||||
let opacity = match brush.kind {
|
||||
BrushKind::Clear => PrimitiveOpacity::translucent(),
|
||||
BrushKind::Solid { ref color, .. } => PrimitiveOpacity::from_alpha(color.a),
|
||||
BrushKind::Image { .. } => PrimitiveOpacity::translucent(),
|
||||
BrushKind::YuvImage { .. } => PrimitiveOpacity::opaque(),
|
||||
BrushKind::RadialGradient { .. } => PrimitiveOpacity::translucent(),
|
||||
BrushKind::LinearGradient { stretch_size, tile_spacing, stops_opacity, .. } => {
|
||||
// If the coverage of the gradient extends to or beyond
|
||||
// the primitive rect, then the opacity can be determined
|
||||
// by the colors of the stops. If we have tiling / spacing
|
||||
// then we just assume the gradient is translucent for now.
|
||||
// (In the future we could consider segmenting in some cases).
|
||||
let stride = stretch_size + tile_spacing;
|
||||
if stride.width >= local_rect.size.width &&
|
||||
stride.height >= local_rect.size.height {
|
||||
stops_opacity
|
||||
} else {
|
||||
PrimitiveOpacity::translucent()
|
||||
}
|
||||
}
|
||||
BrushKind::Picture { .. } => PrimitiveOpacity::translucent(),
|
||||
BrushKind::Border { .. } => PrimitiveOpacity::translucent(),
|
||||
};
|
||||
|
||||
let metadata = PrimitiveMetadata {
|
||||
opacity,
|
||||
..base_metadata
|
||||
};
|
||||
|
||||
|
@ -1511,7 +1553,6 @@ impl PrimitiveStore {
|
|||
}
|
||||
PrimitiveContainer::TextRun(text_cpu) => {
|
||||
let metadata = PrimitiveMetadata {
|
||||
opacity: PrimitiveOpacity::translucent(),
|
||||
..base_metadata
|
||||
};
|
||||
|
||||
|
@ -1723,7 +1764,7 @@ impl PrimitiveStore {
|
|||
|
||||
if new_local_rect != prim.metadata.local_rect {
|
||||
prim.metadata.local_rect = new_local_rect;
|
||||
frame_state.gpu_cache.invalidate(&mut prim.metadata.gpu_location);
|
||||
frame_state.gpu_cache.invalidate(&mut prim_instance.gpu_location);
|
||||
pic_state.local_rect_changed = true;
|
||||
}
|
||||
|
||||
|
@ -1741,7 +1782,7 @@ impl PrimitiveStore {
|
|||
}
|
||||
|
||||
if is_passthrough {
|
||||
prim.metadata.clipped_world_rect = Some(pic_state.map_pic_to_world.bounds);
|
||||
prim_instance.clipped_world_rect = Some(pic_state.map_pic_to_world.bounds);
|
||||
} else {
|
||||
if prim.metadata.local_rect.size.width <= 0.0 ||
|
||||
prim.metadata.local_rect.size.height <= 0.0 {
|
||||
|
@ -1774,7 +1815,7 @@ impl PrimitiveStore {
|
|||
let clip_chain = frame_state
|
||||
.clip_store
|
||||
.build_clip_chain_instance(
|
||||
prim.metadata.clip_chain_id,
|
||||
prim_instance.clip_chain_id,
|
||||
local_rect,
|
||||
prim.metadata.local_clip_rect,
|
||||
prim_context.spatial_node_index,
|
||||
|
@ -1792,7 +1833,7 @@ impl PrimitiveStore {
|
|||
let clip_chain = match clip_chain {
|
||||
Some(clip_chain) => clip_chain,
|
||||
None => {
|
||||
prim.metadata.clipped_world_rect = None;
|
||||
prim_instance.clipped_world_rect = None;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
@ -1835,15 +1876,17 @@ impl PrimitiveStore {
|
|||
}
|
||||
};
|
||||
|
||||
prim.metadata.clipped_world_rect = Some(clipped_world_rect);
|
||||
prim_instance.clipped_world_rect = Some(clipped_world_rect);
|
||||
|
||||
prim.build_prim_segments_if_needed(
|
||||
prim_instance,
|
||||
pic_state,
|
||||
frame_state,
|
||||
frame_context,
|
||||
);
|
||||
|
||||
prim.update_clip_task(
|
||||
prim_instance,
|
||||
prim_context,
|
||||
clipped_world_rect,
|
||||
pic_state.raster_spatial_node_index,
|
||||
|
@ -1876,14 +1919,6 @@ impl PrimitiveStore {
|
|||
true
|
||||
}
|
||||
|
||||
// TODO(gw): Make this simpler / more efficient by tidying
|
||||
// up the logic that early outs from prepare_prim_for_render.
|
||||
pub fn reset_prim_visibility(&mut self) {
|
||||
for prim in &mut self.primitives {
|
||||
prim.metadata.clipped_world_rect = None;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prepare_primitives(
|
||||
&mut self,
|
||||
prim_instances: &mut Vec<PrimitiveInstance>,
|
||||
|
@ -1900,6 +1935,8 @@ impl PrimitiveStore {
|
|||
.display_list;
|
||||
|
||||
for prim_instance in prim_instances {
|
||||
prim_instance.clipped_world_rect = None;
|
||||
|
||||
let prim_index = prim_instance.prim_index;
|
||||
let is_chased = Some(prim_index) == self.chase_id;
|
||||
|
||||
|
@ -1908,24 +1945,21 @@ impl PrimitiveStore {
|
|||
prim_instance.prim_index, pic_context.pipeline_id);
|
||||
}
|
||||
|
||||
// TODO(gw): These workarounds for borrowck are unfortunate. We
|
||||
// should see if we can re-structure these to avoid so
|
||||
// many special borrow blocks.
|
||||
let (spatial_node_index, is_backface_visible) = {
|
||||
let prim = &self.primitives[prim_instance.prim_index.0];
|
||||
(prim.metadata.spatial_node_index, prim.metadata.is_backface_visible)
|
||||
};
|
||||
let is_backface_visible = frame_state
|
||||
.resources
|
||||
.prim_data_store[prim_instance.prim_data_handle]
|
||||
.is_backface_visible;
|
||||
|
||||
let spatial_node = &frame_context
|
||||
.clip_scroll_tree
|
||||
.spatial_nodes[spatial_node_index.0];
|
||||
.spatial_nodes[prim_instance.spatial_node_index.0];
|
||||
|
||||
// TODO(gw): Although constructing these is cheap, they are often
|
||||
// the same for many consecutive primitives, so it may
|
||||
// be worth caching the most recent context.
|
||||
let prim_context = PrimitiveContext::new(
|
||||
spatial_node,
|
||||
spatial_node_index,
|
||||
prim_instance.spatial_node_index,
|
||||
);
|
||||
|
||||
// Do some basic checks first, that can early out
|
||||
|
@ -1950,7 +1984,7 @@ impl PrimitiveStore {
|
|||
spatial_node.coordinate_system_id != CoordinateSystemId::root();
|
||||
|
||||
pic_state.map_local_to_pic.set_target_spatial_node(
|
||||
spatial_node_index,
|
||||
prim_instance.spatial_node_index,
|
||||
frame_context.clip_scroll_tree,
|
||||
);
|
||||
|
||||
|
@ -1992,7 +2026,7 @@ fn build_gradient_stops_request(
|
|||
|
||||
fn decompose_repeated_primitive(
|
||||
visible_tiles: &mut Vec<VisibleGradientTile>,
|
||||
instance: &PrimitiveInstance,
|
||||
instance: &mut PrimitiveInstance,
|
||||
metadata: &mut PrimitiveMetadata,
|
||||
stretch_size: &LayoutSize,
|
||||
tile_spacing: &LayoutSize,
|
||||
|
@ -2009,7 +2043,7 @@ fn decompose_repeated_primitive(
|
|||
.combined_local_clip_rect
|
||||
.intersection(&metadata.local_rect).unwrap();
|
||||
|
||||
let clipped_world_rect = &metadata
|
||||
let clipped_world_rect = &instance
|
||||
.clipped_world_rect
|
||||
.unwrap();
|
||||
|
||||
|
@ -2049,7 +2083,7 @@ fn decompose_repeated_primitive(
|
|||
// Clearing the screen rect has the effect of "culling out" the primitive
|
||||
// from the point of view of the batch builder, and ensures we don't hit
|
||||
// assertions later on because we didn't request any image.
|
||||
metadata.clipped_world_rect = None;
|
||||
instance.clipped_world_rect = None;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2262,6 +2296,7 @@ fn write_brush_segment_description(
|
|||
impl Primitive {
|
||||
fn update_clip_task_for_brush(
|
||||
&mut self,
|
||||
prim_instance: &PrimitiveInstance,
|
||||
root_spatial_node_index: SpatialNodeIndex,
|
||||
prim_bounding_rect: WorldRect,
|
||||
prim_context: &PrimitiveContext,
|
||||
|
@ -2309,7 +2344,7 @@ impl Primitive {
|
|||
let segment_clip_chain = frame_state
|
||||
.clip_store
|
||||
.build_clip_chain_instance(
|
||||
self.metadata.clip_chain_id,
|
||||
prim_instance.clip_chain_id,
|
||||
segment.local_rect,
|
||||
self.metadata.local_clip_rect,
|
||||
prim_context.spatial_node_index,
|
||||
|
@ -2341,8 +2376,11 @@ impl Primitive {
|
|||
// Returns true if the primitive *might* need a clip mask. If
|
||||
// false, there is no need to even check for clip masks for
|
||||
// this primitive.
|
||||
fn reset_clip_task(&mut self) -> bool {
|
||||
self.metadata.clip_task_id = None;
|
||||
fn reset_clip_task(
|
||||
&mut self,
|
||||
prim_instance: &mut PrimitiveInstance,
|
||||
) -> bool {
|
||||
prim_instance.clip_task_id = None;
|
||||
match self.details {
|
||||
PrimitiveDetails::Brush(ref mut brush) => {
|
||||
if let Some(ref mut desc) = brush.segment_desc {
|
||||
|
@ -2360,7 +2398,7 @@ impl Primitive {
|
|||
|
||||
fn prepare_prim_for_render_inner(
|
||||
&mut self,
|
||||
prim_instance: &PrimitiveInstance,
|
||||
prim_instance: &mut PrimitiveInstance,
|
||||
prim_context: &PrimitiveContext,
|
||||
pic_context: &PictureContext,
|
||||
pic_state: &mut PictureState,
|
||||
|
@ -2373,7 +2411,7 @@ impl Primitive {
|
|||
let metadata = &mut self.metadata;
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
metadata.prepared_frame_id = frame_state.render_tasks.frame_id();
|
||||
prim_instance.prepared_frame_id = frame_state.render_tasks.frame_id();
|
||||
}
|
||||
|
||||
match self.details {
|
||||
|
@ -2414,12 +2452,12 @@ impl Primitive {
|
|||
// If the opacity changed, invalidate the GPU cache so that
|
||||
// the new color for this primitive gets uploaded.
|
||||
if opacity_binding.update(frame_context.scene_properties) {
|
||||
frame_state.gpu_cache.invalidate(&mut metadata.gpu_location);
|
||||
frame_state.gpu_cache.invalidate(&mut prim_instance.gpu_location);
|
||||
}
|
||||
|
||||
// Update opacity for this primitive to ensure the correct
|
||||
// batching parameters are used.
|
||||
metadata.opacity.is_opaque =
|
||||
prim_instance.opacity.is_opaque =
|
||||
image_properties.descriptor.is_opaque &&
|
||||
opacity_binding.current == 1.0 &&
|
||||
color.a == 1.0;
|
||||
|
@ -2464,7 +2502,7 @@ impl Primitive {
|
|||
size.height += padding.vertical();
|
||||
|
||||
if padding != DeviceIntSideOffsets::zero() {
|
||||
metadata.opacity.is_opaque = false;
|
||||
prim_instance.opacity.is_opaque = false;
|
||||
}
|
||||
|
||||
let image_cache_key = ImageCacheKey {
|
||||
|
@ -2535,7 +2573,7 @@ impl Primitive {
|
|||
|
||||
let visible_rect = compute_conservative_visible_rect(
|
||||
prim_context,
|
||||
&metadata.clipped_world_rect.unwrap(),
|
||||
&prim_instance.clipped_world_rect.unwrap(),
|
||||
&tight_clip_rect
|
||||
);
|
||||
|
||||
|
@ -2595,7 +2633,7 @@ impl Primitive {
|
|||
// Clearing the screen rect has the effect of "culling out" the primitive
|
||||
// from the point of view of the batch builder, and ensures we don't hit
|
||||
// assertions later on because we didn't request any image.
|
||||
metadata.clipped_world_rect = None;
|
||||
prim_instance.clipped_world_rect = None;
|
||||
}
|
||||
} else if request_source_image {
|
||||
frame_state.resource_cache.request_image(
|
||||
|
@ -2606,6 +2644,8 @@ impl Primitive {
|
|||
}
|
||||
}
|
||||
BrushKind::YuvImage { format, yuv_key, image_rendering, .. } => {
|
||||
prim_instance.opacity = PrimitiveOpacity::opaque();
|
||||
|
||||
let channel_num = format.get_plane_num();
|
||||
debug_assert!(channel_num <= 3);
|
||||
for channel in 0 .. channel_num {
|
||||
|
@ -2629,7 +2669,7 @@ impl Primitive {
|
|||
if let Some(image_properties) = image_properties {
|
||||
// Update opacity for this primitive to ensure the correct
|
||||
// batching parameters are used.
|
||||
metadata.opacity.is_opaque =
|
||||
prim_instance.opacity.is_opaque =
|
||||
image_properties.descriptor.is_opaque;
|
||||
|
||||
frame_state.resource_cache.request_image(
|
||||
|
@ -2702,10 +2742,23 @@ impl Primitive {
|
|||
extend_mode,
|
||||
stretch_size,
|
||||
tile_spacing,
|
||||
stops_opacity,
|
||||
ref mut stops_handle,
|
||||
ref mut visible_tiles,
|
||||
..
|
||||
} => {
|
||||
// If the coverage of the gradient extends to or beyond
|
||||
// the primitive rect, then the opacity can be determined
|
||||
// by the colors of the stops. If we have tiling / spacing
|
||||
// then we just assume the gradient is translucent for now.
|
||||
// (In the future we could consider segmenting in some cases).
|
||||
let stride = stretch_size + tile_spacing;
|
||||
prim_instance.opacity = if stride.width >= metadata.local_rect.size.width &&
|
||||
stride.height >= metadata.local_rect.size.height {
|
||||
stops_opacity
|
||||
} else {
|
||||
PrimitiveOpacity::translucent()
|
||||
};
|
||||
|
||||
build_gradient_stops_request(
|
||||
stops_handle,
|
||||
|
@ -2746,13 +2799,13 @@ impl Primitive {
|
|||
}
|
||||
BrushKind::Picture(ref mut pic) => {
|
||||
if !pic.prepare_for_render(
|
||||
prim_instance.prim_index,
|
||||
prim_instance,
|
||||
metadata,
|
||||
pic_state,
|
||||
frame_context,
|
||||
frame_state,
|
||||
) {
|
||||
metadata.clipped_world_rect = None;
|
||||
prim_instance.clipped_world_rect = None;
|
||||
}
|
||||
}
|
||||
BrushKind::Solid { ref color, ref mut opacity_binding, .. } => {
|
||||
|
@ -2761,9 +2814,9 @@ impl Primitive {
|
|||
// the opacity field that controls which batches this primitive
|
||||
// will be added to.
|
||||
if opacity_binding.update(frame_context.scene_properties) {
|
||||
metadata.opacity = PrimitiveOpacity::from_alpha(opacity_binding.current * color.a);
|
||||
frame_state.gpu_cache.invalidate(&mut metadata.gpu_location);
|
||||
frame_state.gpu_cache.invalidate(&mut prim_instance.gpu_location);
|
||||
}
|
||||
prim_instance.opacity = PrimitiveOpacity::from_alpha(opacity_binding.current * color.a);
|
||||
}
|
||||
BrushKind::Clear => {}
|
||||
}
|
||||
|
@ -2776,7 +2829,7 @@ impl Primitive {
|
|||
}
|
||||
|
||||
// Mark this GPU resource as required for this frame.
|
||||
if let Some(mut request) = frame_state.gpu_cache.request(&mut metadata.gpu_location) {
|
||||
if let Some(mut request) = frame_state.gpu_cache.request(&mut prim_instance.gpu_location) {
|
||||
match self.details {
|
||||
PrimitiveDetails::TextRun(ref mut text) => {
|
||||
text.write_gpu_blocks(&mut request);
|
||||
|
@ -2811,6 +2864,7 @@ impl Primitive {
|
|||
|
||||
fn update_clip_task(
|
||||
&mut self,
|
||||
prim_instance: &mut PrimitiveInstance,
|
||||
prim_context: &PrimitiveContext,
|
||||
prim_bounding_rect: WorldRect,
|
||||
root_spatial_node_index: SpatialNodeIndex,
|
||||
|
@ -2826,12 +2880,13 @@ impl Primitive {
|
|||
}
|
||||
// Reset clips from previous frames since we may clip differently each frame.
|
||||
// If this primitive never needs clip masks, just return straight away.
|
||||
if !self.reset_clip_task() {
|
||||
if !self.reset_clip_task(prim_instance) {
|
||||
return;
|
||||
}
|
||||
|
||||
// First try to render this primitive's mask using optimized brush rendering.
|
||||
if self.update_clip_task_for_brush(
|
||||
prim_instance,
|
||||
root_spatial_node_index,
|
||||
prim_bounding_rect,
|
||||
prim_context,
|
||||
|
@ -2868,10 +2923,10 @@ impl Primitive {
|
|||
|
||||
let clip_task_id = frame_state.render_tasks.add(clip_task);
|
||||
if cfg!(debug_assertions) && is_chased {
|
||||
println!("\tcreated task {:?} with world rect {:?}",
|
||||
clip_task_id, self.metadata.clipped_world_rect);
|
||||
println!("\tcreated task {:?} with device rect {:?}",
|
||||
clip_task_id, device_rect);
|
||||
}
|
||||
self.metadata.clip_task_id = Some(clip_task_id);
|
||||
prim_instance.clip_task_id = Some(clip_task_id);
|
||||
pic_state.tasks.push(clip_task_id);
|
||||
}
|
||||
}
|
||||
|
@ -2879,6 +2934,7 @@ impl Primitive {
|
|||
|
||||
fn build_prim_segments_if_needed(
|
||||
&mut self,
|
||||
prim_instance: &mut PrimitiveInstance,
|
||||
pic_state: &mut PictureState,
|
||||
frame_state: &mut FrameBuildingState,
|
||||
frame_context: &FrameBuildingContext,
|
||||
|
@ -2960,7 +3016,7 @@ impl Primitive {
|
|||
|
||||
// The segments have changed, so force the GPU cache to
|
||||
// re-upload the primitive information.
|
||||
frame_state.gpu_cache.invalidate(&mut self.metadata.gpu_location);
|
||||
frame_state.gpu_cache.invalidate(&mut prim_instance.gpu_location);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ use frame_builder::{FrameBuilder, FrameBuilderConfig};
|
|||
use gpu_cache::GpuCache;
|
||||
use hit_test::{HitTest, HitTester};
|
||||
use internal_types::{DebugOutput, FastHashMap, FastHashSet, RenderedDocument, ResultMsg};
|
||||
use prim_store::PrimitiveDataStore;
|
||||
use profiler::{BackendProfileCounters, IpcProfileCounters, ResourceProfileCounters};
|
||||
use record::ApiRecordingReceiver;
|
||||
use renderer::{AsyncPropertySampler, PipelineInfo};
|
||||
|
@ -89,15 +90,20 @@ pub struct FrameId(pub u32);
|
|||
#[cfg_attr(feature = "capture", derive(Serialize))]
|
||||
#[cfg_attr(feature = "replay", derive(Deserialize))]
|
||||
pub struct FrameResources {
|
||||
// The store of currently active / available clip nodes. This is kept
|
||||
// in sync with the clip interner in the scene builder for each document.
|
||||
/// The store of currently active / available clip nodes. This is kept
|
||||
/// in sync with the clip interner in the scene builder for each document.
|
||||
pub clip_data_store: ClipDataStore,
|
||||
|
||||
/// Currently active / available primitives. Kept in sync with the
|
||||
/// primitive interner in the scene builder, per document.
|
||||
pub prim_data_store: PrimitiveDataStore,
|
||||
}
|
||||
|
||||
impl FrameResources {
|
||||
fn new() -> Self {
|
||||
FrameResources {
|
||||
clip_data_store: ClipDataStore::new(),
|
||||
prim_data_store: PrimitiveDataStore::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -139,6 +145,7 @@ struct Document {
|
|||
/// before rendering again.
|
||||
frame_is_valid: bool,
|
||||
hit_tester_is_valid: bool,
|
||||
rendered_frame_is_valid: bool,
|
||||
|
||||
resources: FrameResources,
|
||||
}
|
||||
|
@ -169,6 +176,7 @@ impl Document {
|
|||
dynamic_properties: SceneProperties::new(),
|
||||
frame_is_valid: false,
|
||||
hit_tester_is_valid: false,
|
||||
rendered_frame_is_valid: false,
|
||||
resources: FrameResources::new(),
|
||||
}
|
||||
}
|
||||
|
@ -656,6 +664,7 @@ impl RenderBackend {
|
|||
replace(&mut txn.frame_ops, Vec::new()),
|
||||
replace(&mut txn.notifications, Vec::new()),
|
||||
txn.render_frame,
|
||||
txn.invalidate_rendered_frame,
|
||||
&mut frame_counter,
|
||||
&mut profile_counters,
|
||||
has_built_scene,
|
||||
|
@ -936,6 +945,7 @@ impl RenderBackend {
|
|||
notifications: transaction_msg.notifications,
|
||||
set_root_pipeline: None,
|
||||
render_frame: transaction_msg.generate_frame,
|
||||
invalidate_rendered_frame: transaction_msg.invalidate_rendered_frame,
|
||||
});
|
||||
|
||||
self.resource_cache.pre_scene_building_update(
|
||||
|
@ -971,6 +981,7 @@ impl RenderBackend {
|
|||
replace(&mut txn.frame_ops, Vec::new()),
|
||||
replace(&mut txn.notifications, Vec::new()),
|
||||
txn.render_frame,
|
||||
txn.invalidate_rendered_frame,
|
||||
frame_counter,
|
||||
profile_counters,
|
||||
false
|
||||
|
@ -1008,6 +1019,7 @@ impl RenderBackend {
|
|||
mut frame_ops: Vec<FrameMsg>,
|
||||
mut notifications: Vec<NotificationRequest>,
|
||||
mut render_frame: bool,
|
||||
invalidate_rendered_frame: bool,
|
||||
frame_counter: &mut u32,
|
||||
profile_counters: &mut BackendProfileCounters,
|
||||
has_built_scene: bool,
|
||||
|
@ -1031,6 +1043,7 @@ impl RenderBackend {
|
|||
// during the scene build, apply them to the data store now.
|
||||
if let Some(updates) = doc_resource_updates {
|
||||
doc.resources.clip_data_store.apply_updates(updates.clip_updates);
|
||||
doc.resources.prim_data_store.apply_updates(updates.prim_updates);
|
||||
}
|
||||
|
||||
// TODO: this scroll variable doesn't necessarily mean we scrolled. It is only used
|
||||
|
@ -1068,11 +1081,19 @@ impl RenderBackend {
|
|||
// Avoid re-building the frame if the current built frame is still valid.
|
||||
let build_frame = render_frame && !doc.frame_is_valid;
|
||||
|
||||
// Request composite is true when we want to composite frame even when
|
||||
// there is no frame update. This happens when video frame is updated under
|
||||
// external image with NativeTexture or when platform requested to composite frame.
|
||||
if invalidate_rendered_frame {
|
||||
doc.rendered_frame_is_valid = false;
|
||||
}
|
||||
|
||||
let mut frame_build_time = None;
|
||||
if build_frame && doc.has_pixels() {
|
||||
profile_scope!("generate frame");
|
||||
|
||||
*frame_counter += 1;
|
||||
doc.rendered_frame_is_valid = false;
|
||||
|
||||
// borrow ck hack for profile_counters
|
||||
let (pending_update, rendered_document) = {
|
||||
|
@ -1133,6 +1154,12 @@ impl RenderBackend {
|
|||
// otherwise gecko can get into a state where it waits (forever) for the
|
||||
// transaction to complete before sending new work.
|
||||
if requested_frame {
|
||||
// If rendered frame is already valid, there is no need to render frame.
|
||||
if doc.rendered_frame_is_valid {
|
||||
render_frame = false;
|
||||
} else if render_frame {
|
||||
doc.rendered_frame_is_valid = true;
|
||||
}
|
||||
self.notifier.new_frame_ready(document_id, scroll, render_frame, frame_build_time);
|
||||
}
|
||||
|
||||
|
@ -1454,6 +1481,7 @@ impl RenderBackend {
|
|||
hit_tester: None,
|
||||
frame_is_valid: false,
|
||||
hit_tester_is_valid: false,
|
||||
rendered_frame_is_valid: false,
|
||||
resources: frame_resources,
|
||||
};
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ use capture::{CaptureConfig, ExternalCaptureImage, PlainExternalImage};
|
|||
use debug_colors;
|
||||
use device::{DepthFunction, Device, FrameId, Program, UploadMethod, Texture, PBO};
|
||||
use device::{ExternalTexture, FBOId, TextureSlot};
|
||||
use device::{FileWatcherHandler, ShaderError, TextureFilter,
|
||||
use device::{ShaderError, TextureFilter,
|
||||
VertexUsageHint, VAO, VBO, CustomVAO};
|
||||
use device::{ProgramCache, ReadPixelsFormat};
|
||||
#[cfg(feature = "debug_renderer")]
|
||||
|
@ -64,7 +64,7 @@ use rayon::{ThreadPool, ThreadPoolBuilder};
|
|||
use record::ApiRecordingReceiver;
|
||||
use render_backend::RenderBackend;
|
||||
use scene_builder::{SceneBuilder, LowPrioritySceneBuilder};
|
||||
use shade::Shaders;
|
||||
use shade::{Shaders, WrShaders};
|
||||
use smallvec::SmallVec;
|
||||
use render_task::{RenderTask, RenderTaskKind, RenderTaskTree};
|
||||
use resource_cache::ResourceCache;
|
||||
|
@ -80,8 +80,9 @@ use std::os::raw::c_void;
|
|||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||
use std::sync::mpsc::{channel, Receiver};
|
||||
use std::thread;
|
||||
use std::cell::RefCell;
|
||||
use texture_cache::TextureCache;
|
||||
use thread_profiler::{register_thread_with_profiler, write_profile};
|
||||
use tiling::{AlphaRenderTarget, ColorRenderTarget};
|
||||
|
@ -1341,18 +1342,6 @@ impl VertexDataTexture {
|
|||
}
|
||||
}
|
||||
|
||||
struct FileWatcher {
|
||||
notifier: Box<RenderNotifier>,
|
||||
result_tx: Sender<ResultMsg>,
|
||||
}
|
||||
|
||||
impl FileWatcherHandler for FileWatcher {
|
||||
fn file_changed(&self, path: PathBuf) {
|
||||
self.result_tx.send(ResultMsg::RefreshShader(path)).ok();
|
||||
self.notifier.wake_up();
|
||||
}
|
||||
}
|
||||
|
||||
struct FrameOutput {
|
||||
last_access: FrameId,
|
||||
fbo_id: FBOId,
|
||||
|
@ -1433,7 +1422,7 @@ pub struct Renderer {
|
|||
pending_shader_updates: Vec<PathBuf>,
|
||||
active_documents: Vec<(DocumentId, RenderedDocument)>,
|
||||
|
||||
shaders: Shaders,
|
||||
shaders: Rc<RefCell<Shaders>>,
|
||||
|
||||
pub gpu_glyph_renderer: GpuGlyphRenderer,
|
||||
|
||||
|
@ -1559,6 +1548,7 @@ impl Renderer {
|
|||
gl: Rc<gl::Gl>,
|
||||
notifier: Box<RenderNotifier>,
|
||||
mut options: RendererOptions,
|
||||
shaders: Option<&mut WrShaders>
|
||||
) -> Result<(Self, RenderApiSender), RendererError> {
|
||||
let (api_tx, api_rx) = channel::msg_channel()?;
|
||||
let (payload_tx, payload_rx) = channel::payload_channel()?;
|
||||
|
@ -1567,16 +1557,10 @@ impl Renderer {
|
|||
|
||||
let debug_server = DebugServer::new(api_tx.clone());
|
||||
|
||||
let file_watch_handler = FileWatcher {
|
||||
result_tx: result_tx.clone(),
|
||||
notifier: notifier.clone(),
|
||||
};
|
||||
|
||||
let mut device = Device::new(
|
||||
gl,
|
||||
options.resource_override_path.clone(),
|
||||
options.upload_method.clone(),
|
||||
Box::new(file_watch_handler),
|
||||
options.cached_programs.take(),
|
||||
);
|
||||
|
||||
|
@ -1608,7 +1592,10 @@ impl Renderer {
|
|||
|
||||
device.begin_frame();
|
||||
|
||||
let shaders = Shaders::new(&mut device, gl_type, &options)?;
|
||||
let shaders = match shaders {
|
||||
Some(shaders) => Rc::clone(&shaders.shaders),
|
||||
None => Rc::new(RefCell::new(Shaders::new(&mut device, gl_type, &options)?)),
|
||||
};
|
||||
|
||||
let backend_profile_counters = BackendProfileCounters::new();
|
||||
|
||||
|
@ -2965,14 +2952,14 @@ impl Renderer {
|
|||
|
||||
match source {
|
||||
TextureSource::PrevPassColor => {
|
||||
self.shaders.cs_scale_rgba8.bind(&mut self.device,
|
||||
&projection,
|
||||
&mut self.renderer_errors);
|
||||
self.shaders.borrow_mut().cs_scale_rgba8.bind(&mut self.device,
|
||||
&projection,
|
||||
&mut self.renderer_errors);
|
||||
}
|
||||
TextureSource::PrevPassAlpha => {
|
||||
self.shaders.cs_scale_a8.bind(&mut self.device,
|
||||
&projection,
|
||||
&mut self.renderer_errors);
|
||||
self.shaders.borrow_mut().cs_scale_a8.bind(&mut self.device,
|
||||
&projection,
|
||||
&mut self.renderer_errors);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
@ -3069,7 +3056,7 @@ impl Renderer {
|
|||
let _timer = self.gpu_profile.start_timer(GPU_TAG_BLUR);
|
||||
|
||||
self.set_blend(false, framebuffer_kind);
|
||||
self.shaders.cs_blur_rgba8
|
||||
self.shaders.borrow_mut().cs_blur_rgba8
|
||||
.bind(&mut self.device, projection, &mut self.renderer_errors);
|
||||
|
||||
if !target.vertical_blurs.is_empty() {
|
||||
|
@ -3127,7 +3114,7 @@ impl Renderer {
|
|||
.iter()
|
||||
.rev()
|
||||
{
|
||||
self.shaders
|
||||
self.shaders.borrow_mut()
|
||||
.get(&batch.key, self.debug_flags)
|
||||
.bind(
|
||||
&mut self.device, projection,
|
||||
|
@ -3174,7 +3161,7 @@ impl Renderer {
|
|||
}
|
||||
|
||||
for batch in &alpha_batch_container.alpha_batches {
|
||||
self.shaders
|
||||
self.shaders.borrow_mut()
|
||||
.get(&batch.key, self.debug_flags)
|
||||
.bind(
|
||||
&mut self.device, projection,
|
||||
|
@ -3361,7 +3348,7 @@ impl Renderer {
|
|||
let _timer = self.gpu_profile.start_timer(GPU_TAG_BLUR);
|
||||
|
||||
self.set_blend(false, FramebufferKind::Other);
|
||||
self.shaders.cs_blur_a8
|
||||
self.shaders.borrow_mut().cs_blur_a8
|
||||
.bind(&mut self.device, projection, &mut self.renderer_errors);
|
||||
|
||||
if !target.vertical_blurs.is_empty() {
|
||||
|
@ -3396,7 +3383,7 @@ impl Renderer {
|
|||
// draw rounded cornered rectangles
|
||||
if !target.clip_batcher.rectangles.is_empty() {
|
||||
let _gm2 = self.gpu_profile.start_marker("clip rectangles");
|
||||
self.shaders.cs_clip_rectangle.bind(
|
||||
self.shaders.borrow_mut().cs_clip_rectangle.bind(
|
||||
&mut self.device,
|
||||
projection,
|
||||
&mut self.renderer_errors,
|
||||
|
@ -3418,7 +3405,7 @@ impl Renderer {
|
|||
TextureSource::Invalid,
|
||||
],
|
||||
};
|
||||
self.shaders.cs_clip_box_shadow
|
||||
self.shaders.borrow_mut().cs_clip_box_shadow
|
||||
.bind(&mut self.device, projection, &mut self.renderer_errors);
|
||||
self.draw_instanced_batch(
|
||||
items,
|
||||
|
@ -3431,7 +3418,7 @@ impl Renderer {
|
|||
// draw line decoration clips
|
||||
if !target.clip_batcher.line_decorations.is_empty() {
|
||||
let _gm2 = self.gpu_profile.start_marker("clip lines");
|
||||
self.shaders.cs_clip_line.bind(
|
||||
self.shaders.borrow_mut().cs_clip_line.bind(
|
||||
&mut self.device,
|
||||
projection,
|
||||
&mut self.renderer_errors,
|
||||
|
@ -3454,7 +3441,7 @@ impl Renderer {
|
|||
TextureSource::Invalid,
|
||||
],
|
||||
};
|
||||
self.shaders.cs_clip_image
|
||||
self.shaders.borrow_mut().cs_clip_image
|
||||
.bind(&mut self.device, projection, &mut self.renderer_errors);
|
||||
self.draw_instanced_batch(
|
||||
items,
|
||||
|
@ -3530,7 +3517,7 @@ impl Renderer {
|
|||
self.set_blend_mode_premultiplied_alpha(FramebufferKind::Other);
|
||||
|
||||
if !target.border_segments_solid.is_empty() {
|
||||
self.shaders.cs_border_solid.bind(
|
||||
self.shaders.borrow_mut().cs_border_solid.bind(
|
||||
&mut self.device,
|
||||
&projection,
|
||||
&mut self.renderer_errors,
|
||||
|
@ -3545,7 +3532,7 @@ impl Renderer {
|
|||
}
|
||||
|
||||
if !target.border_segments_complex.is_empty() {
|
||||
self.shaders.cs_border_segment.bind(
|
||||
self.shaders.borrow_mut().cs_border_segment.bind(
|
||||
&mut self.device,
|
||||
&projection,
|
||||
&mut self.renderer_errors,
|
||||
|
@ -3566,10 +3553,13 @@ impl Renderer {
|
|||
if !target.horizontal_blurs.is_empty() {
|
||||
let _timer = self.gpu_profile.start_timer(GPU_TAG_BLUR);
|
||||
|
||||
match target.target_kind {
|
||||
RenderTargetKind::Alpha => &mut self.shaders.cs_blur_a8,
|
||||
RenderTargetKind::Color => &mut self.shaders.cs_blur_rgba8,
|
||||
}.bind(&mut self.device, &projection, &mut self.renderer_errors);
|
||||
{
|
||||
let mut shaders = self.shaders.borrow_mut();
|
||||
match target.target_kind {
|
||||
RenderTargetKind::Alpha => &mut shaders.cs_blur_a8,
|
||||
RenderTargetKind::Color => &mut shaders.cs_blur_rgba8,
|
||||
}.bind(&mut self.device, &projection, &mut self.renderer_errors);
|
||||
}
|
||||
|
||||
self.draw_instanced_batch(
|
||||
&target.horizontal_blurs,
|
||||
|
@ -4243,7 +4233,9 @@ impl Renderer {
|
|||
for (_, target) in self.output_targets {
|
||||
self.device.delete_fbo(target.fbo_id);
|
||||
}
|
||||
self.shaders.deinit(&mut self.device);
|
||||
if let Ok(shaders) = Rc::try_unwrap(self.shaders) {
|
||||
shaders.into_inner().deinit(&mut self.device);
|
||||
}
|
||||
#[cfg(feature = "capture")]
|
||||
self.device.delete_fbo(self.read_fbo);
|
||||
#[cfg(feature = "replay")]
|
||||
|
|
|
@ -1395,7 +1395,7 @@ impl ResourceCache {
|
|||
debug_assert_eq!(self.state, State::Idle);
|
||||
self.state = State::AddResources;
|
||||
self.texture_cache.begin_frame(frame_id);
|
||||
self.cached_glyphs.begin_frame(&self.texture_cache, &self.cached_render_tasks);
|
||||
self.cached_glyphs.begin_frame(&self.texture_cache, &self.cached_render_tasks, &mut self.glyph_rasterizer);
|
||||
self.cached_render_tasks.begin_frame(&mut self.texture_cache);
|
||||
self.current_frame_id = frame_id;
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ use clip_scroll_tree::ClipScrollTree;
|
|||
use display_list_flattener::DisplayListFlattener;
|
||||
use internal_types::{FastHashMap, FastHashSet};
|
||||
use picture::PictureIdGenerator;
|
||||
use prim_store::{PrimitiveDataInterner, PrimitiveDataUpdateList};
|
||||
use resource_cache::FontInstanceMap;
|
||||
use render_backend::DocumentView;
|
||||
use renderer::{PipelineInfo, SceneBuilderHooks};
|
||||
|
@ -27,6 +28,7 @@ use std::time::Duration;
|
|||
|
||||
pub struct DocumentResourceUpdates {
|
||||
pub clip_updates: ClipDataUpdateList,
|
||||
pub prim_updates: PrimitiveDataUpdateList,
|
||||
}
|
||||
|
||||
/// Represents the work associated to a transaction before scene building.
|
||||
|
@ -44,6 +46,7 @@ pub struct Transaction {
|
|||
pub notifications: Vec<NotificationRequest>,
|
||||
pub set_root_pipeline: Option<PipelineId>,
|
||||
pub render_frame: bool,
|
||||
pub invalidate_rendered_frame: bool,
|
||||
}
|
||||
|
||||
impl Transaction {
|
||||
|
@ -77,6 +80,7 @@ pub struct BuiltTransaction {
|
|||
pub scene_build_start_time: u64,
|
||||
pub scene_build_end_time: u64,
|
||||
pub render_frame: bool,
|
||||
pub invalidate_rendered_frame: bool,
|
||||
}
|
||||
|
||||
pub struct DisplayListUpdate {
|
||||
|
@ -160,12 +164,14 @@ pub enum SceneSwapResult {
|
|||
#[cfg_attr(feature = "replay", derive(Deserialize))]
|
||||
pub struct DocumentResources {
|
||||
pub clip_interner: ClipDataInterner,
|
||||
pub prim_interner: PrimitiveDataInterner,
|
||||
}
|
||||
|
||||
impl DocumentResources {
|
||||
fn new() -> Self {
|
||||
DocumentResources {
|
||||
clip_interner: ClipDataInterner::new(),
|
||||
prim_interner: PrimitiveDataInterner::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -334,9 +340,15 @@ impl SceneBuilder {
|
|||
.clip_interner
|
||||
.end_frame_and_get_pending_updates();
|
||||
|
||||
let prim_updates = item
|
||||
.doc_resources
|
||||
.prim_interner
|
||||
.end_frame_and_get_pending_updates();
|
||||
|
||||
doc_resource_updates = Some(
|
||||
DocumentResourceUpdates {
|
||||
clip_updates,
|
||||
prim_updates,
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -358,6 +370,7 @@ impl SceneBuilder {
|
|||
let txn = Box::new(BuiltTransaction {
|
||||
document_id: item.document_id,
|
||||
render_frame: item.build_frame,
|
||||
invalidate_rendered_frame: false,
|
||||
built_scene,
|
||||
resource_updates: Vec::new(),
|
||||
rasterized_blobs: Vec::new(),
|
||||
|
@ -433,9 +446,15 @@ impl SceneBuilder {
|
|||
.clip_interner
|
||||
.end_frame_and_get_pending_updates();
|
||||
|
||||
let prim_updates = doc
|
||||
.resources
|
||||
.prim_interner
|
||||
.end_frame_and_get_pending_updates();
|
||||
|
||||
doc_resource_updates = Some(
|
||||
DocumentResourceUpdates {
|
||||
clip_updates,
|
||||
prim_updates,
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -468,6 +487,7 @@ impl SceneBuilder {
|
|||
Box::new(BuiltTransaction {
|
||||
document_id: txn.document_id,
|
||||
render_frame: txn.render_frame,
|
||||
invalidate_rendered_frame: txn.invalidate_rendered_frame,
|
||||
built_scene,
|
||||
rasterized_blobs,
|
||||
resource_updates: replace(&mut txn.resource_updates, Vec::new()),
|
||||
|
|
|
@ -20,6 +20,8 @@ use renderer::{
|
|||
use gleam::gl::GlType;
|
||||
use time::precise_time_ns;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
impl ImageBufferKind {
|
||||
pub(crate) fn get_feature_string(&self) -> &'static str {
|
||||
|
@ -817,3 +819,11 @@ impl Shaders {
|
|||
self.ps_split_composite.deinit(device);
|
||||
}
|
||||
}
|
||||
|
||||
// A wrapper around a strong reference to a Shaders
|
||||
// object. We have this so that external (ffi)
|
||||
// consumers can own a reference to a shared Shaders
|
||||
// instance without understanding rust's refcounting.
|
||||
pub struct WrShaders {
|
||||
pub shaders: Rc<RefCell<Shaders>>,
|
||||
}
|
||||
|
|
|
@ -251,10 +251,15 @@ impl SpatialNode {
|
|||
SpatialNodeType::ReferenceFrame(ref mut info) => {
|
||||
// Resolve the transform against any property bindings.
|
||||
let source_transform = scene_properties.resolve_layout_transform(&info.source_transform);
|
||||
// Do a change-basis operation on the perspective matrix using
|
||||
// the scroll offset.
|
||||
let scrolled_perspective = info.source_perspective
|
||||
.pre_translate(&state.parent_accumulated_scroll_offset)
|
||||
.post_translate(-state.parent_accumulated_scroll_offset);
|
||||
info.resolved_transform =
|
||||
LayoutFastTransform::with_vector(info.origin_in_parent_reference_frame)
|
||||
.pre_mul(&source_transform.into())
|
||||
.pre_mul(&info.source_perspective);
|
||||
.pre_mul(&scrolled_perspective);
|
||||
|
||||
// The transformation for this viewport in world coordinates is the transformation for
|
||||
// our parent reference frame, plus any accumulated scrolling offsets from nodes
|
||||
|
|
|
@ -60,6 +60,8 @@ pub struct Transaction {
|
|||
|
||||
generate_frame: bool,
|
||||
|
||||
invalidate_rendered_frame: bool,
|
||||
|
||||
low_priority: bool,
|
||||
}
|
||||
|
||||
|
@ -73,6 +75,7 @@ impl Transaction {
|
|||
notifications: Vec::new(),
|
||||
use_scene_builder_thread: true,
|
||||
generate_frame: false,
|
||||
invalidate_rendered_frame: false,
|
||||
low_priority: false,
|
||||
}
|
||||
}
|
||||
|
@ -90,6 +93,7 @@ impl Transaction {
|
|||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
!self.generate_frame &&
|
||||
!self.invalidate_rendered_frame &&
|
||||
self.scene_ops.is_empty() &&
|
||||
self.frame_ops.is_empty() &&
|
||||
self.resource_updates.is_empty() &&
|
||||
|
@ -246,6 +250,16 @@ impl Transaction {
|
|||
self.generate_frame = true;
|
||||
}
|
||||
|
||||
/// Invalidate rendered frame. It ensure that frame will be rendered during
|
||||
/// next frame generation. WebRender could skip frame rendering if there
|
||||
/// is no update.
|
||||
/// But there are cases that needs to force rendering.
|
||||
/// - Content of image is updated by reusing same ExternalImageId.
|
||||
/// - Platform requests it if pixels become stale (like wakeup from standby).
|
||||
pub fn invalidate_rendered_frame(&mut self) {
|
||||
self.invalidate_rendered_frame = true;
|
||||
}
|
||||
|
||||
/// Supply a list of animated property bindings that should be used to resolve
|
||||
/// bindings in the current display list.
|
||||
pub fn update_dynamic_properties(&mut self, properties: DynamicProperties) {
|
||||
|
@ -280,6 +294,7 @@ impl Transaction {
|
|||
notifications: self.notifications,
|
||||
use_scene_builder_thread: self.use_scene_builder_thread,
|
||||
generate_frame: self.generate_frame,
|
||||
invalidate_rendered_frame: self.invalidate_rendered_frame,
|
||||
low_priority: self.low_priority,
|
||||
},
|
||||
self.payloads,
|
||||
|
@ -390,6 +405,7 @@ pub struct TransactionMsg {
|
|||
pub frame_ops: Vec<FrameMsg>,
|
||||
pub resource_updates: Vec<ResourceUpdate>,
|
||||
pub generate_frame: bool,
|
||||
pub invalidate_rendered_frame: bool,
|
||||
pub use_scene_builder_thread: bool,
|
||||
pub low_priority: bool,
|
||||
|
||||
|
@ -400,6 +416,7 @@ pub struct TransactionMsg {
|
|||
impl TransactionMsg {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
!self.generate_frame &&
|
||||
!self.invalidate_rendered_frame &&
|
||||
self.scene_ops.is_empty() &&
|
||||
self.frame_ops.is_empty() &&
|
||||
self.resource_updates.is_empty() &&
|
||||
|
@ -414,6 +431,7 @@ impl TransactionMsg {
|
|||
resource_updates: Vec::new(),
|
||||
notifications: Vec::new(),
|
||||
generate_frame: false,
|
||||
invalidate_rendered_frame: false,
|
||||
use_scene_builder_thread: false,
|
||||
low_priority: false,
|
||||
}
|
||||
|
@ -426,6 +444,7 @@ impl TransactionMsg {
|
|||
resource_updates: Vec::new(),
|
||||
notifications: Vec::new(),
|
||||
generate_frame: false,
|
||||
invalidate_rendered_frame: false,
|
||||
use_scene_builder_thread: false,
|
||||
low_priority: false,
|
||||
}
|
||||
|
|
|
@ -743,57 +743,6 @@ pub struct ImageMask {
|
|||
pub repeat: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub enum LocalClip {
|
||||
Rect(LayoutRect),
|
||||
RoundedRect(LayoutRect, ComplexClipRegion),
|
||||
}
|
||||
|
||||
impl From<LayoutRect> for LocalClip {
|
||||
fn from(rect: LayoutRect) -> Self {
|
||||
LocalClip::Rect(rect)
|
||||
}
|
||||
}
|
||||
|
||||
impl LocalClip {
|
||||
pub fn clip_rect(&self) -> &LayoutRect {
|
||||
match *self {
|
||||
LocalClip::Rect(ref rect) => rect,
|
||||
LocalClip::RoundedRect(ref rect, _) => rect,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_with_offset(&self, offset: &LayoutVector2D) -> LocalClip {
|
||||
match *self {
|
||||
LocalClip::Rect(rect) => LocalClip::from(rect.translate(offset)),
|
||||
LocalClip::RoundedRect(rect, complex) => LocalClip::RoundedRect(
|
||||
rect.translate(offset),
|
||||
ComplexClipRegion {
|
||||
rect: complex.rect.translate(offset),
|
||||
radii: complex.radii,
|
||||
mode: complex.mode,
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clip_by(&self, rect: &LayoutRect) -> LocalClip {
|
||||
match *self {
|
||||
LocalClip::Rect(clip_rect) => {
|
||||
LocalClip::Rect(
|
||||
clip_rect.intersection(rect).unwrap_or_else(LayoutRect::zero)
|
||||
)
|
||||
}
|
||||
LocalClip::RoundedRect(clip_rect, complex) => {
|
||||
LocalClip::RoundedRect(
|
||||
clip_rect.intersection(rect).unwrap_or_else(LayoutRect::zero),
|
||||
complex,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
|
||||
pub enum ClipMode {
|
||||
|
|
|
@ -83,18 +83,8 @@ RenderThread::Start()
|
|||
|
||||
RefPtr<Runnable> runnable = WrapRunnable(
|
||||
RefPtr<RenderThread>(sRenderThread.get()),
|
||||
&RenderThread::InitSharedGLContext);
|
||||
&RenderThread::InitDeviceTask);
|
||||
sRenderThread->Loop()->PostTask(runnable.forget());
|
||||
|
||||
if (XRE_IsGPUProcess() &&
|
||||
gfx::gfxVars::UseWebRenderProgramBinary()) {
|
||||
MOZ_ASSERT(gfx::gfxVars::UseWebRender());
|
||||
// Initialize program cache if necessary
|
||||
RefPtr<Runnable> runnable = WrapRunnable(
|
||||
RefPtr<RenderThread>(sRenderThread.get()),
|
||||
&RenderThread::ProgramCacheTask);
|
||||
sRenderThread->Loop()->PostTask(runnable.forget());
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
|
@ -666,9 +656,19 @@ RenderThread::GetRenderTexture(wr::WrExternalImageId aExternalImageId)
|
|||
}
|
||||
|
||||
void
|
||||
RenderThread::ProgramCacheTask()
|
||||
RenderThread::InitDeviceTask()
|
||||
{
|
||||
ProgramCache();
|
||||
MOZ_ASSERT(IsInRenderThread());
|
||||
MOZ_ASSERT(!mSharedGL);
|
||||
|
||||
mSharedGL = CreateGLContext();
|
||||
if (XRE_IsGPUProcess() &&
|
||||
gfx::gfxVars::UseWebRenderProgramBinary()) {
|
||||
ProgramCache();
|
||||
}
|
||||
// Query the shared GL context to force the
|
||||
// lazy initialization to happen now.
|
||||
SharedGL();
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -732,21 +732,17 @@ RenderThread::ProgramCache()
|
|||
return mProgramCache.get();
|
||||
}
|
||||
|
||||
void
|
||||
RenderThread::InitSharedGLContext()
|
||||
{
|
||||
MOZ_ASSERT(IsInRenderThread());
|
||||
|
||||
if (!mSharedGL) {
|
||||
mSharedGL = CreateGLContext();
|
||||
}
|
||||
}
|
||||
|
||||
gl::GLContext*
|
||||
RenderThread::SharedGL()
|
||||
{
|
||||
MOZ_ASSERT(IsInRenderThread());
|
||||
InitSharedGLContext();
|
||||
if (!mSharedGL) {
|
||||
mSharedGL = CreateGLContext();
|
||||
mShaders = nullptr;
|
||||
}
|
||||
if (mSharedGL && !mShaders) {
|
||||
mShaders = MakeUnique<WebRenderShaders>(mSharedGL, mProgramCache.get());
|
||||
}
|
||||
|
||||
return mSharedGL.get();
|
||||
}
|
||||
|
@ -755,9 +751,20 @@ void
|
|||
RenderThread::ClearSharedGL()
|
||||
{
|
||||
MOZ_ASSERT(IsInRenderThread());
|
||||
mShaders = nullptr;
|
||||
mSharedGL = nullptr;
|
||||
}
|
||||
|
||||
WebRenderShaders::WebRenderShaders(gl::GLContext* gl,
|
||||
WebRenderProgramCache* programCache)
|
||||
{
|
||||
mGL = gl;
|
||||
mShaders = wr_shaders_new(gl, programCache ? programCache->Raw() : nullptr);
|
||||
}
|
||||
|
||||
WebRenderShaders::~WebRenderShaders() {
|
||||
wr_shaders_delete(mShaders, mGL.get());
|
||||
}
|
||||
|
||||
WebRenderThreadPool::WebRenderThreadPool()
|
||||
{
|
||||
|
@ -802,6 +809,12 @@ CreateGLContextANGLE()
|
|||
auto* egl = gl::GLLibraryEGL::Get();
|
||||
auto flags = gl::CreateContextFlags::PREFER_ES3;
|
||||
|
||||
if (egl->IsExtensionSupported(
|
||||
gl::GLLibraryEGL::MOZ_create_context_provoking_vertex_dont_care))
|
||||
{
|
||||
flags |= gl::CreateContextFlags::PROVOKING_VERTEX_DONT_CARE;
|
||||
}
|
||||
|
||||
// Create GLContext with dummy EGLSurface, the EGLSurface is not used.
|
||||
// Instread we override it with EGLSurface of SwapChain's back buffer.
|
||||
RefPtr<gl::GLContext> gl = gl::GLContextProviderEGL::CreateHeadless(flags, &discardFailureId);
|
||||
|
|
|
@ -59,6 +59,18 @@ protected:
|
|||
wr::WrProgramCache* mProgramCache;
|
||||
};
|
||||
|
||||
class WebRenderShaders {
|
||||
public:
|
||||
WebRenderShaders(gl::GLContext* gl, WebRenderProgramCache* programCache);
|
||||
~WebRenderShaders();
|
||||
|
||||
wr::WrShaders* RawShaders() { return mShaders; }
|
||||
|
||||
protected:
|
||||
RefPtr<gl::GLContext> mGL;
|
||||
wr::WrShaders* mShaders;
|
||||
};
|
||||
|
||||
/// Base class for an event that can be scheduled to run on the render thread.
|
||||
///
|
||||
/// The event can be passed through the same channels as regular WebRender messages
|
||||
|
@ -181,7 +193,8 @@ public:
|
|||
WebRenderProgramCache* ProgramCache();
|
||||
|
||||
/// Can only be called from the render thread.
|
||||
void InitSharedGLContext();
|
||||
WebRenderShaders* Shaders() { return mShaders.get(); }
|
||||
|
||||
/// Can only be called from the render thread.
|
||||
gl::GLContext* SharedGL();
|
||||
|
||||
|
@ -201,7 +214,7 @@ private:
|
|||
|
||||
void DeferredRenderTextureHostDestroy();
|
||||
void ShutDownTask(layers::SynchronousTask* aTask);
|
||||
void ProgramCacheTask();
|
||||
void InitDeviceTask();
|
||||
|
||||
void DoAccumulateMemoryReport(MemoryReport, const RefPtr<MemoryReportPromise::Private>&);
|
||||
|
||||
|
@ -210,7 +223,9 @@ private:
|
|||
base::Thread* const mThread;
|
||||
|
||||
WebRenderThreadPool mThreadPool;
|
||||
|
||||
UniquePtr<WebRenderProgramCache> mProgramCache;
|
||||
UniquePtr<WebRenderShaders> mShaders;
|
||||
|
||||
// An optional shared GLContext to be used for all
|
||||
// windows.
|
||||
|
|
|
@ -73,6 +73,8 @@ public:
|
|||
wr::Renderer* wrRenderer = nullptr;
|
||||
if (!wr_window_new(aWindowId, mSize.width, mSize.height, supportLowPriorityTransactions,
|
||||
compositor->gl(),
|
||||
aRenderThread.ProgramCache() ? aRenderThread.ProgramCache()->Raw() : nullptr,
|
||||
aRenderThread.Shaders() ? aRenderThread.Shaders()->RawShaders() : nullptr,
|
||||
aRenderThread.ThreadPool().Raw(),
|
||||
&WebRenderMallocSizeOf,
|
||||
mDocHandle, &wrRenderer,
|
||||
|
@ -91,9 +93,6 @@ public:
|
|||
if (wrRenderer && renderer) {
|
||||
wr::WrExternalImageHandler handler = renderer->GetExternalImageHandler();
|
||||
wr_renderer_set_external_image_handler(wrRenderer, &handler);
|
||||
if (gfx::gfxVars::UseWebRenderProgramBinary()) {
|
||||
wr_renderer_update_program_cache(wrRenderer, aRenderThread.ProgramCache()->Raw());
|
||||
}
|
||||
}
|
||||
|
||||
if (renderer) {
|
||||
|
@ -209,6 +208,12 @@ TransactionBuilder::GenerateFrame()
|
|||
wr_transaction_generate_frame(mTxn);
|
||||
}
|
||||
|
||||
void
|
||||
TransactionBuilder::InvalidateRenderedFrame()
|
||||
{
|
||||
wr_transaction_invalidate_rendered_frame(mTxn);
|
||||
}
|
||||
|
||||
void
|
||||
TransactionBuilder::UpdateDynamicProperties(const nsTArray<wr::WrOpacityProperty>& aOpacityArray,
|
||||
const nsTArray<wr::WrTransformProperty>& aTransformArray)
|
||||
|
|
|
@ -89,6 +89,8 @@ public:
|
|||
|
||||
void GenerateFrame();
|
||||
|
||||
void InvalidateRenderedFrame();
|
||||
|
||||
void UpdateDynamicProperties(const nsTArray<wr::WrOpacityProperty>& aOpacityArray,
|
||||
const nsTArray<wr::WrTransformProperty>& aTransformArray);
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
3c3f9a4e919b81639f078d7bd101012de61b9396
|
||||
69fddc3faf1a379a560106f12687d08cbbe304dd
|
||||
|
|
|
@ -2,6 +2,7 @@ use std::ffi::{CStr, CString};
|
|||
use std::{mem, slice, ptr, env};
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::os::raw::{c_void, c_char, c_float};
|
||||
|
@ -14,7 +15,7 @@ use webrender::DebugFlags;
|
|||
use webrender::{ApiRecordingReceiver, BinaryRecorder};
|
||||
use webrender::{AsyncPropertySampler, PipelineInfo, SceneBuilderHooks};
|
||||
use webrender::{UploadMethod, VertexUsageHint};
|
||||
use webrender::ShaderPrecacheFlags;
|
||||
use webrender::{Device, Shaders, WrShaders, ShaderPrecacheFlags};
|
||||
use thread_profiler::register_thread_with_profiler;
|
||||
use moz2d_renderer::Moz2dBlobImageHandler;
|
||||
use program_cache::{WrProgramCache, remove_disk_cache};
|
||||
|
@ -924,6 +925,49 @@ fn env_var_to_bool(key: &'static str) -> bool {
|
|||
env::var(key).ok().map_or(false, |v| !v.is_empty())
|
||||
}
|
||||
|
||||
// Call MakeCurrent before this.
|
||||
fn wr_device_new(gl_context: *mut c_void, pc: Option<&mut WrProgramCache>)
|
||||
-> Device
|
||||
{
|
||||
assert!(unsafe { is_in_render_thread() });
|
||||
|
||||
let gl;
|
||||
if unsafe { is_glcontext_egl(gl_context) } {
|
||||
gl = unsafe { gl::GlesFns::load_with(|symbol| get_proc_address(gl_context, symbol)) };
|
||||
} else {
|
||||
gl = unsafe { gl::GlFns::load_with(|symbol| get_proc_address(gl_context, symbol)) };
|
||||
}
|
||||
|
||||
let version = gl.get_string(gl::VERSION);
|
||||
|
||||
info!("WebRender - OpenGL version new {}", version);
|
||||
|
||||
let upload_method = if unsafe { is_glcontext_angle(gl_context) } {
|
||||
UploadMethod::Immediate
|
||||
} else {
|
||||
UploadMethod::PixelBuffer(VertexUsageHint::Dynamic)
|
||||
};
|
||||
|
||||
let resource_override_path = unsafe {
|
||||
let override_charptr = gfx_wr_resource_path_override();
|
||||
if override_charptr.is_null() {
|
||||
None
|
||||
} else {
|
||||
match CStr::from_ptr(override_charptr).to_str() {
|
||||
Ok(override_str) => Some(PathBuf::from(override_str)),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let cached_programs = match pc {
|
||||
Some(cached_programs) => Some(Rc::clone(cached_programs.rc_get())),
|
||||
None => None,
|
||||
};
|
||||
|
||||
Device::new(gl, resource_override_path, upload_method, cached_programs)
|
||||
}
|
||||
|
||||
// Call MakeCurrent before this.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wr_window_new(window_id: WrWindowId,
|
||||
|
@ -931,6 +975,8 @@ pub extern "C" fn wr_window_new(window_id: WrWindowId,
|
|||
window_height: u32,
|
||||
support_low_priority_transactions: bool,
|
||||
gl_context: *mut c_void,
|
||||
program_cache: Option<&mut WrProgramCache>,
|
||||
shaders: Option<&mut WrShaders>,
|
||||
thread_pool: *mut WrThreadPool,
|
||||
size_of_op: VoidPtrToSizeFn,
|
||||
out_handle: &mut *mut DocumentHandle,
|
||||
|
@ -974,6 +1020,11 @@ pub extern "C" fn wr_window_new(window_id: WrWindowId,
|
|||
ShaderPrecacheFlags::empty()
|
||||
};
|
||||
|
||||
let cached_programs = match program_cache {
|
||||
Some(program_cache) => Some(Rc::clone(&program_cache.rc_get())),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let opts = RendererOptions {
|
||||
enable_aa: true,
|
||||
enable_subpixel_aa: true,
|
||||
|
@ -983,6 +1034,7 @@ pub extern "C" fn wr_window_new(window_id: WrWindowId,
|
|||
workers: Some(workers.clone()),
|
||||
thread_listener: Some(Box::new(GeckoProfilerThreadListener::new())),
|
||||
size_of_op: Some(size_of_op),
|
||||
cached_programs,
|
||||
resource_override_path: unsafe {
|
||||
let override_charptr = gfx_wr_resource_path_override();
|
||||
if override_charptr.is_null() {
|
||||
|
@ -1008,7 +1060,7 @@ pub extern "C" fn wr_window_new(window_id: WrWindowId,
|
|||
let notifier = Box::new(CppNotifier {
|
||||
window_id: window_id,
|
||||
});
|
||||
let (renderer, sender) = match Renderer::new(gl, notifier, opts) {
|
||||
let (renderer, sender) = match Renderer::new(gl, notifier, opts, shaders) {
|
||||
Ok((renderer, sender)) => (renderer, sender),
|
||||
Err(e) => {
|
||||
warn!(" Failed to create a Renderer: {:?}", e);
|
||||
|
@ -1222,6 +1274,11 @@ pub extern "C" fn wr_transaction_generate_frame(txn: &mut Transaction) {
|
|||
txn.generate_frame();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wr_transaction_invalidate_rendered_frame(txn: &mut Transaction) {
|
||||
txn.invalidate_rendered_frame();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wr_transaction_update_dynamic_properties(
|
||||
txn: &mut Transaction,
|
||||
|
@ -2595,3 +2652,58 @@ fn unpack_clip_id(id: usize, pipeline_id: PipelineId) -> ClipId {
|
|||
_ => unreachable!("Got a bizarre value for the clip type"),
|
||||
}
|
||||
}
|
||||
|
||||
/// cbindgen:postfix=WR_DESTRUCTOR_SAFE_FUNC
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wr_device_delete(device: *mut Device) {
|
||||
Box::from_raw(device);
|
||||
}
|
||||
|
||||
// Call MakeCurrent before this.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wr_shaders_new(gl_context: *mut c_void,
|
||||
program_cache: Option<&mut WrProgramCache>) -> *mut WrShaders {
|
||||
let mut device = wr_device_new(gl_context, program_cache);
|
||||
|
||||
let precache_flags = if env_var_to_bool("MOZ_WR_PRECACHE_SHADERS") {
|
||||
ShaderPrecacheFlags::FULL_COMPILE
|
||||
} else {
|
||||
ShaderPrecacheFlags::ASYNC_COMPILE
|
||||
};
|
||||
|
||||
let opts = RendererOptions {
|
||||
precache_flags,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let gl_type = device.gl().get_type();
|
||||
device.begin_frame();
|
||||
|
||||
let shaders = Rc::new(RefCell::new(match Shaders::new(&mut device, gl_type, &opts) {
|
||||
Ok(shaders) => shaders,
|
||||
Err(e) => {
|
||||
warn!(" Failed to create a Shaders: {:?}", e);
|
||||
let msg = CString::new(format!("wr_shaders_new: {:?}", e)).unwrap();
|
||||
unsafe {
|
||||
gfx_critical_note(msg.as_ptr());
|
||||
}
|
||||
return ptr::null_mut();
|
||||
}
|
||||
}));
|
||||
|
||||
let shaders = WrShaders { shaders };
|
||||
|
||||
device.end_frame();
|
||||
Box::into_raw(Box::new(shaders))
|
||||
}
|
||||
|
||||
/// cbindgen:postfix=WR_DESTRUCTOR_SAFE_FUNC
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wr_shaders_delete(shaders: *mut WrShaders, gl_context: *mut c_void) {
|
||||
let mut device = wr_device_new(gl_context, None);
|
||||
let shaders = Box::from_raw(shaders);
|
||||
if let Ok(shaders) = Rc::try_unwrap(shaders.shaders) {
|
||||
shaders.into_inner().deinit(&mut device);
|
||||
}
|
||||
// let shaders go out of scope and get dropped
|
||||
}
|
||||
|
|
|
@ -270,6 +270,8 @@ enum class YuvColorSpace : uint32_t {
|
|||
template<typename T>
|
||||
struct Arc;
|
||||
|
||||
struct Device;
|
||||
|
||||
// Geometry in the coordinate system of the render target (screen or intermediate
|
||||
// surface) in physical pixels.
|
||||
struct DevicePixel;
|
||||
|
@ -303,6 +305,8 @@ struct WorldPixel;
|
|||
|
||||
struct WrProgramCache;
|
||||
|
||||
struct WrShaders;
|
||||
|
||||
struct WrState;
|
||||
|
||||
struct WrThreadPool;
|
||||
|
@ -1241,6 +1245,10 @@ WR_INLINE
|
|||
void wr_dec_ref_arc(const VecU8 *aArc)
|
||||
WR_DESTRUCTOR_SAFE_FUNC;
|
||||
|
||||
WR_INLINE
|
||||
void wr_device_delete(Device *aDevice)
|
||||
WR_DESTRUCTOR_SAFE_FUNC;
|
||||
|
||||
WR_INLINE
|
||||
void wr_dp_clear_save(WrState *aState)
|
||||
WR_FUNC;
|
||||
|
@ -1764,6 +1772,16 @@ void wr_set_item_tag(WrState *aState,
|
|||
uint16_t aHitInfo)
|
||||
WR_FUNC;
|
||||
|
||||
WR_INLINE
|
||||
void wr_shaders_delete(WrShaders *aShaders,
|
||||
void *aGlContext)
|
||||
WR_DESTRUCTOR_SAFE_FUNC;
|
||||
|
||||
WR_INLINE
|
||||
WrShaders *wr_shaders_new(void *aGlContext,
|
||||
WrProgramCache *aProgramCache)
|
||||
WR_FUNC;
|
||||
|
||||
WR_INLINE
|
||||
void wr_state_delete(WrState *aState)
|
||||
WR_DESTRUCTOR_SAFE_FUNC;
|
||||
|
@ -1802,6 +1820,10 @@ WR_INLINE
|
|||
void wr_transaction_generate_frame(Transaction *aTxn)
|
||||
WR_FUNC;
|
||||
|
||||
WR_INLINE
|
||||
void wr_transaction_invalidate_rendered_frame(Transaction *aTxn)
|
||||
WR_FUNC;
|
||||
|
||||
WR_INLINE
|
||||
bool wr_transaction_is_empty(const Transaction *aTxn)
|
||||
WR_FUNC;
|
||||
|
@ -1892,6 +1914,8 @@ bool wr_window_new(WrWindowId aWindowId,
|
|||
uint32_t aWindowHeight,
|
||||
bool aSupportLowPriorityTransactions,
|
||||
void *aGlContext,
|
||||
WrProgramCache *aProgramCache,
|
||||
WrShaders *aShaders,
|
||||
WrThreadPool *aThreadPool,
|
||||
VoidPtrToSizeFn aSizeOfOp,
|
||||
DocumentHandle **aOutHandle,
|
||||
|
|
|
@ -238,7 +238,7 @@ impl Wrench {
|
|||
Box::new(Notifier(data))
|
||||
});
|
||||
|
||||
let (renderer, sender) = webrender::Renderer::new(window.clone_gl(), notifier, opts).unwrap();
|
||||
let (renderer, sender) = webrender::Renderer::new(window.clone_gl(), notifier, opts, None).unwrap();
|
||||
let api = sender.create_api();
|
||||
let document_id = api.add_document(size, 0);
|
||||
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -19,6 +19,7 @@
|
|||
#include "frontend/NameFunctions.h"
|
||||
#include "frontend/SharedContext.h"
|
||||
#include "frontend/SourceNotes.h"
|
||||
#include "frontend/ValueUsage.h"
|
||||
#include "vm/BytecodeUtil.h"
|
||||
#include "vm/Interpreter.h"
|
||||
#include "vm/Iteration.h"
|
||||
|
@ -109,18 +110,8 @@ struct CGYieldAndAwaitOffsetList {
|
|||
typedef Vector<jsbytecode, 64> BytecodeVector;
|
||||
typedef Vector<jssrcnote, 64> SrcNotesVector;
|
||||
|
||||
// Used to control whether JSOP_CALL_IGNORES_RV is emitted for function calls.
|
||||
enum class ValueUsage {
|
||||
// Assume the value of the current expression may be used. This is always
|
||||
// correct but prohibits JSOP_CALL_IGNORES_RV.
|
||||
WantValue,
|
||||
|
||||
// Pass this when emitting an expression if the expression's value is
|
||||
// definitely unused by later instructions. You must make sure the next
|
||||
// instruction is JSOP_POP, a jump to a JSOP_POP, or something similar.
|
||||
IgnoreValue
|
||||
};
|
||||
|
||||
class CallOrNewEmitter;
|
||||
class ElemOpEmitter;
|
||||
class EmitterScope;
|
||||
class NestableControl;
|
||||
class TDZCheckCache;
|
||||
|
@ -531,6 +522,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter
|
|||
|
||||
MOZ_MUST_USE bool emitThisLiteral(ThisLiteral* pn);
|
||||
MOZ_MUST_USE bool emitGetFunctionThis(ParseNode* pn);
|
||||
MOZ_MUST_USE bool emitGetFunctionThis(const mozilla::Maybe<uint32_t>& offset);
|
||||
MOZ_MUST_USE bool emitGetThisForSuperBase(UnaryNode* superBase);
|
||||
MOZ_MUST_USE bool emitSetThis(BinaryNode* setThisNode);
|
||||
MOZ_MUST_USE bool emitCheckDerivedClassConstructorReturn();
|
||||
|
@ -558,7 +550,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter
|
|||
MOZ_MUST_USE bool emitIndexOp(JSOp op, uint32_t index);
|
||||
|
||||
MOZ_MUST_USE bool emitAtomOp(JSAtom* atom, JSOp op);
|
||||
MOZ_MUST_USE bool emitAtomOp(NameNode* nameNode, JSOp op);
|
||||
MOZ_MUST_USE bool emitAtomOp(uint32_t atomIndex, JSOp op);
|
||||
|
||||
MOZ_MUST_USE bool emitArrayLiteral(ListNode* array);
|
||||
MOZ_MUST_USE bool emitArray(ParseNode* arrayHead, uint32_t count);
|
||||
|
@ -589,42 +581,11 @@ struct MOZ_STACK_CLASS BytecodeEmitter
|
|||
MOZ_MUST_USE bool emitArgOp(JSOp op, uint16_t slot);
|
||||
MOZ_MUST_USE bool emitEnvCoordOp(JSOp op, EnvironmentCoordinate ec);
|
||||
|
||||
MOZ_MUST_USE bool emitGetNameAtLocation(JSAtom* name, const NameLocation& loc,
|
||||
bool callContext = false);
|
||||
MOZ_MUST_USE bool emitGetNameAtLocationForCompoundAssignment(JSAtom* name,
|
||||
const NameLocation& loc);
|
||||
MOZ_MUST_USE bool emitGetName(JSAtom* name, bool callContext = false) {
|
||||
return emitGetNameAtLocation(name, lookupName(name), callContext);
|
||||
}
|
||||
MOZ_MUST_USE bool emitGetName(ParseNode* pn, bool callContext = false);
|
||||
|
||||
template <typename RHSEmitter>
|
||||
MOZ_MUST_USE bool emitSetOrInitializeNameAtLocation(HandleAtom name, const NameLocation& loc,
|
||||
RHSEmitter emitRhs, bool initialize);
|
||||
template <typename RHSEmitter>
|
||||
MOZ_MUST_USE bool emitSetOrInitializeName(HandleAtom name, RHSEmitter emitRhs,
|
||||
bool initialize)
|
||||
{
|
||||
return emitSetOrInitializeNameAtLocation(name, lookupName(name), emitRhs, initialize);
|
||||
}
|
||||
template <typename RHSEmitter>
|
||||
MOZ_MUST_USE bool emitSetName(ParseNode* pn, RHSEmitter emitRhs) {
|
||||
RootedAtom name(cx, pn->name());
|
||||
return emitSetName(name, emitRhs);
|
||||
}
|
||||
template <typename RHSEmitter>
|
||||
MOZ_MUST_USE bool emitSetName(HandleAtom name, RHSEmitter emitRhs) {
|
||||
return emitSetOrInitializeName(name, emitRhs, false);
|
||||
}
|
||||
template <typename RHSEmitter>
|
||||
MOZ_MUST_USE bool emitInitializeName(ParseNode* pn, RHSEmitter emitRhs) {
|
||||
RootedAtom name(cx, pn->name());
|
||||
return emitInitializeName(name, emitRhs);
|
||||
}
|
||||
template <typename RHSEmitter>
|
||||
MOZ_MUST_USE bool emitInitializeName(HandleAtom name, RHSEmitter emitRhs) {
|
||||
return emitSetOrInitializeName(name, emitRhs, true);
|
||||
MOZ_MUST_USE bool emitGetNameAtLocation(JSAtom* name, const NameLocation& loc);
|
||||
MOZ_MUST_USE bool emitGetName(JSAtom* name) {
|
||||
return emitGetNameAtLocation(name, lookupName(name));
|
||||
}
|
||||
MOZ_MUST_USE bool emitGetName(ParseNode* pn);
|
||||
|
||||
MOZ_MUST_USE bool emitTDZCheckIfNeeded(JSAtom* name, const NameLocation& loc);
|
||||
|
||||
|
@ -657,7 +618,6 @@ struct MOZ_STACK_CLASS BytecodeEmitter
|
|||
MOZ_MUST_USE bool emitAwaitInScope(EmitterScope& currentScope);
|
||||
|
||||
MOZ_MUST_USE bool emitPropLHS(PropertyAccess* prop);
|
||||
MOZ_MUST_USE bool emitPropOp(PropertyAccess* prop, JSOp op);
|
||||
MOZ_MUST_USE bool emitPropIncDec(UnaryNode* incDec);
|
||||
|
||||
MOZ_MUST_USE bool emitAsyncWrapperLambda(unsigned index, bool isArrow);
|
||||
|
@ -672,6 +632,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter
|
|||
enum class EmitElemOption { Get, Call, IncDec, CompoundAssign, Ref };
|
||||
MOZ_MUST_USE bool emitElemOperands(PropertyByValue* elem, EmitElemOption opts);
|
||||
|
||||
MOZ_MUST_USE bool emitElemObjAndKey(PropertyByValue* elem, bool isSuper, ElemOpEmitter& eoe);
|
||||
MOZ_MUST_USE bool emitElemOpBase(JSOp op);
|
||||
MOZ_MUST_USE bool emitElemOp(PropertyByValue* elem, JSOp op);
|
||||
MOZ_MUST_USE bool emitElemIncDec(UnaryNode* incDec);
|
||||
|
@ -774,7 +735,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter
|
|||
|
||||
MOZ_MUST_USE bool emitCallSiteObject(CallSiteNode* callSiteObj);
|
||||
MOZ_MUST_USE bool emitTemplateString(ListNode* templateString);
|
||||
MOZ_MUST_USE bool emitAssignment(ParseNode* lhs, ParseNodeKind pnk, ParseNode* rhs);
|
||||
MOZ_MUST_USE bool emitAssignment(ParseNode* lhs, JSOp compoundOp, ParseNode* rhs);
|
||||
|
||||
MOZ_MUST_USE bool emitReturn(UnaryNode* returnNode);
|
||||
MOZ_MUST_USE bool emitExpressionStatement(UnaryNode* exprStmt);
|
||||
|
@ -802,7 +763,8 @@ struct MOZ_STACK_CLASS BytecodeEmitter
|
|||
|
||||
bool isRestParameter(ParseNode* pn);
|
||||
|
||||
MOZ_MUST_USE bool emitArguments(ListNode* argsList, bool callop, bool spread);
|
||||
MOZ_MUST_USE bool emitArguments(ListNode* argsList, bool isCall, bool isSpread,
|
||||
CallOrNewEmitter& cone);
|
||||
MOZ_MUST_USE bool emitCallOrNew(BinaryNode* callNode,
|
||||
ValueUsage valueUsage = ValueUsage::WantValue);
|
||||
MOZ_MUST_USE bool emitSelfHostedCallFunction(BinaryNode* callNode);
|
||||
|
@ -844,13 +806,12 @@ struct MOZ_STACK_CLASS BytecodeEmitter
|
|||
MOZ_MUST_USE bool emitSpread(bool allowSelfHosted = false);
|
||||
|
||||
MOZ_MUST_USE bool emitClass(ClassNode* classNode);
|
||||
MOZ_MUST_USE bool emitSuperPropLHS(UnaryNode* superBase, bool isCall = false);
|
||||
MOZ_MUST_USE bool emitSuperGetProp(PropertyAccess* prop, bool isCall = false);
|
||||
MOZ_MUST_USE bool emitSuperElemOperands(PropertyByValue* elem,
|
||||
EmitElemOption opts = EmitElemOption::Get);
|
||||
MOZ_MUST_USE bool emitSuperGetElem(PropertyByValue* elem, bool isCall = false);
|
||||
|
||||
MOZ_MUST_USE bool emitCallee(ParseNode* callee, ParseNode* call, bool* callop);
|
||||
MOZ_MUST_USE bool emitCalleeAndThis(ParseNode* callee, ParseNode* call,
|
||||
CallOrNewEmitter& cone);
|
||||
|
||||
MOZ_MUST_USE bool emitPipeline(ListNode* node);
|
||||
|
||||
|
|
|
@ -0,0 +1,319 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
||||
* 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 "frontend/CallOrNewEmitter.h"
|
||||
|
||||
#include "frontend/BytecodeEmitter.h"
|
||||
#include "frontend/NameOpEmitter.h"
|
||||
#include "frontend/SharedContext.h"
|
||||
#include "vm/Opcodes.h"
|
||||
#include "vm/StringType.h"
|
||||
|
||||
using namespace js;
|
||||
using namespace js::frontend;
|
||||
|
||||
using mozilla::Maybe;
|
||||
|
||||
AutoEmittingRunOnceLambda::AutoEmittingRunOnceLambda(BytecodeEmitter* bce)
|
||||
: bce_(bce)
|
||||
{
|
||||
MOZ_ASSERT(!bce_->emittingRunOnceLambda);
|
||||
bce_->emittingRunOnceLambda = true;
|
||||
}
|
||||
|
||||
AutoEmittingRunOnceLambda::~AutoEmittingRunOnceLambda()
|
||||
{
|
||||
bce_->emittingRunOnceLambda = false;
|
||||
}
|
||||
|
||||
CallOrNewEmitter::CallOrNewEmitter(BytecodeEmitter* bce, JSOp op,
|
||||
ArgumentsKind argumentsKind,
|
||||
ValueUsage valueUsage)
|
||||
: bce_(bce),
|
||||
op_(op),
|
||||
argumentsKind_(argumentsKind)
|
||||
{
|
||||
if (op_ == JSOP_CALL && valueUsage == ValueUsage::IgnoreValue) {
|
||||
op_ = JSOP_CALL_IGNORES_RV;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(isCall() || isNew() || isSuperCall());
|
||||
}
|
||||
|
||||
bool
|
||||
CallOrNewEmitter::emitNameCallee(JSAtom* name)
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Start);
|
||||
|
||||
NameOpEmitter noe(bce_, name,
|
||||
isCall()
|
||||
? NameOpEmitter::Kind::Call
|
||||
: NameOpEmitter::Kind::Get);
|
||||
if (!noe.emitGet()) { // CALLEE THIS
|
||||
return false;
|
||||
}
|
||||
|
||||
state_ = State::NameCallee;
|
||||
return true;
|
||||
}
|
||||
|
||||
MOZ_MUST_USE PropOpEmitter&
|
||||
CallOrNewEmitter::prepareForPropCallee(bool isSuperProp)
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Start);
|
||||
|
||||
poe_.emplace(bce_,
|
||||
isCall()
|
||||
? PropOpEmitter::Kind::Call
|
||||
: PropOpEmitter::Kind::Get,
|
||||
isSuperProp
|
||||
? PropOpEmitter::ObjKind::Super
|
||||
: PropOpEmitter::ObjKind::Other);
|
||||
|
||||
state_ = State::PropCallee;
|
||||
return *poe_;
|
||||
}
|
||||
|
||||
MOZ_MUST_USE ElemOpEmitter&
|
||||
CallOrNewEmitter::prepareForElemCallee(bool isSuperElem)
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Start);
|
||||
|
||||
eoe_.emplace(bce_,
|
||||
isCall()
|
||||
? ElemOpEmitter::Kind::Call
|
||||
: ElemOpEmitter::Kind::Get,
|
||||
isSuperElem
|
||||
? ElemOpEmitter::ObjKind::Super
|
||||
: ElemOpEmitter::ObjKind::Other);
|
||||
|
||||
state_ = State::ElemCallee;
|
||||
return *eoe_;
|
||||
}
|
||||
|
||||
bool
|
||||
CallOrNewEmitter::prepareForFunctionCallee()
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Start);
|
||||
|
||||
// Top level lambdas which are immediately invoked should be treated as
|
||||
// only running once. Every time they execute we will create new types and
|
||||
// scripts for their contents, to increase the quality of type information
|
||||
// within them and enable more backend optimizations. Note that this does
|
||||
// not depend on the lambda being invoked at most once (it may be named or
|
||||
// be accessed via foo.caller indirection), as multiple executions will
|
||||
// just cause the inner scripts to be repeatedly cloned.
|
||||
MOZ_ASSERT(!bce_->emittingRunOnceLambda);
|
||||
if (bce_->checkRunOnceContext()) {
|
||||
autoEmittingRunOnceLambda_.emplace(bce_);
|
||||
}
|
||||
|
||||
state_ = State::FunctionCallee;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
CallOrNewEmitter::emitSuperCallee()
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Start);
|
||||
|
||||
if (!bce_->emit1(JSOP_SUPERFUN)) { // CALLEE
|
||||
return false;
|
||||
}
|
||||
if (!bce_->emit1(JSOP_IS_CONSTRUCTING)) { // CALLEE THIS
|
||||
return false;
|
||||
}
|
||||
|
||||
state_ = State::SuperCallee;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
CallOrNewEmitter::prepareForOtherCallee()
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Start);
|
||||
|
||||
state_ = State::OtherCallee;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
CallOrNewEmitter::emitThis()
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::NameCallee ||
|
||||
state_ == State::PropCallee ||
|
||||
state_ == State::ElemCallee ||
|
||||
state_ == State::FunctionCallee ||
|
||||
state_ == State::SuperCallee ||
|
||||
state_ == State::OtherCallee);
|
||||
|
||||
bool needsThis = false;
|
||||
switch (state_) {
|
||||
case State::NameCallee:
|
||||
if (!isCall()) {
|
||||
needsThis = true;
|
||||
}
|
||||
break;
|
||||
case State::PropCallee:
|
||||
poe_.reset();
|
||||
if (!isCall()) {
|
||||
needsThis = true;
|
||||
}
|
||||
break;
|
||||
case State::ElemCallee:
|
||||
eoe_.reset();
|
||||
if (!isCall()) {
|
||||
needsThis = true;
|
||||
}
|
||||
break;
|
||||
case State::FunctionCallee:
|
||||
autoEmittingRunOnceLambda_.reset();
|
||||
needsThis = true;
|
||||
break;
|
||||
case State::SuperCallee:
|
||||
break;
|
||||
case State::OtherCallee:
|
||||
needsThis = true;
|
||||
break;
|
||||
default:;
|
||||
}
|
||||
if (needsThis) {
|
||||
if (isNew() || isSuperCall()) {
|
||||
if (!bce_->emit1(JSOP_IS_CONSTRUCTING)) { // CALLEE THIS
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!bce_->emit1(JSOP_UNDEFINED)) { // CALLEE THIS
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state_ = State::This;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Used by BytecodeEmitter::emitPipeline to reuse CallOrNewEmitter instance
|
||||
// across multiple chained calls.
|
||||
void
|
||||
CallOrNewEmitter::reset()
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::End);
|
||||
state_ = State::Start;
|
||||
}
|
||||
|
||||
bool
|
||||
CallOrNewEmitter::prepareForNonSpreadArguments()
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::This);
|
||||
MOZ_ASSERT(!isSpread());
|
||||
|
||||
state_ = State::Arguments;
|
||||
return true;
|
||||
}
|
||||
|
||||
// See the usage in the comment at the top of the class.
|
||||
bool
|
||||
CallOrNewEmitter::wantSpreadOperand()
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::This);
|
||||
MOZ_ASSERT(isSpread());
|
||||
|
||||
state_ = State::WantSpreadOperand;
|
||||
return isSingleSpreadRest();
|
||||
}
|
||||
|
||||
bool
|
||||
CallOrNewEmitter::emitSpreadArgumentsTest()
|
||||
{
|
||||
// Caller should check wantSpreadOperand before this.
|
||||
MOZ_ASSERT(state_ == State::WantSpreadOperand);
|
||||
MOZ_ASSERT(isSpread());
|
||||
|
||||
if (isSingleSpreadRest()) {
|
||||
// Emit a preparation code to optimize the spread call with a rest
|
||||
// parameter:
|
||||
//
|
||||
// function f(...args) {
|
||||
// g(...args);
|
||||
// }
|
||||
//
|
||||
// If the spread operand is a rest parameter and it's optimizable
|
||||
// array, skip spread operation and pass it directly to spread call
|
||||
// operation. See the comment in OptimizeSpreadCall in
|
||||
// Interpreter.cpp for the optimizable conditons.
|
||||
|
||||
ifNotOptimizable_.emplace(bce_);
|
||||
// // CALLEE THIS ARG0
|
||||
if (!bce_->emit1(JSOP_OPTIMIZE_SPREADCALL)) { // CALLEE THIS ARG0 OPTIMIZED
|
||||
return false;
|
||||
}
|
||||
if (!bce_->emit1(JSOP_NOT)) { // CALLEE THIS ARG0 !OPTIMIZED
|
||||
return false;
|
||||
}
|
||||
if (!ifNotOptimizable_->emitThen()) { // CALLEE THIS ARG0
|
||||
return false;
|
||||
}
|
||||
if (!bce_->emit1(JSOP_POP)) { // CALLEE THIS
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
state_ = State::Arguments;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
CallOrNewEmitter::emitEnd(uint32_t argc, const Maybe<uint32_t>& beginPos)
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Arguments);
|
||||
|
||||
if (isSingleSpreadRest()) {
|
||||
if (!ifNotOptimizable_->emitEnd()) { // CALLEE THIS ARR
|
||||
return false;
|
||||
}
|
||||
|
||||
ifNotOptimizable_.reset();
|
||||
}
|
||||
if (isNew() || isSuperCall()) {
|
||||
if (isSuperCall()) {
|
||||
if (!bce_->emit1(JSOP_NEWTARGET)) { // CALLEE THIS ARG.. NEW.TARGET
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Repush the callee as new.target
|
||||
uint32_t effectiveArgc = isSpread() ? 1 : argc;
|
||||
if (!bce_->emitDupAt(effectiveArgc + 1)) {
|
||||
return false; // CALLEE THIS ARR CALLEE
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isSpread()) {
|
||||
if (!bce_->emitCall(op_, argc, beginPos)) { // RVAL
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (beginPos) {
|
||||
if (!bce_->updateSourceCoordNotes(*beginPos)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!bce_->emit1(op_)) { // RVAL
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bce_->checkTypeSet(op_);
|
||||
|
||||
if (isEval() && beginPos) {
|
||||
uint32_t lineNum = bce_->parser->errorReporter().lineAt(*beginPos);
|
||||
if (!bce_->emitUint32Operand(JSOP_LINENO, lineNum)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
state_ = State::End;
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,338 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
||||
* 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 frontend_CallOrNewEmitter_h
|
||||
#define frontend_CallOrNewEmitter_h
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "frontend/ElemOpEmitter.h"
|
||||
#include "frontend/IfEmitter.h"
|
||||
#include "frontend/PropOpEmitter.h"
|
||||
#include "frontend/ValueUsage.h"
|
||||
#include "js/TypeDecls.h"
|
||||
#include "vm/BytecodeUtil.h"
|
||||
#include "vm/Opcodes.h"
|
||||
|
||||
namespace js {
|
||||
namespace frontend {
|
||||
|
||||
struct BytecodeEmitter;
|
||||
|
||||
class MOZ_RAII AutoEmittingRunOnceLambda
|
||||
{
|
||||
BytecodeEmitter* bce_;
|
||||
|
||||
public:
|
||||
explicit AutoEmittingRunOnceLambda(BytecodeEmitter* bce);
|
||||
~AutoEmittingRunOnceLambda();
|
||||
};
|
||||
|
||||
// Class for emitting bytecode for call or new expression.
|
||||
//
|
||||
// Usage: (check for the return value is omitted for simplicity)
|
||||
//
|
||||
// `print(arg);`
|
||||
// CallOrNewEmitter cone(this, JSOP_CALL,
|
||||
// CallOrNewEmitter::ArgumentsKind::Other,
|
||||
// ValueUsage::WantValue);
|
||||
// cone.emitNameCallee();
|
||||
// emit(print);
|
||||
// cone.emitThis();
|
||||
// cone.prepareForNonSpreadArguments();
|
||||
// emit(arg);
|
||||
// cone.emitEnd(1, Some(offset_of_callee));
|
||||
//
|
||||
// `callee.prop(arg1, arg2);`
|
||||
// CallOrNewEmitter cone(this, JSOP_CALL,
|
||||
// CallOrNewEmitter::ArgumentsKind::Other,
|
||||
// ValueUsage::WantValue);
|
||||
// PropOpEmitter& poe = cone.prepareForPropCallee(false);
|
||||
// ... emit `callee.prop` with `poe` here...
|
||||
// cone.emitThis();
|
||||
// cone.prepareForNonSpreadArguments();
|
||||
// emit(arg1);
|
||||
// emit(arg2);
|
||||
// cone.emitEnd(2, Some(offset_of_callee));
|
||||
//
|
||||
// `callee[key](arg);`
|
||||
// CallOrNewEmitter cone(this, JSOP_CALL,
|
||||
// CallOrNewEmitter::ArgumentsKind::Other,
|
||||
// ValueUsage::WantValue);
|
||||
// ElemOpEmitter& eoe = cone.prepareForElemCallee(false);
|
||||
// ... emit `callee[key]` with `eoe` here...
|
||||
// cone.emitThis();
|
||||
// cone.prepareForNonSpreadArguments();
|
||||
// emit(arg);
|
||||
// cone.emitEnd(1, Some(offset_of_callee));
|
||||
//
|
||||
// `(function() { ... })(arg);`
|
||||
// CallOrNewEmitter cone(this, JSOP_CALL,
|
||||
// CallOrNewEmitter::ArgumentsKind::Other,
|
||||
// ValueUsage::WantValue);
|
||||
// cone.prepareForFunctionCallee();
|
||||
// emit(function);
|
||||
// cone.emitThis();
|
||||
// cone.prepareForNonSpreadArguments();
|
||||
// emit(arg);
|
||||
// cone.emitEnd(1, Some(offset_of_callee));
|
||||
//
|
||||
// `super(arg);`
|
||||
// CallOrNewEmitter cone(this, JSOP_CALL,
|
||||
// CallOrNewEmitter::ArgumentsKind::Other,
|
||||
// ValueUsage::WantValue);
|
||||
// cone.emitSuperCallee();
|
||||
// cone.emitThis();
|
||||
// cone.prepareForNonSpreadArguments();
|
||||
// emit(arg);
|
||||
// cone.emitEnd(1, Some(offset_of_callee));
|
||||
//
|
||||
// `(some_other_expression)(arg);`
|
||||
// CallOrNewEmitter cone(this, JSOP_CALL,
|
||||
// CallOrNewEmitter::ArgumentsKind::Other,
|
||||
// ValueUsage::WantValue);
|
||||
// cone.prepareForOtherCallee();
|
||||
// emit(some_other_expression);
|
||||
// cone.emitThis();
|
||||
// cone.prepareForNonSpreadArguments();
|
||||
// emit(arg);
|
||||
// cone.emitEnd(1, Some(offset_of_callee));
|
||||
//
|
||||
// `print(...arg);`
|
||||
// CallOrNewEmitter cone(this, JSOP_SPREADCALL,
|
||||
// CallOrNewEmitter::ArgumentsKind::Other,
|
||||
// ValueUsage::WantValue);
|
||||
// cone.emitNameCallee();
|
||||
// emit(print);
|
||||
// cone.emitThis();
|
||||
// if (cone.wantSpreadOperand())
|
||||
// emit(arg)
|
||||
// cone.emitSpreadArgumentsTest();
|
||||
// emit([...arg]);
|
||||
// cone.emitEnd(1, Some(offset_of_callee));
|
||||
//
|
||||
// `print(...rest);`
|
||||
// where `rest` is rest parameter
|
||||
// CallOrNewEmitter cone(this, JSOP_SPREADCALL,
|
||||
// CallOrNewEmitter::ArgumentsKind::SingleSpreadRest,
|
||||
// ValueUsage::WantValue);
|
||||
// cone.emitNameCallee();
|
||||
// emit(print);
|
||||
// cone.emitThis();
|
||||
// if (cone.wantSpreadOperand())
|
||||
// emit(arg)
|
||||
// cone.emitSpreadArgumentsTest();
|
||||
// emit([...arg]);
|
||||
// cone.emitEnd(1, Some(offset_of_callee));
|
||||
//
|
||||
// `new f(arg);`
|
||||
// CallOrNewEmitter cone(this, JSOP_NEW,
|
||||
// CallOrNewEmitter::ArgumentsKind::Other,
|
||||
// ValueUsage::WantValue);
|
||||
// cone.emitNameCallee();
|
||||
// emit(f);
|
||||
// cone.emitThis();
|
||||
// cone.prepareForNonSpreadArguments();
|
||||
// emit(arg);
|
||||
// cone.emitEnd(1, Some(offset_of_callee));
|
||||
//
|
||||
class MOZ_STACK_CLASS CallOrNewEmitter
|
||||
{
|
||||
public:
|
||||
enum class ArgumentsKind {
|
||||
Other,
|
||||
|
||||
// Specify this for the following case:
|
||||
//
|
||||
// function f(...rest) {
|
||||
// g(...rest);
|
||||
// }
|
||||
//
|
||||
// This enables optimization to avoid allocating an intermediate array
|
||||
// for spread operation.
|
||||
//
|
||||
// wantSpreadOperand() returns true when this is specified.
|
||||
SingleSpreadRest
|
||||
};
|
||||
|
||||
private:
|
||||
BytecodeEmitter* bce_;
|
||||
|
||||
// The opcode for the call or new.
|
||||
JSOp op_;
|
||||
|
||||
// Whether the call is a spread call with single rest parameter or not.
|
||||
// See the comment in emitSpreadArgumentsTest for more details.
|
||||
ArgumentsKind argumentsKind_;
|
||||
|
||||
// The branch for spread call optimization.
|
||||
mozilla::Maybe<InternalIfEmitter> ifNotOptimizable_;
|
||||
|
||||
mozilla::Maybe<AutoEmittingRunOnceLambda> autoEmittingRunOnceLambda_;
|
||||
|
||||
mozilla::Maybe<PropOpEmitter> poe_;
|
||||
mozilla::Maybe<ElemOpEmitter> eoe_;
|
||||
|
||||
// The state of this emitter.
|
||||
//
|
||||
// +-------+ emitNameCallee +------------+
|
||||
// | Start |-+------------------------->| NameCallee |------+
|
||||
// +-------+ | +------------+ |
|
||||
// | |
|
||||
// | prepareForPropCallee +------------+ v
|
||||
// +------------------------->| PropCallee |----->+
|
||||
// | +------------+ |
|
||||
// | |
|
||||
// | prepareForElemCallee +------------+ v
|
||||
// +------------------------->| ElemCallee |----->+
|
||||
// | +------------+ |
|
||||
// | |
|
||||
// | prepareForFunctionCallee +----------------+ v
|
||||
// +------------------------->| FunctionCallee |->+
|
||||
// | +----------------+ |
|
||||
// | |
|
||||
// | emitSuperCallee +-------------+ v
|
||||
// +------------------------->| SuperCallee |---->+
|
||||
// | +-------------+ |
|
||||
// | |
|
||||
// | prepareForOtherCallee +-------------+ v
|
||||
// +------------------------->| OtherCallee |---->+
|
||||
// +-------------+ |
|
||||
// |
|
||||
// +--------------------------------------------------------+
|
||||
// |
|
||||
// | emitThis +------+
|
||||
// +--------->| This |-+
|
||||
// +------+ |
|
||||
// |
|
||||
// +-------------------+
|
||||
// |
|
||||
// | [!isSpread]
|
||||
// | prepareForNonSpreadArguments +-----------+ emitEnd +-----+
|
||||
// +------------------------------->+->| Arguments |-------->| End |
|
||||
// | ^ +-----------+ +-----+
|
||||
// | |
|
||||
// | +----------------------------------+
|
||||
// | |
|
||||
// | [isSpread] |
|
||||
// | wantSpreadOperand +-------------------+ emitSpreadArgumentsTest |
|
||||
// +-------------------->| WantSpreadOperand |-------------------------+
|
||||
// +-------------------+
|
||||
enum class State {
|
||||
// The initial state.
|
||||
Start,
|
||||
|
||||
// After calling emitNameCallee.
|
||||
NameCallee,
|
||||
|
||||
// After calling prepareForPropCallee.
|
||||
PropCallee,
|
||||
|
||||
// After calling prepareForElemCallee.
|
||||
ElemCallee,
|
||||
|
||||
// After calling prepareForFunctionCallee.
|
||||
FunctionCallee,
|
||||
|
||||
// After calling emitSuperCallee.
|
||||
SuperCallee,
|
||||
|
||||
// After calling prepareForOtherCallee.
|
||||
OtherCallee,
|
||||
|
||||
// After calling emitThis.
|
||||
This,
|
||||
|
||||
// After calling wantSpreadOperand.
|
||||
WantSpreadOperand,
|
||||
|
||||
// After calling prepareForNonSpreadArguments.
|
||||
Arguments,
|
||||
|
||||
// After calling emitEnd.
|
||||
End
|
||||
};
|
||||
State state_ = State::Start;
|
||||
|
||||
public:
|
||||
CallOrNewEmitter(BytecodeEmitter* bce, JSOp op,
|
||||
ArgumentsKind argumentsKind,
|
||||
ValueUsage valueUsage);
|
||||
|
||||
private:
|
||||
MOZ_MUST_USE bool isCall() const {
|
||||
return op_ == JSOP_CALL || op_ == JSOP_CALL_IGNORES_RV ||
|
||||
op_ == JSOP_SPREADCALL ||
|
||||
isEval() || isFunApply() || isFunCall();
|
||||
}
|
||||
|
||||
MOZ_MUST_USE bool isNew() const {
|
||||
return op_ == JSOP_NEW || op_ == JSOP_SPREADNEW;
|
||||
}
|
||||
|
||||
MOZ_MUST_USE bool isSuperCall() const {
|
||||
return op_ == JSOP_SUPERCALL || op_ == JSOP_SPREADSUPERCALL;
|
||||
}
|
||||
|
||||
MOZ_MUST_USE bool isEval() const {
|
||||
return op_ == JSOP_EVAL || op_ == JSOP_STRICTEVAL ||
|
||||
op_ == JSOP_SPREADEVAL || op_ == JSOP_STRICTSPREADEVAL;
|
||||
}
|
||||
|
||||
MOZ_MUST_USE bool isFunApply() const {
|
||||
return op_ == JSOP_FUNAPPLY;
|
||||
}
|
||||
|
||||
MOZ_MUST_USE bool isFunCall() const {
|
||||
return op_ == JSOP_FUNCALL;
|
||||
}
|
||||
|
||||
MOZ_MUST_USE bool isSpread() const {
|
||||
return JOF_OPTYPE(op_) == JOF_BYTE;
|
||||
}
|
||||
|
||||
MOZ_MUST_USE bool isSingleSpreadRest() const {
|
||||
return argumentsKind_ == ArgumentsKind::SingleSpreadRest;
|
||||
}
|
||||
|
||||
public:
|
||||
MOZ_MUST_USE bool emitNameCallee(JSAtom* name);
|
||||
MOZ_MUST_USE PropOpEmitter& prepareForPropCallee(bool isSuperProp);
|
||||
MOZ_MUST_USE ElemOpEmitter& prepareForElemCallee(bool isSuperElem);
|
||||
MOZ_MUST_USE bool prepareForFunctionCallee();
|
||||
MOZ_MUST_USE bool emitSuperCallee();
|
||||
MOZ_MUST_USE bool prepareForOtherCallee();
|
||||
|
||||
MOZ_MUST_USE bool emitThis();
|
||||
|
||||
// Used by BytecodeEmitter::emitPipeline to reuse CallOrNewEmitter instance
|
||||
// across multiple chained calls.
|
||||
void reset();
|
||||
|
||||
MOZ_MUST_USE bool prepareForNonSpreadArguments();
|
||||
|
||||
// See the usage in the comment at the top of the class.
|
||||
MOZ_MUST_USE bool wantSpreadOperand();
|
||||
MOZ_MUST_USE bool emitSpreadArgumentsTest();
|
||||
|
||||
// Parameters are the offset in the source code for each character below:
|
||||
//
|
||||
// callee(arg);
|
||||
// ^
|
||||
// |
|
||||
// beginPos
|
||||
//
|
||||
// Can be Nothing() if not available.
|
||||
MOZ_MUST_USE bool emitEnd(uint32_t argc, const mozilla::Maybe<uint32_t>& beginPos);
|
||||
};
|
||||
|
||||
} /* namespace frontend */
|
||||
} /* namespace js */
|
||||
|
||||
#endif /* frontend_CallOrNewEmitter_h */
|
|
@ -0,0 +1,288 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
||||
* 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 "frontend/ElemOpEmitter.h"
|
||||
|
||||
#include "frontend/BytecodeEmitter.h"
|
||||
#include "frontend/SharedContext.h"
|
||||
#include "vm/Opcodes.h"
|
||||
|
||||
using namespace js;
|
||||
using namespace js::frontend;
|
||||
|
||||
ElemOpEmitter::ElemOpEmitter(BytecodeEmitter* bce, Kind kind, ObjKind objKind)
|
||||
: bce_(bce),
|
||||
kind_(kind),
|
||||
objKind_(objKind)
|
||||
{}
|
||||
|
||||
bool
|
||||
ElemOpEmitter::prepareForObj()
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Start);
|
||||
|
||||
#ifdef DEBUG
|
||||
state_ = State::Obj;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ElemOpEmitter::prepareForKey()
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Obj);
|
||||
|
||||
if (!isSuper() && isIncDec()) {
|
||||
if (!bce_->emit1(JSOP_CHECKOBJCOERCIBLE)) { // OBJ
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (isCall()) {
|
||||
if (!bce_->emit1(JSOP_DUP)) { // [Super]
|
||||
// // THIS THIS
|
||||
// // [Other]
|
||||
// // OBJ OBJ
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
state_ = State::Key;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ElemOpEmitter::emitGet()
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Key);
|
||||
|
||||
if (isIncDec() || isCompoundAssignment()) {
|
||||
if (!bce_->emit1(JSOP_TOID)) { // [Super]
|
||||
// // THIS KEY
|
||||
// // [Other]
|
||||
// // OBJ KEY
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (isSuper()) {
|
||||
if (!bce_->emit1(JSOP_SUPERBASE)) { // THIS? THIS KEY SUPERBASE
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (isIncDec() || isCompoundAssignment()) {
|
||||
if (isSuper()) {
|
||||
// There's no such thing as JSOP_DUP3, so we have to be creative.
|
||||
// Note that pushing things again is no fewer JSOps.
|
||||
if (!bce_->emitDupAt(2)) { // THIS KEY SUPERBASE THIS
|
||||
return false;
|
||||
}
|
||||
if (!bce_->emitDupAt(2)) { // THIS KEY SUPERBASE THIS KEY
|
||||
return false;
|
||||
}
|
||||
if (!bce_->emitDupAt(2)) { // THIS KEY SUPERBASE THIS KEY SUPERBASE
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!bce_->emit1(JSOP_DUP2)) { // OBJ KEY OBJ KEY
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JSOp op;
|
||||
if (isSuper()) {
|
||||
op = JSOP_GETELEM_SUPER;
|
||||
} else if (isCall()) {
|
||||
op = JSOP_CALLELEM;
|
||||
} else {
|
||||
op = JSOP_GETELEM;
|
||||
}
|
||||
if (!bce_->emitElemOpBase(op)) { // [Get]
|
||||
// // ELEM
|
||||
// // [Call]
|
||||
// // THIS ELEM
|
||||
// // [Inc/Dec/Assignment,
|
||||
// // Super]
|
||||
// // THIS KEY SUPERBASE ELEM
|
||||
// // [Inc/Dec/Assignment,
|
||||
// // Other]
|
||||
// // OBJ KEY ELEM
|
||||
return false;
|
||||
}
|
||||
if (isCall()) {
|
||||
if (!bce_->emit1(JSOP_SWAP)) { // ELEM THIS
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
state_ = State::Get;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ElemOpEmitter::prepareForRhs()
|
||||
{
|
||||
MOZ_ASSERT(isSimpleAssignment() || isCompoundAssignment());
|
||||
MOZ_ASSERT_IF(isSimpleAssignment(), state_ == State::Key);
|
||||
MOZ_ASSERT_IF(isCompoundAssignment(), state_ == State::Get);
|
||||
|
||||
if (isSimpleAssignment()) {
|
||||
// For CompoundAssignment, SUPERBASE is already emitted by emitGet.
|
||||
if (isSuper()) {
|
||||
if (!bce_->emit1(JSOP_SUPERBASE)) { // THIS KEY SUPERBASE
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
state_ = State::Rhs;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ElemOpEmitter::skipObjAndKeyAndRhs()
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Start);
|
||||
MOZ_ASSERT(isSimpleAssignment());
|
||||
|
||||
#ifdef DEBUG
|
||||
state_ = State::Rhs;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ElemOpEmitter::emitDelete()
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Key);
|
||||
MOZ_ASSERT(isDelete());
|
||||
|
||||
if (isSuper()) {
|
||||
if (!bce_->emit1(JSOP_TOID)) { // THIS KEY
|
||||
return false;
|
||||
}
|
||||
if (!bce_->emit1(JSOP_SUPERBASE)) { // THIS KEY SUPERBASE
|
||||
return false;
|
||||
}
|
||||
|
||||
// Unconditionally throw when attempting to delete a super-reference.
|
||||
if (!bce_->emitUint16Operand(JSOP_THROWMSG, JSMSG_CANT_DELETE_SUPER)) {
|
||||
return false; // THIS KEY SUPERBASE
|
||||
}
|
||||
|
||||
// Another wrinkle: Balance the stack from the emitter's point of view.
|
||||
// Execution will not reach here, as the last bytecode threw.
|
||||
if (!bce_->emitPopN(2)) { // THIS
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
JSOp op = bce_->sc->strict() ? JSOP_STRICTDELELEM : JSOP_DELELEM;
|
||||
if (!bce_->emitElemOpBase(op)){ // SUCCEEDED
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
state_ = State::Delete;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ElemOpEmitter::emitAssignment()
|
||||
{
|
||||
MOZ_ASSERT(isSimpleAssignment() || isCompoundAssignment());
|
||||
MOZ_ASSERT(state_ == State::Rhs);
|
||||
|
||||
JSOp setOp = isSuper()
|
||||
? bce_->sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER
|
||||
: bce_->sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM;
|
||||
if (!bce_->emitElemOpBase(setOp)) { // ELEM
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
state_ = State::Assignment;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ElemOpEmitter::emitIncDec()
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Key);
|
||||
MOZ_ASSERT(isIncDec());
|
||||
|
||||
if (!emitGet()) { // ... ELEM
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(state_ == State::Get);
|
||||
|
||||
JSOp binOp = isInc() ? JSOP_ADD : JSOP_SUB;
|
||||
if (!bce_->emit1(JSOP_POS)) { // ... N
|
||||
return false;
|
||||
}
|
||||
if (isPostIncDec()) {
|
||||
if (!bce_->emit1(JSOP_DUP)) { // ... N? N
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!bce_->emit1(JSOP_ONE)) { // ... N? N 1
|
||||
return false;
|
||||
}
|
||||
if (!bce_->emit1(binOp)) { // ... N? N+1
|
||||
return false;
|
||||
}
|
||||
if (isPostIncDec()) {
|
||||
if (isSuper()) { // THIS KEY OBJ N N+1
|
||||
if (!bce_->emit2(JSOP_PICK, 4)) { // KEY SUPERBASE N N+1 THIS
|
||||
return false;
|
||||
}
|
||||
if (!bce_->emit2(JSOP_PICK, 4)) { // SUPERBASE N N+1 THIS KEY
|
||||
return false;
|
||||
}
|
||||
if (!bce_->emit2(JSOP_PICK, 4)) { // N N+1 THIS KEY SUPERBASE
|
||||
return false;
|
||||
}
|
||||
if (!bce_->emit2(JSOP_PICK, 3)) { // N THIS KEY SUPERBASE N+1
|
||||
return false;
|
||||
}
|
||||
} else { // OBJ KEY N N+1
|
||||
if (!bce_->emit2(JSOP_PICK, 3)) { // KEY N N+1 OBJ
|
||||
return false;
|
||||
}
|
||||
if (!bce_->emit2(JSOP_PICK, 3)) { // N N+1 OBJ KEY
|
||||
return false;
|
||||
}
|
||||
if (!bce_->emit2(JSOP_PICK, 2)) { // N OBJ KEY N+1
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JSOp setOp = isSuper()
|
||||
? (bce_->sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER)
|
||||
: (bce_->sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM);
|
||||
if (!bce_->emitElemOpBase(setOp)) { // N? N+1
|
||||
return false;
|
||||
}
|
||||
if (isPostIncDec()) {
|
||||
if (!bce_->emit1(JSOP_POP)) { // N
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
state_ = State::IncDec;
|
||||
#endif
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,278 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
||||
* 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 frontend_ElemOpEmitter_h
|
||||
#define frontend_ElemOpEmitter_h
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
|
||||
namespace js {
|
||||
namespace frontend {
|
||||
|
||||
struct BytecodeEmitter;
|
||||
|
||||
// Class for emitting bytecode for element operation.
|
||||
//
|
||||
// Usage: (check for the return value is omitted for simplicity)
|
||||
//
|
||||
// `obj[key];`
|
||||
// ElemOpEmitter eoe(this,
|
||||
// ElemOpEmitter::Kind::Get,
|
||||
// ElemOpEmitter::ObjKind::Other);
|
||||
// eoe.prepareForObj();
|
||||
// emit(obj);
|
||||
// eoe.prepareForKey();
|
||||
// emit(key);
|
||||
// eoe.emitGet();
|
||||
//
|
||||
// `super[key];`
|
||||
// ElemOpEmitter eoe(this,
|
||||
// ElemOpEmitter::Kind::Get,
|
||||
// ElemOpEmitter::ObjKind::Super);
|
||||
// eoe.prepareForObj();
|
||||
// emit(this_for_super);
|
||||
// eoe.prepareForKey();
|
||||
// emit(key);
|
||||
// eoe.emitGet();
|
||||
//
|
||||
// `obj[key]();`
|
||||
// ElemOpEmitter eoe(this,
|
||||
// ElemOpEmitter::Kind::Call,
|
||||
// ElemOpEmitter::ObjKind::Other);
|
||||
// eoe.prepareForObj();
|
||||
// emit(obj);
|
||||
// eoe.prepareForKey();
|
||||
// emit(key);
|
||||
// eoe.emitGet();
|
||||
// emit_call_here();
|
||||
//
|
||||
// `new obj[key]();`
|
||||
// ElemOpEmitter eoe(this,
|
||||
// ElemOpEmitter::Kind::Call,
|
||||
// ElemOpEmitter::ObjKind::Other);
|
||||
// eoe.prepareForObj();
|
||||
// emit(obj);
|
||||
// eoe.prepareForKey();
|
||||
// emit(key);
|
||||
// eoe.emitGet();
|
||||
// emit_call_here();
|
||||
//
|
||||
// `delete obj[key];`
|
||||
// ElemOpEmitter eoe(this,
|
||||
// ElemOpEmitter::Kind::Delete,
|
||||
// ElemOpEmitter::ObjKind::Other);
|
||||
// eoe.prepareForObj();
|
||||
// emit(obj);
|
||||
// eoe.prepareForKey();
|
||||
// emit(key);
|
||||
// eoe.emitDelete();
|
||||
//
|
||||
// `delete super[key];`
|
||||
// ElemOpEmitter eoe(this,
|
||||
// ElemOpEmitter::Kind::Delete,
|
||||
// ElemOpEmitter::ObjKind::Super);
|
||||
// eoe.prepareForObj();
|
||||
// emit(this_for_super);
|
||||
// eoe.prepareForKey();
|
||||
// emit(key);
|
||||
// eoe.emitDelete();
|
||||
//
|
||||
// `obj[key]++;`
|
||||
// ElemOpEmitter eoe(this,
|
||||
// ElemOpEmitter::Kind::PostIncrement,
|
||||
// ElemOpEmitter::ObjKind::Other);
|
||||
// eoe.prepareForObj();
|
||||
// emit(obj);
|
||||
// eoe.prepareForKey();
|
||||
// emit(key);
|
||||
// eoe.emitIncDec();
|
||||
//
|
||||
// `obj[key] = value;`
|
||||
// ElemOpEmitter eoe(this,
|
||||
// ElemOpEmitter::Kind::SimpleAssignment,
|
||||
// ElemOpEmitter::ObjKind::Other);
|
||||
// eoe.prepareForObj();
|
||||
// emit(obj);
|
||||
// eoe.prepareForKey();
|
||||
// emit(key);
|
||||
// eoe.prepareForRhs();
|
||||
// emit(value);
|
||||
// eoe.emitAssignment();
|
||||
//
|
||||
// `obj[key] += value;`
|
||||
// ElemOpEmitter eoe(this,
|
||||
// ElemOpEmitter::Kind::CompoundAssignment,
|
||||
// ElemOpEmitter::ObjKind::Other);
|
||||
// eoe.prepareForObj();
|
||||
// emit(obj);
|
||||
// eoe.prepareForKey();
|
||||
// emit(key);
|
||||
// eoe.emitGet();
|
||||
// eoe.prepareForRhs();
|
||||
// emit(value);
|
||||
// emit_add_op_here();
|
||||
// eoe.emitAssignment();
|
||||
//
|
||||
class MOZ_STACK_CLASS ElemOpEmitter
|
||||
{
|
||||
public:
|
||||
enum class Kind {
|
||||
Get,
|
||||
Call,
|
||||
Set,
|
||||
Delete,
|
||||
PostIncrement,
|
||||
PreIncrement,
|
||||
PostDecrement,
|
||||
PreDecrement,
|
||||
SimpleAssignment,
|
||||
CompoundAssignment
|
||||
};
|
||||
enum class ObjKind {
|
||||
Super,
|
||||
Other
|
||||
};
|
||||
|
||||
private:
|
||||
BytecodeEmitter* bce_;
|
||||
|
||||
Kind kind_;
|
||||
ObjKind objKind_;
|
||||
|
||||
#ifdef DEBUG
|
||||
// The state of this emitter.
|
||||
//
|
||||
// skipObjAndKeyAndRhs
|
||||
// +------------------------------------------------+
|
||||
// | |
|
||||
// +-------+ | prepareForObj +-----+ prepareForKey +-----+ |
|
||||
// | Start |-+-------------->| Obj |-------------->| Key |-+ |
|
||||
// +-------+ +-----+ +-----+ | |
|
||||
// | |
|
||||
// +-------------------------------------------------------+ |
|
||||
// | |
|
||||
// | |
|
||||
// | |
|
||||
// | [Get] |
|
||||
// | [Call] |
|
||||
// | emitGet +-----+ |
|
||||
// +---------->| Get | |
|
||||
// | +-----+ |
|
||||
// | |
|
||||
// | [Delete] |
|
||||
// | emitDelete +--------+ |
|
||||
// +------------->| Delete | |
|
||||
// | +--------+ |
|
||||
// | |
|
||||
// | [PostIncrement] |
|
||||
// | [PreIncrement] |
|
||||
// | [PostDecrement] |
|
||||
// | [PreDecrement] |
|
||||
// | emitIncDec +--------+ |
|
||||
// +------------->| IncDec | |
|
||||
// | +--------+ |
|
||||
// | +-------------------+
|
||||
// | [SimpleAssignment] |
|
||||
// | prepareForRhs v +-----+
|
||||
// +--------------------->+-------------->+->| Rhs |-+
|
||||
// | ^ +-----+ |
|
||||
// | | |
|
||||
// | | +-------------+
|
||||
// | [CompoundAssignment] | |
|
||||
// | emitGet +-----+ | | emitAssignment +------------+
|
||||
// +---------->| Get |----+ +--------------->| Assignment |
|
||||
// +-----+ +------------+
|
||||
enum class State {
|
||||
// The initial state.
|
||||
Start,
|
||||
|
||||
// After calling prepareForObj.
|
||||
Obj,
|
||||
|
||||
// After calling emitKey.
|
||||
Key,
|
||||
|
||||
// After calling emitGet.
|
||||
Get,
|
||||
|
||||
// After calling emitDelete.
|
||||
Delete,
|
||||
|
||||
// After calling emitIncDec.
|
||||
IncDec,
|
||||
|
||||
// After calling prepareForRhs or skipObjAndKeyAndRhs.
|
||||
Rhs,
|
||||
|
||||
// After calling emitAssignment.
|
||||
Assignment,
|
||||
};
|
||||
State state_ = State::Start;
|
||||
#endif
|
||||
|
||||
public:
|
||||
ElemOpEmitter(BytecodeEmitter* bce, Kind kind, ObjKind objKind);
|
||||
|
||||
private:
|
||||
MOZ_MUST_USE bool isCall() const {
|
||||
return kind_ == Kind::Call;
|
||||
}
|
||||
|
||||
MOZ_MUST_USE bool isSimpleAssignment() const {
|
||||
return kind_ == Kind::SimpleAssignment;
|
||||
}
|
||||
|
||||
MOZ_MUST_USE bool isDelete() const {
|
||||
return kind_ == Kind::Delete;
|
||||
}
|
||||
|
||||
MOZ_MUST_USE bool isCompoundAssignment() const {
|
||||
return kind_ == Kind::CompoundAssignment;
|
||||
}
|
||||
|
||||
MOZ_MUST_USE bool isIncDec() const {
|
||||
return isPostIncDec() || isPreIncDec();
|
||||
}
|
||||
|
||||
MOZ_MUST_USE bool isPostIncDec() const {
|
||||
return kind_ == Kind::PostIncrement ||
|
||||
kind_ == Kind::PostDecrement;
|
||||
}
|
||||
|
||||
MOZ_MUST_USE bool isPreIncDec() const {
|
||||
return kind_ == Kind::PreIncrement ||
|
||||
kind_ == Kind::PreDecrement;
|
||||
}
|
||||
|
||||
MOZ_MUST_USE bool isInc() const {
|
||||
return kind_ == Kind::PostIncrement ||
|
||||
kind_ == Kind::PreIncrement;
|
||||
}
|
||||
|
||||
MOZ_MUST_USE bool isSuper() const {
|
||||
return objKind_ == ObjKind::Super;
|
||||
}
|
||||
|
||||
public:
|
||||
MOZ_MUST_USE bool prepareForObj();
|
||||
MOZ_MUST_USE bool prepareForKey();
|
||||
|
||||
MOZ_MUST_USE bool emitGet();
|
||||
|
||||
MOZ_MUST_USE bool prepareForRhs();
|
||||
MOZ_MUST_USE bool skipObjAndKeyAndRhs();
|
||||
|
||||
MOZ_MUST_USE bool emitDelete();
|
||||
|
||||
MOZ_MUST_USE bool emitAssignment();
|
||||
|
||||
MOZ_MUST_USE bool emitIncDec();
|
||||
};
|
||||
|
||||
} /* namespace frontend */
|
||||
} /* namespace js */
|
||||
|
||||
#endif /* frontend_ElemOpEmitter_h */
|
|
@ -0,0 +1,56 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
||||
* 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 "frontend/ExpressionStatementEmitter.h"
|
||||
|
||||
#include "frontend/BytecodeEmitter.h"
|
||||
#include "vm/Opcodes.h"
|
||||
|
||||
using namespace js;
|
||||
using namespace js::frontend;
|
||||
|
||||
using mozilla::Maybe;
|
||||
|
||||
ExpressionStatementEmitter::ExpressionStatementEmitter(BytecodeEmitter* bce,
|
||||
ValueUsage valueUsage)
|
||||
: bce_(bce),
|
||||
valueUsage_(valueUsage)
|
||||
{}
|
||||
|
||||
bool
|
||||
ExpressionStatementEmitter::prepareForExpr(const Maybe<uint32_t>& beginPos)
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Start);
|
||||
|
||||
if (beginPos) {
|
||||
if (!bce_->updateSourceCoordNotes(*beginPos)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
depth_ = bce_->stackDepth;
|
||||
state_ = State::Expr;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ExpressionStatementEmitter::emitEnd()
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Expr);
|
||||
MOZ_ASSERT(bce_->stackDepth == depth_ + 1);
|
||||
|
||||
JSOp op = valueUsage_ == ValueUsage::WantValue ? JSOP_SETRVAL : JSOP_POP;
|
||||
if (!bce_->emit1(op)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
state_ = State::End;
|
||||
#endif
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
||||
* 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 frontend_ExpressionStatementEmitter_h
|
||||
#define frontend_ExpressionStatementEmitter_h
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "frontend/ValueUsage.h"
|
||||
|
||||
namespace js {
|
||||
namespace frontend {
|
||||
|
||||
struct BytecodeEmitter;
|
||||
|
||||
// Class for emitting bytecode for expression statement.
|
||||
//
|
||||
// Usage: (check for the return value is omitted for simplicity)
|
||||
//
|
||||
// `expr;`
|
||||
// // IgnoreValue if this is in normal script.
|
||||
// // WantValue if this is in eval script.
|
||||
// ValueUsage valueUsage = ...;
|
||||
//
|
||||
// ExpressionStatementEmitter ese(this, valueUsage);
|
||||
// ese.prepareForExpr(Some(offset_of_expr));
|
||||
// emit(expr);
|
||||
// ese.emitEnd();
|
||||
//
|
||||
class MOZ_STACK_CLASS ExpressionStatementEmitter
|
||||
{
|
||||
BytecodeEmitter* bce_;
|
||||
|
||||
#ifdef DEBUG
|
||||
// The stack depth before emitting expression.
|
||||
int32_t depth_;
|
||||
#endif
|
||||
|
||||
// The usage of the value of the expression.
|
||||
ValueUsage valueUsage_;
|
||||
|
||||
#ifdef DEBUG
|
||||
// The state of this emitter.
|
||||
//
|
||||
// +-------+ prepareForExpr +------+ emitEnd +-----+
|
||||
// | Start |--------------->| Expr |-------->| End |
|
||||
// +-------+ +------+ +-----+
|
||||
enum class State
|
||||
{
|
||||
// The initial state.
|
||||
Start,
|
||||
|
||||
// After calling prepareForExpr.
|
||||
Expr,
|
||||
|
||||
// After calling emitEnd.
|
||||
End
|
||||
};
|
||||
State state_ = State::Start;
|
||||
#endif
|
||||
|
||||
public:
|
||||
ExpressionStatementEmitter(BytecodeEmitter* bce, ValueUsage valueUsage);
|
||||
|
||||
// Parameters are the offset in the source code for each character below:
|
||||
//
|
||||
// expr;
|
||||
// ^
|
||||
// |
|
||||
// beginPos
|
||||
//
|
||||
// Can be Nothing() if not available.
|
||||
MOZ_MUST_USE bool prepareForExpr(const mozilla::Maybe<uint32_t>& beginPos);
|
||||
MOZ_MUST_USE bool emitEnd();
|
||||
};
|
||||
|
||||
} // namespace frontend
|
||||
} // namespace js
|
||||
|
||||
#endif /* frontend_ExpressionStatementEmitter_h */
|
|
@ -0,0 +1,381 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
||||
* 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 "frontend/NameOpEmitter.h"
|
||||
|
||||
#include "frontend/BytecodeEmitter.h"
|
||||
#include "frontend/SharedContext.h"
|
||||
#include "frontend/TDZCheckCache.h"
|
||||
#include "vm/Opcodes.h"
|
||||
#include "vm/Scope.h"
|
||||
#include "vm/StringType.h"
|
||||
|
||||
using namespace js;
|
||||
using namespace js::frontend;
|
||||
|
||||
NameOpEmitter::NameOpEmitter(BytecodeEmitter* bce, JSAtom* name, Kind kind)
|
||||
: bce_(bce),
|
||||
kind_(kind),
|
||||
name_(bce_->cx, name),
|
||||
loc_(bce_->lookupName(name_))
|
||||
{}
|
||||
|
||||
NameOpEmitter::NameOpEmitter(BytecodeEmitter* bce, JSAtom* name, const NameLocation& loc,
|
||||
Kind kind)
|
||||
: bce_(bce),
|
||||
kind_(kind),
|
||||
name_(bce_->cx, name),
|
||||
loc_(loc)
|
||||
{}
|
||||
|
||||
bool
|
||||
NameOpEmitter::emitGet()
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Start);
|
||||
|
||||
switch (loc_.kind()) {
|
||||
case NameLocation::Kind::Dynamic:
|
||||
if (!bce_->emitAtomOp(name_, JSOP_GETNAME)) { // VAL
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case NameLocation::Kind::Global:
|
||||
if (!bce_->emitAtomOp(name_, JSOP_GETGNAME)) {// VAL
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case NameLocation::Kind::Intrinsic:
|
||||
if (!bce_->emitAtomOp(name_, JSOP_GETINTRINSIC)) {
|
||||
return false; // VAL
|
||||
}
|
||||
break;
|
||||
case NameLocation::Kind::NamedLambdaCallee:
|
||||
if (!bce_->emit1(JSOP_CALLEE)) { // VAL
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case NameLocation::Kind::Import:
|
||||
if (!bce_->emitAtomOp(name_, JSOP_GETIMPORT)) {
|
||||
return false; // VAL
|
||||
}
|
||||
break;
|
||||
case NameLocation::Kind::ArgumentSlot:
|
||||
if (!bce_->emitArgOp(JSOP_GETARG, loc_.argumentSlot())) {
|
||||
return false; // VAL
|
||||
}
|
||||
break;
|
||||
case NameLocation::Kind::FrameSlot:
|
||||
if (loc_.isLexical()) {
|
||||
if (!bce_->emitTDZCheckIfNeeded(name_, loc_)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!bce_->emitLocalOp(JSOP_GETLOCAL, loc_.frameSlot())) {
|
||||
return false; // VAL
|
||||
}
|
||||
break;
|
||||
case NameLocation::Kind::EnvironmentCoordinate:
|
||||
if (loc_.isLexical()) {
|
||||
if (!bce_->emitTDZCheckIfNeeded(name_, loc_)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!bce_->emitEnvCoordOp(JSOP_GETALIASEDVAR, loc_.environmentCoordinate())) {
|
||||
return false; // VAL
|
||||
}
|
||||
break;
|
||||
case NameLocation::Kind::DynamicAnnexBVar:
|
||||
MOZ_CRASH("Synthesized vars for Annex B.3.3 should only be used in initialization");
|
||||
}
|
||||
|
||||
if (isCall()) {
|
||||
switch (loc_.kind()) {
|
||||
case NameLocation::Kind::Dynamic: {
|
||||
JSOp thisOp = bce_->needsImplicitThis() ? JSOP_IMPLICITTHIS : JSOP_GIMPLICITTHIS;
|
||||
if (!bce_->emitAtomOp(name_, thisOp)) { // CALLEE THIS
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NameLocation::Kind::Global:
|
||||
if (!bce_->emitAtomOp(name_, JSOP_GIMPLICITTHIS)) {
|
||||
return false; // CALLEE THIS
|
||||
}
|
||||
break;
|
||||
case NameLocation::Kind::Intrinsic:
|
||||
case NameLocation::Kind::NamedLambdaCallee:
|
||||
case NameLocation::Kind::Import:
|
||||
case NameLocation::Kind::ArgumentSlot:
|
||||
case NameLocation::Kind::FrameSlot:
|
||||
case NameLocation::Kind::EnvironmentCoordinate:
|
||||
if (!bce_->emit1(JSOP_UNDEFINED)) { // CALLEE UNDEF
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case NameLocation::Kind::DynamicAnnexBVar:
|
||||
MOZ_CRASH("Synthesized vars for Annex B.3.3 should only be used in initialization");
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
state_ = State::Get;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
NameOpEmitter::prepareForRhs()
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Start);
|
||||
|
||||
switch (loc_.kind()) {
|
||||
case NameLocation::Kind::Dynamic:
|
||||
case NameLocation::Kind::Import:
|
||||
case NameLocation::Kind::DynamicAnnexBVar:
|
||||
if (!bce_->makeAtomIndex(name_, &atomIndex_)) {
|
||||
return false;
|
||||
}
|
||||
if (loc_.kind() == NameLocation::Kind::DynamicAnnexBVar) {
|
||||
// Annex B vars always go on the nearest variable environment,
|
||||
// even if lexical environments in between contain same-named
|
||||
// bindings.
|
||||
if (!bce_->emit1(JSOP_BINDVAR)) { // ENV
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!bce_->emitIndexOp(JSOP_BINDNAME, atomIndex_)) {
|
||||
return false; // ENV
|
||||
}
|
||||
}
|
||||
emittedBindOp_ = true;
|
||||
break;
|
||||
case NameLocation::Kind::Global:
|
||||
if (!bce_->makeAtomIndex(name_, &atomIndex_)) {
|
||||
return false;
|
||||
}
|
||||
if (loc_.isLexical() && isInitialize()) {
|
||||
// INITGLEXICAL always gets the global lexical scope. It doesn't
|
||||
// need a BINDGNAME.
|
||||
MOZ_ASSERT(bce_->innermostScope()->is<GlobalScope>());
|
||||
} else {
|
||||
if (!bce_->emitIndexOp(JSOP_BINDGNAME, atomIndex_)) {
|
||||
return false; // ENV
|
||||
}
|
||||
emittedBindOp_ = true;
|
||||
}
|
||||
break;
|
||||
case NameLocation::Kind::Intrinsic:
|
||||
break;
|
||||
case NameLocation::Kind::NamedLambdaCallee:
|
||||
break;
|
||||
case NameLocation::Kind::ArgumentSlot: {
|
||||
// If we assign to a positional formal parameter and the arguments
|
||||
// object is unmapped (strict mode or function with
|
||||
// default/rest/destructing args), parameters do not alias
|
||||
// arguments[i], and to make the arguments object reflect initial
|
||||
// parameter values prior to any mutation we create it eagerly
|
||||
// whenever parameters are (or might, in the case of calls to eval)
|
||||
// assigned.
|
||||
FunctionBox* funbox = bce_->sc->asFunctionBox();
|
||||
if (funbox->argumentsHasLocalBinding() && !funbox->hasMappedArgsObj()) {
|
||||
funbox->setDefinitelyNeedsArgsObj();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NameLocation::Kind::FrameSlot:
|
||||
break;
|
||||
case NameLocation::Kind::EnvironmentCoordinate:
|
||||
break;
|
||||
}
|
||||
|
||||
// For compound assignments, first get the LHS value, then emit
|
||||
// the RHS and the op.
|
||||
if (isCompoundAssignment() || isIncDec()) {
|
||||
if (loc_.kind() == NameLocation::Kind::Dynamic) {
|
||||
// For dynamic accesses we need to emit GETBOUNDNAME instead of
|
||||
// GETNAME for correctness: looking up @@unscopables on the
|
||||
// environment chain (due to 'with' environments) must only happen
|
||||
// once.
|
||||
//
|
||||
// GETBOUNDNAME uses the environment already pushed on the stack
|
||||
// from the earlier BINDNAME.
|
||||
if (!bce_->emit1(JSOP_DUP)) { // ENV ENV
|
||||
return false;
|
||||
}
|
||||
if (!bce_->emitAtomOp(name_, JSOP_GETBOUNDNAME)) {
|
||||
return false; // ENV V
|
||||
}
|
||||
} else {
|
||||
if (!emitGet()) { // ENV? V
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
state_ = State::Rhs;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
NameOpEmitter::emitAssignment()
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Rhs);
|
||||
|
||||
switch (loc_.kind()) {
|
||||
case NameLocation::Kind::Dynamic:
|
||||
case NameLocation::Kind::Import:
|
||||
case NameLocation::Kind::DynamicAnnexBVar:
|
||||
if (!bce_->emitIndexOp(bce_->strictifySetNameOp(JSOP_SETNAME), atomIndex_)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case NameLocation::Kind::Global: {
|
||||
JSOp op;
|
||||
if (emittedBindOp_) {
|
||||
op = bce_->strictifySetNameOp(JSOP_SETGNAME);
|
||||
} else {
|
||||
op = JSOP_INITGLEXICAL;
|
||||
}
|
||||
if (!bce_->emitIndexOp(op, atomIndex_)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NameLocation::Kind::Intrinsic:
|
||||
if (!bce_->emitAtomOp(name_, JSOP_SETINTRINSIC)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case NameLocation::Kind::NamedLambdaCallee:
|
||||
// Assigning to the named lambda is a no-op in sloppy mode but
|
||||
// throws in strict mode.
|
||||
if (bce_->sc->strict()) {
|
||||
if (!bce_->emit1(JSOP_THROWSETCALLEE)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NameLocation::Kind::ArgumentSlot:
|
||||
if (!bce_->emitArgOp(JSOP_SETARG, loc_.argumentSlot())) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case NameLocation::Kind::FrameSlot: {
|
||||
JSOp op = JSOP_SETLOCAL;
|
||||
if (loc_.isLexical()) {
|
||||
if (isInitialize()) {
|
||||
op = JSOP_INITLEXICAL;
|
||||
} else {
|
||||
if (loc_.isConst()) {
|
||||
op = JSOP_THROWSETCONST;
|
||||
}
|
||||
|
||||
if (!bce_->emitTDZCheckIfNeeded(name_, loc_)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!bce_->emitLocalOp(op, loc_.frameSlot())) {
|
||||
return false;
|
||||
}
|
||||
if (op == JSOP_INITLEXICAL) {
|
||||
if (!bce_->innermostTDZCheckCache->noteTDZCheck(bce_, name_, DontCheckTDZ)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NameLocation::Kind::EnvironmentCoordinate: {
|
||||
JSOp op = JSOP_SETALIASEDVAR;
|
||||
if (loc_.isLexical()) {
|
||||
if (isInitialize()) {
|
||||
op = JSOP_INITALIASEDLEXICAL;
|
||||
} else {
|
||||
if (loc_.isConst()) {
|
||||
op = JSOP_THROWSETALIASEDCONST;
|
||||
}
|
||||
|
||||
if (!bce_->emitTDZCheckIfNeeded(name_, loc_)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (loc_.bindingKind() == BindingKind::NamedLambdaCallee) {
|
||||
// Assigning to the named lambda is a no-op in sloppy mode and throws
|
||||
// in strict mode.
|
||||
op = JSOP_THROWSETALIASEDCONST;
|
||||
if (bce_->sc->strict()) {
|
||||
if (!bce_->emitEnvCoordOp(op, loc_.environmentCoordinate())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!bce_->emitEnvCoordOp(op, loc_.environmentCoordinate())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (op == JSOP_INITALIASEDLEXICAL) {
|
||||
if (!bce_->innermostTDZCheckCache->noteTDZCheck(bce_, name_, DontCheckTDZ)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
state_ = State::Assignment;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
NameOpEmitter::emitIncDec()
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Start);
|
||||
|
||||
JSOp binOp = isInc() ? JSOP_ADD : JSOP_SUB;
|
||||
if (!prepareForRhs()) { // ENV? V
|
||||
return false;
|
||||
}
|
||||
if (!bce_->emit1(JSOP_POS)) { // ENV? N
|
||||
return false;
|
||||
}
|
||||
if (isPostIncDec()) {
|
||||
if (!bce_->emit1(JSOP_DUP)) { // ENV? N? N
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!bce_->emit1(JSOP_ONE)) { // ENV? N? N 1
|
||||
return false;
|
||||
}
|
||||
if (!bce_->emit1(binOp)) { // ENV? N? N+1
|
||||
return false;
|
||||
}
|
||||
if (isPostIncDec() && emittedBindOp()) {
|
||||
if (!bce_->emit2(JSOP_PICK, 2)) { // N? N+1 ENV?
|
||||
return false;
|
||||
}
|
||||
if (!bce_->emit1(JSOP_SWAP)) { // N? ENV? N+1
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!emitAssignment()) { // N? N+1
|
||||
return false;
|
||||
}
|
||||
if (isPostIncDec()) {
|
||||
if (!bce_->emit1(JSOP_POP)) { // N
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
state_ = State::IncDec;
|
||||
#endif
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
||||
* 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 frontend_NameOpEmitter_h
|
||||
#define frontend_NameOpEmitter_h
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "frontend/NameAnalysisTypes.h"
|
||||
#include "js/TypeDecls.h"
|
||||
|
||||
namespace js {
|
||||
namespace frontend {
|
||||
|
||||
struct BytecodeEmitter;
|
||||
|
||||
// Class for emitting bytecode for name operation.
|
||||
//
|
||||
// Usage: (check for the return value is omitted for simplicity)
|
||||
//
|
||||
// `name;`
|
||||
// NameOpEmitter noe(this, atom_of_name
|
||||
// ElemOpEmitter::Kind::Get);
|
||||
// noe.emitGet();
|
||||
//
|
||||
// `name();`
|
||||
// this is handled in CallOrNewEmitter
|
||||
//
|
||||
// `name++;`
|
||||
// NameOpEmitter noe(this, atom_of_name
|
||||
// ElemOpEmitter::Kind::PostIncrement);
|
||||
// noe.emitIncDec();
|
||||
//
|
||||
// `name = 10;`
|
||||
// NameOpEmitter noe(this, atom_of_name
|
||||
// ElemOpEmitter::Kind::SimpleAssignment);
|
||||
// noe.prepareForRhs();
|
||||
// emit(10);
|
||||
// noe.emitAssignment();
|
||||
//
|
||||
// `name += 10;`
|
||||
// NameOpEmitter noe(this, atom_of_name
|
||||
// ElemOpEmitter::Kind::CompoundAssignment);
|
||||
// noe.prepareForRhs();
|
||||
// emit(10);
|
||||
// emit_add_op_here();
|
||||
// noe.emitAssignment();
|
||||
//
|
||||
// `name = 10;` part of `let name = 10;`
|
||||
// NameOpEmitter noe(this, atom_of_name
|
||||
// ElemOpEmitter::Kind::Initialize);
|
||||
// noe.prepareForRhs();
|
||||
// emit(10);
|
||||
// noe.emitAssignment();
|
||||
//
|
||||
class MOZ_STACK_CLASS NameOpEmitter
|
||||
{
|
||||
public:
|
||||
enum class Kind {
|
||||
Get,
|
||||
Call,
|
||||
PostIncrement,
|
||||
PreIncrement,
|
||||
PostDecrement,
|
||||
PreDecrement,
|
||||
SimpleAssignment,
|
||||
CompoundAssignment,
|
||||
Initialize
|
||||
};
|
||||
|
||||
private:
|
||||
BytecodeEmitter* bce_;
|
||||
|
||||
Kind kind_;
|
||||
|
||||
bool emittedBindOp_ = false;
|
||||
|
||||
RootedAtom name_;
|
||||
|
||||
uint32_t atomIndex_;
|
||||
|
||||
NameLocation loc_;
|
||||
|
||||
#ifdef DEBUG
|
||||
// The state of this emitter.
|
||||
//
|
||||
// [Get]
|
||||
// [Call]
|
||||
// +-------+ emitGet +-----+
|
||||
// | Start |-+-+--------->| Get |
|
||||
// +-------+ | +-----+
|
||||
// |
|
||||
// | [PostIncrement]
|
||||
// | [PreIncrement]
|
||||
// | [PostDecrement]
|
||||
// | [PreDecrement]
|
||||
// | emitIncDec +--------+
|
||||
// +------------->| IncDec |
|
||||
// | +--------+
|
||||
// |
|
||||
// | [SimpleAssignment]
|
||||
// | prepareForRhs +-----+
|
||||
// +--------------------->+-------------->| Rhs |-+
|
||||
// | ^ +-----+ |
|
||||
// | | |
|
||||
// | | +------------------+
|
||||
// | [CompoundAssignment] | |
|
||||
// | emitGet +-----+ | | emitAssignment +------------+
|
||||
// +---------->| Get |----+ + -------------->| Assignment |
|
||||
// +-----+ +------------+
|
||||
enum class State {
|
||||
// The initial state.
|
||||
Start,
|
||||
|
||||
// After calling emitGet.
|
||||
Get,
|
||||
|
||||
// After calling emitIncDec.
|
||||
IncDec,
|
||||
|
||||
// After calling prepareForRhs.
|
||||
Rhs,
|
||||
|
||||
// After calling emitAssignment.
|
||||
Assignment,
|
||||
};
|
||||
State state_ = State::Start;
|
||||
#endif
|
||||
|
||||
public:
|
||||
NameOpEmitter(BytecodeEmitter* bce, JSAtom* name, Kind kind);
|
||||
NameOpEmitter(BytecodeEmitter* bce, JSAtom* name, const NameLocation& loc, Kind kind);
|
||||
|
||||
private:
|
||||
MOZ_MUST_USE bool isCall() const {
|
||||
return kind_ == Kind::Call;
|
||||
}
|
||||
|
||||
MOZ_MUST_USE bool isSimpleAssignment() const {
|
||||
return kind_ == Kind::SimpleAssignment;
|
||||
}
|
||||
|
||||
MOZ_MUST_USE bool isCompoundAssignment() const {
|
||||
return kind_ == Kind::CompoundAssignment;
|
||||
}
|
||||
|
||||
MOZ_MUST_USE bool isIncDec() const {
|
||||
return isPostIncDec() || isPreIncDec();
|
||||
}
|
||||
|
||||
MOZ_MUST_USE bool isPostIncDec() const {
|
||||
return kind_ == Kind::PostIncrement ||
|
||||
kind_ == Kind::PostDecrement;
|
||||
}
|
||||
|
||||
MOZ_MUST_USE bool isPreIncDec() const {
|
||||
return kind_ == Kind::PreIncrement ||
|
||||
kind_ == Kind::PreDecrement;
|
||||
}
|
||||
|
||||
MOZ_MUST_USE bool isInc() const {
|
||||
return kind_ == Kind::PostIncrement ||
|
||||
kind_ == Kind::PreIncrement;
|
||||
}
|
||||
|
||||
MOZ_MUST_USE bool isInitialize() const {
|
||||
return kind_ == Kind::Initialize;
|
||||
}
|
||||
|
||||
public:
|
||||
MOZ_MUST_USE bool emittedBindOp() const {
|
||||
return emittedBindOp_;
|
||||
}
|
||||
|
||||
MOZ_MUST_USE const NameLocation& loc() const {
|
||||
return loc_;
|
||||
}
|
||||
|
||||
MOZ_MUST_USE bool emitGet();
|
||||
MOZ_MUST_USE bool prepareForRhs();
|
||||
MOZ_MUST_USE bool emitAssignment();
|
||||
MOZ_MUST_USE bool emitIncDec();
|
||||
};
|
||||
|
||||
} /* namespace frontend */
|
||||
} /* namespace js */
|
||||
|
||||
#endif /* frontend_NameOpEmitter_h */
|
|
@ -0,0 +1,275 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
||||
* 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 "frontend/PropOpEmitter.h"
|
||||
|
||||
#include "frontend/BytecodeEmitter.h"
|
||||
#include "frontend/SharedContext.h"
|
||||
#include "vm/Opcodes.h"
|
||||
#include "vm/StringType.h"
|
||||
|
||||
using namespace js;
|
||||
using namespace js::frontend;
|
||||
|
||||
PropOpEmitter::PropOpEmitter(BytecodeEmitter* bce, Kind kind, ObjKind objKind)
|
||||
: bce_(bce),
|
||||
kind_(kind),
|
||||
objKind_(objKind)
|
||||
{}
|
||||
|
||||
bool
|
||||
PropOpEmitter::prepareAtomIndex(JSAtom* prop)
|
||||
{
|
||||
if (!bce_->makeAtomIndex(prop, &propAtomIndex_)) {
|
||||
return false;
|
||||
}
|
||||
isLength_ = prop == bce_->cx->names().length;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
PropOpEmitter::prepareForObj()
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Start);
|
||||
|
||||
#ifdef DEBUG
|
||||
state_ = State::Obj;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
PropOpEmitter::emitGet(JSAtom* prop)
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Obj);
|
||||
|
||||
if (!prepareAtomIndex(prop)) {
|
||||
return false;
|
||||
}
|
||||
if (isCall()) {
|
||||
if (!bce_->emit1(JSOP_DUP)) { // [Super]
|
||||
// // THIS THIS
|
||||
// // [Other]
|
||||
// // OBJ OBJ
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (isSuper()) {
|
||||
if (!bce_->emit1(JSOP_SUPERBASE)) { // THIS? THIS SUPERBASE
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (isIncDec() || isCompoundAssignment()) {
|
||||
if (isSuper()) {
|
||||
if (!bce_->emit1(JSOP_DUP2)) { // THIS SUPERBASE THIS SUPERBASE
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!bce_->emit1(JSOP_DUP)) { // OBJ OBJ
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JSOp op;
|
||||
if (isSuper()) {
|
||||
op = JSOP_GETPROP_SUPER;
|
||||
} else if (isCall()) {
|
||||
op = JSOP_CALLPROP;
|
||||
} else {
|
||||
op = isLength_ ? JSOP_LENGTH : JSOP_GETPROP;
|
||||
}
|
||||
if (!bce_->emitAtomOp(propAtomIndex_, op)) { // [Get]
|
||||
// // PROP
|
||||
// // [Call]
|
||||
// // THIS PROP
|
||||
// // [Inc/Dec/Compound,
|
||||
// // Super]
|
||||
// // THIS SUPERBASE PROP
|
||||
// // [Inc/Dec/Compound,
|
||||
// // Other]
|
||||
// // OBJ PROP
|
||||
return false;
|
||||
}
|
||||
if (isCall()) {
|
||||
if (!bce_->emit1(JSOP_SWAP)) { // PROP THIS
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
state_ = State::Get;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
PropOpEmitter::prepareForRhs()
|
||||
{
|
||||
MOZ_ASSERT(isSimpleAssignment() || isCompoundAssignment());
|
||||
MOZ_ASSERT_IF(isSimpleAssignment(), state_ == State::Obj);
|
||||
MOZ_ASSERT_IF(isCompoundAssignment(), state_ == State::Get);
|
||||
|
||||
if (isSimpleAssignment()) {
|
||||
// For CompoundAssignment, SUPERBASE is already emitted by emitGet.
|
||||
if (isSuper()) {
|
||||
if (!bce_->emit1(JSOP_SUPERBASE)) { // THIS SUPERBASE
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
state_ = State::Rhs;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
PropOpEmitter::skipObjAndRhs()
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Start);
|
||||
MOZ_ASSERT(isSimpleAssignment());
|
||||
|
||||
#ifdef DEBUG
|
||||
state_ = State::Rhs;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
PropOpEmitter::emitDelete(JSAtom* prop)
|
||||
{
|
||||
MOZ_ASSERT_IF(!isSuper(), state_ == State::Obj);
|
||||
MOZ_ASSERT_IF(isSuper(), state_ == State::Start);
|
||||
MOZ_ASSERT(isDelete());
|
||||
|
||||
if (!prepareAtomIndex(prop)) {
|
||||
return false;
|
||||
}
|
||||
if (isSuper()) {
|
||||
if (!bce_->emit1(JSOP_SUPERBASE)) { // THIS SUPERBASE
|
||||
return false;
|
||||
}
|
||||
|
||||
// Unconditionally throw when attempting to delete a super-reference.
|
||||
if (!bce_->emitUint16Operand(JSOP_THROWMSG, JSMSG_CANT_DELETE_SUPER)) {
|
||||
return false; // THIS SUPERBASE
|
||||
}
|
||||
|
||||
// Another wrinkle: Balance the stack from the emitter's point of view.
|
||||
// Execution will not reach here, as the last bytecode threw.
|
||||
if (!bce_->emit1(JSOP_POP)) { // THIS
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
JSOp op = bce_->sc->strict() ? JSOP_STRICTDELPROP : JSOP_DELPROP;
|
||||
if (!bce_->emitAtomOp(propAtomIndex_, op)) { // SUCCEEDED
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
state_ = State::Delete;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
PropOpEmitter::emitAssignment(JSAtom* prop)
|
||||
{
|
||||
MOZ_ASSERT(isSimpleAssignment() || isCompoundAssignment());
|
||||
MOZ_ASSERT(state_ == State::Rhs);
|
||||
|
||||
if (isSimpleAssignment()) {
|
||||
if (!prepareAtomIndex(prop)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
JSOp setOp = isSuper()
|
||||
? bce_->sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER
|
||||
: bce_->sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP;
|
||||
if (!bce_->emitAtomOp(propAtomIndex_, setOp)) { // VAL
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
state_ = State::Assignment;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
PropOpEmitter::emitIncDec(JSAtom* prop)
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Obj);
|
||||
MOZ_ASSERT(isIncDec());
|
||||
|
||||
if (!emitGet(prop)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(state_ == State::Get);
|
||||
|
||||
JSOp binOp = isInc() ? JSOP_ADD : JSOP_SUB;
|
||||
|
||||
if (!bce_->emit1(JSOP_POS)) { // ... N
|
||||
return false;
|
||||
}
|
||||
if (isPostIncDec()) {
|
||||
if (!bce_->emit1(JSOP_DUP)) { // ... N N
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!bce_->emit1(JSOP_ONE)) { // ... N? N 1
|
||||
return false;
|
||||
}
|
||||
if (!bce_->emit1(binOp)) { // ... N? N+1
|
||||
return false;
|
||||
}
|
||||
if (isPostIncDec()) {
|
||||
if (isSuper()) { // THIS OBJ N N+1
|
||||
if (!bce_->emit2(JSOP_PICK, 3)) { // OBJ N N+1 THIS
|
||||
return false;
|
||||
}
|
||||
if (!bce_->emit1(JSOP_SWAP)) { // OBJ N THIS N+1
|
||||
return false;
|
||||
}
|
||||
if (!bce_->emit2(JSOP_PICK, 3)) { // N THIS N+1 OBJ
|
||||
return false;
|
||||
}
|
||||
if (!bce_->emit1(JSOP_SWAP)) { // N THIS OBJ N+1
|
||||
return false;
|
||||
}
|
||||
} else { // OBJ N N+1
|
||||
if (!bce_->emit2(JSOP_PICK, 2)) { // N N+1 OBJ
|
||||
return false;
|
||||
}
|
||||
if (!bce_->emit1(JSOP_SWAP)) { // N OBJ N+1
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JSOp setOp = isSuper()
|
||||
? bce_->sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER
|
||||
: bce_->sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP;
|
||||
if (!bce_->emitAtomOp(propAtomIndex_, setOp)) { // N? N+1
|
||||
return false;
|
||||
}
|
||||
if (isPostIncDec()) {
|
||||
if (!bce_->emit1(JSOP_POP)) { // N
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
state_ = State::IncDec;
|
||||
#endif
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,267 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
||||
* 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 frontend_PropOpEmitter_h
|
||||
#define frontend_PropOpEmitter_h
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "js/TypeDecls.h"
|
||||
|
||||
namespace js {
|
||||
namespace frontend {
|
||||
|
||||
struct BytecodeEmitter;
|
||||
|
||||
// Class for emitting bytecode for property operation.
|
||||
//
|
||||
// Usage: (check for the return value is omitted for simplicity)
|
||||
//
|
||||
// `obj.prop;`
|
||||
// PropOpEmitter poe(this,
|
||||
// PropOpEmitter::Kind::Get,
|
||||
// PropOpEmitter::ObjKind::Other);
|
||||
// poe.prepareForObj();
|
||||
// emit(obj);
|
||||
// poe.emitGet(atom_of_prop);
|
||||
//
|
||||
// `super.prop;`
|
||||
// PropOpEmitter poe(this,
|
||||
// PropOpEmitter::Kind::Get,
|
||||
// PropOpEmitter::ObjKind::Super);
|
||||
// poe.prepareForObj();
|
||||
// emit(obj);
|
||||
// poe.emitGet(atom_of_prop);
|
||||
//
|
||||
// `obj.prop();`
|
||||
// PropOpEmitter poe(this,
|
||||
// PropOpEmitter::Kind::Call,
|
||||
// PropOpEmitter::ObjKind::Other);
|
||||
// poe.prepareForObj();
|
||||
// emit(obj);
|
||||
// poe.emitGet(atom_of_prop);
|
||||
// emit_call_here();
|
||||
//
|
||||
// `new obj.prop();`
|
||||
// PropOpEmitter poe(this,
|
||||
// PropOpEmitter::Kind::Call,
|
||||
// PropOpEmitter::ObjKind::Other);
|
||||
// poe.prepareForObj();
|
||||
// emit(obj);
|
||||
// poe.emitGet(atom_of_prop);
|
||||
// emit_call_here();
|
||||
//
|
||||
// `delete obj.prop;`
|
||||
// PropOpEmitter poe(this,
|
||||
// PropOpEmitter::Kind::Delete,
|
||||
// PropOpEmitter::ObjKind::Other);
|
||||
// poe.prepareForObj();
|
||||
// emit(obj);
|
||||
// poe.emitDelete(atom_of_prop);
|
||||
//
|
||||
// `delete super.prop;`
|
||||
// PropOpEmitter poe(this,
|
||||
// PropOpEmitter::Kind::Delete,
|
||||
// PropOpEmitter::ObjKind::Other);
|
||||
// poe.emitDelete(atom_of_prop);
|
||||
//
|
||||
// `obj.prop++;`
|
||||
// PropOpEmitter poe(this,
|
||||
// PropOpEmitter::Kind::PostIncrement,
|
||||
// PropOpEmitter::ObjKind::Other);
|
||||
// poe.prepareForObj();
|
||||
// emit(obj);
|
||||
// poe.emitIncDec(atom_of_prop);
|
||||
//
|
||||
// `obj.prop = value;`
|
||||
// PropOpEmitter poe(this,
|
||||
// PropOpEmitter::Kind::SimpleAssignment,
|
||||
// PropOpEmitter::ObjKind::Other);
|
||||
// poe.prepareForObj();
|
||||
// emit(obj);
|
||||
// poe.prepareForRhs();
|
||||
// emit(value);
|
||||
// poe.emitAssignment(atom_of_prop);
|
||||
//
|
||||
// `obj.prop += value;`
|
||||
// PropOpEmitter poe(this,
|
||||
// PropOpEmitter::Kind::CompoundAssignment,
|
||||
// PropOpEmitter::ObjKind::Other);
|
||||
// poe.prepareForObj();
|
||||
// emit(obj);
|
||||
// poe.emitGet(atom_of_prop);
|
||||
// poe.prepareForRhs();
|
||||
// emit(value);
|
||||
// emit_add_op_here();
|
||||
// poe.emitAssignment(nullptr); // nullptr for CompoundAssignment
|
||||
//
|
||||
class MOZ_STACK_CLASS PropOpEmitter
|
||||
{
|
||||
public:
|
||||
enum class Kind {
|
||||
Get,
|
||||
Call,
|
||||
Set,
|
||||
Delete,
|
||||
PostIncrement,
|
||||
PreIncrement,
|
||||
PostDecrement,
|
||||
PreDecrement,
|
||||
SimpleAssignment,
|
||||
CompoundAssignment
|
||||
};
|
||||
enum class ObjKind {
|
||||
Super,
|
||||
Other
|
||||
};
|
||||
|
||||
private:
|
||||
BytecodeEmitter* bce_;
|
||||
|
||||
Kind kind_;
|
||||
ObjKind objKind_;
|
||||
|
||||
// The index for the property name's atom.
|
||||
uint32_t propAtomIndex_ = 0;
|
||||
|
||||
// Whether the property name is `length` or not.
|
||||
bool isLength_ = false;
|
||||
|
||||
#ifdef DEBUG
|
||||
// The state of this emitter.
|
||||
//
|
||||
// skipObjAndRhs
|
||||
// +----------------------------+
|
||||
// | |
|
||||
// +-------+ | prepareForObj +-----+ |
|
||||
// | Start |-+-------------->| Obj |-+ |
|
||||
// +-------+ +-----+ | |
|
||||
// | |
|
||||
// +---------------------------------+ |
|
||||
// | |
|
||||
// | |
|
||||
// | [Get] |
|
||||
// | [Call] |
|
||||
// | emitGet +-----+ |
|
||||
// +---------->| Get | |
|
||||
// | +-----+ |
|
||||
// | |
|
||||
// | [Delete] |
|
||||
// | emitDelete +--------+ |
|
||||
// +------------->| Delete | |
|
||||
// | +--------+ |
|
||||
// | |
|
||||
// | [PostIncrement] |
|
||||
// | [PreIncrement] |
|
||||
// | [PostDecrement] |
|
||||
// | [PreDecrement] |
|
||||
// | emitIncDec +--------+ |
|
||||
// +------------->| IncDec | |
|
||||
// | +--------+ |
|
||||
// | |
|
||||
// | [SimpleAssignment] |
|
||||
// | prepareForRhs | +-----+
|
||||
// +--------------------->+-------------->+->| Rhs |-+
|
||||
// | ^ +-----+ |
|
||||
// | | |
|
||||
// | | +---------+
|
||||
// | [CompoundAssignment] | |
|
||||
// | emitGet +-----+ | | emitAssignment +------------+
|
||||
// +---------->| Get |----+ + -------------->| Assignment |
|
||||
// +-----+ +------------+
|
||||
enum class State {
|
||||
// The initial state.
|
||||
Start,
|
||||
|
||||
// After calling prepareForObj.
|
||||
Obj,
|
||||
|
||||
// After calling emitGet.
|
||||
Get,
|
||||
|
||||
// After calling emitDelete.
|
||||
Delete,
|
||||
|
||||
// After calling emitIncDec.
|
||||
IncDec,
|
||||
|
||||
// After calling prepareForRhs or skipObjAndRhs.
|
||||
Rhs,
|
||||
|
||||
// After calling emitAssignment.
|
||||
Assignment,
|
||||
};
|
||||
State state_ = State::Start;
|
||||
#endif
|
||||
|
||||
public:
|
||||
PropOpEmitter(BytecodeEmitter* bce, Kind kind, ObjKind objKind);
|
||||
|
||||
private:
|
||||
MOZ_MUST_USE bool isCall() const {
|
||||
return kind_ == Kind::Call;
|
||||
}
|
||||
|
||||
MOZ_MUST_USE bool isSuper() const {
|
||||
return objKind_ == ObjKind::Super;
|
||||
}
|
||||
|
||||
MOZ_MUST_USE bool isSimpleAssignment() const {
|
||||
return kind_ == Kind::SimpleAssignment;
|
||||
}
|
||||
|
||||
MOZ_MUST_USE bool isDelete() const {
|
||||
return kind_ == Kind::Delete;
|
||||
}
|
||||
|
||||
MOZ_MUST_USE bool isCompoundAssignment() const {
|
||||
return kind_ == Kind::CompoundAssignment;
|
||||
}
|
||||
|
||||
MOZ_MUST_USE bool isIncDec() const {
|
||||
return isPostIncDec() || isPreIncDec();
|
||||
}
|
||||
|
||||
MOZ_MUST_USE bool isPostIncDec() const {
|
||||
return kind_ == Kind::PostIncrement ||
|
||||
kind_ == Kind::PostDecrement;
|
||||
}
|
||||
|
||||
MOZ_MUST_USE bool isPreIncDec() const {
|
||||
return kind_ == Kind::PreIncrement ||
|
||||
kind_ == Kind::PreDecrement;
|
||||
}
|
||||
|
||||
MOZ_MUST_USE bool isInc() const {
|
||||
return kind_ == Kind::PostIncrement ||
|
||||
kind_ == Kind::PreIncrement;
|
||||
}
|
||||
|
||||
MOZ_MUST_USE bool
|
||||
prepareAtomIndex(JSAtom* prop);
|
||||
|
||||
public:
|
||||
MOZ_MUST_USE bool prepareForObj();
|
||||
|
||||
MOZ_MUST_USE bool emitGet(JSAtom* prop);
|
||||
|
||||
MOZ_MUST_USE bool prepareForRhs();
|
||||
MOZ_MUST_USE bool skipObjAndRhs();
|
||||
|
||||
MOZ_MUST_USE bool emitDelete(JSAtom* prop);
|
||||
|
||||
// `prop` can be nullptr for CompoundAssignment.
|
||||
MOZ_MUST_USE bool emitAssignment(JSAtom* prop);
|
||||
|
||||
MOZ_MUST_USE bool emitIncDec(JSAtom* prop);
|
||||
};
|
||||
|
||||
} /* namespace frontend */
|
||||
} /* namespace js */
|
||||
|
||||
#endif /* frontend_PropOpEmitter_h */
|
|
@ -0,0 +1,28 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
||||
* 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 frontend_ValueUsage_h
|
||||
#define frontend_ValueUsage_h
|
||||
|
||||
namespace js {
|
||||
namespace frontend {
|
||||
|
||||
// Used to control whether JSOP_CALL_IGNORES_RV is emitted for function calls.
|
||||
enum class ValueUsage {
|
||||
// Assume the value of the current expression may be used. This is always
|
||||
// correct but prohibits JSOP_CALL_IGNORES_RV.
|
||||
WantValue,
|
||||
|
||||
// Pass this when emitting an expression if the expression's value is
|
||||
// definitely unused by later instructions. You must make sure the next
|
||||
// instruction is JSOP_POP, a jump to a JSOP_POP, or something similar.
|
||||
IgnoreValue
|
||||
};
|
||||
|
||||
} /* namespace frontend */
|
||||
} /* namespace js */
|
||||
|
||||
#endif /* frontend_ValueUsage_h */
|
|
@ -226,9 +226,12 @@ UNIFIED_SOURCES += [
|
|||
'frontend/BytecodeCompiler.cpp',
|
||||
'frontend/BytecodeControlStructures.cpp',
|
||||
'frontend/BytecodeEmitter.cpp',
|
||||
'frontend/CallOrNewEmitter.cpp',
|
||||
'frontend/CForEmitter.cpp',
|
||||
'frontend/DoWhileEmitter.cpp',
|
||||
'frontend/ElemOpEmitter.cpp',
|
||||
'frontend/EmitterScope.cpp',
|
||||
'frontend/ExpressionStatementEmitter.cpp',
|
||||
'frontend/FoldConstants.cpp',
|
||||
'frontend/ForInEmitter.cpp',
|
||||
'frontend/ForOfEmitter.cpp',
|
||||
|
@ -236,7 +239,9 @@ UNIFIED_SOURCES += [
|
|||
'frontend/IfEmitter.cpp',
|
||||
'frontend/JumpList.cpp',
|
||||
'frontend/NameFunctions.cpp',
|
||||
'frontend/NameOpEmitter.cpp',
|
||||
'frontend/ParseNode.cpp',
|
||||
'frontend/PropOpEmitter.cpp',
|
||||
'frontend/SwitchEmitter.cpp',
|
||||
'frontend/TDZCheckCache.cpp',
|
||||
'frontend/TokenStream.cpp',
|
||||
|
|
|
@ -4960,7 +4960,7 @@ nsCSSFrameConstructor::ConstructFrameWithAnonymousChild(
|
|||
nsFrameItems& aFrameItems,
|
||||
ContainerFrameCreationFunc aConstructor,
|
||||
ContainerFrameCreationFunc aInnerConstructor,
|
||||
nsICSSAnonBoxPseudo* aInnerPseudo,
|
||||
nsCSSAnonBoxPseudoStaticAtom* aInnerPseudo,
|
||||
bool aCandidateRootFrame)
|
||||
{
|
||||
nsIContent* const content = aItem.mContent;
|
||||
|
@ -9780,7 +9780,7 @@ nsCSSFrameConstructor::WrapItemsInPseudoParent(nsIContent* aParentContent,
|
|||
const FCItemIterator& aEndIter)
|
||||
{
|
||||
const PseudoParentData& pseudoData = sPseudoParentData[aWrapperType];
|
||||
nsICSSAnonBoxPseudo* pseudoType = pseudoData.mPseudoType;
|
||||
nsCSSAnonBoxPseudoStaticAtom* pseudoType = pseudoData.mPseudoType;
|
||||
StyleDisplay parentDisplay = aParentStyle->StyleDisplay()->mDisplay;
|
||||
|
||||
if (pseudoType == nsCSSAnonBoxes::table() &&
|
||||
|
|
|
@ -33,7 +33,7 @@ struct nsGenConInitializer;
|
|||
class nsContainerFrame;
|
||||
class nsFirstLineFrame;
|
||||
class nsFirstLetterFrame;
|
||||
class nsICSSAnonBoxPseudo;
|
||||
class nsCSSAnonBoxPseudoStaticAtom;
|
||||
class nsIDocument;
|
||||
class nsPageContentFrame;
|
||||
struct PendingBinding;
|
||||
|
@ -742,7 +742,7 @@ private:
|
|||
FrameFullConstructor mFullConstructor;
|
||||
// For cases when FCDATA_CREATE_BLOCK_WRAPPER_FOR_ALL_KIDS is set, the
|
||||
// anonymous box type to use for that wrapper.
|
||||
nsICSSAnonBoxPseudo* const mAnonBoxPseudo;
|
||||
nsCSSAnonBoxPseudoStaticAtom* const mAnonBoxPseudo;
|
||||
};
|
||||
|
||||
/* Structure representing a mapping of an atom to a FrameConstructionData.
|
||||
|
@ -781,7 +781,7 @@ private:
|
|||
for a table pseudo-frame */
|
||||
struct PseudoParentData {
|
||||
const FrameConstructionData mFCData;
|
||||
nsICSSAnonBoxPseudo* const mPseudoType;
|
||||
nsCSSAnonBoxPseudoStaticAtom* const mPseudoType;
|
||||
};
|
||||
/* Array of such structures that we use to properly construct table
|
||||
pseudo-frames as needed */
|
||||
|
@ -1586,7 +1586,7 @@ private:
|
|||
nsFrameItems& aFrameItems,
|
||||
ContainerFrameCreationFunc aConstructor,
|
||||
ContainerFrameCreationFunc aInnerConstructor,
|
||||
nsICSSAnonBoxPseudo* aInnerPseudo,
|
||||
nsCSSAnonBoxPseudoStaticAtom* aInnerPseudo,
|
||||
bool aCandidateRootFrame);
|
||||
|
||||
/**
|
||||
|
|
|
@ -52,7 +52,7 @@ fuzzy-if(Android,0-6,0-4) skip == offscreen-clipped-blendmode-3.html offscreen-c
|
|||
fuzzy-if(Android,0-6,0-4) skip-if(!asyncPan) == offscreen-clipped-blendmode-4.html offscreen-clipped-blendmode-ref.html
|
||||
fuzzy-if(Android,0-7,0-4) skip-if(!asyncPan) == perspective-scrolling-1.html perspective-scrolling-1-ref.html
|
||||
fuzzy-if(Android,0-7,0-4) skip-if(!asyncPan) == perspective-scrolling-2.html perspective-scrolling-2-ref.html
|
||||
fuzzy-if(Android,0-7,0-4) fails-if(webrender) skip-if(!asyncPan) == perspective-scrolling-3.html perspective-scrolling-3-ref.html # bug 1361720 for webrender
|
||||
fuzzy-if(Android,0-7,0-4) skip-if(!asyncPan) == perspective-scrolling-3.html perspective-scrolling-3-ref.html
|
||||
fuzzy-if(Android,0-7,0-4) skip-if(!asyncPan) == perspective-scrolling-4.html perspective-scrolling-4-ref.html
|
||||
pref(apz.disable_for_scroll_linked_effects,true) skip-if(!asyncPan) == disable-apz-for-sle-pages.html disable-apz-for-sle-pages-ref.html
|
||||
fuzzy-if(browserIsRemote&&d2d,0-1,0-20) skip-if(!asyncPan) == background-blend-mode-1.html background-blend-mode-1-ref.html
|
||||
|
|
|
@ -673,7 +673,7 @@ ServoStyleSet::ResolveNonInheritingAnonymousBoxStyle(nsAtom* aPseudoTag)
|
|||
#ifdef MOZ_XUL
|
||||
already_AddRefed<ComputedStyle>
|
||||
ServoStyleSet::ResolveXULTreePseudoStyle(dom::Element* aParentElement,
|
||||
nsICSSAnonBoxPseudo* aPseudoTag,
|
||||
nsCSSAnonBoxPseudoStaticAtom* aPseudoTag,
|
||||
ComputedStyle* aParentContext,
|
||||
const AtomArray& aInputWord)
|
||||
{
|
||||
|
|
|
@ -221,7 +221,7 @@ public:
|
|||
#ifdef MOZ_XUL
|
||||
already_AddRefed<ComputedStyle>
|
||||
ResolveXULTreePseudoStyle(dom::Element* aParentElement,
|
||||
nsICSSAnonBoxPseudo* aPseudoTag,
|
||||
nsCSSAnonBoxPseudoStaticAtom* aPseudoTag,
|
||||
ComputedStyle* aParentContext,
|
||||
const AtomArray& aInputWord);
|
||||
#endif
|
||||
|
|
|
@ -94,12 +94,12 @@ public:
|
|||
static void AssertAtoms();
|
||||
#endif
|
||||
|
||||
// Alias nsCSSAnonBoxes::foo() to alias nsGkAtoms::AnonBox_foo.
|
||||
// Alias nsCSSAnonBoxes::foo() to nsGkAtoms::AnonBox_foo.
|
||||
#define CSS_ANON_BOX(name_, value_) \
|
||||
static nsICSSAnonBoxPseudo* name_() \
|
||||
static nsCSSAnonBoxPseudoStaticAtom* name_() \
|
||||
{ \
|
||||
return const_cast<nsICSSAnonBoxPseudo*>( \
|
||||
static_cast<const nsICSSAnonBoxPseudo*>( \
|
||||
return const_cast<nsCSSAnonBoxPseudoStaticAtom*>( \
|
||||
static_cast<const nsCSSAnonBoxPseudoStaticAtom*>( \
|
||||
nsGkAtoms::AnonBox_##name_)); \
|
||||
}
|
||||
#include "nsCSSAnonBoxList.h"
|
||||
|
|
|
@ -103,12 +103,12 @@ public:
|
|||
static void AssertAtoms();
|
||||
#endif
|
||||
|
||||
// Alias nsCSSPseudoElements::foo() to alias nsGkAtoms::foo.
|
||||
// Alias nsCSSPseudoElements::foo() to nsGkAtoms::foo.
|
||||
#define CSS_PSEUDO_ELEMENT(name_, value_, flags_) \
|
||||
static nsICSSPseudoElement* name_() \
|
||||
static nsCSSPseudoElementStaticAtom* name_() \
|
||||
{ \
|
||||
return const_cast<nsICSSPseudoElement*>( \
|
||||
static_cast<const nsICSSPseudoElement*>( \
|
||||
return const_cast<nsCSSPseudoElementStaticAtom*>( \
|
||||
static_cast<const nsCSSPseudoElementStaticAtom*>( \
|
||||
nsGkAtoms::PseudoElement_##name_)); \
|
||||
}
|
||||
#include "nsCSSPseudoElementList.h"
|
||||
|
|
|
@ -994,7 +994,7 @@ nsTreeBodyFrame::GetCellAt(int32_t aX, int32_t aY, int32_t* aRow,
|
|||
}
|
||||
|
||||
nsTreeColumn* col;
|
||||
nsICSSAnonBoxPseudo* child;
|
||||
nsCSSAnonBoxPseudoStaticAtom* child;
|
||||
GetCellAt(point.x, point.y, aRow, &col, &child);
|
||||
|
||||
if (col) {
|
||||
|
@ -1424,7 +1424,7 @@ nsTreeBodyFrame::AdjustForCellText(nsAutoString& aText,
|
|||
aTextRect.width = width;
|
||||
}
|
||||
|
||||
nsICSSAnonBoxPseudo*
|
||||
nsCSSAnonBoxPseudoStaticAtom*
|
||||
nsTreeBodyFrame::GetItemWithinCellAt(nscoord aX, const nsRect& aCellRect,
|
||||
int32_t aRowIndex,
|
||||
nsTreeColumn* aColumn)
|
||||
|
@ -1575,7 +1575,7 @@ nsTreeBodyFrame::GetItemWithinCellAt(nscoord aX, const nsRect& aCellRect,
|
|||
void
|
||||
nsTreeBodyFrame::GetCellAt(nscoord aX, nscoord aY, int32_t* aRow,
|
||||
nsTreeColumn** aCol,
|
||||
nsICSSAnonBoxPseudo** aChildElt)
|
||||
nsCSSAnonBoxPseudoStaticAtom** aChildElt)
|
||||
{
|
||||
*aCol = nullptr;
|
||||
*aChildElt = nullptr;
|
||||
|
@ -2437,7 +2437,7 @@ nsTreeBodyFrame::GetCursor(const nsPoint& aPoint,
|
|||
if (mView && GetContent()->GetComposedDoc()->GetScriptHandlingObject(dummy)) {
|
||||
int32_t row;
|
||||
nsTreeColumn* col;
|
||||
nsICSSAnonBoxPseudo* child;
|
||||
nsCSSAnonBoxPseudoStaticAtom* child;
|
||||
GetCellAt(aPoint.x, aPoint.y, &row, &col, &child);
|
||||
|
||||
if (child) {
|
||||
|
@ -4200,7 +4200,8 @@ nsTreeBodyFrame::ThumbMoved(nsScrollbarFrame* aScrollbar,
|
|||
|
||||
// The style cache.
|
||||
ComputedStyle*
|
||||
nsTreeBodyFrame::GetPseudoComputedStyle(nsICSSAnonBoxPseudo* aPseudoElement)
|
||||
nsTreeBodyFrame::GetPseudoComputedStyle(
|
||||
nsCSSAnonBoxPseudoStaticAtom* aPseudoElement)
|
||||
{
|
||||
return mStyleCache.GetComputedStyle(PresContext(), mContent,
|
||||
mComputedStyle, aPseudoElement,
|
||||
|
|
|
@ -314,15 +314,15 @@ protected:
|
|||
nsRect& aTextRect);
|
||||
|
||||
// A helper used when hit testing.
|
||||
nsICSSAnonBoxPseudo* GetItemWithinCellAt(nscoord aX,
|
||||
const nsRect& aCellRect,
|
||||
int32_t aRowIndex,
|
||||
nsTreeColumn* aColumn);
|
||||
nsCSSAnonBoxPseudoStaticAtom* GetItemWithinCellAt(nscoord aX,
|
||||
const nsRect& aCellRect,
|
||||
int32_t aRowIndex,
|
||||
nsTreeColumn* aColumn);
|
||||
|
||||
// An internal hit test. aX and aY are expected to be in twips in the
|
||||
// coordinate system of this frame.
|
||||
void GetCellAt(nscoord aX, nscoord aY, int32_t* aRow, nsTreeColumn** aCol,
|
||||
nsICSSAnonBoxPseudo** aChildElt);
|
||||
nsCSSAnonBoxPseudoStaticAtom** aChildElt);
|
||||
|
||||
// Retrieve the area for the twisty for a cell.
|
||||
nsITheme* GetTwistyRect(int32_t aRowIndex,
|
||||
|
@ -360,7 +360,8 @@ protected:
|
|||
|
||||
// Looks up a ComputedStyle in the style cache. On a cache miss we resolve
|
||||
// the pseudo-styles passed in and place them into the cache.
|
||||
ComputedStyle* GetPseudoComputedStyle(nsICSSAnonBoxPseudo* aPseudoElement);
|
||||
ComputedStyle* GetPseudoComputedStyle(
|
||||
nsCSSAnonBoxPseudoStaticAtom* aPseudoElement);
|
||||
|
||||
// Retrieves the scrollbars and scrollview relevant to this treebody. We
|
||||
// traverse the frame tree under our base element, in frame order, looking
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче