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:
2023-09-07 10:56:10 +08:00
parent e5bf86fbc7
commit 75be9ceb09
63 changed files with 1570 additions and 338 deletions
+1
View File
@@ -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)
{
+4 -7
View File
@@ -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
View File
@@ -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);
}
}
}
+16
View File
@@ -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)
+31 -6
View File
@@ -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 ====================
// ======================================================
+2 -3
View File
@@ -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;
}
+6 -6
View File
@@ -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;
-52
View File
@@ -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
+26 -30
View File
@@ -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();
}
+2 -2
View File
@@ -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
+2 -2
View File
@@ -54,5 +54,5 @@ TestInterfaceSetlike::GetParentObject() const
return mParent;
}
}
}
} // namespace dom
} // namespace mozilla
+1 -1
View File
@@ -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);
+31 -11
View File
@@ -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
+1 -1
View File
@@ -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
+2 -14
View File
@@ -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.
+2 -2
View 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;
+5 -5
View File
@@ -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");
+1 -1
View File
@@ -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:
+6 -2
View File
@@ -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;
+2 -2
View File
@@ -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;
+3 -2
View File
@@ -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;
};
+2 -2
View File
@@ -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);
+6 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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;
-2
View File
@@ -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>,
+331
View File
@@ -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
-7
View File
@@ -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
-1
View File
@@ -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,
+184 -38
View File
@@ -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()",
+1
View File
@@ -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"
}
}
});
+2
View File
@@ -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);
+357 -1
View File
@@ -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
View File
@@ -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
+2
View File
@@ -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
View File
@@ -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)
+2
View File
@@ -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',
+25 -3
View File
@@ -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;
}
+8 -9
View File
@@ -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;
-7
View File
@@ -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
{
+1 -1
View File
@@ -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,
-5
View File
@@ -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
View File
@@ -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.
+31
View File
@@ -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
-1
View File
@@ -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);
+3 -5
View File
@@ -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();
}
+3
View File
@@ -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;
};
/**
+18 -9
View File
@@ -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
View File
@@ -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) {}
+17
View File
@@ -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,
+59
View File
@@ -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"
@@ -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>
@@ -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"