Merge m-c to autoland. a=merge

This commit is contained in:
Ryan VanderMeulen 2018-10-10 12:32:05 -04:00
Родитель 866aa38d6d ea3d29a96f
Коммит 44c10c107a
185 изменённых файлов: 8608 добавлений и 4378 удалений

Просмотреть файл

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

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше