mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-05-26 14:25:44 +00:00
import changes from `dev' branch of rmottola/Arctic-Fox:
- Bug 1240094 - nsDocShell should initialize mUserContextId to nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID, r=smaug (a3a8358561) - Bug 1142768 - Return the original document URI from ServiceWorkerClient.url; r=bkelly (17892631ab) - Bug 1233613 - Make RegisterFrameCaptureListener fallible. r=mt (0a68d5f4ac) - Bug 1233613 - Locate parent document before getting presentation shell for canvas capture. r=mt (47a285deab) - Bug 866513 - Non-empty MediaStreamTrack labels. r=jib (1b1f364811) - Bug 1170958 - Don't create owned MediaStreamTracks in MetadataLoaded. r=roc (3f031298fb) - fix misspatch of Bug 1131802 part 2 (3a1e0d2799) - trivial fixes (bffbd65448) - Bug 1218454 - part 2a - fix bootlegging of nsContentUtils.h includes from nsILoadContext.h; r=bz (a587b686cd) - bit of Bug 1223078 - Release WrappedJS eagerly (1fcb1a490b) - Bug 1245767 followup - Throw an error when gczeal argument is out of range. r=jonco on IRC (32de34e6b7) - Bug 1241934 - Remove the unused validategc API; r=jonco (a80436895b) - Bug 961323 - Add a method for finding shortest retaining paths of `JS::ubi::Node` heap graphs; r=jimb (b2d3735d7d) - Bug 1240090 - Make owned copies of filenames in JS::ubi::ByFilename. r=jimb (049ab3c6ed) - Bug 1247412 - Add a reverse method to mozilla::Vector; r=Waldo (ad417ff38b) - Bug 1247413 - Give JS::ubi::BreadthFirst handlers a non-const reference; r=jimb (9baadfa50e) - bit of Bug 1246061 (51b6ef81dc) - Bug 1243198 - Use rvalue references for JS::ubi::ByFilename constructor; r=jimb (5b1dab61a5) - Bug 1216001 part 1 - Optimize nsRange::IsNodeSelected. r=bz (39aad5f0d4) - Bug 1216001 part 2 - Optimize nsRange::ExcludeNonSelectableNodes by counting ignorable whitespace text nodes next to an unselectable node as unselectable too. r=bz (c6589b508e) - Bug 1216001 part 3 - Cache the result of IsSelected() for the duration of painting. r=bz (aa5c000b4c) - Bug 1188364 - Supress GC while transplanting to prevent compacting GC observing intermediate state r=terrence (6963b58989) - Bug 1246318 - Make the proxy enumerate trap non-standard. r=efaust (f34d0a7c2a) - Bug 1254293. Fix dom::GetArrayIndexFromId to actually follow the spec for large indices (i.e. ones that don't fit in in int32_t). r=peterv (b71cffbbbd) - Bug 1256688 - Change BPH::has to follow [[HasProperty]] for ordinary objects. r=jorendorff (39b8de1a3d)
This commit is contained in:
@@ -804,6 +804,7 @@ nsDocShell::nsDocShell()
|
||||
, mBlankTiming(false)
|
||||
, mFrameType(eFrameTypeRegular)
|
||||
, mOwnOrContainingAppId(nsIScriptSecurityManager::UNKNOWN_APP_ID)
|
||||
, mUserContextId(nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID)
|
||||
, mParentCharsetSource(0)
|
||||
, mJSRunToCompletionDepth(0)
|
||||
{
|
||||
|
||||
@@ -824,8 +824,7 @@ nsOuterWindowProxy::defineProperty(JSContext* cx,
|
||||
JS::Handle<JS::PropertyDescriptor> desc,
|
||||
JS::ObjectOpResult &result) const
|
||||
{
|
||||
int32_t index = GetArrayIndexFromId(cx, id);
|
||||
if (IsArrayIndex(index)) {
|
||||
if (IsArrayIndex(GetArrayIndexFromId(cx, id))) {
|
||||
// Spec says to Reject whether this is a supported index or not,
|
||||
// since we have no indexed setter or indexed creator. It is up
|
||||
// to the caller to decide whether to throw a TypeError.
|
||||
@@ -884,8 +883,7 @@ nsOuterWindowProxy::delete_(JSContext *cx, JS::Handle<JSObject*> proxy,
|
||||
return result.failCantDeleteWindowElement();
|
||||
}
|
||||
|
||||
int32_t index = GetArrayIndexFromId(cx, id);
|
||||
if (IsArrayIndex(index)) {
|
||||
if (IsArrayIndex(GetArrayIndexFromId(cx, id))) {
|
||||
// Indexed, but not supported. Spec says return true.
|
||||
return result.succeed();
|
||||
}
|
||||
@@ -968,8 +966,7 @@ nsOuterWindowProxy::set(JSContext *cx, JS::Handle<JSObject*> proxy,
|
||||
JS::Handle<JS::Value> receiver,
|
||||
JS::ObjectOpResult &result) const
|
||||
{
|
||||
int32_t index = GetArrayIndexFromId(cx, id);
|
||||
if (IsArrayIndex(index)) {
|
||||
if (IsArrayIndex(GetArrayIndexFromId(cx, id))) {
|
||||
// Reject the set. It's up to the caller to decide whether to throw a
|
||||
// TypeError. If the caller is strict mode JS code, it'll throw.
|
||||
return result.failReadOnly();
|
||||
@@ -1030,7 +1027,7 @@ nsOuterWindowProxy::GetSubframeWindow(JSContext *cx,
|
||||
JS::Handle<JSObject*> proxy,
|
||||
JS::Handle<jsid> id) const
|
||||
{
|
||||
int32_t index = GetArrayIndexFromId(cx, id);
|
||||
uint32_t index = GetArrayIndexFromId(cx, id);
|
||||
if (!IsArrayIndex(index)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
+81
-15
@@ -151,6 +151,34 @@ GetNextRangeCommonAncestor(nsINode* aNode)
|
||||
return aNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* A Comparator suitable for mozilla::BinarySearchIf for searching a collection
|
||||
* of nsRange* for an overlap of (mNode, mStartOffset) .. (mNode, mEndOffset).
|
||||
*/
|
||||
struct IsItemInRangeComparator
|
||||
{
|
||||
nsINode* mNode;
|
||||
uint32_t mStartOffset;
|
||||
uint32_t mEndOffset;
|
||||
|
||||
int operator()(const nsRange* const aRange) const
|
||||
{
|
||||
int32_t cmp = nsContentUtils::ComparePoints(mNode, mEndOffset,
|
||||
aRange->GetStartParent(),
|
||||
aRange->StartOffset());
|
||||
if (cmp == 1) {
|
||||
cmp = nsContentUtils::ComparePoints(mNode, mStartOffset,
|
||||
aRange->GetEndParent(),
|
||||
aRange->EndOffset());
|
||||
if (cmp == -1) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
/* static */ bool
|
||||
nsRange::IsNodeSelected(nsINode* aNode, uint32_t aStartOffset,
|
||||
uint32_t aEndOffset)
|
||||
@@ -160,24 +188,45 @@ nsRange::IsNodeSelected(nsINode* aNode, uint32_t aStartOffset,
|
||||
nsINode* n = GetNextRangeCommonAncestor(aNode);
|
||||
NS_ASSERTION(n || !aNode->IsSelectionDescendant(),
|
||||
"orphan selection descendant");
|
||||
|
||||
// Collect the potential ranges and their selection objects.
|
||||
RangeHashTable ancestorSelectionRanges;
|
||||
nsTHashtable<nsPtrHashKey<Selection>> ancestorSelections;
|
||||
uint32_t maxRangeCount = 0;
|
||||
for (; n; n = GetNextRangeCommonAncestor(n->GetParentNode())) {
|
||||
RangeHashTable* ranges =
|
||||
static_cast<RangeHashTable*>(n->GetProperty(nsGkAtoms::range));
|
||||
for (auto iter = ranges->ConstIter(); !iter.Done(); iter.Next()) {
|
||||
nsRange* range = iter.Get()->GetKey();
|
||||
if (range->IsInSelection() && !range->Collapsed()) {
|
||||
int32_t cmp = nsContentUtils::ComparePoints(aNode, aEndOffset,
|
||||
range->GetStartParent(),
|
||||
range->StartOffset());
|
||||
if (cmp == 1) {
|
||||
cmp = nsContentUtils::ComparePoints(aNode, aStartOffset,
|
||||
range->GetEndParent(),
|
||||
range->EndOffset());
|
||||
if (cmp == -1) {
|
||||
return true;
|
||||
}
|
||||
ancestorSelectionRanges.PutEntry(range);
|
||||
Selection* selection = range->mSelection;
|
||||
ancestorSelections.PutEntry(selection);
|
||||
maxRangeCount = std::max(maxRangeCount, selection->RangeCount());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!ancestorSelectionRanges.IsEmpty()) {
|
||||
nsTArray<const nsRange*> sortedRanges(maxRangeCount);
|
||||
for (auto iter = ancestorSelections.ConstIter(); !iter.Done(); iter.Next()) {
|
||||
Selection* selection = iter.Get()->GetKey();
|
||||
// Sort the found ranges for |selection| in document order
|
||||
// (Selection::GetRangeAt returns its ranges ordered).
|
||||
for (uint32_t i = 0, len = selection->RangeCount(); i < len; ++i) {
|
||||
nsRange* range = selection->GetRangeAt(i);
|
||||
if (ancestorSelectionRanges.Contains(range)) {
|
||||
sortedRanges.AppendElement(range);
|
||||
}
|
||||
}
|
||||
MOZ_ASSERT(!sortedRanges.IsEmpty());
|
||||
// Binary search the now sorted ranges.
|
||||
IsItemInRangeComparator comparator = { aNode, aStartOffset, aEndOffset };
|
||||
size_t unused;
|
||||
if (mozilla::BinarySearchIf(sortedRanges, 0, sortedRanges.Length(), comparator, &unused)) {
|
||||
return true;
|
||||
}
|
||||
sortedRanges.ClearAndRetainStorage();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -3090,6 +3139,12 @@ nsRange::Constructor(const GlobalObject& aGlobal,
|
||||
return window->GetDoc()->CreateRange(aRv);
|
||||
}
|
||||
|
||||
static bool ExcludeIfNextToNonSelectable(nsIContent* aContent)
|
||||
{
|
||||
return aContent->IsNodeOfType(nsINode::eTEXT) &&
|
||||
aContent->HasFlag(NS_CREATE_FRAME_IF_NON_WHITESPACE);
|
||||
}
|
||||
|
||||
void
|
||||
nsRange::ExcludeNonSelectableNodes(nsTArray<RefPtr<nsRange>>* aOutRanges)
|
||||
{
|
||||
@@ -3108,6 +3163,10 @@ nsRange::ExcludeNonSelectableNodes(nsTArray<RefPtr<nsRange>>* aOutRanges)
|
||||
|
||||
bool added = false;
|
||||
bool seenSelectable = false;
|
||||
// |firstNonSelectableContent| is the first node in a consecutive sequence
|
||||
// of non-IsSelectable nodes. When we find a selectable node after such
|
||||
// a sequence we'll end the last nsRange, create a new one and restart
|
||||
// the outer loop.
|
||||
nsIContent* firstNonSelectableContent = nullptr;
|
||||
while (true) {
|
||||
ErrorResult err;
|
||||
@@ -3117,12 +3176,19 @@ nsRange::ExcludeNonSelectableNodes(nsTArray<RefPtr<nsRange>>* aOutRanges)
|
||||
nsIContent* content =
|
||||
node && node->IsContent() ? node->AsContent() : nullptr;
|
||||
if (content) {
|
||||
nsIFrame* frame = content->GetPrimaryFrame();
|
||||
for (nsIContent* p = content; !frame && (p = p->GetParent()); ) {
|
||||
frame = p->GetPrimaryFrame();
|
||||
if (firstNonSelectableContent && ExcludeIfNextToNonSelectable(content)) {
|
||||
// Ignorable whitespace next to a sequence of non-selectable nodes
|
||||
// counts as non-selectable (bug 1216001).
|
||||
selectable = false;
|
||||
}
|
||||
if (frame) {
|
||||
frame->IsSelectable(&selectable, nullptr);
|
||||
if (selectable) {
|
||||
nsIFrame* frame = content->GetPrimaryFrame();
|
||||
for (nsIContent* p = content; !frame && (p = p->GetParent()); ) {
|
||||
frame = p->GetPrimaryFrame();
|
||||
}
|
||||
if (frame) {
|
||||
frame->IsSelectable(&selectable, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -251,6 +251,14 @@ public:
|
||||
bool *outNodeBefore,
|
||||
bool *outNodeAfter);
|
||||
|
||||
/**
|
||||
* Return true if any part of (aNode, aStartOffset) .. (aNode, aEndOffset)
|
||||
* overlaps any nsRange in aNode's GetNextRangeCommonAncestor ranges (i.e.
|
||||
* where aNode is a descendant of a range's common ancestor node).
|
||||
* If a nsRange starts in (aNode, aEndOffset) or if it ends in
|
||||
* (aNode, aStartOffset) then it is non-overlapping and the result is false
|
||||
* for that nsRange. Collapsed ranges always counts as non-overlapping.
|
||||
*/
|
||||
static bool IsNodeSelected(nsINode* aNode, uint32_t aStartOffset,
|
||||
uint32_t aEndOffset);
|
||||
|
||||
@@ -297,6 +305,14 @@ protected:
|
||||
*/
|
||||
nsINode* GetRegisteredCommonAncestor();
|
||||
|
||||
// Helper to IsNodeSelected.
|
||||
static bool IsNodeInSortedRanges(nsINode* aNode,
|
||||
uint32_t aStartOffset,
|
||||
uint32_t aEndOffset,
|
||||
const nsTArray<const nsRange*>& aRanges,
|
||||
size_t aRangeStart,
|
||||
size_t aRangeEnd);
|
||||
|
||||
struct MOZ_STACK_CLASS AutoInvalidateSelection
|
||||
{
|
||||
explicit AutoInvalidateSelection(nsRange* aRange) : mRange(aRange)
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
src: url("Ahem.ttf");
|
||||
}
|
||||
body { font-family: Ahem; font-size: 20px; }
|
||||
s { -moz-user-select: none; }
|
||||
s, .non-selectable { -moz-user-select: none; }
|
||||
n { display: none; }
|
||||
a { position:absolute; bottom: 0; right:0; }
|
||||
.text { -moz-user-select: text; }
|
||||
@@ -34,9 +34,16 @@ a { position:absolute; bottom: 0; right:0; }
|
||||
<div id="testB"><n><s>aaaa</s>aaa</n>bbbbbbbbccccccc</div>
|
||||
<div id="testC">aaaaaaabbbbbbbb<n>cc<s>c</s>cccc</n></div>
|
||||
<div id="testE">aaa<s id="testEc1">aaaa<a class="text">bbbb</a>dd<a>cccc</a>ddddddd</s>eeee</div>
|
||||
<div id="testF" style="white-space:pre">aaaa
|
||||
<div class="non-selectable">x</div><input>
|
||||
bbbbbbb</div>
|
||||
<div id="testF">aaaa
|
||||
<div class="non-selectable">x</div>
|
||||
<div class="non-selectable">x</div>
|
||||
<div class="non-selectable">x</div>
|
||||
bbbb</div>
|
||||
<div id="testG" style="white-space:pre">aaaa
|
||||
<div class="non-selectable">x</div>
|
||||
<div class="non-selectable">x</div>
|
||||
<div class="non-selectable">x</div>
|
||||
bbbb</div>
|
||||
|
||||
<iframe id="testD" src="data:text/html,<body>aaaa<span style='-moz-user-select:none'>bbbb</span>cccc"></iframe>
|
||||
|
||||
@@ -103,9 +110,11 @@ function test()
|
||||
is(NL(r.toString()), text, e.id + ": range["+index+"].toString()")
|
||||
}
|
||||
|
||||
function node(e, index)
|
||||
function node(e, arg)
|
||||
{
|
||||
return index == -1 ? e : e.childNodes[index];
|
||||
if (typeof arg == "number")
|
||||
return arg == -1 ? e : e.childNodes[arg];
|
||||
return arg;
|
||||
}
|
||||
|
||||
function checkRangeCount(n, e)
|
||||
@@ -272,6 +281,22 @@ function test()
|
||||
|
||||
doneTest(e);
|
||||
|
||||
clear();
|
||||
e = document.getElementById('testF');
|
||||
synthesizeMouse(e, 1, 1, {});
|
||||
synthesizeMouse(e, 400, 100, { shiftKey: true });
|
||||
checkText("aaaa bbbb", e);
|
||||
checkRanges([[0,0,-1,1],[6,0,6,5]], e);
|
||||
doneTest(e);
|
||||
|
||||
clear();
|
||||
e = document.getElementById('testG');
|
||||
synthesizeMouse(e, 1, 1, {});
|
||||
synthesizeMouse(e, 400, 180, { shiftKey: true });
|
||||
checkText("aaaa bbbb", e); // XXX this doesn't seem right - bug 1247799
|
||||
checkRanges([[0,0,-1,1],[2,0,-1,3],[4,0,-1,5],[6,0,6,5]], e);
|
||||
doneTest(e);
|
||||
|
||||
// ======================================================
|
||||
// ==================== Script tests ====================
|
||||
// ======================================================
|
||||
|
||||
@@ -1161,9 +1161,8 @@ XrayCreateFunction(JSContext* cx, JS::Handle<JSObject*> wrapper,
|
||||
js::SetFunctionNativeReserved(obj, XRAY_DOM_FUNCTION_PARENT_WRAPPER_SLOT,
|
||||
JS::ObjectValue(*wrapper));
|
||||
#ifdef DEBUG
|
||||
js::SetFunctionNativeReserved(obj, XRAY_DOM_FUNCTION_NATIVE_SLOT_FOR_ASSERT,
|
||||
JS::PrivateValue(JS_FUNC_TO_DATA_PTR(void *,
|
||||
native.op)));
|
||||
js::SetFunctionNativeReserved(obj, XRAY_DOM_FUNCTION_NATIVE_SLOT_FOR_SELF,
|
||||
JS::ObjectValue(*obj));
|
||||
#endif
|
||||
return obj;
|
||||
}
|
||||
|
||||
@@ -10862,7 +10862,7 @@ class CGDOMJSProxyHandler_getOwnPropDescriptor(ClassMethod):
|
||||
}
|
||||
getIndexed = fill(
|
||||
"""
|
||||
int32_t index = GetArrayIndexFromId(cx, id);
|
||||
uint32_t index = GetArrayIndexFromId(cx, id);
|
||||
if (IsArrayIndex(index)) {
|
||||
$*{callGetter}
|
||||
}
|
||||
@@ -10973,7 +10973,7 @@ class CGDOMJSProxyHandler_defineProperty(ClassMethod):
|
||||
raise TypeError("Can't handle creator that's different from the setter")
|
||||
set += fill(
|
||||
"""
|
||||
int32_t index = GetArrayIndexFromId(cx, id);
|
||||
uint32_t index = GetArrayIndexFromId(cx, id);
|
||||
if (IsArrayIndex(index)) {
|
||||
*defined = true;
|
||||
$*{callSetter}
|
||||
@@ -11121,7 +11121,7 @@ class CGDOMJSProxyHandler_delete(ClassMethod):
|
||||
if indexedBody is not None:
|
||||
delete += fill(
|
||||
"""
|
||||
int32_t index = GetArrayIndexFromId(cx, id);
|
||||
uint32_t index = GetArrayIndexFromId(cx, id);
|
||||
if (IsArrayIndex(index)) {
|
||||
bool deleteSucceeded;
|
||||
$*{indexedBody}
|
||||
@@ -11242,7 +11242,7 @@ class CGDOMJSProxyHandler_hasOwn(ClassMethod):
|
||||
if self.descriptor.supportsIndexedProperties():
|
||||
indexed = fill(
|
||||
"""
|
||||
int32_t index = GetArrayIndexFromId(cx, id);
|
||||
uint32_t index = GetArrayIndexFromId(cx, id);
|
||||
if (IsArrayIndex(index)) {
|
||||
bool found = false;
|
||||
$*{presenceChecker}
|
||||
@@ -11345,7 +11345,7 @@ class CGDOMJSProxyHandler_get(ClassMethod):
|
||||
if self.descriptor.supportsIndexedProperties():
|
||||
getIndexedOrExpando = fill(
|
||||
"""
|
||||
int32_t index = GetArrayIndexFromId(cx, id);
|
||||
uint32_t index = GetArrayIndexFromId(cx, id);
|
||||
if (IsArrayIndex(index)) {
|
||||
$*{callGetter}
|
||||
// Even if we don't have this index, we don't forward the
|
||||
@@ -11444,7 +11444,7 @@ class CGDOMJSProxyHandler_setCustom(ClassMethod):
|
||||
"also an indexed creator")
|
||||
setIndexed = fill(
|
||||
"""
|
||||
int32_t index = GetArrayIndexFromId(cx, id);
|
||||
uint32_t index = GetArrayIndexFromId(cx, id);
|
||||
if (IsArrayIndex(index)) {
|
||||
$*{callSetter}
|
||||
*done = true;
|
||||
|
||||
@@ -263,58 +263,6 @@ BaseDOMProxyHandler::getOwnEnumerablePropertyKeys(JSContext* cx,
|
||||
return ownPropNames(cx, proxy, JSITER_OWNONLY, props);
|
||||
}
|
||||
|
||||
bool
|
||||
BaseDOMProxyHandler::enumerate(JSContext *cx, JS::Handle<JSObject*> proxy,
|
||||
JS::MutableHandle<JSObject*> objp) const
|
||||
{
|
||||
return BaseProxyHandler::enumerate(cx, proxy, objp);
|
||||
}
|
||||
|
||||
bool
|
||||
DOMProxyHandler::has(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id, bool* bp) const
|
||||
{
|
||||
if (!hasOwn(cx, proxy, id, bp)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (*bp) {
|
||||
// We have the property ourselves; no need to worry about our prototype
|
||||
// chain.
|
||||
return true;
|
||||
}
|
||||
|
||||
// OK, now we have to look at the proto
|
||||
JS::Rooted<JSObject*> proto(cx);
|
||||
if (!js::GetObjectProto(cx, proxy, &proto)) {
|
||||
return false;
|
||||
}
|
||||
if (!proto) {
|
||||
return true;
|
||||
}
|
||||
bool protoHasProp;
|
||||
bool ok = JS_HasPropertyById(cx, proto, id, &protoHasProp);
|
||||
if (ok) {
|
||||
*bp = protoHasProp;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
int32_t
|
||||
IdToInt32(JSContext* cx, JS::Handle<jsid> id)
|
||||
{
|
||||
JS::Rooted<JS::Value> idval(cx);
|
||||
double array_index;
|
||||
int32_t i;
|
||||
if (JSID_IS_SYMBOL(id) ||
|
||||
!::JS_IdToValue(cx, id, &idval) ||
|
||||
!JS::ToNumber(cx, idval, &array_index) ||
|
||||
!::JS_DoubleIsInt32(array_index, &i)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
bool
|
||||
DOMProxyHandler::setCustom(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
|
||||
JS::Handle<JS::Value> v, bool *done) const
|
||||
|
||||
@@ -59,9 +59,6 @@ public:
|
||||
virtual bool ownPropertyKeys(JSContext* cx, JS::Handle<JSObject*> proxy,
|
||||
JS::AutoIdVector &props) const override;
|
||||
|
||||
virtual bool enumerate(JSContext *cx, JS::Handle<JSObject*> proxy,
|
||||
JS::MutableHandle<JSObject*> objp) const override;
|
||||
|
||||
// We override getOwnEnumerablePropertyKeys() and implement it directly
|
||||
// instead of using the default implementation, which would call
|
||||
// ownPropertyKeys and then filter out the non-enumerable ones. This avoids
|
||||
@@ -117,8 +114,6 @@ public:
|
||||
JS::ObjectOpResult& result) const override;
|
||||
bool isExtensible(JSContext *cx, JS::Handle<JSObject*> proxy, bool *extensible)
|
||||
const override;
|
||||
bool has(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
|
||||
bool* bp) const override;
|
||||
bool set(JSContext *cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
|
||||
JS::Handle<JS::Value> v, JS::Handle<JS::Value> receiver, JS::ObjectOpResult &result)
|
||||
const override;
|
||||
@@ -157,44 +152,45 @@ GetDOMProxyHandler(JSObject* obj)
|
||||
|
||||
extern jsid s_length_id;
|
||||
|
||||
int32_t IdToInt32(JSContext* cx, JS::Handle<jsid> id);
|
||||
|
||||
// XXXbz this should really return uint32_t, with the maximum value
|
||||
// meaning "not an index"...
|
||||
inline int32_t
|
||||
// A return value of UINT32_MAX indicates "not an array index". Note, in
|
||||
// particular, that UINT32_MAX itself is not a valid array index in general.
|
||||
inline uint32_t
|
||||
GetArrayIndexFromId(JSContext* cx, JS::Handle<jsid> id)
|
||||
{
|
||||
// Much like js::IdIsIndex, except with a fast path for "length" and another
|
||||
// fast path for starting with a lowercase ascii char. Is that second one
|
||||
// really needed? I guess it is because StringIsArrayIndex is out of line...
|
||||
if (MOZ_LIKELY(JSID_IS_INT(id))) {
|
||||
return JSID_TO_INT(id);
|
||||
}
|
||||
if (MOZ_LIKELY(id == s_length_id)) {
|
||||
return -1;
|
||||
return UINT32_MAX;
|
||||
}
|
||||
if (MOZ_UNLIKELY(!JSID_IS_ATOM(id))) {
|
||||
return UINT32_MAX;
|
||||
}
|
||||
if (MOZ_LIKELY(JSID_IS_ATOM(id))) {
|
||||
JSAtom* atom = JSID_TO_ATOM(id);
|
||||
char16_t s;
|
||||
{
|
||||
JS::AutoCheckCannotGC nogc;
|
||||
if (js::AtomHasLatin1Chars(atom)) {
|
||||
s = *js::GetLatin1AtomChars(nogc, atom);
|
||||
} else {
|
||||
s = *js::GetTwoByteAtomChars(nogc, atom);
|
||||
}
|
||||
}
|
||||
if (MOZ_LIKELY((unsigned)s >= 'a' && (unsigned)s <= 'z'))
|
||||
return -1;
|
||||
|
||||
uint32_t i;
|
||||
JSLinearString* str = js::AtomToLinearString(JSID_TO_ATOM(id));
|
||||
return js::StringIsArrayIndex(str, &i) ? i : -1;
|
||||
JSLinearString* str = js::AtomToLinearString(JSID_TO_ATOM(id));
|
||||
char16_t s;
|
||||
{
|
||||
JS::AutoCheckCannotGC nogc;
|
||||
if (js::LinearStringHasLatin1Chars(str)) {
|
||||
s = *js::GetLatin1LinearStringChars(nogc, str);
|
||||
} else {
|
||||
s = *js::GetTwoByteLinearStringChars(nogc, str);
|
||||
}
|
||||
}
|
||||
return IdToInt32(cx, id);
|
||||
if (MOZ_LIKELY((unsigned)s >= 'a' && (unsigned)s <= 'z'))
|
||||
return UINT32_MAX;
|
||||
|
||||
uint32_t i;
|
||||
return js::StringIsArrayIndex(str, &i) ? i : UINT32_MAX;
|
||||
}
|
||||
|
||||
inline bool
|
||||
IsArrayIndex(int32_t index)
|
||||
IsArrayIndex(uint32_t index)
|
||||
{
|
||||
return index >= 0;
|
||||
return index < UINT32_MAX;
|
||||
}
|
||||
|
||||
inline void
|
||||
|
||||
@@ -42,7 +42,7 @@ TestInterfaceIterableDouble::Constructor(const GlobalObject& aGlobal,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsRefPtr<TestInterfaceIterableDouble> r = new TestInterfaceIterableDouble(window);
|
||||
RefPtr<TestInterfaceIterableDouble> r = new TestInterfaceIterableDouble(window);
|
||||
return r.forget();
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ TestInterfaceIterableSingle::Constructor(const GlobalObject& aGlobal,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsRefPtr<TestInterfaceIterableSingle> r = new TestInterfaceIterableSingle(window);
|
||||
RefPtr<TestInterfaceIterableSingle> r = new TestInterfaceIterableSingle(window);
|
||||
return r.forget();
|
||||
}
|
||||
|
||||
|
||||
@@ -80,5 +80,5 @@ TestInterfaceMaplike::HasInternal(const nsAString& aKey)
|
||||
return TestInterfaceMaplikeBinding::MaplikeHelpers::Has(this, aKey, rv);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
@@ -84,5 +84,5 @@ TestInterfaceMaplikeObject::HasInternal(const nsAString& aKey)
|
||||
return TestInterfaceMaplikeObjectBinding::MaplikeHelpers::Has(this, aKey, rv);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
@@ -54,5 +54,5 @@ TestInterfaceSetlike::GetParentObject() const
|
||||
return mParent;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
@@ -529,7 +529,7 @@ nsDOMCameraControl::TrackCreated(TrackID aTrackID) {
|
||||
// This track is not connected through a port.
|
||||
MediaInputPort* inputPort = nullptr;
|
||||
dom::VideoStreamTrack* track =
|
||||
new dom::VideoStreamTrack(this, aTrackID);
|
||||
new dom::VideoStreamTrack(this, aTrackID, nsString());
|
||||
RefPtr<TrackPort> port =
|
||||
new TrackPort(inputPort, track,
|
||||
TrackPort::InputPortOwnership::OWNED);
|
||||
|
||||
@@ -682,8 +682,14 @@ HTMLCanvasElement::CaptureStream(const Optional<double>& aFrameRate,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
stream->CreateOwnDOMTrack(videoTrackId, MediaSegment::VIDEO);
|
||||
RegisterFrameCaptureListener(stream->FrameCaptureListener());
|
||||
stream->CreateOwnDOMTrack(videoTrackId, MediaSegment::VIDEO, nsString());
|
||||
|
||||
rv = RegisterFrameCaptureListener(stream->FrameCaptureListener());
|
||||
if (NS_FAILED(rv)) {
|
||||
aRv.Throw(rv);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return stream.forget();
|
||||
}
|
||||
|
||||
@@ -1128,38 +1134,52 @@ HTMLCanvasElement::IsContextCleanForFrameCapture()
|
||||
return mCurrentContext && mCurrentContext->IsContextCleanForFrameCapture();
|
||||
}
|
||||
|
||||
void
|
||||
nsresult
|
||||
HTMLCanvasElement::RegisterFrameCaptureListener(FrameCaptureListener* aListener)
|
||||
{
|
||||
WeakPtr<FrameCaptureListener> listener = aListener;
|
||||
|
||||
if (mRequestedFrameListeners.Contains(listener)) {
|
||||
return;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
mRequestedFrameListeners.AppendElement(listener);
|
||||
|
||||
if (!mRequestedFrameRefreshObserver) {
|
||||
nsIDocument* doc = OwnerDoc();
|
||||
MOZ_RELEASE_ASSERT(doc);
|
||||
if (!doc) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
while (doc->GetParentDocument()) {
|
||||
doc = doc->GetParentDocument();
|
||||
}
|
||||
|
||||
nsIPresShell* shell = doc->GetShell();
|
||||
MOZ_RELEASE_ASSERT(shell);
|
||||
if (!shell) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsPresContext* context = shell->GetPresContext();
|
||||
MOZ_RELEASE_ASSERT(context);
|
||||
if (!context) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
context = context->GetRootPresContext();
|
||||
MOZ_RELEASE_ASSERT(context);
|
||||
if (!context) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsRefreshDriver* driver = context->RefreshDriver();
|
||||
MOZ_RELEASE_ASSERT(driver);
|
||||
if (!driver) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
mRequestedFrameRefreshObserver =
|
||||
new RequestedFrameRefreshObserver(this, driver);
|
||||
}
|
||||
|
||||
mRequestedFrameListeners.AppendElement(listener);
|
||||
mRequestedFrameRefreshObserver->Register();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
bool
|
||||
|
||||
@@ -266,7 +266,7 @@ public:
|
||||
* caller's responsibility to keep them alive. Once a registered
|
||||
* FrameCaptureListener is destroyed it will be automatically deregistered.
|
||||
*/
|
||||
void RegisterFrameCaptureListener(FrameCaptureListener* aListener);
|
||||
nsresult RegisterFrameCaptureListener(FrameCaptureListener* aListener);
|
||||
|
||||
/*
|
||||
* Returns true when there is at least one registered FrameCaptureListener
|
||||
|
||||
@@ -1890,11 +1890,11 @@ HTMLMediaElement::CaptureStreamInternal(bool aFinishWhenEnded,
|
||||
// Expose the tracks to JS directly.
|
||||
if (HasAudio()) {
|
||||
TrackID audioTrackId = mMediaInfo.mAudio.mTrackId;
|
||||
out->mStream->CreateOwnDOMTrack(audioTrackId, MediaSegment::AUDIO);
|
||||
out->mStream->CreateOwnDOMTrack(audioTrackId, MediaSegment::AUDIO, nsString());
|
||||
}
|
||||
if (HasVideo()) {
|
||||
TrackID videoTrackId = mMediaInfo.mVideo.mTrackId;
|
||||
out->mStream->CreateOwnDOMTrack(videoTrackId, MediaSegment::VIDEO);
|
||||
out->mStream->CreateOwnDOMTrack(videoTrackId, MediaSegment::VIDEO, nsString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3404,18 +3404,6 @@ void HTMLMediaElement::MetadataLoaded(const MediaInfo* aInfo,
|
||||
}
|
||||
}
|
||||
|
||||
// Expose the tracks to JS directly.
|
||||
for (OutputMediaStream& out : mOutputStreams) {
|
||||
if (aInfo->HasAudio()) {
|
||||
TrackID audioTrackId = aInfo->mAudio.mTrackId;
|
||||
out.mStream->CreateOwnDOMTrack(audioTrackId, MediaSegment::AUDIO);
|
||||
}
|
||||
if (aInfo->HasVideo()) {
|
||||
TrackID videoTrackId = aInfo->mVideo.mTrackId;
|
||||
out.mStream->CreateOwnDOMTrack(videoTrackId, MediaSegment::VIDEO);
|
||||
}
|
||||
}
|
||||
|
||||
// If this element had a video track, but consists only of an audio track now,
|
||||
// delete the VideoFrameContainer. This happens when the src is changed to an
|
||||
// audio only file.
|
||||
|
||||
@@ -14,8 +14,8 @@ namespace dom {
|
||||
|
||||
class AudioStreamTrack : public MediaStreamTrack {
|
||||
public:
|
||||
AudioStreamTrack(DOMMediaStream* aStream, TrackID aTrackID)
|
||||
: MediaStreamTrack(aStream, aTrackID) {}
|
||||
AudioStreamTrack(DOMMediaStream* aStream, TrackID aTrackID, const nsString& aLabel)
|
||||
: MediaStreamTrack(aStream, aTrackID, aLabel) {}
|
||||
|
||||
virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ public:
|
||||
NS_WARN_IF_FALSE(!mStream->mTracks.IsEmpty(),
|
||||
"A new track was detected on the input stream; creating a corresponding MediaStreamTrack. "
|
||||
"Initial tracks should be added manually to immediately and synchronously be available to JS.");
|
||||
mStream->CreateOwnDOMTrack(aTrackId, aType);
|
||||
mStream->CreateOwnDOMTrack(aTrackId, aType, nsString());
|
||||
}
|
||||
|
||||
void DoNotifyTrackEnded(TrackID aTrackId)
|
||||
@@ -618,7 +618,7 @@ DOMMediaStream::InitAudioCaptureStream(nsIDOMWindow* aWindow,
|
||||
InitInputStreamCommon(aGraph->CreateAudioCaptureStream(this, AUDIO_TRACK), aGraph);
|
||||
InitOwnedStreamCommon(aGraph);
|
||||
InitPlaybackStreamCommon(aGraph);
|
||||
CreateOwnDOMTrack(AUDIO_TRACK, MediaSegment::AUDIO);
|
||||
CreateOwnDOMTrack(AUDIO_TRACK, MediaSegment::AUDIO, nsString());
|
||||
}
|
||||
|
||||
void
|
||||
@@ -770,7 +770,7 @@ DOMMediaStream::RemovePrincipalChangeObserver(PrincipalChangeObserver* aObserver
|
||||
}
|
||||
|
||||
MediaStreamTrack*
|
||||
DOMMediaStream::CreateOwnDOMTrack(TrackID aTrackID, MediaSegment::Type aType)
|
||||
DOMMediaStream::CreateOwnDOMTrack(TrackID aTrackID, MediaSegment::Type aType, const nsString& aLabel)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(mInputStream);
|
||||
MOZ_RELEASE_ASSERT(mOwnedStream);
|
||||
@@ -780,10 +780,10 @@ DOMMediaStream::CreateOwnDOMTrack(TrackID aTrackID, MediaSegment::Type aType)
|
||||
MediaStreamTrack* track;
|
||||
switch (aType) {
|
||||
case MediaSegment::AUDIO:
|
||||
track = new AudioStreamTrack(this, aTrackID);
|
||||
track = new AudioStreamTrack(this, aTrackID, aLabel);
|
||||
break;
|
||||
case MediaSegment::VIDEO:
|
||||
track = new VideoStreamTrack(this, aTrackID);
|
||||
track = new VideoStreamTrack(this, aTrackID, aLabel);
|
||||
break;
|
||||
default:
|
||||
MOZ_CRASH("Unhandled track type");
|
||||
|
||||
@@ -476,7 +476,7 @@ public:
|
||||
*
|
||||
* Creates a MediaStreamTrack, adds it to mTracks and returns it.
|
||||
*/
|
||||
MediaStreamTrack* CreateOwnDOMTrack(TrackID aTrackID, MediaSegment::Type aType);
|
||||
MediaStreamTrack* CreateOwnDOMTrack(TrackID aTrackID, MediaSegment::Type aType, const nsString& aLabel);
|
||||
|
||||
class OnTracksAvailableCallback {
|
||||
public:
|
||||
|
||||
@@ -1009,10 +1009,14 @@ public:
|
||||
msg);
|
||||
|
||||
if (mAudioDevice) {
|
||||
domStream->CreateOwnDOMTrack(kAudioTrack, MediaSegment::AUDIO);
|
||||
nsString audioDeviceName;
|
||||
mAudioDevice->GetName(audioDeviceName);
|
||||
domStream->CreateOwnDOMTrack(kAudioTrack, MediaSegment::AUDIO, audioDeviceName);
|
||||
}
|
||||
if (mVideoDevice) {
|
||||
domStream->CreateOwnDOMTrack(kVideoTrack, MediaSegment::VIDEO);
|
||||
nsString videoDeviceName;
|
||||
mVideoDevice->GetName(videoDeviceName);
|
||||
domStream->CreateOwnDOMTrack(kVideoTrack, MediaSegment::VIDEO, videoDeviceName);
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIPrincipal> principal;
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
MediaStreamTrack::MediaStreamTrack(DOMMediaStream* aStream, TrackID aTrackID)
|
||||
: mOwningStream(aStream), mTrackID(aTrackID), mEnded(false), mEnabled(true)
|
||||
MediaStreamTrack::MediaStreamTrack(DOMMediaStream* aStream, TrackID aTrackID, const nsString& aLabel)
|
||||
: mOwningStream(aStream), mTrackID(aTrackID), mLabel(aLabel), mEnded(false), mEnabled(true)
|
||||
{
|
||||
|
||||
nsresult rv;
|
||||
|
||||
@@ -29,7 +29,7 @@ public:
|
||||
* aTrackID is the MediaStreamGraph track ID for the track in the
|
||||
* MediaStream owned by aStream.
|
||||
*/
|
||||
MediaStreamTrack(DOMMediaStream* aStream, TrackID aTrackID);
|
||||
MediaStreamTrack(DOMMediaStream* aStream, TrackID aTrackID, const nsString& aLabel);
|
||||
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MediaStreamTrack,
|
||||
@@ -54,7 +54,7 @@ public:
|
||||
// WebIDL
|
||||
virtual void GetKind(nsAString& aKind) = 0;
|
||||
void GetId(nsAString& aID) const;
|
||||
void GetLabel(nsAString& aLabel) { aLabel.Truncate(); }
|
||||
void GetLabel(nsAString& aLabel) { aLabel.Assign(mLabel); }
|
||||
bool Enabled() { return mEnabled; }
|
||||
void SetEnabled(bool aEnabled);
|
||||
void Stop();
|
||||
@@ -75,6 +75,7 @@ protected:
|
||||
RefPtr<DOMMediaStream> mOwningStream;
|
||||
TrackID mTrackID;
|
||||
nsString mID;
|
||||
nsString mLabel;
|
||||
bool mEnded;
|
||||
bool mEnabled;
|
||||
};
|
||||
|
||||
@@ -14,8 +14,8 @@ namespace dom {
|
||||
|
||||
class VideoStreamTrack : public MediaStreamTrack {
|
||||
public:
|
||||
VideoStreamTrack(DOMMediaStream* aStream, TrackID aTrackID)
|
||||
: MediaStreamTrack(aStream, aTrackID) {}
|
||||
VideoStreamTrack(DOMMediaStream* aStream, TrackID aTrackID, const nsString& aLabel)
|
||||
: MediaStreamTrack(aStream, aTrackID, aLabel) {}
|
||||
|
||||
virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
|
||||
@@ -32,15 +32,25 @@ runTest(() =>
|
||||
.then(() => navigator.mediaDevices.enumerateDevices())
|
||||
.then(devices => {
|
||||
ok(devices.length > 0, "At least one device found");
|
||||
devices.forEach(d => {
|
||||
var jsoned = JSON.parse(JSON.stringify(devices));
|
||||
is(jsoned[0].kind, devices[0].kind, "kind survived serializer");
|
||||
is(jsoned[0].deviceId, devices[0].deviceId, "deviceId survived serializer");
|
||||
return devices.reduce((p, d) => {
|
||||
ok(d.kind == "videoinput" || d.kind == "audioinput", "Known device kind");
|
||||
is(d.deviceId.length, 44, "Correct device id length");
|
||||
ok(d.label.length !== undefined, "Device label: " + d.label);
|
||||
is(d.groupId, "", "Don't support groupId yet");
|
||||
});
|
||||
var jsoned = JSON.parse(JSON.stringify(devices));
|
||||
is(jsoned[0].kind, devices[0].kind, "kind survived serializer");
|
||||
is(jsoned[0].deviceId, devices[0].deviceId, "deviceId survived serializer");
|
||||
var c = {};
|
||||
c[d.kind == "videoinput" ? "video" : "audio"] = { deviceId: d.deviceId };
|
||||
return p
|
||||
.then(() => navigator.mediaDevices.getUserMedia(c))
|
||||
.then(stream => {
|
||||
stream.getTracks().forEach(track => {
|
||||
is(typeof(track.label), "string", "Label is a string")
|
||||
is(track.label, d.label, "Label is the device label")
|
||||
})
|
||||
});
|
||||
}, Promise.resolve());
|
||||
})
|
||||
// Check deviceId failure paths for video.
|
||||
.then(() => mustSucceed("unknown plain deviceId on video",
|
||||
|
||||
@@ -34,7 +34,7 @@ MediaStreamAudioDestinationNode::MediaStreamAudioDestinationNode(AudioContext* a
|
||||
aContext->Graph()))
|
||||
{
|
||||
// Ensure an audio track with the correct ID is exposed to JS
|
||||
mDOMStream->CreateOwnDOMTrack(AudioNodeStream::AUDIO_TRACK, MediaSegment::AUDIO);
|
||||
mDOMStream->CreateOwnDOMTrack(AudioNodeStream::AUDIO_TRACK, MediaSegment::AUDIO, nsString());
|
||||
|
||||
ProcessedMediaStream* outputStream = mDOMStream->GetInputStream()->AsProcessedStream();
|
||||
MOZ_ASSERT(!!outputStream);
|
||||
|
||||
@@ -46,7 +46,12 @@ ServiceWorkerClientInfo::ServiceWorkerClientInfo(nsIDocument* aDoc)
|
||||
mWindowId = innerWindow->WindowID();
|
||||
}
|
||||
|
||||
aDoc->GetURL(mUrl);
|
||||
nsCOMPtr<nsIURI> originalURI = aDoc->GetOriginalURI();
|
||||
if (originalURI) {
|
||||
nsAutoCString spec;
|
||||
originalURI->GetSpec(spec);
|
||||
CopyUTF8toUTF16(spec, mUrl);
|
||||
}
|
||||
mVisibilityState = aDoc->VisibilityState();
|
||||
|
||||
ErrorResult result;
|
||||
|
||||
+4
-12
@@ -59,16 +59,15 @@ class JS_FRIEND_API(Wrapper);
|
||||
*
|
||||
* ### Proxies and internal methods
|
||||
*
|
||||
* ES6 draft rev 27 (24 August 2014) specifies 14 internal methods. The runtime
|
||||
* semantics of just about everything a script can do to an object is specified
|
||||
* in terms of these internal methods. For example:
|
||||
* ES2016 specifies 13 internal methods. The runtime semantics of just
|
||||
* about everything a script can do to an object is specified in terms
|
||||
* of these internal methods. For example:
|
||||
*
|
||||
* JS code ES6 internal method that gets called
|
||||
* --------------------------- --------------------------------
|
||||
* obj.prop obj.[[Get]](obj, "prop")
|
||||
* "prop" in obj obj.[[HasProperty]]("prop")
|
||||
* new obj() obj.[[Construct]](<empty argument List>)
|
||||
* for (k in obj) {} obj.[[Enumerate]]()
|
||||
*
|
||||
* With regard to the implementation of these internal methods, there are three
|
||||
* very different kinds of object in SpiderMonkey.
|
||||
@@ -261,14 +260,6 @@ class JS_FRIEND_API(BaseProxyHandler)
|
||||
virtual bool delete_(JSContext* cx, HandleObject proxy, HandleId id,
|
||||
ObjectOpResult& result) const = 0;
|
||||
|
||||
/*
|
||||
* Because [[Enumerate]] is one of the standard traps it should be overridden.
|
||||
* However for convenience BaseProxyHandler includes a pure virtual implementation,
|
||||
* that turns the properties returned by getOwnEnumerablePropertyKeys (and proto walking)
|
||||
* into an Iterator object.
|
||||
*/
|
||||
virtual bool enumerate(JSContext* cx, HandleObject proxy, MutableHandleObject objp) const = 0;
|
||||
|
||||
/*
|
||||
* These methods are standard, but the engine does not normally call them.
|
||||
* They're opt-in. See "Proxy prototype chains" above.
|
||||
@@ -315,6 +306,7 @@ class JS_FRIEND_API(BaseProxyHandler)
|
||||
virtual bool construct(JSContext* cx, HandleObject proxy, const CallArgs& args) const;
|
||||
|
||||
/* SpiderMonkey extensions. */
|
||||
virtual bool enumerate(JSContext* cx, HandleObject proxy, MutableHandleObject objp) const;
|
||||
virtual bool getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
|
||||
MutableHandle<PropertyDescriptor> desc) const;
|
||||
virtual bool hasOwn(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const;
|
||||
|
||||
+4
-1
@@ -808,6 +808,8 @@ class JS_FRIEND_API(Node) {
|
||||
};
|
||||
};
|
||||
|
||||
using NodeSet = js::HashSet<Node, js::DefaultHasher<Node>, js::SystemAllocPolicy>;
|
||||
using NodeSetPtr = mozilla::UniquePtr<NodeSet, JS::DeletePolicy<NodeSet>>;
|
||||
|
||||
/*** Edge and EdgeRange ***************************************************************************/
|
||||
|
||||
@@ -844,7 +846,8 @@ class Edge {
|
||||
// false as the wantNames parameter.
|
||||
//
|
||||
// The storage is owned by this Edge, and will be freed when this Edge is
|
||||
// destructed.
|
||||
// destructed. You may take ownership of the name by `mozilla::Move`ing it
|
||||
// out of the edge; it is just a UniquePtr.
|
||||
//
|
||||
// (In real life we'll want a better representation for names, to avoid
|
||||
// creating tons of strings when the names follow a pattern; and we'll need
|
||||
|
||||
@@ -134,7 +134,7 @@ struct BreadthFirst {
|
||||
for (; !range->empty(); range->popFront()) {
|
||||
MOZ_ASSERT(!stopRequested);
|
||||
|
||||
const Edge& edge = range->front();
|
||||
Edge& edge = range->front();
|
||||
typename NodeMap::AddPtr a = visited.lookupForAdd(edge.referent);
|
||||
bool first = !a;
|
||||
|
||||
|
||||
@@ -71,8 +71,6 @@ class JS_EXPORT_API(DominatorTree)
|
||||
private:
|
||||
// Types.
|
||||
|
||||
using NodeSet = js::HashSet<Node, js::DefaultHasher<Node>, js::SystemAllocPolicy>;
|
||||
using NodeSetPtr = mozilla::UniquePtr<NodeSet, JS::DeletePolicy<NodeSet>>;
|
||||
using PredecessorSets = js::HashMap<Node, NodeSetPtr, js::DefaultHasher<Node>,
|
||||
js::SystemAllocPolicy>;
|
||||
using NodeToIndexMap = js::HashMap<Node, uint32_t, js::DefaultHasher<Node>,
|
||||
|
||||
@@ -0,0 +1,331 @@
|
||||
/* -*- 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 js_UbiNodeShortestPaths_h
|
||||
#define js_UbiNodeShortestPaths_h
|
||||
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/Move.h"
|
||||
|
||||
#include "jsalloc.h"
|
||||
|
||||
#include "js/UbiNodeBreadthFirst.h"
|
||||
#include "js/Vector.h"
|
||||
|
||||
namespace JS {
|
||||
namespace ubi {
|
||||
|
||||
/**
|
||||
* A back edge along a path in the heap graph.
|
||||
*/
|
||||
struct JS_PUBLIC_API(BackEdge)
|
||||
{
|
||||
private:
|
||||
Node predecessor_;
|
||||
EdgeName name_;
|
||||
|
||||
public:
|
||||
using Ptr = mozilla::UniquePtr<BackEdge, JS::DeletePolicy<BackEdge>>;
|
||||
|
||||
BackEdge() : predecessor_(), name_(nullptr) { }
|
||||
|
||||
bool init(const Node& predecessor, Edge& edge) {
|
||||
MOZ_ASSERT(!predecessor_);
|
||||
MOZ_ASSERT(!name_);
|
||||
|
||||
predecessor_ = predecessor;
|
||||
name_ = mozilla::Move(edge.name);
|
||||
return true;
|
||||
}
|
||||
|
||||
BackEdge(const BackEdge&) = delete;
|
||||
BackEdge& operator=(const BackEdge&) = delete;
|
||||
|
||||
BackEdge(BackEdge&& rhs)
|
||||
: predecessor_(rhs.predecessor_)
|
||||
, name_(mozilla::Move(rhs.name_))
|
||||
{
|
||||
MOZ_ASSERT(&rhs != this);
|
||||
}
|
||||
|
||||
BackEdge& operator=(BackEdge&& rhs) {
|
||||
this->~BackEdge();
|
||||
new(this) BackEdge(Move(rhs));
|
||||
return *this;
|
||||
}
|
||||
|
||||
Ptr clone() const;
|
||||
|
||||
const EdgeName& name() const { return name_; }
|
||||
EdgeName& name() { return name_; }
|
||||
|
||||
const JS::ubi::Node& predecessor() const { return predecessor_; }
|
||||
};
|
||||
|
||||
/**
|
||||
* A path is a series of back edges from which we discovered a target node.
|
||||
*/
|
||||
using Path = mozilla::Vector<BackEdge*>;
|
||||
|
||||
/**
|
||||
* The `JS::ubi::ShortestPaths` type represents a collection of up to N shortest
|
||||
* retaining paths for each of a target set of nodes, starting from the same
|
||||
* root node.
|
||||
*/
|
||||
struct JS_PUBLIC_API(ShortestPaths)
|
||||
{
|
||||
private:
|
||||
// Types, type aliases, and data members.
|
||||
|
||||
using BackEdgeVector = mozilla::Vector<BackEdge::Ptr>;
|
||||
using NodeToBackEdgeVectorMap = js::HashMap<Node, BackEdgeVector, js::DefaultHasher<Node>,
|
||||
js::SystemAllocPolicy>;
|
||||
|
||||
struct Handler;
|
||||
using Traversal = BreadthFirst<Handler>;
|
||||
|
||||
/**
|
||||
* A `JS::ubi::BreadthFirst` traversal handler that records back edges for
|
||||
* how we reached each node, allowing us to reconstruct the shortest
|
||||
* retaining paths after the traversal.
|
||||
*/
|
||||
struct Handler
|
||||
{
|
||||
using NodeData = BackEdge;
|
||||
|
||||
ShortestPaths& shortestPaths;
|
||||
size_t totalMaxPathsToRecord;
|
||||
size_t totalPathsRecorded;
|
||||
|
||||
explicit Handler(ShortestPaths& shortestPaths)
|
||||
: shortestPaths(shortestPaths)
|
||||
, totalMaxPathsToRecord(shortestPaths.targets_.count() * shortestPaths.maxNumPaths_)
|
||||
, totalPathsRecorded(0)
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
operator()(Traversal& traversal, JS::ubi::Node origin, JS::ubi::Edge& edge,
|
||||
BackEdge* back, bool first)
|
||||
{
|
||||
MOZ_ASSERT(back);
|
||||
MOZ_ASSERT(traversal.visited.has(origin));
|
||||
MOZ_ASSERT(totalPathsRecorded < totalMaxPathsToRecord);
|
||||
|
||||
if (first && !back->init(origin, edge))
|
||||
return false;
|
||||
|
||||
if (!shortestPaths.targets_.has(edge.referent))
|
||||
return true;
|
||||
|
||||
// If `first` is true, then we moved the edge's name into `back` in
|
||||
// the above call to `init`. So clone that back edge to get the
|
||||
// correct edge name. If `first` is not true, then our edge name is
|
||||
// still in `edge`. This accounts for the asymmetry between
|
||||
// `back->clone()` in the first branch, and the `init` call in the
|
||||
// second branch.
|
||||
|
||||
auto ptr = shortestPaths.paths_.lookupForAdd(edge.referent);
|
||||
if (first) {
|
||||
MOZ_ASSERT(!ptr);
|
||||
BackEdgeVector paths;
|
||||
if (!paths.reserve(shortestPaths.maxNumPaths_))
|
||||
return false;
|
||||
auto cloned = back->clone();
|
||||
if (!cloned)
|
||||
return false;
|
||||
paths.infallibleAppend(mozilla::Move(cloned));
|
||||
if (!shortestPaths.paths_.add(ptr, edge.referent, mozilla::Move(paths)))
|
||||
return false;
|
||||
totalPathsRecorded++;
|
||||
} else if (ptr->value().length() < shortestPaths.maxNumPaths_) {
|
||||
MOZ_ASSERT(ptr);
|
||||
BackEdge::Ptr thisBackEdge(js_new<BackEdge>());
|
||||
if (!thisBackEdge || !thisBackEdge->init(origin, edge))
|
||||
return false;
|
||||
ptr->value().infallibleAppend(mozilla::Move(thisBackEdge));
|
||||
totalPathsRecorded++;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(totalPathsRecorded <= totalMaxPathsToRecord);
|
||||
if (totalPathsRecorded == totalMaxPathsToRecord)
|
||||
traversal.stop();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// The maximum number of paths to record for each node.
|
||||
uint32_t maxNumPaths_;
|
||||
|
||||
// The root node we are starting the search from.
|
||||
Node root_;
|
||||
|
||||
// The set of nodes we are searching for paths to.
|
||||
NodeSet targets_;
|
||||
|
||||
// The resulting paths.
|
||||
NodeToBackEdgeVectorMap paths_;
|
||||
|
||||
// Need to keep alive the traversal's back edges so we can walk them later
|
||||
// when the traversal is over when recreating the shortest paths.
|
||||
Traversal::NodeMap backEdges_;
|
||||
|
||||
private:
|
||||
// Private methods.
|
||||
|
||||
ShortestPaths(uint32_t maxNumPaths, const Node& root, NodeSet&& targets)
|
||||
: maxNumPaths_(maxNumPaths)
|
||||
, root_(root)
|
||||
, targets_(mozilla::Move(targets))
|
||||
, paths_()
|
||||
, backEdges_()
|
||||
{
|
||||
MOZ_ASSERT(maxNumPaths_ > 0);
|
||||
MOZ_ASSERT(root_);
|
||||
MOZ_ASSERT(targets_.initialized());
|
||||
}
|
||||
|
||||
bool initialized() const {
|
||||
return targets_.initialized() &&
|
||||
paths_.initialized() &&
|
||||
backEdges_.initialized();
|
||||
}
|
||||
|
||||
public:
|
||||
// Public methods.
|
||||
|
||||
ShortestPaths(ShortestPaths&& rhs)
|
||||
: maxNumPaths_(rhs.maxNumPaths_)
|
||||
, root_(rhs.root_)
|
||||
, targets_(mozilla::Move(rhs.targets_))
|
||||
, paths_(mozilla::Move(rhs.paths_))
|
||||
, backEdges_(mozilla::Move(rhs.backEdges_))
|
||||
{
|
||||
MOZ_ASSERT(this != &rhs, "self-move is not allowed");
|
||||
}
|
||||
|
||||
ShortestPaths& operator=(ShortestPaths&& rhs) {
|
||||
this->~ShortestPaths();
|
||||
new (this) ShortestPaths(mozilla::Move(rhs));
|
||||
return *this;
|
||||
}
|
||||
|
||||
ShortestPaths(const ShortestPaths&) = delete;
|
||||
ShortestPaths& operator=(const ShortestPaths&) = delete;
|
||||
|
||||
/**
|
||||
* Construct a new `JS::ubi::ShortestPaths`, finding up to `maxNumPaths`
|
||||
* shortest retaining paths for each target node in `targets` starting from
|
||||
* `root`.
|
||||
*
|
||||
* The resulting `ShortestPaths` instance must not outlive the
|
||||
* `JS::ubi::Node` graph it was constructed from.
|
||||
*
|
||||
* - For `JS::ubi::Node` graphs backed by the live heap graph, this means
|
||||
* that the `ShortestPaths`'s lifetime _must_ be contained within the
|
||||
* scope of the provided `AutoCheckCannotGC` reference because a GC will
|
||||
* invalidate the nodes.
|
||||
*
|
||||
* - For `JS::ubi::Node` graphs backed by some other offline structure
|
||||
* provided by the embedder, the resulting `ShortestPaths`'s lifetime is
|
||||
* bounded by that offline structure's lifetime.
|
||||
*
|
||||
* Returns `mozilla::Nothing()` on OOM failure. It is the caller's
|
||||
* responsibility to handle and report the OOM.
|
||||
*/
|
||||
static mozilla::Maybe<ShortestPaths>
|
||||
Create(JSRuntime* rt, AutoCheckCannotGC& noGC, uint32_t maxNumPaths, const Node& root, NodeSet&& targets) {
|
||||
MOZ_ASSERT(targets.count() > 0);
|
||||
MOZ_ASSERT(maxNumPaths > 0);
|
||||
|
||||
size_t count = targets.count();
|
||||
ShortestPaths paths(maxNumPaths, root, mozilla::Move(targets));
|
||||
if (!paths.paths_.init(count))
|
||||
return mozilla::Nothing();
|
||||
|
||||
Handler handler(paths);
|
||||
Traversal traversal(rt, handler, noGC);
|
||||
traversal.wantNames = true;
|
||||
if (!traversal.init() || !traversal.addStartVisited(root) || !traversal.traverse())
|
||||
return mozilla::Nothing();
|
||||
|
||||
// Take ownership of the back edges we created while traversing the
|
||||
// graph so that we can follow them from `paths_` and don't
|
||||
// use-after-free.
|
||||
paths.backEdges_ = mozilla::Move(traversal.visited);
|
||||
|
||||
MOZ_ASSERT(paths.initialized());
|
||||
return mozilla::Some(mozilla::Move(paths));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a range that iterates over each target node we searched for retaining
|
||||
* paths for. The returned range must not outlive the `ShortestPaths`
|
||||
* instance.
|
||||
*/
|
||||
NodeSet::Range eachTarget() const {
|
||||
MOZ_ASSERT(initialized());
|
||||
return targets_.all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the provided functor/lambda/callable once for each retaining path
|
||||
* discovered for `target`. The `func` is passed a single `JS::ubi::Path&`
|
||||
* argument, which contains each edge along the path ordered starting from
|
||||
* the root and ending at the target, and must not outlive the scope of the
|
||||
* call.
|
||||
*
|
||||
* Note that it is possible that we did not find any paths from the root to
|
||||
* the given target, in which case `func` will not be invoked.
|
||||
*/
|
||||
template <class Func>
|
||||
bool forEachPath(const Node& target, Func func) {
|
||||
MOZ_ASSERT(initialized());
|
||||
MOZ_ASSERT(targets_.has(target));
|
||||
|
||||
auto ptr = paths_.lookup(target);
|
||||
|
||||
// We didn't find any paths to this target, so nothing to do here.
|
||||
if (!ptr)
|
||||
return true;
|
||||
|
||||
MOZ_ASSERT(ptr->value().length() <= maxNumPaths_);
|
||||
|
||||
Path path;
|
||||
for (const auto& backEdge : ptr->value()) {
|
||||
path.clear();
|
||||
|
||||
if (!path.append(backEdge.get()))
|
||||
return false;
|
||||
|
||||
Node here = backEdge->predecessor();
|
||||
MOZ_ASSERT(here);
|
||||
|
||||
while (here != root_) {
|
||||
auto p = backEdges_.lookup(here);
|
||||
MOZ_ASSERT(p);
|
||||
if (!path.append(&p->value()))
|
||||
return false;
|
||||
here = p->value().predecessor();
|
||||
MOZ_ASSERT(here);
|
||||
}
|
||||
|
||||
path.reverse();
|
||||
|
||||
if (!func(path))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ubi
|
||||
} // namespace JS
|
||||
|
||||
#endif // js_UbiNodeShortestPaths_h
|
||||
@@ -488,13 +488,6 @@ ModuleNamespaceObject::ProxyHandler::delete_(JSContext* cx, HandleObject proxy,
|
||||
return result.succeed();
|
||||
}
|
||||
|
||||
bool
|
||||
ModuleNamespaceObject::ProxyHandler::enumerate(JSContext* cx, HandleObject proxy,
|
||||
MutableHandleObject objp) const
|
||||
{
|
||||
return BaseProxyHandler::enumerate(cx, proxy, objp);
|
||||
}
|
||||
|
||||
bool
|
||||
ModuleNamespaceObject::ProxyHandler::ownPropertyKeys(JSContext* cx, HandleObject proxy,
|
||||
AutoIdVector& props) const
|
||||
|
||||
@@ -160,7 +160,6 @@ class ModuleNamespaceObject : public ProxyObject
|
||||
AutoIdVector& props) const override;
|
||||
bool delete_(JSContext* cx, HandleObject proxy, HandleId id,
|
||||
ObjectOpResult& result) const override;
|
||||
bool enumerate(JSContext* cx, HandleObject proxy, MutableHandleObject objp) const override;
|
||||
bool getPrototype(JSContext* cx, HandleObject proxy,
|
||||
MutableHandleObject protop) const override;
|
||||
bool setPrototype(JSContext* cx, HandleObject proxy, HandleObject proto,
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "js/StructuredClone.h"
|
||||
#include "js/UbiNode.h"
|
||||
#include "js/UbiNodeBreadthFirst.h"
|
||||
#include "js/UbiNodeShortestPaths.h"
|
||||
#include "js/UniquePtr.h"
|
||||
#include "js/Vector.h"
|
||||
#include "vm/GlobalObject.h"
|
||||
@@ -580,6 +581,11 @@ GCZeal(JSContext* cx, unsigned argc, Value* vp)
|
||||
if (!ToUint32(cx, args.get(0), &zeal))
|
||||
return false;
|
||||
|
||||
if (zeal > uint32_t(gc::ZealMode::Limit)) {
|
||||
JS_ReportError(cx, "gczeal argument out of range");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t frequency = JS_DEFAULT_ZEAL_FREQ;
|
||||
if (args.length() >= 2) {
|
||||
if (!ToUint32(cx, args.get(1), &frequency))
|
||||
@@ -726,40 +732,6 @@ DeterministicGC(JSContext* cx, unsigned argc, Value* vp)
|
||||
}
|
||||
#endif /* JS_GC_ZEAL */
|
||||
|
||||
static bool
|
||||
ValidateGC(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
if (args.length() != 1) {
|
||||
RootedObject callee(cx, &args.callee());
|
||||
ReportUsageError(cx, callee, "Wrong number of arguments");
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifndef JS_GC_ZEAL
|
||||
RootedObject callee(cx, &args.callee());
|
||||
ReportUsageError(cx, callee, "Called ValidateGC in a build without GC Zeal.");
|
||||
return false;
|
||||
#else
|
||||
uint8_t zeal;
|
||||
uint32_t freq;
|
||||
uint32_t scheduled;
|
||||
cx->runtime()->gc.getZeal(&zeal, &freq, &scheduled);
|
||||
if (zeal != 0 && zeal != js::gc::ZealIncrementalMarkingValidator) {
|
||||
RootedObject callee(cx, &args.callee());
|
||||
ReportUsageError(cx, callee, "Attempting to enter Marking Validation while another Zeal "
|
||||
"mode is set.");
|
||||
return false;
|
||||
}
|
||||
|
||||
int zealMode = ToBoolean(args[0]) ? 11 : 0;
|
||||
JS_SetGCZeal(cx, zealMode, 0);
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
#endif // JS_GC_ZEAL
|
||||
}
|
||||
|
||||
static bool
|
||||
StartGC(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
@@ -2529,6 +2501,177 @@ FindPath(JSContext* cx, unsigned argc, Value* vp)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
ShortestPaths(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
if (!args.requireAtLeast(cx, "shortestPaths", 3))
|
||||
return false;
|
||||
|
||||
// We don't ToString non-objects given as 'start' or 'target', because this
|
||||
// test is all about object identity, and ToString doesn't preserve that.
|
||||
// Non-GCThing endpoints don't make much sense.
|
||||
if (!args[0].isObject() && !args[0].isString() && !args[0].isSymbol()) {
|
||||
ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE,
|
||||
JSDVG_SEARCH_STACK, args[0], nullptr,
|
||||
"not an object, string, or symbol", nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!args[1].isObject() || !args[1].toObject().is<ArrayObject>()) {
|
||||
ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE,
|
||||
JSDVG_SEARCH_STACK, args[1], nullptr,
|
||||
"not an array object", nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedArrayObject objs(cx, &args[1].toObject().as<ArrayObject>());
|
||||
size_t length = objs->getDenseInitializedLength();
|
||||
if (length == 0) {
|
||||
ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE,
|
||||
JSDVG_SEARCH_STACK, args[1], nullptr,
|
||||
"not a dense array object with one or more elements", nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
RootedValue el(cx, objs->getDenseElement(i));
|
||||
if (!el.isObject() && !el.isString() && !el.isSymbol()) {
|
||||
ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE,
|
||||
JSDVG_SEARCH_STACK, el, nullptr,
|
||||
"not an object, string, or symbol", nullptr);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t maxNumPaths;
|
||||
if (!JS::ToInt32(cx, args[2], &maxNumPaths))
|
||||
return false;
|
||||
if (maxNumPaths <= 0) {
|
||||
ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE,
|
||||
JSDVG_SEARCH_STACK, args[2], nullptr,
|
||||
"not greater than 0", nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
// We accumulate the results into a GC-stable form, due to the fact that the
|
||||
// JS::ubi::ShortestPaths lifetime (when operating on the live heap graph)
|
||||
// is bounded within an AutoCheckCannotGC.
|
||||
Rooted<GCVector<GCVector<GCVector<Value>>>> values(cx, GCVector<GCVector<GCVector<Value>>>(cx));
|
||||
Vector<Vector<Vector<JS::ubi::EdgeName>>> names(cx);
|
||||
|
||||
{
|
||||
JS::AutoCheckCannotGC noGC(cx->runtime());
|
||||
|
||||
JS::ubi::NodeSet targets;
|
||||
if (!targets.init()) {
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
RootedValue val(cx, objs->getDenseElement(i));
|
||||
JS::ubi::Node node(val);
|
||||
if (!targets.put(node)) {
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
JS::ubi::Node root(args[0]);
|
||||
auto maybeShortestPaths = JS::ubi::ShortestPaths::Create(cx->runtime(), noGC, maxNumPaths,
|
||||
root, mozilla::Move(targets));
|
||||
if (maybeShortestPaths.isNothing()) {
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
auto& shortestPaths = *maybeShortestPaths;
|
||||
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
if (!values.append(GCVector<GCVector<Value>>(cx)) ||
|
||||
!names.append(Vector<Vector<JS::ubi::EdgeName>>(cx)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedValue val(cx, objs->getDenseElement(i));
|
||||
JS::ubi::Node target(val);
|
||||
|
||||
bool ok = shortestPaths.forEachPath(target, [&](JS::ubi::Path& path) {
|
||||
Rooted<GCVector<Value>> pathVals(cx, GCVector<Value>(cx));
|
||||
Vector<JS::ubi::EdgeName> pathNames(cx);
|
||||
|
||||
for (auto& part : path) {
|
||||
if (!pathVals.append(part->predecessor().exposeToJS()) ||
|
||||
!pathNames.append(mozilla::Move(part->name())))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return values.back().append(mozilla::Move(pathVals.get())) &&
|
||||
names.back().append(mozilla::Move(pathNames));
|
||||
});
|
||||
|
||||
if (!ok)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
MOZ_ASSERT(values.length() == names.length());
|
||||
MOZ_ASSERT(values.length() == length);
|
||||
|
||||
RootedArrayObject results(cx, NewDenseFullyAllocatedArray(cx, length));
|
||||
if (!results)
|
||||
return false;
|
||||
results->ensureDenseInitializedLength(cx, 0, length);
|
||||
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
size_t numPaths = values[i].length();
|
||||
MOZ_ASSERT(names[i].length() == numPaths);
|
||||
|
||||
RootedArrayObject pathsArray(cx, NewDenseFullyAllocatedArray(cx, numPaths));
|
||||
if (!pathsArray)
|
||||
return false;
|
||||
pathsArray->ensureDenseInitializedLength(cx, 0, numPaths);
|
||||
|
||||
for (size_t j = 0; j < numPaths; j++) {
|
||||
size_t pathLength = values[i][j].length();
|
||||
MOZ_ASSERT(names[i][j].length() == pathLength);
|
||||
|
||||
RootedArrayObject path(cx, NewDenseFullyAllocatedArray(cx, pathLength));
|
||||
if (!path)
|
||||
return false;
|
||||
path->ensureDenseInitializedLength(cx, 0, pathLength);
|
||||
|
||||
for (size_t k = 0; k < pathLength; k++) {
|
||||
RootedPlainObject part(cx, NewBuiltinClassInstance<PlainObject>(cx));
|
||||
if (!part)
|
||||
return false;
|
||||
|
||||
RootedValue predecessor(cx, values[i][j][k]);
|
||||
if (!JS_DefineProperty(cx, part, "predecessor", predecessor, JSPROP_ENUMERATE))
|
||||
return false;
|
||||
|
||||
if (names[i][j][k]) {
|
||||
RootedString edge(cx, NewStringCopyZ<CanGC>(cx, names[i][j][k].get()));
|
||||
if (!edge || !JS_DefineProperty(cx, part, "edge", edge, JSPROP_ENUMERATE))
|
||||
return false;
|
||||
}
|
||||
|
||||
path->setDenseElement(k, ObjectValue(*part));
|
||||
}
|
||||
|
||||
pathsArray->setDenseElement(j, ObjectValue(*path));
|
||||
}
|
||||
|
||||
results->setDenseElement(i, ObjectValue(*pathsArray));
|
||||
}
|
||||
|
||||
args.rval().setObject(*results);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
EvalReturningScope(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
@@ -3346,10 +3489,6 @@ gc::ZealModeHelpText),
|
||||
"abortgc()",
|
||||
" Abort the current incremental GC."),
|
||||
|
||||
JS_FN_HELP("validategc", ValidateGC, 1, 0,
|
||||
"validategc(true|false)",
|
||||
" If true, a separate validation step is performed after an incremental GC."),
|
||||
|
||||
JS_FN_HELP("fullcompartmentchecks", FullCompartmentChecks, 1, 0,
|
||||
"fullcompartmentchecks(true|false)",
|
||||
" If true, check for compartment mismatches before every GC."),
|
||||
@@ -3539,6 +3678,13 @@ gc::ZealModeHelpText),
|
||||
" element's edge is the node of the i+1'th array element; the destination of\n"
|
||||
" the last array element is implicitly |target|.\n"),
|
||||
|
||||
JS_FN_HELP("shortestPaths", ShortestPaths, 3, 0,
|
||||
"shortestPaths(start, targets, maxNumPaths)",
|
||||
" Return an array of arrays of shortest retaining paths. There is an array of\n"
|
||||
" shortest retaining paths for each object in |targets|. The maximum number of\n"
|
||||
" paths in each of those arrays is bounded by |maxNumPaths|. Each element in a\n"
|
||||
" path is of the form |{ predecessor, edge }|."),
|
||||
|
||||
#ifdef DEBUG
|
||||
JS_FN_HELP("dumpObject", DumpObject, 1, 0,
|
||||
"dumpObject()",
|
||||
|
||||
@@ -790,6 +790,7 @@ class GCRuntime
|
||||
|
||||
bool isIncrementalGc() const { return isIncremental; }
|
||||
bool isFullGc() const { return isFull; }
|
||||
bool isCompactingGc() const { return isCompacting; }
|
||||
|
||||
bool shouldCleanUpEverything() { return cleanUpEverything; }
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
gczeal(2);
|
||||
g = newGlobal();
|
||||
dbg = Debugger(g);
|
||||
dbg.onNewScript = function() function() this;
|
||||
schedulegc(10);
|
||||
g.eval("setLazyParsingDisabled(true)");
|
||||
g.evaluate("function one() {}");
|
||||
g.evaluate(`
|
||||
function target () {}
|
||||
function two2() {}
|
||||
`, {});
|
||||
g.evaluate(`
|
||||
function three1() {}
|
||||
function three2() {}
|
||||
function three3() {}
|
||||
`, {});
|
||||
dbg.memory.takeCensus({
|
||||
breakdown: {
|
||||
by: "coarseType",
|
||||
scripts: {
|
||||
by: "filename"
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,2 @@
|
||||
// |jit-test| error:out of range
|
||||
gczeal(123);
|
||||
@@ -0,0 +1,44 @@
|
||||
// The shortestPaths function exists solely to let the fuzzers go to town and
|
||||
// exercise the code paths it calls into, hence there is nothing to assert here.
|
||||
//
|
||||
// The actual behavior of JS::ubi::ShortestPaths is tested in
|
||||
// js/src/jsapi-tests/testUbiNode.cpp, where we can actually control the
|
||||
// structure of the heap graph to test specific shapes.
|
||||
|
||||
function f(x) {
|
||||
return x + x;
|
||||
}
|
||||
|
||||
var g = f.bind(null, 5);
|
||||
|
||||
var o = {
|
||||
p: g
|
||||
};
|
||||
|
||||
function dumpPaths(results) {
|
||||
results = results.map(paths => {
|
||||
return paths.map(path => {
|
||||
return path.map(part => {
|
||||
return {
|
||||
predecessor: Object.prototype.toString.call(part.predecessor),
|
||||
edge: part.edge
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
print(JSON.stringify(results, null, 2));
|
||||
}
|
||||
|
||||
print("shortestPaths(this, [Object, f, o.p], 5)");
|
||||
var paths = shortestPaths(this, [Object, f, o.p], 5);
|
||||
dumpPaths(paths);
|
||||
|
||||
print();
|
||||
print("shortestPaths(o, [f], 1)")
|
||||
paths = shortestPaths(o, [f], 1);
|
||||
dumpPaths(paths);
|
||||
|
||||
print();
|
||||
print("shortestPaths(this, [f], 5)")
|
||||
paths = shortestPaths(this, [f], 5);
|
||||
dumpPaths(paths);
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "js/UbiNode.h"
|
||||
#include "js/UbiNodeDominatorTree.h"
|
||||
#include "js/UbiNodePostOrder.h"
|
||||
#include "js/UbiNodeShortestPaths.h"
|
||||
#include "jsapi-tests/tests.h"
|
||||
#include "vm/SavedFrame.h"
|
||||
|
||||
@@ -23,8 +24,15 @@ struct FakeNode
|
||||
|
||||
explicit FakeNode(char name) : name(name), edges() { }
|
||||
|
||||
bool addEdgeTo(FakeNode& referent) {
|
||||
bool addEdgeTo(FakeNode& referent, const char16_t* edgeName = nullptr) {
|
||||
JS::ubi::Node node(&referent);
|
||||
|
||||
if (edgeName) {
|
||||
auto ownedName = js::DuplicateString(edgeName);
|
||||
MOZ_RELEASE_ASSERT(ownedName);
|
||||
return edges.emplaceBack(ownedName.release(), node);
|
||||
}
|
||||
|
||||
return edges.emplaceBack(nullptr, node);
|
||||
}
|
||||
};
|
||||
@@ -648,3 +656,351 @@ BEGIN_TEST(test_JS_ubi_Node_scriptFilename)
|
||||
return true;
|
||||
}
|
||||
END_TEST(test_JS_ubi_Node_scriptFilename)
|
||||
|
||||
#define LAMBDA_CHECK(cond) \
|
||||
do { \
|
||||
if (!(cond)) { \
|
||||
fprintf(stderr,"%s:%d:CHECK failed: " #cond "\n", __FILE__, __LINE__); \
|
||||
return false; \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
static void
|
||||
dumpPath(JS::ubi::Path& path)
|
||||
{
|
||||
for (size_t i = 0; i < path.length(); i++) {
|
||||
fprintf(stderr, "path[%llu]->predecessor() = '%c'\n",
|
||||
(long long unsigned) i,
|
||||
path[i]->predecessor().as<FakeNode>()->name);
|
||||
}
|
||||
}
|
||||
|
||||
BEGIN_TEST(test_JS_ubi_ShortestPaths_no_path)
|
||||
{
|
||||
// Create the following graph:
|
||||
//
|
||||
// .---. .---. .---.
|
||||
// | a | <--> | c | | b |
|
||||
// '---' '---' '---'
|
||||
FakeNode a('a');
|
||||
FakeNode b('b');
|
||||
FakeNode c('c');
|
||||
CHECK(a.addEdgeTo(c));
|
||||
CHECK(c.addEdgeTo(a));
|
||||
|
||||
mozilla::Maybe<JS::ubi::ShortestPaths> maybeShortestPaths;
|
||||
{
|
||||
JS::AutoCheckCannotGC noGC(rt);
|
||||
|
||||
JS::ubi::NodeSet targets;
|
||||
CHECK(targets.init());
|
||||
CHECK(targets.put(&b));
|
||||
|
||||
maybeShortestPaths = JS::ubi::ShortestPaths::Create(rt, noGC, 10, &a,
|
||||
mozilla::Move(targets));
|
||||
}
|
||||
|
||||
CHECK(maybeShortestPaths);
|
||||
auto& paths = *maybeShortestPaths;
|
||||
|
||||
size_t numPathsFound = 0;
|
||||
bool ok = paths.forEachPath(&b, [&](JS::ubi::Path& path) {
|
||||
numPathsFound++;
|
||||
dumpPath(path);
|
||||
return true;
|
||||
});
|
||||
CHECK(ok);
|
||||
CHECK(numPathsFound == 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
END_TEST(test_JS_ubi_ShortestPaths_no_path)
|
||||
|
||||
BEGIN_TEST(test_JS_ubi_ShortestPaths_one_path)
|
||||
{
|
||||
// Create the following graph:
|
||||
//
|
||||
// .---. .---. .---.
|
||||
// | a | <--> | c | --> | b |
|
||||
// '---' '---' '---'
|
||||
FakeNode a('a');
|
||||
FakeNode b('b');
|
||||
FakeNode c('c');
|
||||
CHECK(a.addEdgeTo(c));
|
||||
CHECK(c.addEdgeTo(a));
|
||||
CHECK(c.addEdgeTo(b));
|
||||
|
||||
mozilla::Maybe<JS::ubi::ShortestPaths> maybeShortestPaths;
|
||||
{
|
||||
JS::AutoCheckCannotGC noGC(rt);
|
||||
|
||||
JS::ubi::NodeSet targets;
|
||||
CHECK(targets.init());
|
||||
CHECK(targets.put(&b));
|
||||
|
||||
maybeShortestPaths = JS::ubi::ShortestPaths::Create(rt, noGC, 10, &a,
|
||||
mozilla::Move(targets));
|
||||
}
|
||||
|
||||
CHECK(maybeShortestPaths);
|
||||
auto& paths = *maybeShortestPaths;
|
||||
|
||||
size_t numPathsFound = 0;
|
||||
bool ok = paths.forEachPath(&b, [&](JS::ubi::Path& path) {
|
||||
numPathsFound++;
|
||||
|
||||
dumpPath(path);
|
||||
LAMBDA_CHECK(path.length() == 2);
|
||||
LAMBDA_CHECK(path[0]->predecessor() == JS::ubi::Node(&a));
|
||||
LAMBDA_CHECK(path[1]->predecessor() == JS::ubi::Node(&c));
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
CHECK(ok);
|
||||
CHECK(numPathsFound == 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
END_TEST(test_JS_ubi_ShortestPaths_one_path)
|
||||
|
||||
BEGIN_TEST(test_JS_ubi_ShortestPaths_multiple_paths)
|
||||
{
|
||||
// Create the following graph:
|
||||
//
|
||||
// .---.
|
||||
// .-----| a |-----.
|
||||
// | '---' |
|
||||
// V | V
|
||||
// .---. | .---.
|
||||
// | b | | | d |
|
||||
// '---' | '---'
|
||||
// | | |
|
||||
// V | V
|
||||
// .---. | .---.
|
||||
// | c | | | e |
|
||||
// '---' V '---'
|
||||
// | .---. |
|
||||
// '---->| f |<----'
|
||||
// '---'
|
||||
FakeNode a('a');
|
||||
FakeNode b('b');
|
||||
FakeNode c('c');
|
||||
FakeNode d('d');
|
||||
FakeNode e('e');
|
||||
FakeNode f('f');
|
||||
CHECK(a.addEdgeTo(b));
|
||||
CHECK(a.addEdgeTo(f));
|
||||
CHECK(a.addEdgeTo(d));
|
||||
CHECK(b.addEdgeTo(c));
|
||||
CHECK(c.addEdgeTo(f));
|
||||
CHECK(d.addEdgeTo(e));
|
||||
CHECK(e.addEdgeTo(f));
|
||||
|
||||
mozilla::Maybe<JS::ubi::ShortestPaths> maybeShortestPaths;
|
||||
{
|
||||
JS::AutoCheckCannotGC noGC(rt);
|
||||
|
||||
JS::ubi::NodeSet targets;
|
||||
CHECK(targets.init());
|
||||
CHECK(targets.put(&f));
|
||||
|
||||
maybeShortestPaths = JS::ubi::ShortestPaths::Create(rt, noGC, 10, &a,
|
||||
mozilla::Move(targets));
|
||||
}
|
||||
|
||||
CHECK(maybeShortestPaths);
|
||||
auto& paths = *maybeShortestPaths;
|
||||
|
||||
size_t numPathsFound = 0;
|
||||
bool ok = paths.forEachPath(&f, [&](JS::ubi::Path& path) {
|
||||
numPathsFound++;
|
||||
dumpPath(path);
|
||||
|
||||
switch (path.back()->predecessor().as<FakeNode>()->name) {
|
||||
case 'a': {
|
||||
LAMBDA_CHECK(path.length() == 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'c': {
|
||||
LAMBDA_CHECK(path.length() == 3);
|
||||
LAMBDA_CHECK(path[0]->predecessor() == JS::ubi::Node(&a));
|
||||
LAMBDA_CHECK(path[1]->predecessor() == JS::ubi::Node(&b));
|
||||
LAMBDA_CHECK(path[2]->predecessor() == JS::ubi::Node(&c));
|
||||
break;
|
||||
}
|
||||
|
||||
case 'e': {
|
||||
LAMBDA_CHECK(path.length() == 3);
|
||||
LAMBDA_CHECK(path[0]->predecessor() == JS::ubi::Node(&a));
|
||||
LAMBDA_CHECK(path[1]->predecessor() == JS::ubi::Node(&d));
|
||||
LAMBDA_CHECK(path[2]->predecessor() == JS::ubi::Node(&e));
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
// Unexpected path!
|
||||
LAMBDA_CHECK(false);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
CHECK(ok);
|
||||
fprintf(stderr, "numPathsFound = %llu\n", (long long unsigned) numPathsFound);
|
||||
CHECK(numPathsFound == 3);
|
||||
|
||||
return true;
|
||||
}
|
||||
END_TEST(test_JS_ubi_ShortestPaths_multiple_paths)
|
||||
|
||||
BEGIN_TEST(test_JS_ubi_ShortestPaths_more_paths_than_max)
|
||||
{
|
||||
// Create the following graph:
|
||||
//
|
||||
// .---.
|
||||
// .-----| a |-----.
|
||||
// | '---' |
|
||||
// V | V
|
||||
// .---. | .---.
|
||||
// | b | | | d |
|
||||
// '---' | '---'
|
||||
// | | |
|
||||
// V | V
|
||||
// .---. | .---.
|
||||
// | c | | | e |
|
||||
// '---' V '---'
|
||||
// | .---. |
|
||||
// '---->| f |<----'
|
||||
// '---'
|
||||
FakeNode a('a');
|
||||
FakeNode b('b');
|
||||
FakeNode c('c');
|
||||
FakeNode d('d');
|
||||
FakeNode e('e');
|
||||
FakeNode f('f');
|
||||
CHECK(a.addEdgeTo(b));
|
||||
CHECK(a.addEdgeTo(f));
|
||||
CHECK(a.addEdgeTo(d));
|
||||
CHECK(b.addEdgeTo(c));
|
||||
CHECK(c.addEdgeTo(f));
|
||||
CHECK(d.addEdgeTo(e));
|
||||
CHECK(e.addEdgeTo(f));
|
||||
|
||||
mozilla::Maybe<JS::ubi::ShortestPaths> maybeShortestPaths;
|
||||
{
|
||||
JS::AutoCheckCannotGC noGC(rt);
|
||||
|
||||
JS::ubi::NodeSet targets;
|
||||
CHECK(targets.init());
|
||||
CHECK(targets.put(&f));
|
||||
|
||||
maybeShortestPaths = JS::ubi::ShortestPaths::Create(rt, noGC, 1, &a,
|
||||
mozilla::Move(targets));
|
||||
}
|
||||
|
||||
CHECK(maybeShortestPaths);
|
||||
auto& paths = *maybeShortestPaths;
|
||||
|
||||
size_t numPathsFound = 0;
|
||||
bool ok = paths.forEachPath(&f, [&](JS::ubi::Path& path) {
|
||||
numPathsFound++;
|
||||
dumpPath(path);
|
||||
return true;
|
||||
});
|
||||
|
||||
CHECK(ok);
|
||||
fprintf(stderr, "numPathsFound = %llu\n", (long long unsigned) numPathsFound);
|
||||
CHECK(numPathsFound == 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
END_TEST(test_JS_ubi_ShortestPaths_more_paths_than_max)
|
||||
|
||||
BEGIN_TEST(test_JS_ubi_ShortestPaths_multiple_edges_to_target)
|
||||
{
|
||||
// Create the following graph:
|
||||
//
|
||||
// .---.
|
||||
// .-----| a |-----.
|
||||
// | '---' |
|
||||
// | | |
|
||||
// |x |y |z
|
||||
// | | |
|
||||
// | V |
|
||||
// | .---. |
|
||||
// '---->| b |<----'
|
||||
// '---'
|
||||
FakeNode a('a');
|
||||
FakeNode b('b');
|
||||
CHECK(a.addEdgeTo(b, MOZ_UTF16("x")));
|
||||
CHECK(a.addEdgeTo(b, MOZ_UTF16("y")));
|
||||
CHECK(a.addEdgeTo(b, MOZ_UTF16("z")));
|
||||
|
||||
mozilla::Maybe<JS::ubi::ShortestPaths> maybeShortestPaths;
|
||||
{
|
||||
JS::AutoCheckCannotGC noGC(rt);
|
||||
|
||||
JS::ubi::NodeSet targets;
|
||||
CHECK(targets.init());
|
||||
CHECK(targets.put(&b));
|
||||
|
||||
maybeShortestPaths = JS::ubi::ShortestPaths::Create(rt, noGC, 10, &a,
|
||||
mozilla::Move(targets));
|
||||
}
|
||||
|
||||
CHECK(maybeShortestPaths);
|
||||
auto& paths = *maybeShortestPaths;
|
||||
|
||||
size_t numPathsFound = 0;
|
||||
bool foundX = false;
|
||||
bool foundY = false;
|
||||
bool foundZ = false;
|
||||
|
||||
bool ok = paths.forEachPath(&b, [&](JS::ubi::Path& path) {
|
||||
numPathsFound++;
|
||||
dumpPath(path);
|
||||
|
||||
LAMBDA_CHECK(path.length() == 1);
|
||||
LAMBDA_CHECK(path.back()->name());
|
||||
LAMBDA_CHECK(js_strlen(path.back()->name().get()) == 1);
|
||||
|
||||
auto c = uint8_t(path.back()->name().get()[0]);
|
||||
fprintf(stderr, "Edge name = '%c'\n", c);
|
||||
|
||||
switch (c) {
|
||||
case 'x': {
|
||||
foundX = true;
|
||||
break;
|
||||
}
|
||||
case 'y': {
|
||||
foundY = true;
|
||||
break;
|
||||
}
|
||||
case 'z': {
|
||||
foundZ = true;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// Unexpected edge!
|
||||
LAMBDA_CHECK(false);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
CHECK(ok);
|
||||
fprintf(stderr, "numPathsFound = %llu\n", (long long unsigned) numPathsFound);
|
||||
CHECK(numPathsFound == 3);
|
||||
CHECK(foundX);
|
||||
CHECK(foundY);
|
||||
CHECK(foundZ);
|
||||
|
||||
return true;
|
||||
}
|
||||
END_TEST(test_JS_ubi_ShortestPaths_multiple_edges_to_target)
|
||||
|
||||
#undef LAMBDA_CHECK
|
||||
|
||||
+43
-42
@@ -903,53 +903,54 @@ JS_TransplantObject(JSContext* cx, HandleObject origobj, HandleObject target)
|
||||
RootedValue origv(cx, ObjectValue(*origobj));
|
||||
RootedObject newIdentity(cx);
|
||||
|
||||
{
|
||||
AutoDisableProxyCheck adpc(cx->runtime());
|
||||
// Don't allow a compacting GC to observe any intermediate state.
|
||||
AutoDisableCompactingGC nocgc(cx->runtime());
|
||||
|
||||
JSCompartment* destination = target->compartment();
|
||||
AutoDisableProxyCheck adpc(cx->runtime());
|
||||
|
||||
if (origobj->compartment() == destination) {
|
||||
// If the original object is in the same compartment as the
|
||||
// destination, then we know that we won't find a wrapper in the
|
||||
// destination's cross compartment map and that the same
|
||||
// object will continue to work.
|
||||
if (!JSObject::swap(cx, origobj, target))
|
||||
MOZ_CRASH();
|
||||
newIdentity = origobj;
|
||||
} else if (WrapperMap::Ptr p = destination->lookupWrapper(origv)) {
|
||||
// There might already be a wrapper for the original object in
|
||||
// the new compartment. If there is, we use its identity and swap
|
||||
// in the contents of |target|.
|
||||
newIdentity = &p->value().get().toObject();
|
||||
JSCompartment* destination = target->compartment();
|
||||
|
||||
// When we remove origv from the wrapper map, its wrapper, newIdentity,
|
||||
// must immediately cease to be a cross-compartment wrapper. Nuke it.
|
||||
destination->removeWrapper(p);
|
||||
NukeCrossCompartmentWrapper(cx, newIdentity);
|
||||
|
||||
if (!JSObject::swap(cx, newIdentity, target))
|
||||
MOZ_CRASH();
|
||||
} else {
|
||||
// Otherwise, we use |target| for the new identity object.
|
||||
newIdentity = target;
|
||||
}
|
||||
|
||||
// Now, iterate through other scopes looking for references to the
|
||||
// old object, and update the relevant cross-compartment wrappers.
|
||||
if (!RemapAllWrappersForObject(cx, origobj, newIdentity))
|
||||
if (origobj->compartment() == destination) {
|
||||
// If the original object is in the same compartment as the
|
||||
// destination, then we know that we won't find a wrapper in the
|
||||
// destination's cross compartment map and that the same
|
||||
// object will continue to work.
|
||||
if (!JSObject::swap(cx, origobj, target))
|
||||
MOZ_CRASH();
|
||||
newIdentity = origobj;
|
||||
} else if (WrapperMap::Ptr p = destination->lookupWrapper(origv)) {
|
||||
// There might already be a wrapper for the original object in
|
||||
// the new compartment. If there is, we use its identity and swap
|
||||
// in the contents of |target|.
|
||||
newIdentity = &p->value().get().toObject();
|
||||
|
||||
// Lastly, update the original object to point to the new one.
|
||||
if (origobj->compartment() != destination) {
|
||||
RootedObject newIdentityWrapper(cx, newIdentity);
|
||||
AutoCompartment ac(cx, origobj);
|
||||
if (!JS_WrapObject(cx, &newIdentityWrapper))
|
||||
MOZ_CRASH();
|
||||
MOZ_ASSERT(Wrapper::wrappedObject(newIdentityWrapper) == newIdentity);
|
||||
if (!JSObject::swap(cx, origobj, newIdentityWrapper))
|
||||
MOZ_CRASH();
|
||||
origobj->compartment()->putWrapper(cx, CrossCompartmentKey(newIdentity), origv);
|
||||
}
|
||||
// When we remove origv from the wrapper map, its wrapper, newIdentity,
|
||||
// must immediately cease to be a cross-compartment wrapper. Nuke it.
|
||||
destination->removeWrapper(p);
|
||||
NukeCrossCompartmentWrapper(cx, newIdentity);
|
||||
|
||||
if (!JSObject::swap(cx, newIdentity, target))
|
||||
MOZ_CRASH();
|
||||
} else {
|
||||
// Otherwise, we use |target| for the new identity object.
|
||||
newIdentity = target;
|
||||
}
|
||||
|
||||
// Now, iterate through other scopes looking for references to the
|
||||
// old object, and update the relevant cross-compartment wrappers.
|
||||
if (!RemapAllWrappersForObject(cx, origobj, newIdentity))
|
||||
MOZ_CRASH();
|
||||
|
||||
// Lastly, update the original object to point to the new one.
|
||||
if (origobj->compartment() != destination) {
|
||||
RootedObject newIdentityWrapper(cx, newIdentity);
|
||||
AutoCompartment ac(cx, origobj);
|
||||
if (!JS_WrapObject(cx, &newIdentityWrapper))
|
||||
MOZ_CRASH();
|
||||
MOZ_ASSERT(Wrapper::wrappedObject(newIdentityWrapper) == newIdentity);
|
||||
if (!JSObject::swap(cx, origobj, newIdentityWrapper))
|
||||
MOZ_CRASH();
|
||||
origobj->compartment()->putWrapper(cx, CrossCompartmentKey(newIdentity), origv);
|
||||
}
|
||||
|
||||
// The new identity object might be one of several things. Return it to avoid
|
||||
|
||||
@@ -1999,6 +1999,8 @@ AutoDisableCompactingGC::AutoDisableCompactingGC(JSRuntime* rt)
|
||||
: gc(rt->gc)
|
||||
{
|
||||
gc.disableCompactingGC();
|
||||
if (gc.isIncrementalGCInProgress() && gc.isCompactingGc())
|
||||
AutoFinishGC finishGC(rt);
|
||||
}
|
||||
|
||||
AutoDisableCompactingGC::~AutoDisableCompactingGC()
|
||||
|
||||
+1
-1
@@ -300,7 +300,7 @@ IsCrossCompartmentWrapper(JSObject* obj);
|
||||
void
|
||||
NukeCrossCompartmentWrapper(JSContext* cx, JSObject* wrapper);
|
||||
|
||||
bool
|
||||
void
|
||||
RemapWrapper(JSContext* cx, JSObject* wobj, JSObject* newTarget);
|
||||
|
||||
JS_FRIEND_API(bool)
|
||||
|
||||
@@ -141,6 +141,7 @@ EXPORTS.js += [
|
||||
'../public/UbiNodeCensus.h',
|
||||
'../public/UbiNodeDominatorTree.h',
|
||||
'../public/UbiNodePostOrder.h',
|
||||
'../public/UbiNodeShortestPaths.h',
|
||||
'../public/UniquePtr.h',
|
||||
'../public/Utility.h',
|
||||
'../public/Value.h',
|
||||
@@ -344,6 +345,7 @@ UNIFIED_SOURCES += [
|
||||
'vm/TypeInference.cpp',
|
||||
'vm/UbiNode.cpp',
|
||||
'vm/UbiNodeCensus.cpp',
|
||||
'vm/UbiNodeShortestPaths.cpp',
|
||||
'vm/UnboxedObject.cpp',
|
||||
'vm/Unicode.cpp',
|
||||
'vm/Value.cpp',
|
||||
|
||||
@@ -26,10 +26,32 @@ bool
|
||||
BaseProxyHandler::has(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const
|
||||
{
|
||||
assertEnteredPolicy(cx, proxy, id, GET);
|
||||
Rooted<PropertyDescriptor> desc(cx);
|
||||
if (!getPropertyDescriptor(cx, proxy, id, &desc))
|
||||
|
||||
// This method is not covered by any spec, but we follow ES 2016
|
||||
// (February 11, 2016) 9.1.7.1 fairly closely.
|
||||
|
||||
// Step 2. (Step 1 is a superfluous assertion.)
|
||||
// Non-standard: Use our faster hasOwn trap.
|
||||
if (!hasOwn(cx, proxy, id, bp))
|
||||
return false;
|
||||
*bp = !!desc.object();
|
||||
|
||||
// Step 3.
|
||||
if (*bp)
|
||||
return true;
|
||||
|
||||
// The spec calls this variable "parent", but that word has weird
|
||||
// connotations in SpiderMonkey, so let's go with "proto".
|
||||
// Step 4.
|
||||
RootedObject proto(cx);
|
||||
if (!GetPrototype(cx, proxy, &proto))
|
||||
return false;
|
||||
|
||||
// Step 5.,5.a.
|
||||
if (proto)
|
||||
return HasProperty(cx, proto, id, bp);
|
||||
|
||||
// Step 6.
|
||||
*bp = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -493,7 +493,9 @@ js::NukeCrossCompartmentWrappers(JSContext* cx,
|
||||
// Given a cross-compartment wrapper |wobj|, update it to point to
|
||||
// |newTarget|. This recomputes the wrapper with JS_WrapValue, and thus can be
|
||||
// useful even if wrapper already points to newTarget.
|
||||
bool
|
||||
// This operation crashes on failure rather than leaving the heap in an
|
||||
// inconsistent state.
|
||||
void
|
||||
js::RemapWrapper(JSContext* cx, JSObject* wobjArg, JSObject* newTargetArg)
|
||||
{
|
||||
RootedObject wobj(cx, wobjArg);
|
||||
@@ -550,8 +552,8 @@ js::RemapWrapper(JSContext* cx, JSObject* wobjArg, JSObject* newTargetArg)
|
||||
// Update the entry in the compartment's wrapper map to point to the old
|
||||
// wrapper, which has now been updated (via reuse or swap).
|
||||
MOZ_ASSERT(wobj->is<WrapperObject>());
|
||||
wcompartment->putWrapper(cx, CrossCompartmentKey(newTarget), ObjectValue(*wobj));
|
||||
return true;
|
||||
if (!wcompartment->putWrapper(cx, CrossCompartmentKey(newTarget), ObjectValue(*wobj)))
|
||||
MOZ_CRASH();
|
||||
}
|
||||
|
||||
// Remap all cross-compartment wrappers pointing to |oldTarget| to point to
|
||||
@@ -574,10 +576,8 @@ js::RemapAllWrappersForObject(JSContext* cx, JSObject* oldTargetArg,
|
||||
}
|
||||
}
|
||||
|
||||
for (const WrapperValue& v : toTransplant) {
|
||||
if (!RemapWrapper(cx, &v.toObject(), newTarget))
|
||||
MOZ_CRASH();
|
||||
}
|
||||
for (const WrapperValue& v : toTransplant)
|
||||
RemapWrapper(cx, &v.toObject(), newTarget);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -614,8 +614,7 @@ js::RecomputeWrappers(JSContext* cx, const CompartmentFilter& sourceFilter,
|
||||
for (const WrapperValue& v : toRecompute) {
|
||||
JSObject* wrapper = &v.toObject();
|
||||
JSObject* wrapped = Wrapper::wrappedObject(wrapper);
|
||||
if (!RemapWrapper(cx, wrapper, wrapped))
|
||||
MOZ_CRASH();
|
||||
RemapWrapper(cx, wrapper, wrapped);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -53,13 +53,6 @@ DeadObjectProxy::delete_(JSContext* cx, HandleObject wrapper, HandleId id,
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
DeadObjectProxy::enumerate(JSContext* cx, HandleObject wrapper, MutableHandleObject objp) const
|
||||
{
|
||||
ReportDead(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
DeadObjectProxy::getPrototype(JSContext* cx, HandleObject proxy, MutableHandleObject protop) const
|
||||
{
|
||||
|
||||
@@ -28,7 +28,6 @@ class DeadObjectProxy : public BaseProxyHandler
|
||||
AutoIdVector& props) const override;
|
||||
virtual bool delete_(JSContext* cx, HandleObject wrapper, HandleId id,
|
||||
ObjectOpResult& result) const override;
|
||||
virtual bool enumerate(JSContext* cx, HandleObject wrapper, MutableHandleObject objp) const override;
|
||||
virtual bool getPrototype(JSContext* cx, HandleObject proxy,
|
||||
MutableHandleObject protop) const override;
|
||||
virtual bool preventExtensions(JSContext* cx, HandleObject proxy,
|
||||
@@ -39,6 +38,7 @@ class DeadObjectProxy : public BaseProxyHandler
|
||||
|
||||
/* SpiderMonkey extensions. */
|
||||
// BaseProxyHandler::getPropertyDescriptor will throw by calling getOwnPropertyDescriptor.
|
||||
// BaseProxyHandler::enumerate will throw by calling ownKeys.
|
||||
virtual bool nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl,
|
||||
const CallArgs& args) const override;
|
||||
virtual bool hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v,
|
||||
|
||||
@@ -2272,11 +2272,6 @@ class DebugScopeProxy : public BaseProxyHandler
|
||||
return true;
|
||||
}
|
||||
|
||||
bool enumerate(JSContext* cx, HandleObject proxy, MutableHandleObject objp) const override
|
||||
{
|
||||
return BaseProxyHandler::enumerate(cx, proxy, objp);
|
||||
}
|
||||
|
||||
bool has(JSContext* cx, HandleObject proxy, HandleId id_, bool* bp) const override
|
||||
{
|
||||
RootedId id(cx, id_);
|
||||
|
||||
+43
-14
@@ -280,16 +280,22 @@ static int compareEntries(const void* lhsVoid, const void* rhsVoid) {
|
||||
// A hash map mapping from C strings to counts.
|
||||
using CStringCountMap = HashMap<const char*, CountBasePtr, CStringHasher, SystemAllocPolicy>;
|
||||
|
||||
// Convert a CStringCountMap into an object with each key one of the c strings
|
||||
// from the map and each value the associated count's report. For use with
|
||||
// Convert a HashMap into an object with each key one of the entries from the
|
||||
// map and each value the associated count's report. For use during census
|
||||
// reporting.
|
||||
//
|
||||
// `Map` must be a `HashMap` from some key type to a `CountBasePtr`.
|
||||
//
|
||||
// `GetName` must be a callable type which takes `const Map::Key&` and returns
|
||||
// `const char*`.
|
||||
template <class Map, class GetName>
|
||||
static PlainObject*
|
||||
cStringCountMapToObject(JSContext* cx, CStringCountMap& map) {
|
||||
countMapToObject(JSContext* cx, Map& map, GetName getName) {
|
||||
// Build a vector of pointers to entries; sort by total; and then use
|
||||
// that to build the result object. This makes the ordering of entries
|
||||
// more interesting, and a little less non-deterministic.
|
||||
|
||||
mozilla::Vector<CStringCountMap::Entry*> entries;
|
||||
mozilla::Vector<typename Map::Entry*> entries;
|
||||
if (!entries.reserve(map.count())) {
|
||||
ReportOutOfMemory(cx);
|
||||
return nullptr;
|
||||
@@ -299,7 +305,7 @@ cStringCountMapToObject(JSContext* cx, CStringCountMap& map) {
|
||||
entries.infallibleAppend(&r.front());
|
||||
|
||||
qsort(entries.begin(), entries.length(), sizeof(*entries.begin()),
|
||||
compareEntries<CStringCountMap::Entry>);
|
||||
compareEntries<typename Map::Entry>);
|
||||
|
||||
RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx));
|
||||
if (!obj)
|
||||
@@ -311,7 +317,7 @@ cStringCountMapToObject(JSContext* cx, CStringCountMap& map) {
|
||||
if (!thenCount->report(cx, &thenReport))
|
||||
return nullptr;
|
||||
|
||||
const char* name = entry->key();
|
||||
const char* name = getName(entry->key());
|
||||
MOZ_ASSERT(name);
|
||||
JSAtom* atom = Atomize(cx, name, strlen(name));
|
||||
if (!atom)
|
||||
@@ -417,7 +423,9 @@ ByObjectClass::report(JSContext* cx, CountBase& countBase, MutableHandleValue re
|
||||
{
|
||||
Count& count = static_cast<Count&>(countBase);
|
||||
|
||||
RootedPlainObject obj(cx, cStringCountMapToObject(cx, count.table));
|
||||
RootedPlainObject obj(cx, countMapToObject(cx, count.table, [](const char* key) {
|
||||
return key;
|
||||
}));
|
||||
if (!obj)
|
||||
return false;
|
||||
|
||||
@@ -671,7 +679,7 @@ ByAllocationStack::report(JSContext* cx, CountBase& countBase, MutableHandleValu
|
||||
|
||||
#ifdef DEBUG
|
||||
// Check that nothing rehashes our table while we hold pointers into it.
|
||||
uint32_t generation = count.table.generation();
|
||||
Generation generation = count.table.generation();
|
||||
#endif
|
||||
|
||||
// Build a vector of pointers to entries; sort by total; and then use
|
||||
@@ -726,10 +734,25 @@ ByAllocationStack::report(JSContext* cx, CountBase& countBase, MutableHandleValu
|
||||
|
||||
// A count type that categorizes nodes by their script's filename.
|
||||
class ByFilename : public CountType {
|
||||
using UniqueCString = UniquePtr<char, JS::FreePolicy>;
|
||||
|
||||
struct UniqueCStringHasher {
|
||||
using Lookup = UniqueCString;
|
||||
|
||||
static js::HashNumber hash(const Lookup& lookup) {
|
||||
return CStringHasher::hash(lookup.get());
|
||||
}
|
||||
|
||||
static bool match(const UniqueCString& key, const Lookup& lookup) {
|
||||
return CStringHasher::match(key.get(), lookup.get());
|
||||
}
|
||||
};
|
||||
|
||||
// A table mapping filenames to their counts. Note that we treat scripts
|
||||
// with the same filename as equivalent. If you have several sources with
|
||||
// the same filename, then all their scripts will get bucketed together.
|
||||
using Table = CStringCountMap;
|
||||
using Table = HashMap<UniqueCString, CountBasePtr, UniqueCStringHasher,
|
||||
SystemAllocPolicy>;
|
||||
using Entry = Table::Entry;
|
||||
|
||||
struct Count : public CountBase {
|
||||
@@ -750,7 +773,7 @@ class ByFilename : public CountType {
|
||||
CountTypePtr noFilenameType;
|
||||
|
||||
public:
|
||||
ByFilename(CountTypePtr& thenType, CountTypePtr& noFilenameType)
|
||||
ByFilename(CountTypePtr&& thenType, CountTypePtr&& noFilenameType)
|
||||
: CountType(),
|
||||
thenType(Move(thenType)),
|
||||
noFilenameType(Move(noFilenameType))
|
||||
@@ -804,10 +827,14 @@ ByFilename::count(CountBase& countBase, mozilla::MallocSizeOf mallocSizeOf, cons
|
||||
if (!filename)
|
||||
return count.noFilename->count(mallocSizeOf, node);
|
||||
|
||||
Table::AddPtr p = count.table.lookupForAdd(filename);
|
||||
UniqueCString myFilename(js_strdup(filename));
|
||||
if (!myFilename)
|
||||
return false;
|
||||
|
||||
Table::AddPtr p = count.table.lookupForAdd(myFilename);
|
||||
if (!p) {
|
||||
CountBasePtr thenCount(thenType->makeCount());
|
||||
if (!thenCount || !count.table.add(p, filename, Move(thenCount)))
|
||||
if (!thenCount || !count.table.add(p, Move(myFilename), Move(thenCount)))
|
||||
return false;
|
||||
}
|
||||
return p->value()->count(mallocSizeOf, node);
|
||||
@@ -818,7 +845,9 @@ ByFilename::report(JSContext* cx, CountBase& countBase, MutableHandleValue repor
|
||||
{
|
||||
Count& count = static_cast<Count&>(countBase);
|
||||
|
||||
RootedPlainObject obj(cx, cStringCountMapToObject(cx, count.table));
|
||||
RootedPlainObject obj(cx, countMapToObject(cx, count.table, [](const UniqueCString& key) {
|
||||
return key.get();
|
||||
}));
|
||||
if (!obj)
|
||||
return false;
|
||||
|
||||
@@ -1008,7 +1037,7 @@ ParseBreakdown(JSContext* cx, HandleValue breakdownValue)
|
||||
if (!noFilenameType)
|
||||
return nullptr;
|
||||
|
||||
return CountTypePtr(js_new<ByFilename>(thenType, noFilenameType));
|
||||
return CountTypePtr(js_new<ByFilename>(Move(thenType), Move(noFilenameType)));
|
||||
}
|
||||
|
||||
// We didn't recognize the breakdown type; complain.
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/* -*- 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 "js/UbiNodeShortestPaths.h"
|
||||
|
||||
#include "jsstr.h"
|
||||
|
||||
namespace JS {
|
||||
namespace ubi {
|
||||
|
||||
JS_PUBLIC_API(BackEdge::Ptr)
|
||||
BackEdge::clone() const
|
||||
{
|
||||
BackEdge::Ptr clone(js_new<BackEdge>());
|
||||
if (!clone)
|
||||
return nullptr;
|
||||
|
||||
clone->predecessor_ = predecessor();
|
||||
if (name()) {
|
||||
clone->name_ = js::DuplicateString(name().get());
|
||||
if (!clone->name_)
|
||||
return nullptr;
|
||||
}
|
||||
return mozilla::Move(clone);
|
||||
}
|
||||
|
||||
} // namespace ubi
|
||||
} // namespace JS
|
||||
@@ -924,7 +924,6 @@ XPCJSRuntime::WeakPointerZoneGroupCallback(JSRuntime* rt, void* data)
|
||||
// about to be finalized and update any pointers to moved GC things.
|
||||
XPCJSRuntime* self = static_cast<XPCJSRuntime*>(data);
|
||||
|
||||
MOZ_ASSERT(self->WrappedJSToReleaseArray().IsEmpty());
|
||||
self->mWrappedJSMap->UpdateWeakPointersAfterGC(self);
|
||||
|
||||
XPCWrappedNativeScope::UpdateWeakPointersAfterGC(self);
|
||||
|
||||
@@ -400,8 +400,7 @@ JSXrayTraits::resolveOwnProperty(JSContext* cx, const Wrapper& jsWrapper,
|
||||
if (key == JSProto_Object || key == JSProto_Array) {
|
||||
return getOwnPropertyFromWrapperIfSafe(cx, wrapper, id, desc);
|
||||
} else if (IsTypedArrayKey(key)) {
|
||||
int32_t index = GetArrayIndexFromId(cx, id);
|
||||
if (IsArrayIndex(index)) {
|
||||
if (IsArrayIndex(GetArrayIndexFromId(cx, id))) {
|
||||
// WebExtensions can't use cloneInto(), so we just let them do
|
||||
// the slow thing to maximize compatibility.
|
||||
if (CompartmentPrivate::Get(CurrentGlobalOrNull(cx))->isWebExtensionContentScript) {
|
||||
@@ -1452,7 +1451,7 @@ XPCWrappedNativeXrayTraits::resolveOwnProperty(JSContext* cx, const Wrapper& jsW
|
||||
return ok;
|
||||
|
||||
// Check for indexed access on a window.
|
||||
int32_t index = GetArrayIndexFromId(cx, id);
|
||||
uint32_t index = GetArrayIndexFromId(cx, id);
|
||||
if (IsArrayIndex(index)) {
|
||||
nsGlobalWindow* win = AsWindow(cx, wrapper);
|
||||
// Note: As() unwraps outer windows to get to the inner window.
|
||||
@@ -1649,8 +1648,7 @@ DOMXrayTraits::defineProperty(JSContext* cx, HandleObject wrapper, HandleId id,
|
||||
// Check for an indexed property on a Window. If that's happening, do
|
||||
// nothing but claim we defined it so it won't get added as an expando.
|
||||
if (IsWindow(cx, wrapper)) {
|
||||
int32_t index = GetArrayIndexFromId(cx, id);
|
||||
if (IsArrayIndex(index)) {
|
||||
if (IsArrayIndex(GetArrayIndexFromId(cx, id))) {
|
||||
*defined = true;
|
||||
return result.succeed();
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "DisplayListClipState.h"
|
||||
#include "LayerState.h"
|
||||
#include "FrameMetrics.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/gfx/UserData.h"
|
||||
|
||||
@@ -4376,6 +4377,8 @@ public:
|
||||
// regardless of bidi directionality; top and bottom in vertical modes).
|
||||
nscoord mVisIStartEdge;
|
||||
nscoord mVisIEndEdge;
|
||||
// Cached result of mFrame->IsSelected(). Only initialized when needed.
|
||||
mutable mozilla::Maybe<bool> mIsFrameSelected;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -4694,10 +4694,12 @@ nsTextFrame::CharacterDataChanged(CharacterDataChangeInfo* aInfo)
|
||||
|
||||
class nsDisplayText : public nsCharClipDisplayItem {
|
||||
public:
|
||||
nsDisplayText(nsDisplayListBuilder* aBuilder, nsTextFrame* aFrame) :
|
||||
nsDisplayText(nsDisplayListBuilder* aBuilder, nsTextFrame* aFrame,
|
||||
Maybe<bool> aIsSelected) :
|
||||
nsCharClipDisplayItem(aBuilder, aFrame),
|
||||
mOpacity(1.0f),
|
||||
mDisableSubpixelAA(false) {
|
||||
mIsFrameSelected = aIsSelected;
|
||||
MOZ_COUNT_CTOR(nsDisplayText);
|
||||
}
|
||||
#ifdef NS_BUILD_REFCNT_LOGGING
|
||||
@@ -4871,18 +4873,22 @@ nsTextFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
|
||||
|
||||
DO_GLOBAL_REFLOW_COUNT_DSP("nsTextFrame");
|
||||
|
||||
Maybe<bool> isSelected;
|
||||
if (((GetStateBits() & TEXT_NO_RENDERED_GLYPHS) ||
|
||||
(NS_GET_A(StyleColor()->mColor) == 0 && !StyleText()->HasTextShadow())) &&
|
||||
aBuilder->IsForPainting() && !IsSVGText() && !IsSelected()) {
|
||||
TextDecorations textDecs;
|
||||
GetTextDecorations(PresContext(), eResolvedColors, textDecs);
|
||||
if (!textDecs.HasDecorationLines()) {
|
||||
return;
|
||||
aBuilder->IsForPainting() && !IsSVGText()) {
|
||||
isSelected.emplace(IsSelected());
|
||||
if (!isSelected) {
|
||||
TextDecorations textDecs;
|
||||
GetTextDecorations(PresContext(), eResolvedColors, textDecs);
|
||||
if (!textDecs.HasDecorationLines()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
aLists.Content()->AppendNewToTop(
|
||||
new (aBuilder) nsDisplayText(aBuilder, this));
|
||||
new (aBuilder) nsDisplayText(aBuilder, this, isSelected));
|
||||
}
|
||||
|
||||
static nsIFrame*
|
||||
@@ -6536,9 +6542,12 @@ nsTextFrame::PaintText(nsRenderingContext* aRenderingContext, nsPoint aPt,
|
||||
return;
|
||||
|
||||
PropertyProvider provider(this, iter, nsTextFrame::eInflated);
|
||||
if (aItem.mIsFrameSelected.isNothing()) {
|
||||
aItem.mIsFrameSelected.emplace(IsSelected());
|
||||
}
|
||||
// Trim trailing whitespace, unless we're painting a selection highlight,
|
||||
// which should include trailing spaces if present (bug 1146754).
|
||||
provider.InitializeForDisplay(!IsSelected());
|
||||
provider.InitializeForDisplay(!aItem.mIsFrameSelected.value());
|
||||
|
||||
gfxContext* ctx = aRenderingContext->ThebesContext();
|
||||
const bool reversed = mTextRun->IsInlineReversed();
|
||||
@@ -6582,7 +6591,7 @@ nsTextFrame::PaintText(nsRenderingContext* aRenderingContext, nsPoint aPt,
|
||||
gfxRect dirtyRect(aDirtyRect.x, aDirtyRect.y,
|
||||
aDirtyRect.width, aDirtyRect.height);
|
||||
// Fork off to the (slower) paint-with-selection path if necessary.
|
||||
if (IsSelected()) {
|
||||
if (aItem.mIsFrameSelected.value()) {
|
||||
MOZ_ASSERT(aOpacity == 1.0f, "We don't support opacity with selections!");
|
||||
gfxSkipCharsIterator tmp(provider.GetStart());
|
||||
int32_t contentOffset = tmp.ConvertSkippedToOriginal(startOffset);
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@ namespace mozilla {
|
||||
* struct Comparator {
|
||||
* int operator()(int val) const {
|
||||
* if (mTarget < val) return -1;
|
||||
* if (mValue > val) return 1;
|
||||
* if (mTarget > val) return 1;
|
||||
* return 0;
|
||||
* }
|
||||
* Comparator(int target) : mTarget(target) {}
|
||||
|
||||
@@ -518,6 +518,11 @@ public:
|
||||
|
||||
/* mutators */
|
||||
|
||||
/**
|
||||
* Reverse the order of the elements in the vector in place.
|
||||
*/
|
||||
void reverse();
|
||||
|
||||
/**
|
||||
* Given that the vector is empty and has no inline storage, grow to
|
||||
* |capacity|.
|
||||
@@ -788,6 +793,18 @@ Vector<T, N, AP>::~Vector()
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, size_t N, class AP>
|
||||
MOZ_ALWAYS_INLINE void
|
||||
Vector<T, N, AP>::reverse() {
|
||||
MOZ_REENTRANCY_GUARD_ET_AL;
|
||||
T* elems = mBegin;
|
||||
size_t len = mLength;
|
||||
size_t mid = len / 2;
|
||||
for (size_t i = 0; i < mid; i++) {
|
||||
Swap(elems[i], elems[len - i - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This function will create a new heap buffer with capacity aNewCap,
|
||||
* move all elements in the inline buffer to this new buffer,
|
||||
|
||||
@@ -19,6 +19,7 @@ struct mozilla::detail::VectorTesting
|
||||
static void testReserved();
|
||||
static void testConstRange();
|
||||
static void testEmplaceBack();
|
||||
static void testReverse();
|
||||
};
|
||||
|
||||
void
|
||||
@@ -164,10 +165,68 @@ mozilla::detail::VectorTesting::testEmplaceBack()
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
mozilla::detail::VectorTesting::testReverse()
|
||||
{
|
||||
// Use UniquePtr to make sure that reverse() can handler move-only types.
|
||||
Vector<UniquePtr<uint8_t>, 0> vec;
|
||||
|
||||
// Reverse an odd number of elements.
|
||||
|
||||
for (uint8_t i = 0; i < 5; i++) {
|
||||
auto p = MakeUnique<uint8_t>(i);
|
||||
MOZ_RELEASE_ASSERT(p);
|
||||
MOZ_RELEASE_ASSERT(vec.append(mozilla::Move(p)));
|
||||
}
|
||||
|
||||
vec.reverse();
|
||||
|
||||
MOZ_RELEASE_ASSERT(*vec[0] == 4);
|
||||
MOZ_RELEASE_ASSERT(*vec[1] == 3);
|
||||
MOZ_RELEASE_ASSERT(*vec[2] == 2);
|
||||
MOZ_RELEASE_ASSERT(*vec[3] == 1);
|
||||
MOZ_RELEASE_ASSERT(*vec[4] == 0);
|
||||
|
||||
// Reverse an even number of elements.
|
||||
|
||||
vec.popBack();
|
||||
vec.reverse();
|
||||
|
||||
MOZ_RELEASE_ASSERT(*vec[0] == 1);
|
||||
MOZ_RELEASE_ASSERT(*vec[1] == 2);
|
||||
MOZ_RELEASE_ASSERT(*vec[2] == 3);
|
||||
MOZ_RELEASE_ASSERT(*vec[3] == 4);
|
||||
|
||||
// Reverse an empty vector.
|
||||
|
||||
vec.clear();
|
||||
MOZ_RELEASE_ASSERT(vec.length() == 0);
|
||||
vec.reverse();
|
||||
MOZ_RELEASE_ASSERT(vec.length() == 0);
|
||||
|
||||
// Reverse a vector using only inline storage.
|
||||
|
||||
Vector<UniquePtr<uint8_t>, 5> vec2;
|
||||
for (uint8_t i = 0; i < 5; i++) {
|
||||
auto p = MakeUnique<uint8_t>(i);
|
||||
MOZ_RELEASE_ASSERT(p);
|
||||
MOZ_RELEASE_ASSERT(vec2.append(mozilla::Move(p)));
|
||||
}
|
||||
|
||||
vec2.reverse();
|
||||
|
||||
MOZ_RELEASE_ASSERT(*vec2[0] == 4);
|
||||
MOZ_RELEASE_ASSERT(*vec2[1] == 3);
|
||||
MOZ_RELEASE_ASSERT(*vec2[2] == 2);
|
||||
MOZ_RELEASE_ASSERT(*vec2[3] == 1);
|
||||
MOZ_RELEASE_ASSERT(*vec2[4] == 0);
|
||||
}
|
||||
|
||||
int
|
||||
main()
|
||||
{
|
||||
VectorTesting::testReserved();
|
||||
VectorTesting::testConstRange();
|
||||
VectorTesting::testEmplaceBack();
|
||||
VectorTesting::testReverse();
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "nsContentSecurityManager.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "RtspChannelChild.h"
|
||||
#include "mozilla/ipc/URIUtils.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
|
||||
+6
@@ -1,2 +1,8 @@
|
||||
<!DOCTYPE html>
|
||||
<title>Empty doc</title>
|
||||
<!--
|
||||
Change the page URL using the History API to ensure that ServiceWorkerClient
|
||||
uses the creation URL.
|
||||
-->
|
||||
<body onload="history.pushState({}, 'title', 'new-url.html')">
|
||||
</body>
|
||||
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title></title>
|
||||
<script src=/resources/testharness.js></script>
|
||||
<script src=/resources/testharnessreport.js></script>
|
||||
<!-- We want to use a tag name that will not interact with our test harness,
|
||||
so just make one up. "foo" is a good one -->
|
||||
|
||||
<!-- Ids that look like negative indices. These should come first, so we can
|
||||
assert that lookups for nonnegative indices find these by index -->
|
||||
<foo id="-2"></foo>
|
||||
<foo id="-1"></foo>
|
||||
|
||||
<!-- Ids that look like nonnegative indices -->
|
||||
<foo id="0"></foo>
|
||||
<foo id="1"></foo>
|
||||
|
||||
<!-- Ids that look like nonnegative indices near 2^31 = 2147483648 -->
|
||||
<foo id="2147483645"></foo> <!-- 2^31 - 3 -->
|
||||
<foo id="2147483646"></foo> <!-- 2^31 - 2 -->
|
||||
<foo id="2147483647"></foo> <!-- 2^31 - 1 -->
|
||||
<foo id="2147483648"></foo> <!-- 2^31 -->
|
||||
<foo id="2147483649"></foo> <!-- 2^31 + 1 -->
|
||||
|
||||
<!-- Ids that look like nonnegative indices near 2^32 = 4294967296 -->
|
||||
<foo id="4294967293"></foo> <!-- 2^32 - 3 -->
|
||||
<foo id="4294967294"></foo> <!-- 2^32 - 2 -->
|
||||
<foo id="4294967295"></foo> <!-- 2^32 - 1 -->
|
||||
<foo id="4294967296"></foo> <!-- 2^32 -->
|
||||
<foo id="4294967297"></foo> <!-- 2^32 + 1 -->
|
||||
|
||||
<script>
|
||||
test(function() {
|
||||
var collection = document.getElementsByTagName("foo");
|
||||
assert_equals(collection.item(-2), null);
|
||||
assert_equals(collection.item(-1), null);
|
||||
assert_equals(collection.namedItem(-2), document.getElementById("-2"));
|
||||
assert_equals(collection.namedItem(-1), document.getElementById("-1"));
|
||||
assert_equals(collection[-2], document.getElementById("-2"));
|
||||
assert_equals(collection[-1], document.getElementById("-1"));
|
||||
}, "Handling of property names that look like negative integers");
|
||||
|
||||
test(function() {
|
||||
var collection = document.getElementsByTagName("foo");
|
||||
assert_equals(collection.item(0), document.getElementById("-2"));
|
||||
assert_equals(collection.item(1), document.getElementById("-1"));
|
||||
assert_equals(collection.namedItem(0), document.getElementById("0"));
|
||||
assert_equals(collection.namedItem(1), document.getElementById("1"));
|
||||
assert_equals(collection[0], document.getElementById("-2"));
|
||||
assert_equals(collection[1], document.getElementById("-1"));
|
||||
}, "Handling of property names that look like small nonnegative integers");
|
||||
|
||||
test(function() {
|
||||
var collection = document.getElementsByTagName("foo");
|
||||
assert_equals(collection.item(2147483645), null);
|
||||
assert_equals(collection.item(2147483646), null);
|
||||
assert_equals(collection.item(2147483647), null);
|
||||
assert_equals(collection.item(2147483648), null);
|
||||
assert_equals(collection.item(2147483649), null);
|
||||
assert_equals(collection.namedItem(2147483645),
|
||||
document.getElementById("2147483645"));
|
||||
assert_equals(collection.namedItem(2147483646),
|
||||
document.getElementById("2147483646"));
|
||||
assert_equals(collection.namedItem(2147483647),
|
||||
document.getElementById("2147483647"));
|
||||
assert_equals(collection.namedItem(2147483648),
|
||||
document.getElementById("2147483648"));
|
||||
assert_equals(collection.namedItem(2147483649),
|
||||
document.getElementById("2147483649"));
|
||||
assert_equals(collection[2147483645], undefined);
|
||||
assert_equals(collection[2147483646], undefined);
|
||||
assert_equals(collection[2147483647], undefined);
|
||||
assert_equals(collection[2147483648], undefined);
|
||||
assert_equals(collection[2147483649], undefined);
|
||||
}, "Handling of property names that look like integers around 2^31");
|
||||
|
||||
test(function() {
|
||||
var collection = document.getElementsByTagName("foo");
|
||||
assert_equals(collection.item(4294967293), null);
|
||||
assert_equals(collection.item(4294967294), null);
|
||||
assert_equals(collection.item(4294967295), null);
|
||||
assert_equals(collection.item(4294967296), document.getElementById("-2"));
|
||||
assert_equals(collection.item(4294967297), document.getElementById("-1"));
|
||||
assert_equals(collection.namedItem(4294967293),
|
||||
document.getElementById("4294967293"));
|
||||
assert_equals(collection.namedItem(4294967294),
|
||||
document.getElementById("4294967294"));
|
||||
assert_equals(collection.namedItem(4294967295),
|
||||
document.getElementById("4294967295"));
|
||||
assert_equals(collection.namedItem(4294967296),
|
||||
document.getElementById("4294967296"));
|
||||
assert_equals(collection.namedItem(4294967297),
|
||||
document.getElementById("4294967297"));
|
||||
assert_equals(collection[4294967293], undefined);
|
||||
assert_equals(collection[4294967294], undefined);
|
||||
assert_equals(collection[4294967295], document.getElementById("4294967295"));
|
||||
assert_equals(collection[4294967296], document.getElementById("4294967296"));
|
||||
assert_equals(collection[4294967297], document.getElementById("4294967297"));
|
||||
}, "Handling of property names that look like integers around 2^32");
|
||||
</script>
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "nsXPIDLString.h"
|
||||
#include "nsReadableUtils.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsIServiceManager.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
#include "nsIInterfaceRequestor.h"
|
||||
|
||||
Reference in New Issue
Block a user