merge mozilla-inbound to mozilla-central a=merge

This commit is contained in:
Carsten "Tomcat" Book 2016-01-18 14:22:46 +01:00
commit 15d6f2b06d
86 changed files with 2377 additions and 1742 deletions

View File

@ -83,6 +83,32 @@ namespace {
// Animation interface:
//
// ---------------------------------------------------------------------------
/* static */ already_AddRefed<Animation>
Animation::Constructor(const GlobalObject& aGlobal,
KeyframeEffectReadOnly* aEffect,
AnimationTimeline* aTimeline,
ErrorResult& aRv)
{
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
RefPtr<Animation> animation = new Animation(global);
if (!aTimeline) {
// Bug 1096776: We do not support null timeline yet.
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
if (!aEffect) {
// Bug 1049975: We do not support null effect yet.
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
animation->SetTimeline(aTimeline);
animation->SetEffect(aEffect);
return animation.forget();
}
void
Animation::SetId(const nsAString& aId)
{

View File

@ -90,6 +90,11 @@ public:
};
// Animation interface methods
static already_AddRefed<Animation>
Constructor(const GlobalObject& aGlobal,
KeyframeEffectReadOnly* aEffect,
AnimationTimeline* aTimeline,
ErrorResult& aRv);
void GetId(nsAString& aResult) const { aResult = mId; }
void SetId(const nsAString& aId);
KeyframeEffectReadOnly* GetEffect() const { return mEffect; }

View File

@ -70,12 +70,13 @@ EffectSet::GetEffectSet(const nsIFrame* aFrame)
return nullptr;
}
} else {
if (!content->MayHaveAnimations()) {
return nullptr;
}
propName = nsGkAtoms::animationEffectsProperty;
}
if (!content->MayHaveAnimations()) {
return nullptr;
}
return static_cast<EffectSet*>(content->GetProperty(propName));
}
@ -101,9 +102,7 @@ EffectSet::GetOrCreateEffectSet(dom::Element* aElement,
return nullptr;
}
if (aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement) {
aElement->SetMayHaveAnimations();
}
aElement->SetMayHaveAnimations();
return effectSet;
}

View File

@ -63,6 +63,7 @@ PendingAnimationTracker::TriggerPendingAnimationsOnNextTick(const TimeStamp&
// itself on the next tick where it has a timeline.
if (!timeline) {
iter.Remove();
continue;
}
// When the timeline's refresh driver is under test control, its values

View File

@ -1352,10 +1352,11 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FragmentOrElement)
for (uint32_t i = 0; props[i]; ++i) {
tmp->DeleteProperty(*props[i]);
}
// Bug 1226091: Call MayHaveAnimations() first
nsIAtom** effectProps = EffectSet::GetEffectSetPropertyAtoms();
for (uint32_t i = 0; effectProps[i]; ++i) {
tmp->DeleteProperty(effectProps[i]);
if (tmp->MayHaveAnimations()) {
nsIAtom** effectProps = EffectSet::GetEffectSetPropertyAtoms();
for (uint32_t i = 0; effectProps[i]; ++i) {
tmp->DeleteProperty(effectProps[i]);
}
}
}
}
@ -1899,6 +1900,14 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(FragmentOrElement)
tmp->OwnerDoc()->BindingManager()->Traverse(tmp, cb);
// Check that whenever we have effect properties, MayHaveAnimations is set.
#ifdef DEBUG
nsIAtom** effectProps = EffectSet::GetEffectSetPropertyAtoms();
for (uint32_t i = 0; effectProps[i]; ++i) {
MOZ_ASSERT_IF(tmp->GetProperty(effectProps[i]), tmp->MayHaveAnimations());
}
#endif
if (tmp->HasProperties()) {
if (tmp->IsHTMLElement() || tmp->IsSVGElement()) {
nsIAtom*** props = Element::HTMLSVGPropertiesToTraverseAndUnlink();
@ -1907,13 +1916,14 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(FragmentOrElement)
static_cast<nsISupports*>(tmp->GetProperty(*props[i]));
cb.NoteXPCOMChild(property);
}
// Bug 1226091: Check MayHaveAnimations() first
nsIAtom** effectProps = EffectSet::GetEffectSetPropertyAtoms();
for (uint32_t i = 0; effectProps[i]; ++i) {
EffectSet* effectSet =
static_cast<EffectSet*>(tmp->GetProperty(effectProps[i]));
if (effectSet) {
effectSet->Traverse(cb);
if (tmp->MayHaveAnimations()) {
nsIAtom** effectProps = EffectSet::GetEffectSetPropertyAtoms();
for (uint32_t i = 0; effectProps[i]; ++i) {
EffectSet* effectSet =
static_cast<EffectSet*>(tmp->GetProperty(effectProps[i]));
if (effectSet) {
effectSet->Traverse(cb);
}
}
}
}

View File

@ -28,6 +28,7 @@ CaptureStreamTestHelper.prototype = {
blackTransparent: { data: [0, 0, 0, 0], name: "blackTransparent" },
green: { data: [0, 255, 0, 255], name: "green" },
red: { data: [255, 0, 0, 255], name: "red" },
blue: { data: [0, 0, 255, 255], name: "blue"},
grey: { data: [128, 128, 128, 255], name: "grey" },
/* Default element size for createAndAppendElement() */

View File

@ -231,11 +231,11 @@ Request::Constructor(const GlobalObject& aGlobal,
RefPtr<Request> inputReq = &aInput.GetAsRequest();
nsCOMPtr<nsIInputStream> body;
inputReq->GetBody(getter_AddRefs(body));
if (inputReq->BodyUsed()) {
aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
return nullptr;
}
if (body) {
if (inputReq->BodyUsed()) {
aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
return nullptr;
}
temporaryBody = body;
}

View File

@ -1925,8 +1925,7 @@ MediaManager::GetUserMedia(nsPIDOMWindow* aWindow,
nsPIDOMWindow *outer = aWindow->GetOuterWindow();
vc.mBrowserWindow.Construct(outer->WindowID());
}
// | Fall through
// V
MOZ_FALLTHROUGH;
case dom::MediaSourceEnum::Screen:
case dom::MediaSourceEnum::Application:
case dom::MediaSourceEnum::Window:

View File

@ -252,7 +252,7 @@ already_AddRefed<SharedThreadPool> GetMediaThreadPool(MediaThreadType aType)
name = "MediaPDecoder";
break;
default:
MOZ_ASSERT(false);
MOZ_FALLTHROUGH_ASSERT("Unexpected MediaThreadType");
case MediaThreadType::PLAYBACK:
name = "MediaPlayback";
break;

View File

@ -90,10 +90,10 @@ MediaCodecProxy::MediaCodecProxy(sp<ALooper> aLooper,
: mCodecLooper(aLooper)
, mCodecMime(aMime)
, mCodecEncoder(aEncoder)
, mMediaCodecLock("MediaCodecProxy::mMediaCodecLock")
, mPendingRequestMediaResource(false)
, mPromiseMonitor("MediaCodecProxy::mPromiseMonitor")
{
MOZ_ASSERT(mCodecLooper != nullptr, "ALooper should not be nullptr.");
mCodecPromise.SetMonitor(&mPromiseMonitor);
}
MediaCodecProxy::~MediaCodecProxy()
@ -134,6 +134,7 @@ MediaCodecProxy::AsyncAllocateVideoMediaCodec()
mResourceClient->SetListener(this);
mResourceClient->Acquire();
mozilla::MonitorAutoLock lock(mPromiseMonitor);
RefPtr<CodecPromise> p = mCodecPromise.Ensure(__func__);
return p.forget();
}
@ -141,23 +142,16 @@ MediaCodecProxy::AsyncAllocateVideoMediaCodec()
void
MediaCodecProxy::ReleaseMediaCodec()
{
mCodecPromise.RejectIfExists(true, __func__);
releaseCodec();
if (!mResourceClient) {
return;
}
mozilla::MonitorAutoLock mon(mMediaCodecLock);
if (mPendingRequestMediaResource) {
mPendingRequestMediaResource = false;
mon.NotifyAll();
}
// At first, release mResourceClient's resource to prevent a conflict with
// mResourceClient's callback.
if (mResourceClient) {
mResourceClient->ReleaseResource();
mResourceClient = nullptr;
}
mozilla::MonitorAutoLock lock(mPromiseMonitor);
mCodecPromise.RejectIfExists(true, __func__);
releaseCodec();
}
bool
@ -473,18 +467,12 @@ void
MediaCodecProxy::ResourceReserved()
{
MCP_LOG("resourceReserved");
mozilla::MonitorAutoLock lock(mPromiseMonitor);
// Create MediaCodec
if (!allocateCodec()) {
ReleaseMediaCodec();
mCodecPromise.RejectIfExists(true, __func__);
return;
}
// Notify initialization waiting.
mozilla::MonitorAutoLock mon(mMediaCodecLock);
mPendingRequestMediaResource = false;
mon.NotifyAll();
mCodecPromise.ResolveIfExists(true, __func__);
}
@ -492,7 +480,7 @@ MediaCodecProxy::ResourceReserved()
void
MediaCodecProxy::ResourceReserveFailed()
{
ReleaseMediaCodec();
mozilla::MonitorAutoLock lock(mPromiseMonitor);
mCodecPromise.RejectIfExists(true, __func__);
}

View File

@ -163,6 +163,8 @@ private:
bool mCodecEncoder;
mozilla::MozPromiseHolder<CodecPromise> mCodecPromise;
// When mPromiseMonitor is held, mResourceClient's functions should not be called.
mozilla::Monitor mPromiseMonitor;
// Media Resource Management
RefPtr<mozilla::MediaSystemResourceClient> mResourceClient;
@ -174,9 +176,6 @@ private:
//MediaCodec buffers to hold input/output data.
Vector<sp<ABuffer> > mInputBuffers;
Vector<sp<ABuffer> > mOutputBuffers;
mozilla::Monitor mMediaCodecLock;
bool mPendingRequestMediaResource;
};
} // namespace android

View File

@ -64,6 +64,13 @@ OMXCodecProxy::OMXCodecProxy(
OMXCodecProxy::~OMXCodecProxy()
{
// At first, release mResourceClient's resource to prevent a conflict with
// mResourceClient's callback.
if (mResourceClient) {
mResourceClient->ReleaseResource();
mResourceClient = nullptr;
}
mState = ResourceState::END;
mCodecPromise.RejectIfExists(true, __func__);
@ -78,11 +85,6 @@ OMXCodecProxy::~OMXCodecProxy()
// Complete all pending Binder ipc transactions
IPCThreadState::self()->flushCommands();
if (mResourceClient) {
mResourceClient->ReleaseResource();
mResourceClient = nullptr;
}
mSource.clear();
free(mComponentName);
mComponentName = nullptr;

View File

@ -112,6 +112,8 @@ tags=capturestream
skip-if = toolkit == 'gonk' || buildapp == 'mulet' || android_version == '18' # b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator)
[test_peerConnection_captureStream_canvas_2d.html]
skip-if = toolkit == 'gonk' || buildapp == 'mulet' || (android_version == '18' && debug) # b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator)
[test_peerConnection_multiple_captureStream_canvas_2d.html]
skip-if = toolkit == 'gonk' || buildapp == 'mulet' || (android_version == '18' && debug) # b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator)
[test_peerConnection_captureStream_canvas_webgl.html]
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
# [test_peerConnection_certificates.html] # bug 1180968
@ -206,10 +208,13 @@ skip-if = toolkit == 'gonk' || android_version == '18'
[test_peerConnection_addDataChannel.html]
skip-if = toolkit == 'gonk' # B2G emulator seems to be so slow that DTLS cannot establish properly
[test_peerConnection_addDataChannelNoBundle.html]
# b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator)
skip-if = toolkit == 'gonk' || (android_version == '18' && debug) # b2g(emulator seems to be so slow that DTLS cannot establish properly), android(bug 1240256, intermittent ICE failures starting w/bug 1232082, possibly from timeout)
# b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator)
[test_peerConnection_verifyAudioAfterRenegotiation.html]
skip-if = toolkit == 'gonk' || (android_version == '18' && debug) # B2G emulator is too slow to finish a renegotiation test in under 5 minutes, android(Bug 1189784, timeouts on 4.3 emulator)
[test_peerConnection_verifyVideoAfterRenegotiation.html]
# B2G emulator is too slow to finish a renegotiation test in under 5 minutes, Bug 1180000 for Linux debug e10s, android(Bug 1189784, timeouts on 4.3 emulator)
skip-if = toolkit == 'gonk' || android_version == '18'
[test_peerConnection_webAudio.html]
tags = webaudio webrtc
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)

View File

@ -0,0 +1,100 @@
<!DOCTYPE HTML>
<html>
<head>
<script type="application/javascript" src="pc.js"></script>
<script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
</head>
<body>
<pre id="test">
<script type="application/javascript">
createHTML({
bug: "1166832",
title: "Canvas(2D)::Multiple CaptureStream as video-only input to peerconnection",
visible: true
});
/**
* Test to verify using multiple capture streams concurrently.
*/
runNetworkTest(() => {
var test = new PeerConnectionTest();
var h = new CaptureStreamTestHelper2D(50, 50);
var vremote1;
var stream1;
var canvas1 = h.createAndAppendElement('canvas', 'source_canvas1');
var vremote2;
var stream2;
var canvas2 = h.createAndAppendElement('canvas', 'source_canvas2');
test.setMediaConstraints([{video: true}, {video: true}], []);
test.chain.replace("PC_LOCAL_GUM", [
function DRAW_INITIAL_LOCAL1_GREEN(test) {
h.drawColor(canvas1, h.green);
},
function DRAW_INITIAL_LOCAL2_BLUE(test) {
h.drawColor(canvas2, h.blue);
},
function PC_LOCAL_CANVAS_CAPTURESTREAM(test) {
stream1 = canvas1.captureStream(0); // fps = 0 to capture single frame
test.pcLocal.attachMedia(stream1, 'video', 'local');
stream2 = canvas2.captureStream(0); // fps = 0 to capture single frame
test.pcLocal.attachMedia(stream2, 'video', 'local');
}
]);
test.chain.append([
function CHECK_REMOTE_VIDEO() {
var testremote = document.getElementById('pcRemote_remote3_video');
ok(!testremote, "Should not have remote3 video element for pcRemote");
vremote1 = document.getElementById('pcRemote_remote1_video');
vremote2 = document.getElementById('pcRemote_remote2_video');
// since we don't know which remote video is created first, we don't know
// which should be blue or green, but this will make sure that one is
// green and one is blue
return Promise.race([
Promise.all([
h.waitForPixelColor(vremote1, h.green, 128,
"pcRemote's remote1 should become green"),
h.waitForPixelColor(vremote2, h.blue, 128,
"pcRemote's remote2 should become blue")
]),
Promise.all([
h.waitForPixelColor(vremote2, h.green, 128,
"pcRemote's remote2 should become green"),
h.waitForPixelColor(vremote1, h.blue, 128,
"pcRemote's remote1 should become blue")
])
]);
},
function DRAW_LOCAL1_RED() {
// After requesting a frame it will be captured at the time of next render.
// Next render will happen at next stable state, at the earliest,
// i.e., this order of `requestFrame(); draw();` should work.
stream1.requestFrame();
h.drawColor(canvas1, h.red);
},
function DRAW_LOCAL2_RED() {
// After requesting a frame it will be captured at the time of next render.
// Next render will happen at next stable state, at the earliest,
// i.e., this order of `requestFrame(); draw();` should work.
stream2.requestFrame();
h.drawColor(canvas2, h.red);
},
function WAIT_FOR_REMOTE1_RED() {
return h.waitForPixelColor(vremote1, h.red, 128,
"pcRemote's remote1 should become red");
},
function WAIT_FOR_REMOTE2_RED() {
return h.waitForPixelColor(vremote2, h.red, 128,
"pcRemote's remote2 should become red");
}
]);
test.run();
});
</script>
</pre>
</body>
</html>

View File

@ -0,0 +1,123 @@
<!DOCTYPE HTML>
<html>
<head>
<script type="application/javascript" src="pc.js"></script>
</head>
<body>
<pre id="test">
<script type="application/javascript">
createHTML({
bug: "1166832",
title: "Renegotiation: verify audio after renegotiation"
});
var test;
runNetworkTest(function (options) {
test = new PeerConnectionTest(options);
var checkAudio = (analyser, fun) => {
analyser.enableDebugCanvas();
return analyser.waitForAnalysisSuccess(fun)
.then(() => analyser.disableDebugCanvas());
};
var checkAudioEnabled = (analyser, freq) =>
checkAudio(analyser, array => array[freq] > 200);
var checkAudioDisabled = (analyser, freq) =>
checkAudio(analyser, array => array[freq] < 50);
var ac = new AudioContext();
var local1Analyser;
var remote1Analyser;
test.chain.append([
function CHECK_ASSUMPTIONS() {
is(test.pcLocal.mediaElements.length, 1,
"pcLocal should only have one media element");
is(test.pcRemote.mediaElements.length, 1,
"pcRemote should only have one media element");
is(test.pcLocal.streams.length, 1,
"pcLocal should only have one stream (the local one)");
is(test.pcRemote.streams.length, 1,
"pcRemote should only have one stream (the remote one)");
},
function CHECK_AUDIO() {
local1Analyser = new AudioStreamAnalyser(ac, test.pcLocal.streams[0]);
remote1Analyser = new AudioStreamAnalyser(ac, test.pcRemote.streams[0]);
freq1k = local1Analyser.binIndexForFrequency(1000);
return Promise.resolve()
.then(() => info("Checking local audio enabled"))
.then(() => checkAudioEnabled(local1Analyser, freq1k))
.then(() => info("Checking remote audio enabled"))
.then(() => checkAudioEnabled(remote1Analyser, freq1k))
.then(() => test.pcLocal.streams[0].getAudioTracks()[0].enabled = false)
.then(() => info("Checking local audio disabled"))
.then(() => checkAudioDisabled(local1Analyser, freq1k))
.then(() => info("Checking remote audio disabled"))
.then(() => checkAudioDisabled(remote1Analyser, freq1k))
}
]);
addRenegotiation(test.chain,
[
function PC_LOCAL_ADD_SECOND_STREAM(test) {
test.setMediaConstraints([{audio: true}],
[]);
return test.pcLocal.getAllUserMedia([{audio: true}]);
},
]
);
test.chain.append([
function CHECK_ASSUMPTIONS2() {
is(test.pcLocal.mediaElements.length, 2,
"pcLocal should have two media elements");
is(test.pcRemote.mediaElements.length, 2,
"pcRemote should have two media elements");
is(test.pcLocal.streams.length, 2,
"pcLocal should have two streams");
is(test.pcRemote.streams.length, 2,
"pcRemote should have two streams");
},
function RE_CHECK_AUDIO() {
local2Analyser = new AudioStreamAnalyser(ac, test.pcLocal.streams[1]);
remote2Analyser = new AudioStreamAnalyser(ac, test.pcRemote.streams[1]);
freq1k = local2Analyser.binIndexForFrequency(1000);
return Promise.resolve()
.then(() => info("Checking local audio disabled"))
.then(() => checkAudioDisabled(local1Analyser, freq1k))
.then(() => info("Checking remote audio disabled"))
.then(() => checkAudioDisabled(remote1Analyser, freq1k))
.then(() => info("Checking local2 audio enabled"))
.then(() => checkAudioEnabled(local2Analyser, freq1k))
.then(() => info("Checking remote2 audio enabled"))
.then(() => checkAudioEnabled(remote2Analyser, freq1k))
.then(() => test.pcLocal.streams[1].getAudioTracks()[0].enabled = false)
.then(() => test.pcLocal.streams[0].getAudioTracks()[0].enabled = true)
.then(() => info("Checking local2 audio disabled"))
.then(() => checkAudioDisabled(local2Analyser, freq1k))
.then(() => info("Checking remote2 audio disabled"))
.then(() => checkAudioDisabled(remote2Analyser, freq1k))
.then(() => info("Checking local audio enabled"))
.then(() => checkAudioEnabled(local1Analyser, freq1k))
.then(() => info("Checking remote audio enabled"))
.then(() => checkAudioEnabled(remote1Analyser, freq1k))
}
]);
test.setMediaConstraints([{audio: true}], []);
test.run();
});
</script>
</pre>
</body>
</html>

View File

@ -0,0 +1,100 @@
<!DOCTYPE HTML>
<html>
<head>
<script type="application/javascript" src="pc.js"></script>
<script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
</head>
<body>
<pre id="test">
<script type="application/javascript">
createHTML({
bug: "1166832",
title: "Renegotiation: verify video after renegotiation"
});
runNetworkTest(() => {
var test = new PeerConnectionTest();
var h1 = new CaptureStreamTestHelper2D(50, 50);
var canvas1 = h1.createAndAppendElement('canvas', 'source_canvas1');
var stream1;
var vremote1;
var h2 = new CaptureStreamTestHelper2D(50, 50);
var canvas2;
var stream2;
var vremote2;
test.setMediaConstraints([{video: true}], []);
test.chain.replace("PC_LOCAL_GUM", [
function DRAW_INITIAL_LOCAL_GREEN(test) {
h1.drawColor(canvas1, h1.green);
},
function PC_LOCAL_CANVAS_CAPTURESTREAM(test) {
stream1 = canvas1.captureStream(0);
test.pcLocal.attachMedia(stream1, 'video', 'local');
}
]);
test.chain.append([
function FIND_REMOTE_VIDEO() {
vremote1 = document.getElementById('pcRemote_remote1_video');
ok(!!vremote1, "Should have remote video element for pcRemote");
},
function WAIT_FOR_REMOTE_GREEN() {
return h1.waitForPixelColor(vremote1, h1.green, 128,
"pcRemote's remote should become green");
},
function DRAW_LOCAL_RED() {
// After requesting a frame it will be captured at the time of next render.
// Next render will happen at next stable state, at the earliest,
// i.e., this order of `requestFrame(); draw();` should work.
stream1.requestFrame();
h1.drawColor(canvas1, h1.red);
},
function WAIT_FOR_REMOTE_RED() {
return h1.waitForPixelColor(vremote1, h1.red, 128,
"pcRemote's remote should become red");
}
]);
addRenegotiation(test.chain,
[
function PC_LOCAL_ADD_SECOND_STREAM(test) {
canvas2 = h2.createAndAppendElement('canvas', 'source_canvas2');
h2.drawColor(canvas2, h2.blue);
stream2 = canvas2.captureStream(0);
// can't use test.pcLocal.getAllUserMedia([{video: true}]);
// because it doesn't let us substitute the capture stream
return test.pcLocal.attachMedia(stream2, 'video', 'local');
}
]
);
test.chain.append([
function FIND_REMOTE2_VIDEO() {
vremote2 = document.getElementById('pcRemote_remote2_video');
ok(!!vremote2, "Should have remote2 video element for pcRemote");
},
function WAIT_FOR_REMOTE2_BLUE() {
return h2.waitForPixelColor(vremote2, h2.blue, 128,
"pcRemote's remote2 should become blue");
},
function DRAW_NEW_LOCAL_GREEN(test) {
stream1.requestFrame();
h1.drawColor(canvas1, h1.green);
},
function WAIT_FOR_REMOTE1_GREEN() {
return h1.waitForPixelColor(vremote1, h1.green, 128,
"pcRemote's remote1 should become green");
}
]);
test.run();
});
</script>
</pre>
</body>
</html>

View File

@ -569,7 +569,7 @@ public:
aPrevious->mCurve, aPrevious->mCurveLength,
aPrevious->mDuration, aTime);
case AudioTimelineEvent::SetTarget:
MOZ_ASSERT(false, "unreached");
MOZ_FALLTHROUGH_ASSERT("AudioTimelineEvent::SetTarget");
case AudioTimelineEvent::SetValue:
case AudioTimelineEvent::Cancel:
case AudioTimelineEvent::Stream:
@ -617,7 +617,7 @@ public:
aPrevious->mCurve, aPrevious->mCurveLength,
aPrevious->mDuration, aTime);
case AudioTimelineEvent::SetTarget:
MOZ_ASSERT(false, "unreached");
MOZ_FALLTHROUGH_ASSERT("AudioTimelineEvent::SetTarget");
case AudioTimelineEvent::SetValue:
case AudioTimelineEvent::Cancel:
case AudioTimelineEvent::Stream:

View File

@ -570,7 +570,7 @@ WebAudioDecodeJob::OnFailure(ErrorCode aErrorCode)
const char* errorMessage;
switch (aErrorCode) {
case NoError:
MOZ_ASSERT(false, "Who passed NoError to OnFailure?");
MOZ_FALLTHROUGH_ASSERT("Who passed NoError to OnFailure?");
// Fall through to get some sort of a sane error message if this actually
// happens at runtime.
case UnknownError:

View File

@ -133,7 +133,7 @@ void WebMBufferedParser::Append(const unsigned char* aBuffer, uint32_t aLength,
case EBML_ID:
mLastInitStartOffset = mCurrentOffset + (p - aBuffer) -
(mElement.mID.mLength + mElement.mSize.mLength);
/* FALLTHROUGH */
MOZ_FALLTHROUGH;
default:
mSkipBytes = mElement.mSize.mValue;
mState = SKIP_DATA;

View File

@ -187,7 +187,7 @@ ParseClockValue(RangedPtr<const char16_t>& aIter,
!ParseColon(iter, aEnd)) {
return false;
}
// intentional fall through
MOZ_FALLTHROUGH;
case PARTIAL_CLOCK_VALUE:
if (!ParseSecondsOrMinutes(iter, aEnd, minutes) ||
!ParseColon(iter, aEnd) ||

View File

@ -352,7 +352,7 @@ ConvertPathSegmentData(SVGPathDataAndInfo::const_iterator& aStart,
aResult[5] = aStart[5];
aResult[6] = aStart[6];
AdjustSegmentForRelativeness(adjustmentType, aResult + 5, aState);
// fall through
MOZ_FALLTHROUGH;
case PATHSEG_CURVETO_QUADRATIC_ABS:
case PATHSEG_CURVETO_QUADRATIC_REL:
case PATHSEG_CURVETO_CUBIC_SMOOTH_ABS:
@ -360,7 +360,7 @@ ConvertPathSegmentData(SVGPathDataAndInfo::const_iterator& aStart,
aResult[3] = aStart[3];
aResult[4] = aStart[4];
AdjustSegmentForRelativeness(adjustmentType, aResult + 3, aState);
// fall through
MOZ_FALLTHROUGH;
case PATHSEG_MOVETO_ABS:
case PATHSEG_MOVETO_REL:
case PATHSEG_LINETO_ABS:

View File

@ -143,7 +143,7 @@ SVGTransformListParser::ParseTranslate()
switch (count) {
case 1:
t[1] = 0.f;
// fall-through
MOZ_FALLTHROUGH;
case 2:
{
nsSVGTransform* transform = mTransforms.AppendElement(fallible);
@ -171,7 +171,7 @@ SVGTransformListParser::ParseScale()
switch (count) {
case 1:
s[1] = s[0];
// fall-through
MOZ_FALLTHROUGH;
case 2:
{
nsSVGTransform* transform = mTransforms.AppendElement(fallible);
@ -200,7 +200,7 @@ SVGTransformListParser::ParseRotate()
switch (count) {
case 1:
r[1] = r[2] = 0.f;
// fall-through
MOZ_FALLTHROUGH;
case 3:
{
nsSVGTransform* transform = mTransforms.AppendElement(fallible);

View File

@ -124,6 +124,19 @@ function testBug1109574() {
var r3 = new Request(r1);
}
// Bug 1184550 - Request constructor should always throw if used flag is set,
// even if body is null
function testBug1184550() {
var req = new Request("", { method: 'post', body: "Test" });
fetch(req);
ok(req.bodyUsed, "Request body should be used immediately after fetch()");
return fetch(req).then(function(resp) {
ok(false, "Second fetch with same request should fail.");
}).catch(function(err) {
is(err.name, 'TypeError', "Second fetch with same request should fail.");
});
}
function testHeaderGuard() {
var headers = {
"Cookie": "Custom cookie",
@ -500,6 +513,7 @@ function runTest() {
testUrlMalformed();
testMethod();
testBug1109574();
testBug1184550();
testHeaderGuard();
testModeCorsPreflightEnumValue();
testBug1154268();

View File

@ -12,7 +12,9 @@
enum AnimationPlayState { "idle", "pending", "running", "paused", "finished" };
[Func="nsDocument::IsWebAnimationsEnabled"]
[Func="nsDocument::IsWebAnimationsEnabled",
Constructor (optional KeyframeEffectReadOnly? effect = null,
optional AnimationTimeline? timeline = null)]
interface Animation : EventTarget {
attribute DOMString id;
// Bug 1049975: Make 'effect' writeable

View File

@ -76,8 +76,7 @@ LayerManager::GetRootScrollableLayerId()
return FrameMetrics::NULL_SCROLL_ID;
}
nsTArray<LayerMetricsWrapper> queue;
queue.AppendElement(LayerMetricsWrapper(mRoot));
nsTArray<LayerMetricsWrapper> queue = { LayerMetricsWrapper(mRoot) };
while (queue.Length()) {
LayerMetricsWrapper layer = queue[0];
queue.RemoveElementAt(0);
@ -110,8 +109,7 @@ LayerManager::GetRootScrollableLayers(nsTArray<Layer*>& aArray)
return;
}
nsTArray<Layer*> queue;
queue.AppendElement(mRoot);
nsTArray<Layer*> queue = { mRoot };
while (queue.Length()) {
Layer* layer = queue[0];
queue.RemoveElementAt(0);
@ -134,8 +132,7 @@ LayerManager::GetScrollableLayers(nsTArray<Layer*>& aArray)
return;
}
nsTArray<Layer*> queue;
queue.AppendElement(mRoot);
nsTArray<Layer*> queue = { mRoot };
while (!queue.IsEmpty()) {
Layer* layer = queue.LastElement();
queue.RemoveElementAt(queue.Length() - 1);

View File

@ -890,9 +890,8 @@ TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_NoTouchAction) {
TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_TouchActionNone) {
SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
nsTArray<uint32_t> behaviors;
behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::NONE);
behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::NONE);
nsTArray<uint32_t> behaviors = { mozilla::layers::AllowedTouchBehavior::NONE,
mozilla::layers::AllowedTouchBehavior::NONE };
DoPinchTest(false, &behaviors);
}

View File

@ -12,6 +12,7 @@
#include "gfx2DGlue.h"
#include "gfxUtils.h"
#include "mozilla/gfx/2D.h"
#include "GeckoProfiler.h"
using namespace mozilla::gfx;
@ -227,6 +228,7 @@ TextureSourceD3D9::DataToTexture(DeviceManagerD3D9* aDeviceManager,
_D3DFORMAT aFormat,
uint32_t aBPP)
{
PROFILER_LABEL_FUNC(js::ProfileEntry::Category::GRAPHICS);
RefPtr<IDirect3DSurface9> surface;
D3DLOCKED_RECT lockedRect;
RefPtr<IDirect3DTexture9> texture = InitTextures(aDeviceManager, aSize, aFormat,
@ -322,6 +324,7 @@ DataTextureSourceD3D9::Update(gfx::DataSourceSurface* aSurface,
nsIntRegion* aDestRegion,
gfx::IntPoint* aSrcOffset)
{
PROFILER_LABEL_FUNC(js::ProfileEntry::Category::GRAPHICS);
// Right now we only support full surface update. If aDestRegion is provided,
// It will be ignored. Incremental update with a source offset is only used
// on Mac so it is not clear that we ever will need to support it for D3D.
@ -636,6 +639,7 @@ DXGID3D9TextureData::Create(gfx::IntSize aSize, gfx::SurfaceFormat aFormat,
TextureFlags aFlags,
IDirect3DDevice9* aDevice)
{
PROFILER_LABEL_FUNC(js::ProfileEntry::Category::GRAPHICS);
MOZ_ASSERT(aFormat == gfx::SurfaceFormat::B8G8R8X8);
if (aFormat != gfx::SurfaceFormat::B8G8R8X8) {
return nullptr;
@ -707,6 +711,7 @@ bool
DataTextureSourceD3D9::UpdateFromTexture(IDirect3DTexture9* aTexture,
const nsIntRegion* aRegion)
{
PROFILER_LABEL_FUNC(js::ProfileEntry::Category::GRAPHICS);
MOZ_ASSERT(aTexture);
D3DSURFACE_DESC desc;

View File

@ -105,9 +105,8 @@ CommonAnimationManager::GetAnimationCollection(dom::Element *aElement,
AnimationCollection::PropertyDtor(aElement, propName, collection, nullptr);
return nullptr;
}
if (aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement) {
aElement->SetMayHaveAnimations();
}
aElement->SetMayHaveAnimations();
AddElementCollection(collection);
}
@ -124,9 +123,7 @@ CommonAnimationManager::GetAnimationCollection(const nsIFrame* aFrame)
return nullptr;
}
if (pseudoElement->second() ==
nsCSSPseudoElements::ePseudo_NotPseudoElement &&
!pseudoElement->first()->MayHaveAnimations()) {
if (!pseudoElement->first()->MayHaveAnimations()) {
return nullptr;
}

View File

@ -112,7 +112,7 @@ MP4Metadata::~MP4Metadata()
#ifdef MOZ_RUST_MP4PARSE
// Helper to test the rust parser on a data source.
static bool try_rust(const UniquePtr<mp4parse_state, FreeMP4ParseState>& aRustState, RefPtr<Stream> aSource, int32_t* aCount)
static bool try_rust(const UniquePtr<mp4parse_state, FreeMP4ParseState>& aRustState, RefPtr<Stream> aSource)
{
static LazyLogModule sLog("MP4Metadata");
int64_t length;
@ -129,9 +129,7 @@ static bool try_rust(const UniquePtr<mp4parse_state, FreeMP4ParseState>& aRustSt
MOZ_LOG(sLog, LogLevel::Warning, ("Error copying mp4 data"));
return false;
}
*aCount = mp4parse_read(aRustState.get(), buffer.data(), bytes_read);
MOZ_LOG(sLog, LogLevel::Info, ("rust parser found %d tracks", int(*aCount)));
return true;
return mp4parse_read(aRustState.get(), buffer.data(), bytes_read);
}
#endif
@ -139,12 +137,18 @@ uint32_t
MP4Metadata::GetNumberTracks(mozilla::TrackInfo::TrackType aType) const
{
#ifdef MOZ_RUST_MP4PARSE
static LazyLogModule sLog("MP4Metadata");
// Try in rust first.
mRustState.reset(mp4parse_new());
int32_t rust_tracks = 0;
bool rust_mp4parse_success = try_rust(mRustState, mSource, &rust_tracks);
int32_t rust_mp4parse_success = try_rust(mRustState, mSource);
Telemetry::Accumulate(Telemetry::MEDIA_RUST_MP4PARSE_SUCCESS,
rust_mp4parse_success);
rust_mp4parse_success == 0);
if (rust_mp4parse_success < 0) {
Telemetry::Accumulate(Telemetry::MEDIA_RUST_MP4PARSE_ERROR_CODE,
-rust_mp4parse_success);
}
uint32_t rust_tracks = mp4parse_get_track_count(mRustState.get());
MOZ_LOG(sLog, LogLevel::Info, ("rust parser found %u tracks", rust_tracks));
#endif
size_t tracks = mPrivate->mMetadataExtractor->countTracks();
uint32_t total = 0;
@ -176,7 +180,7 @@ MP4Metadata::GetNumberTracks(mozilla::TrackInfo::TrackType aType) const
uint32_t rust_total = 0;
const char* rust_track_type = nullptr;
if (rust_mp4parse_success && rust_tracks > 0) {
for (int32_t i = 0; i < rust_tracks; ++i) {
for (uint32_t i = 0; i < rust_tracks; ++i) {
mp4parse_track_info track_info;
int32_t r = mp4parse_get_track_info(mRustState.get(), i, &track_info);
switch (aType) {
@ -197,7 +201,6 @@ MP4Metadata::GetNumberTracks(mozilla::TrackInfo::TrackType aType) const
}
}
}
static LazyLogModule sLog("MP4Metadata");
MOZ_LOG(sLog, LogLevel::Info, ("%s tracks found: stagefright=%u rust=%u",
rust_track_type, total, rust_total));
switch (aType) {

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,8 @@
diff --git a/media/libstagefright/binding/byteorder/mod.rs b/media/libstagefright/binding/byteorder/mod.rs
index 59ba692..9d2d1d5 100644
index 7eea1e3..8a108cf 100644
--- a/media/libstagefright/binding/byteorder/mod.rs
+++ b/media/libstagefright/binding/byteorder/mod.rs
@@ -36,16 +36,16 @@ assert_eq!(wtr, vec![5, 2, 0, 3]);
@@ -36,7 +36,6 @@ assert_eq!(wtr, vec![5, 2, 0, 3]);
```
*/
@ -10,12 +10,14 @@ index 59ba692..9d2d1d5 100644
#![doc(html_root_url = "http://burntsushi.net/rustdoc/byteorder")]
#![deny(missing_docs)]
@@ -45,10 +44,11 @@ use std::mem::transmute;
use std::ptr::copy_nonoverlapping;
use std::mem::transmute;
#[cfg(not(feature = "no-std"))]
-pub use new::{ReadBytesExt, WriteBytesExt, Error, Result};
+pub use byteorder::new::{ReadBytesExt, WriteBytesExt, Error, Result};
#[cfg(not(feature = "no-std"))]
-mod new;
+// Re-export new so gecko can build us as a mod intead of a crate.
+pub mod new;
@ -23,7 +25,7 @@ index 59ba692..9d2d1d5 100644
#[inline]
fn extend_sign(val: u64, nbytes: usize) -> i64 {
diff --git a/media/libstagefright/binding/byteorder/new.rs b/media/libstagefright/binding/byteorder/new.rs
index bbef0cd..a2e5393 100644
index 54ee6a7..4efcbc3 100644
--- a/media/libstagefright/binding/byteorder/new.rs
+++ b/media/libstagefright/binding/byteorder/new.rs
@@ -3,7 +3,7 @@ use std::fmt;

View File

@ -41,18 +41,48 @@ assert_eq!(wtr, vec![5, 2, 0, 3]);
#![deny(missing_docs)]
use std::mem::transmute;
use std::ptr::copy_nonoverlapping;
#[cfg(not(feature = "no-std"))]
pub use byteorder::new::{ReadBytesExt, WriteBytesExt, Error, Result};
#[cfg(not(feature = "no-std"))]
// Re-export new so gecko can build us as a mod intead of a crate.
pub mod new;
#[inline]
fn extend_sign(val: u64, nbytes: usize) -> i64 {
let shift = (8 - nbytes) * 8;
let shift = (8 - nbytes) * 8;
(val << shift) as i64 >> shift
}
#[inline]
fn unextend_sign(val: i64, nbytes: usize) -> u64 {
let shift = (8 - nbytes) * 8;
(val << shift) as u64 >> shift
}
#[inline]
fn pack_size(n: u64) -> usize {
if n < 1 << 8 {
1
} else if n < 1 << 16 {
2
} else if n < 1 << 24 {
3
} else if n < 1 << 32 {
4
} else if n < 1 << 40 {
5
} else if n < 1 << 48 {
6
} else if n < 1 << 56 {
7
} else {
8
}
}
/// ByteOrder describes types that can serialize integers as bytes.
///
/// Note that `Self` does not appear anywhere in this trait's definition!
@ -120,6 +150,12 @@ pub trait ByteOrder {
/// Panics when `buf.len() < 8`.
fn write_u64(buf: &mut [u8], n: u64);
/// Writes an unsigned integer `n` to `buf` using only `nbytes`.
///
/// If `n` is not representable in `nbytes`, or if `nbytes` is `> 8`, then
/// this method panics.
fn write_uint(buf: &mut [u8], n: u64, nbytes: usize);
/// Reads a signed 16 bit integer from `buf`.
///
/// Panics when `buf.len() < 2`.
@ -193,6 +229,15 @@ pub trait ByteOrder {
Self::write_u64(buf, n as u64)
}
/// Writes a signed integer `n` to `buf` using only `nbytes`.
///
/// If `n` is not representable in `nbytes`, or if `nbytes` is `> 8`, then
/// this method panics.
#[inline]
fn write_int(buf: &mut [u8], n: i64, nbytes: usize) {
Self::write_uint(buf, unextend_sign(n, nbytes), nbytes)
}
/// Writes a IEEE754 single-precision (4 bytes) floating point number.
///
/// Panics when `buf.len() < 4`.
@ -238,41 +283,16 @@ pub type NativeEndian = BigEndian;
macro_rules! read_num_bytes {
($ty:ty, $size:expr, $src:expr, $which:ident) => ({
assert!($src.len() >= $size); // critical for memory safety!
assert!($size <= $src.len());
unsafe {
(*($src.as_ptr() as *const $ty)).$which()
}
});
($ty:ty, $size:expr, le $bytes:expr, $src:expr, $which:ident) => ({
use std::ptr::copy_nonoverlapping;
assert!($bytes > 0 && $bytes < 9 && $bytes <= $src.len());
let mut out = [0u8; $size];
let ptr_out = out.as_mut_ptr();
unsafe {
copy_nonoverlapping($src.as_ptr(), ptr_out, $bytes);
(*(ptr_out as *const $ty)).$which()
}
});
($ty:ty, $size:expr, be $bytes:expr, $src:expr, $which:ident) => ({
use std::ptr::copy_nonoverlapping;
assert!($bytes > 0 && $bytes < 9 && $bytes <= $src.len());
let mut out = [0u8; $size];
let ptr_out = out.as_mut_ptr();
unsafe {
copy_nonoverlapping($src.as_ptr(),
ptr_out.offset((8 - $bytes) as isize), $bytes);
(*(ptr_out as *const $ty)).$which()
}
});
}
macro_rules! write_num_bytes {
($ty:ty, $size:expr, $n:expr, $dst:expr, $which:ident) => ({
use std::ptr::copy_nonoverlapping;
assert!($dst.len() >= $size); // critical for memory safety!
assert!($size <= $dst.len());
unsafe {
// N.B. https://github.com/rust-lang/rust/issues/22776
let bytes = transmute::<_, [u8; $size]>($n.$which());
@ -299,7 +319,14 @@ impl ByteOrder for BigEndian {
#[inline]
fn read_uint(buf: &[u8], nbytes: usize) -> u64 {
read_num_bytes!(u64, 8, be nbytes, buf, to_be)
assert!(1 <= nbytes && nbytes <= 8 && nbytes <= buf.len());
let mut out = [0u8; 8];
let ptr_out = out.as_mut_ptr();
unsafe {
copy_nonoverlapping(
buf.as_ptr(), ptr_out.offset((8 - nbytes) as isize), nbytes);
(*(ptr_out as *const u64)).to_be()
}
}
#[inline]
@ -316,6 +343,19 @@ impl ByteOrder for BigEndian {
fn write_u64(buf: &mut [u8], n: u64) {
write_num_bytes!(u64, 8, n, buf, to_be);
}
#[inline]
fn write_uint(buf: &mut [u8], n: u64, nbytes: usize) {
assert!(pack_size(n) <= nbytes && nbytes <= 8);
assert!(nbytes <= buf.len());
unsafe {
let bytes: [u8; 8] = transmute(n.to_be());
copy_nonoverlapping(
bytes.as_ptr().offset((8 - nbytes) as isize),
buf.as_mut_ptr(),
nbytes);
}
}
}
impl ByteOrder for LittleEndian {
@ -336,7 +376,13 @@ impl ByteOrder for LittleEndian {
#[inline]
fn read_uint(buf: &[u8], nbytes: usize) -> u64 {
read_num_bytes!(u64, 8, le nbytes, buf, to_le)
assert!(1 <= nbytes && nbytes <= 8 && nbytes <= buf.len());
let mut out = [0u8; 8];
let ptr_out = out.as_mut_ptr();
unsafe {
copy_nonoverlapping(buf.as_ptr(), ptr_out, nbytes);
(*(ptr_out as *const u64)).to_le()
}
}
#[inline]
@ -353,6 +399,16 @@ impl ByteOrder for LittleEndian {
fn write_u64(buf: &mut [u8], n: u64) {
write_num_bytes!(u64, 8, n, buf, to_le);
}
#[inline]
fn write_uint(buf: &mut [u8], n: u64, nbytes: usize) {
assert!(pack_size(n as u64) <= nbytes && nbytes <= 8);
assert!(nbytes <= buf.len());
unsafe {
let bytes: [u8; 8] = transmute(n.to_le());
copy_nonoverlapping(bytes.as_ptr(), buf.as_mut_ptr(), nbytes);
}
}
}
#[cfg(test)]
@ -386,8 +442,8 @@ mod test {
let max = ($max - 1) >> (8 * (8 - $bytes));
fn prop(n: $ty_int) -> bool {
let mut buf = [0; 8];
BigEndian::$write(&mut buf, n);
n == BigEndian::$read(&mut buf[8 - $bytes..], $bytes)
BigEndian::$write(&mut buf, n, $bytes);
n == BigEndian::$read(&mut buf[..$bytes], $bytes)
}
qc_sized(prop as fn($ty_int) -> bool, max);
}
@ -397,7 +453,7 @@ mod test {
let max = ($max - 1) >> (8 * (8 - $bytes));
fn prop(n: $ty_int) -> bool {
let mut buf = [0; 8];
LittleEndian::$write(&mut buf, n);
LittleEndian::$write(&mut buf, n, $bytes);
n == LittleEndian::$read(&mut buf[..$bytes], $bytes)
}
qc_sized(prop as fn($ty_int) -> bool, max);
@ -408,7 +464,7 @@ mod test {
let max = ($max - 1) >> (8 * (8 - $bytes));
fn prop(n: $ty_int) -> bool {
let mut buf = [0; 8];
NativeEndian::$write(&mut buf, n);
NativeEndian::$write(&mut buf, n, $bytes);
n == NativeEndian::$read(&mut buf[..$bytes], $bytes)
}
qc_sized(prop as fn($ty_int) -> bool, max);
@ -467,23 +523,23 @@ mod test {
qc_byte_order!(prop_f32, f32, ::std::u64::MAX as u64, read_f32, write_f32);
qc_byte_order!(prop_f64, f64, ::std::i64::MAX as u64, read_f64, write_f64);
qc_byte_order!(prop_uint_1, u64, super::U64_MAX, 1, read_uint, write_u64);
qc_byte_order!(prop_uint_2, u64, super::U64_MAX, 2, read_uint, write_u64);
qc_byte_order!(prop_uint_3, u64, super::U64_MAX, 3, read_uint, write_u64);
qc_byte_order!(prop_uint_4, u64, super::U64_MAX, 4, read_uint, write_u64);
qc_byte_order!(prop_uint_5, u64, super::U64_MAX, 5, read_uint, write_u64);
qc_byte_order!(prop_uint_6, u64, super::U64_MAX, 6, read_uint, write_u64);
qc_byte_order!(prop_uint_7, u64, super::U64_MAX, 7, read_uint, write_u64);
qc_byte_order!(prop_uint_8, u64, super::U64_MAX, 8, read_uint, write_u64);
qc_byte_order!(prop_uint_1, u64, super::U64_MAX, 1, read_uint, write_uint);
qc_byte_order!(prop_uint_2, u64, super::U64_MAX, 2, read_uint, write_uint);
qc_byte_order!(prop_uint_3, u64, super::U64_MAX, 3, read_uint, write_uint);
qc_byte_order!(prop_uint_4, u64, super::U64_MAX, 4, read_uint, write_uint);
qc_byte_order!(prop_uint_5, u64, super::U64_MAX, 5, read_uint, write_uint);
qc_byte_order!(prop_uint_6, u64, super::U64_MAX, 6, read_uint, write_uint);
qc_byte_order!(prop_uint_7, u64, super::U64_MAX, 7, read_uint, write_uint);
qc_byte_order!(prop_uint_8, u64, super::U64_MAX, 8, read_uint, write_uint);
qc_byte_order!(prop_int_1, i64, super::I64_MAX, 1, read_int, write_i64);
qc_byte_order!(prop_int_2, i64, super::I64_MAX, 2, read_int, write_i64);
qc_byte_order!(prop_int_3, i64, super::I64_MAX, 3, read_int, write_i64);
qc_byte_order!(prop_int_4, i64, super::I64_MAX, 4, read_int, write_i64);
qc_byte_order!(prop_int_5, i64, super::I64_MAX, 5, read_int, write_i64);
qc_byte_order!(prop_int_6, i64, super::I64_MAX, 6, read_int, write_i64);
qc_byte_order!(prop_int_7, i64, super::I64_MAX, 7, read_int, write_i64);
qc_byte_order!(prop_int_8, i64, super::I64_MAX, 8, read_int, write_i64);
qc_byte_order!(prop_int_1, i64, super::I64_MAX, 1, read_int, write_int);
qc_byte_order!(prop_int_2, i64, super::I64_MAX, 2, read_int, write_int);
qc_byte_order!(prop_int_3, i64, super::I64_MAX, 3, read_int, write_int);
qc_byte_order!(prop_int_4, i64, super::I64_MAX, 4, read_int, write_int);
qc_byte_order!(prop_int_5, i64, super::I64_MAX, 5, read_int, write_int);
qc_byte_order!(prop_int_6, i64, super::I64_MAX, 6, read_int, write_int);
qc_byte_order!(prop_int_7, i64, super::I64_MAX, 7, read_int, write_int);
qc_byte_order!(prop_int_8, i64, super::I64_MAX, 8, read_int, write_int);
macro_rules! qc_bytes_ext {
($name:ident, $ty_int:ident, $max:expr,

View File

@ -295,6 +295,36 @@ pub trait WriteBytesExt: io::Write {
write_all(self, &buf)
}
/// Writes an unsigned n-bytes integer to the underlying writer.
///
/// If the given integer is not representable in the given number of bytes,
/// this method panics. If `nbytes > 8`, this method panics.
#[inline]
fn write_uint<T: ByteOrder>(
&mut self,
n: u64,
nbytes: usize,
) -> Result<()> {
let mut buf = [0; 8];
T::write_uint(&mut buf, n, nbytes);
write_all(self, &buf[0..nbytes])
}
/// Writes a signed n-bytes integer to the underlying writer.
///
/// If the given integer is not representable in the given number of bytes,
/// this method panics. If `nbytes > 8`, this method panics.
#[inline]
fn write_int<T: ByteOrder>(
&mut self,
n: i64,
nbytes: usize,
) -> Result<()> {
let mut buf = [0; 8];
T::write_int(&mut buf, n, nbytes);
write_all(self, &buf[0..nbytes])
}
/// Writes a IEEE754 single-precision (4 bytes) floating point number to
/// the underlying writer.
#[inline]

View File

@ -34,16 +34,30 @@ use media_time_to_ms;
use track_time_to_ms;
use SampleEntry;
// These constants *must* match those in include/mp4parse.h.
/// Map Error to int32 return codes.
const MP4PARSE_OK: i32 = 0;
const MP4PARSE_ERROR_BADARG: i32 = -1;
const MP4PARSE_ERROR_INVALID: i32 = -2;
const MP4PARSE_ERROR_UNSUPPORTED: i32 = -3;
const MP4PARSE_ERROR_EOF: i32 = -4;
const MP4PARSE_ASSERT: i32 = -5;
const MP4PARSE_ERROR_IO: i32 = -6;
/// Map TrackType to uint32 constants.
const TRACK_TYPE_H264: u32 = 0;
const TRACK_TYPE_AAC: u32 = 1;
const TRACK_TYPE_AAC: u32 = 1;
// These structs *must* match those declared in include/mp4parse.h.
#[repr(C)]
pub struct TrackInfo {
track_type: u32,
track_id: u32,
duration: u64,
media_time: i64, // wants to be u64? understand how elst adjustment works
// TODO(kinetik): include crypto guff
}
#[repr(C)]
@ -51,8 +65,11 @@ pub struct TrackAudioInfo {
channels: u16,
bit_depth: u16,
sample_rate: u32,
// profile: i32,
// extended_profile: i32, // check types
// TODO(kinetik):
// int32_t profile;
// int32_t extended_profile; // check types
// extra_data
// codec_specific_config
}
#[repr(C)]
@ -61,8 +78,13 @@ pub struct TrackVideoInfo {
display_height: u32,
image_width: u16,
image_height: u16,
// TODO(kinetik):
// extra_data
// codec_specific_config
}
// C API wrapper functions.
/// Allocate an opaque rust-side parser context.
#[no_mangle]
pub extern "C" fn mp4parse_new() -> *mut MediaContext {
@ -78,7 +100,7 @@ pub unsafe extern "C" fn mp4parse_free(context: *mut MediaContext) {
}
/// Feed a buffer through `read_mp4()` with the given rust-side
/// parser context, returning the number of detected tracks.
/// parser context, returning success or an error code.
///
/// This is safe to call with NULL arguments but will crash
/// if given invalid pointers, as is usual for C.
@ -86,7 +108,7 @@ pub unsafe extern "C" fn mp4parse_free(context: *mut MediaContext) {
pub unsafe extern "C" fn mp4parse_read(context: *mut MediaContext, buffer: *const u8, size: usize) -> i32 {
// Validate arguments from C.
if context.is_null() || buffer.is_null() || size < 8 {
return -1;
return MP4PARSE_ERROR_BADARG;
}
let mut context: &mut MediaContext = &mut *context;
@ -96,24 +118,38 @@ pub unsafe extern "C" fn mp4parse_read(context: *mut MediaContext, buffer: *cons
let mut c = Cursor::new(b);
// Parse in a subthread to catch any panics.
let task = std::thread::spawn(move || {
match read_mp4(&mut c, &mut context) {
Ok(_) => {},
Err(Error::UnexpectedEOF) => {},
Err(e) => { panic!(e); },
}
// Make sure the track count fits in an i32 so we can use
// negative values for failure.
assert!(context.tracks.len() < i32::max_value() as usize);
context.tracks.len() as i32
});
task.join().unwrap_or(-1)
let task = std::thread::spawn(move || read_mp4(&mut c, &mut context));
// The task's JoinHandle will return an error result if the
// thread panicked, and will wrap the closure's return'd
// result in an Ok(..) otherwise, meaning we could see
// Ok(Err(Error::..)) here. So map thread failures back
// to an mp4parse::Error before converting to a C return value.
match task.join().or(Err(Error::AssertCaught)) {
Ok(_) => MP4PARSE_OK,
Err(Error::InvalidData) => MP4PARSE_ERROR_INVALID,
Err(Error::Unsupported) => MP4PARSE_ERROR_UNSUPPORTED,
Err(Error::UnexpectedEOF) => MP4PARSE_ERROR_EOF,
Err(Error::AssertCaught) => MP4PARSE_ASSERT,
Err(Error::Io(_)) => MP4PARSE_ERROR_IO,
}
}
/// Return the number of tracks parsed by previous `read_mp4()` calls.
#[no_mangle]
pub unsafe extern "C" fn mp4parse_get_track_count(context: *const MediaContext) -> u32 {
// Validate argument from C.
assert!(!context.is_null());
let context = &*context;
// Make sure the track count fits in a u32.
assert!(context.tracks.len() < u32::max_value() as usize);
context.tracks.len() as u32
}
#[no_mangle]
pub unsafe extern "C" fn mp4parse_get_track_info(context: *mut MediaContext, track: i32, info: *mut TrackInfo) -> i32 {
if context.is_null() || track < 0 || info.is_null() {
return -1;
pub unsafe extern "C" fn mp4parse_get_track_info(context: *mut MediaContext, track: u32, info: *mut TrackInfo) -> i32 {
if context.is_null() || info.is_null() {
return MP4PARSE_ERROR_BADARG;
}
let context: &mut MediaContext = &mut *context;
@ -121,13 +157,13 @@ pub unsafe extern "C" fn mp4parse_get_track_info(context: *mut MediaContext, tra
let info: &mut TrackInfo = &mut *info;
if track_index >= context.tracks.len() {
return -1;
return MP4PARSE_ERROR_BADARG;
}
info.track_type = match context.tracks[track_index].track_type {
TrackType::Video => TRACK_TYPE_H264,
TrackType::Audio => TRACK_TYPE_AAC,
TrackType::Unknown => return -1,
TrackType::Unknown => return MP4PARSE_ERROR_UNSUPPORTED,
};
// Maybe context & track should just have a single simple is_valid() instead?
@ -135,8 +171,8 @@ pub unsafe extern "C" fn mp4parse_get_track_info(context: *mut MediaContext, tra
context.tracks[track_index].timescale.is_none() ||
context.tracks[track_index].duration.is_none() ||
context.tracks[track_index].track_id.is_none() {
return -1;
}
return MP4PARSE_ERROR_INVALID;
}
std::thread::spawn(move || {
let track = &context.tracks[track_index];
@ -152,98 +188,102 @@ pub unsafe extern "C" fn mp4parse_get_track_info(context: *mut MediaContext, tra
};
info.duration = track_time_to_ms(track.duration.unwrap(), track.timescale.unwrap());
info.track_id = track.track_id.unwrap();
0
}).join().unwrap_or(-1)
MP4PARSE_OK
}).join().unwrap_or(MP4PARSE_ERROR_INVALID)
}
#[no_mangle]
pub unsafe extern "C" fn mp4parse_get_track_audio_info(context: *mut MediaContext, track: i32, info: *mut TrackAudioInfo) -> i32 {
if context.is_null() || track < 0 || info.is_null() {
return -1;
pub unsafe extern "C" fn mp4parse_get_track_audio_info(context: *mut MediaContext, track: u32, info: *mut TrackAudioInfo) -> i32 {
if context.is_null() || info.is_null() {
return MP4PARSE_ERROR_BADARG;
}
let context: &mut MediaContext = &mut *context;
if track as usize >= context.tracks.len() {
return -1;
return MP4PARSE_ERROR_BADARG;
}
let track = &context.tracks[track as usize];
match track.track_type {
TrackType::Audio => {},
_ => return -1,
TrackType::Audio => {}
_ => return MP4PARSE_ERROR_INVALID,
};
let audio = match track.data {
Some(ref data) => data,
None => return -1,
None => return MP4PARSE_ERROR_INVALID,
};
let audio = match audio {
&SampleEntry::Audio(ref x) => x,
_ => return -1,
let audio = match *audio {
SampleEntry::Audio(ref x) => x,
_ => return MP4PARSE_ERROR_INVALID,
};
(*info).channels = audio.channelcount;
(*info).bit_depth = audio.samplesize;
(*info).sample_rate = audio.samplerate >> 16; // 16.16 fixed point
0
MP4PARSE_OK
}
#[no_mangle]
pub unsafe extern "C" fn mp4parse_get_track_video_info(context: *mut MediaContext, track: i32, info: *mut TrackVideoInfo) -> i32 {
if context.is_null() || track < 0 || info.is_null() {
return -1;
pub unsafe extern "C" fn mp4parse_get_track_video_info(context: *mut MediaContext, track: u32, info: *mut TrackVideoInfo) -> i32 {
if context.is_null() || info.is_null() {
return MP4PARSE_ERROR_BADARG;
}
let context: &mut MediaContext = &mut *context;
if track as usize >= context.tracks.len() {
return -1;
return MP4PARSE_ERROR_BADARG;
}
let track = &context.tracks[track as usize];
match track.track_type {
TrackType::Video => {},
_ => return -1,
TrackType::Video => {}
_ => return MP4PARSE_ERROR_INVALID,
};
let video = match track.data {
Some(ref data) => data,
None => return -1,
None => return MP4PARSE_ERROR_INVALID,
};
let video = match video {
&SampleEntry::Video(ref x) => x,
_ => return -1,
let video = match *video {
SampleEntry::Video(ref x) => x,
_ => return MP4PARSE_ERROR_INVALID,
};
if let Some(ref tkhd) = track.tkhd {
(*info).display_width = tkhd.width >> 16; // 16.16 fixed point
(*info).display_height = tkhd.height >> 16; // 16.16 fixed point
} else {
return -1
return MP4PARSE_ERROR_INVALID;
}
(*info).image_width = video.width;
(*info).image_width = video.height;
0
MP4PARSE_OK
}
#[test]
fn new_context() {
let context = mp4parse_new();
assert!(!context.is_null());
unsafe { mp4parse_free(context); }
unsafe {
mp4parse_free(context);
}
}
#[test]
#[should_panic(expected = "assertion failed")]
fn free_null_context() {
unsafe { mp4parse_free(std::ptr::null_mut()); }
unsafe {
mp4parse_free(std::ptr::null_mut());
}
}
#[test]
@ -257,16 +297,21 @@ fn arg_validation() {
let buffer = vec![0u8; 8];
unsafe {
assert_eq!(-1, mp4parse_read(null_context, null_buffer, 0));
assert_eq!(-1, mp4parse_read(context, null_buffer, 0));
assert_eq!(MP4PARSE_ERROR_BADARG,
mp4parse_read(null_context, null_buffer, 0));
assert_eq!(MP4PARSE_ERROR_BADARG,
mp4parse_read(context, null_buffer, 0));
}
for size in 0..buffer.len() {
println!("testing buffer length {}", size);
unsafe {
assert_eq!(-1, mp4parse_read(context, buffer.as_ptr(), size));
assert_eq!(MP4PARSE_ERROR_BADARG,
mp4parse_read(context, buffer.as_ptr(), size));
}
}
unsafe { mp4parse_free(context); }
unsafe {
mp4parse_free(context);
}
}

View File

@ -11,6 +11,14 @@ extern "C" {
struct mp4parse_state;
#define MP4PARSE_OK 0
#define MP4PARSE_ERROR_BADARG -1 // Argument validation failure
#define MP4PARSE_ERROR_INVALID -2 // Error::InvalidData
#define MP4PARSE_ERROR_UNSUPPORTED -3 // Error::Unsupported
#define MP4PARSE_ERROR_EOF -4 // Error::UnexpectedEOF
#define MP4PARSE_ASSERT -5 // Error::AssertCaught
#define MP4PARSE_ERROR_IO -6 // Error::Io(_)
#define MP4PARSE_TRACK_TYPE_H264 0 // "video/avc"
#define MP4PARSE_TRACK_TYPE_AAC 1 // "audio/mp4a-latm"
@ -18,12 +26,6 @@ struct mp4parse_track_audio_info {
uint16_t channels;
uint16_t bit_depth;
uint32_t sample_rate;
//int32_t profile;
//int32_t extended_profile; // check types
// TODO(kinetik):
// extra_data
// codec_specific_config
};
struct mp4parse_track_video_info {
@ -31,10 +33,6 @@ struct mp4parse_track_video_info {
uint32_t display_height;
uint16_t image_width;
uint16_t image_height;
// TODO(kinetik):
// extra_data
// codec_specific_config
};
struct mp4parse_track_info {
@ -42,7 +40,6 @@ struct mp4parse_track_info {
uint32_t track_id;
uint64_t duration;
int64_t media_time;
// TODO(kinetik): crypto guff
};
struct mp4parse_state* mp4parse_new(void);
@ -50,11 +47,13 @@ void mp4parse_free(struct mp4parse_state* state);
int32_t mp4parse_read(struct mp4parse_state* state, uint8_t *buffer, size_t size);
int32_t mp4parse_get_track_info(struct mp4parse_state* state, int32_t track, struct mp4parse_track_info* track_info);
uint32_t mp4parse_get_track_count(struct mp4parse_state* state);
int32_t mp4parse_get_track_audio_info(struct mp4parse_state* state, int32_t track, struct mp4parse_track_audio_info* track_info);
int32_t mp4parse_get_track_info(struct mp4parse_state* state, uint32_t track, struct mp4parse_track_info* track_info);
int32_t mp4parse_get_track_video_info(struct mp4parse_state* state, int32_t track, struct mp4parse_track_video_info* track_info);
int32_t mp4parse_get_track_audio_info(struct mp4parse_state* state, uint32_t track, struct mp4parse_track_audio_info* track_info);
int32_t mp4parse_get_track_video_info(struct mp4parse_state* state, uint32_t track, struct mp4parse_track_video_info* track_info);
#ifdef __cplusplus
}

View File

@ -2,7 +2,7 @@
# Script to update mp4parse-rust sources to latest upstream
# Default version.
VER=v0.1.4
VER=v0.1.6
# Accept version or commit from the command line.
if test -n "$1"; then
@ -23,7 +23,7 @@ cp _upstream/mp4parse/include/mp4parse.h include/
git clone https://github.com/BurntSushi/byteorder _upstream/byteorder
pushd _upstream/byteorder
git checkout 0.3.13
git checkout 0.4.2
popd
cp _upstream/byteorder/src/lib.rs byteorder/mod.rs
cp _upstream/byteorder/src/new.rs byteorder/new.rs

View File

@ -22,27 +22,27 @@ TEST(rust, MP4MetadataEmpty)
ASSERT_NE(context, nullptr);
rv = mp4parse_read(nullptr, nullptr, 0);
EXPECT_EQ(rv, -1);
EXPECT_EQ(rv, MP4PARSE_ERROR_BADARG);
rv = mp4parse_read(context, nullptr, 0);
EXPECT_EQ(rv, -1);
EXPECT_EQ(rv, MP4PARSE_ERROR_BADARG);
size_t len = 4097;
rv = mp4parse_read(nullptr, nullptr, len);
EXPECT_EQ(rv, -1);
EXPECT_EQ(rv, MP4PARSE_ERROR_BADARG);
rv = mp4parse_read(context, nullptr, len);
EXPECT_EQ(rv, -1);
EXPECT_EQ(rv, MP4PARSE_ERROR_BADARG);
std::vector<uint8_t> buf;
rv = mp4parse_read(nullptr, buf.data(), buf.size());
EXPECT_EQ(rv, -1);
EXPECT_EQ(rv, MP4PARSE_ERROR_BADARG);
rv = mp4parse_read(context, buf.data(), buf.size());
EXPECT_EQ(rv, -1);
EXPECT_EQ(rv, MP4PARSE_ERROR_BADARG);
buf.reserve(len);
rv = mp4parse_read(nullptr, buf.data(), buf.size());
EXPECT_EQ(rv, -1);
EXPECT_EQ(rv, MP4PARSE_ERROR_BADARG);
rv = mp4parse_read(context, buf.data(), buf.size());
EXPECT_EQ(rv, -1);
EXPECT_EQ(rv, MP4PARSE_ERROR_BADARG);
mp4parse_free(context);
}
@ -62,7 +62,10 @@ TEST(rust, MP4Metadata)
ASSERT_NE(context, nullptr);
int32_t rv = mp4parse_read(context, buf.data(), buf.size());
EXPECT_EQ(rv, 2);
EXPECT_EQ(rv, MP4PARSE_OK);
uint32_t tracks = mp4parse_get_track_count(context);
EXPECT_EQ(tracks, 2U);
mp4parse_free(context);
}

View File

@ -1144,6 +1144,8 @@ WebrtcVideoConduit::SelectSendResolution(unsigned short width,
bool changed = false;
if (mSendingWidth != width || mSendingHeight != height)
{
CSFLogDebug(logTag, "%s: resolution changing to %ux%u (from %ux%u)",
__FUNCTION__, width, height, mSendingWidth, mSendingHeight);
// This will avoid us continually retrying this operation if it fails.
// If the resolution changes, we'll try again. In the meantime, we'll
// keep using the old size in the encoder.
@ -1155,6 +1157,8 @@ WebrtcVideoConduit::SelectSendResolution(unsigned short width,
// uses mSendingWidth/Height
unsigned int framerate = SelectSendFrameRate(mSendingFramerate);
if (mSendingFramerate != framerate) {
CSFLogDebug(logTag, "%s: framerate changing to %u (from %u)",
__FUNCTION__, framerate, mSendingFramerate);
mSendingFramerate = framerate;
changed = true;
}
@ -1221,6 +1225,10 @@ WebrtcVideoConduit::ReconfigureSendCodec(unsigned short width,
CSFLogError(logTag, "%s: GetSendCodec failed, err %d", __FUNCTION__, err);
return NS_ERROR_FAILURE;
}
CSFLogDebug(logTag,
"%s: Requesting resolution change to %ux%u (from %ux%u)",
__FUNCTION__, width, height, vie_codec.width, vie_codec.height);
// Likely spurious unless there was some error, but rarely checked
if (vie_codec.width != width || vie_codec.height != height ||
vie_codec.maxFramerate != mSendingFramerate)
@ -1399,6 +1407,8 @@ WebrtcVideoConduit::SendVideoFrame(webrtc::I420VideoFrame& frame)
return kMediaConduitNoError;
}
if (frame.width() != mLastWidth || frame.height() != mLastHeight) {
CSFLogDebug(logTag, "%s: call SelectSendResolution with %ux%u",
__FUNCTION__, frame.width(), frame.height());
if (SelectSendResolution(frame.width(), frame.height(), &frame)) {
// SelectSendResolution took ownership of the data in i420_frame.
// Submit the frame after reconfig is done

View File

@ -1,308 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ClosingService.h"
#include "nsIOService.h"
#ifdef MOZ_NUWA_PROCESS
#include "ipc/Nuwa.h"
#endif
class ClosingLayerSecret
{
public:
explicit ClosingLayerSecret(mozilla::net::ClosingService *aClosingService)
: mClosingService(aClosingService)
{
}
~ClosingLayerSecret()
{
mClosingService = nullptr;
}
RefPtr<mozilla::net::ClosingService> mClosingService;
};
namespace mozilla {
namespace net {
static PRIOMethods sTcpUdpPRCloseLayerMethods;
static PRIOMethods *sTcpUdpPRCloseLayerMethodsPtr = nullptr;
static PRDescIdentity sTcpUdpPRCloseLayerId;
static PRStatus
TcpUdpPRCloseLayerClose(PRFileDesc *aFd)
{
if (!aFd) {
return PR_FAILURE;
}
PRFileDesc* layer = PR_PopIOLayer(aFd, PR_TOP_IO_LAYER);
MOZ_RELEASE_ASSERT(layer &&
layer->identity == sTcpUdpPRCloseLayerId,
"Closing Layer not on top of stack");
ClosingLayerSecret *closingLayerSecret =
reinterpret_cast<ClosingLayerSecret *>(layer->secret);
PRStatus status = PR_SUCCESS;
if (aFd) {
// If this is called during shutdown do not call ..method->close(fd) and
// let it leak.
if (gIOService->IsNetTearingDown()) {
// If the ClosingService layer is the first layer above PR_NSPR_IO_LAYER
// we are not going to leak anything, but the PR_Close will not be called.
PR_Free(aFd);
} else if (closingLayerSecret->mClosingService) {
closingLayerSecret->mClosingService->PostRequest(aFd);
} else {
// Socket is created before closing service has been started or there was
// a problem with starting it.
PR_Close(aFd);
}
}
layer->secret = nullptr;
layer->dtor(layer);
delete closingLayerSecret;
return status;
}
ClosingService* ClosingService::sInstance = nullptr;
ClosingService::ClosingService()
: mShutdown(false)
, mMonitor("ClosingService.mMonitor")
{
MOZ_ASSERT(!sInstance,
"multiple ClosingService instances!");
}
// static
void
ClosingService::Start()
{
if (!sTcpUdpPRCloseLayerMethodsPtr) {
sTcpUdpPRCloseLayerId = PR_GetUniqueIdentity("TCP and UDP PRClose layer");
PR_ASSERT(PR_INVALID_IO_LAYER != sTcpUdpPRCloseLayerId);
sTcpUdpPRCloseLayerMethods = *PR_GetDefaultIOMethods();
sTcpUdpPRCloseLayerMethods.close = TcpUdpPRCloseLayerClose;
sTcpUdpPRCloseLayerMethodsPtr = &sTcpUdpPRCloseLayerMethods;
}
if (!sInstance) {
ClosingService* service = new ClosingService();
if (NS_SUCCEEDED(service->StartInternal())) {
NS_ADDREF(service);
sInstance = service;
} else {
delete service;
}
}
}
nsresult
ClosingService::StartInternal()
{
mThread = PR_CreateThread(PR_USER_THREAD, ThreadFunc, this,
PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
PR_JOINABLE_THREAD, 32 * 1024);
if (!mThread) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
// static
nsresult
ClosingService::AttachIOLayer(PRFileDesc *aFd)
{
// We are going to remove ClosingService soon.
// This change is going to turn it off, so ClosingService is not used.
// Bug 1238010.
return NS_OK;
if (!sTcpUdpPRCloseLayerMethodsPtr) {
return NS_OK;
}
PRFileDesc * layer;
PRStatus status;
layer = PR_CreateIOLayerStub(sTcpUdpPRCloseLayerId,
sTcpUdpPRCloseLayerMethodsPtr);
if (!layer) {
return NS_OK;
}
ClosingLayerSecret *secret = new ClosingLayerSecret(sInstance);
layer->secret = reinterpret_cast<PRFilePrivate *>(secret);
status = PR_PushIOLayer(aFd, PR_NSPR_IO_LAYER, layer);
if (status == PR_FAILURE) {
delete secret;
PR_DELETE(layer);
}
return NS_OK;
}
void
ClosingService::PostRequest(PRFileDesc *aFd)
{
mozilla::MonitorAutoLock mon(mMonitor);
// Check if shutdown is called.
if (mShutdown) {
// Let the socket leak. We are in shutdown and some PRClose can take a long
// time. To prevent shutdown crash (bug 1152046) do not accept sockets any
// more.
// If the ClosingService layer is the first layer above PR_NSPR_IO_LAYER
// we are not going to leak anything, but PR_Close will not be called.
PR_Free(aFd);
return;
}
mQueue.AppendElement(aFd);
if (mQueue.Length() == 1) {
mon.Notify();
}
}
// static
void
ClosingService::Shutdown()
{
MOZ_ASSERT(NS_IsMainThread());
if (sInstance) {
sInstance->ShutdownInternal();
NS_RELEASE(sInstance);
}
}
void
ClosingService::ShutdownInternal()
{
{
mozilla::MonitorAutoLock mon(mMonitor);
if (mShutdown) {
// This should not happen.
return;
}
mShutdown = true;
// If it is waiting on the empty queue, wake it up.
if (mQueue.Length() == 0) {
mon.Notify();
}
}
if (mThread) {
PR_JoinThread(mThread);
mThread = nullptr;
}
}
void
ClosingService::ThreadFunc()
{
PR_SetCurrentThreadName("Closing Service");
#ifdef MOZ_NUWA_PROCESS
if (IsNuwaProcess()) {
NuwaMarkCurrentThread(nullptr, nullptr);
}
#endif
for (;;) {
PRFileDesc *fd;
{
mozilla::MonitorAutoLock mon(mMonitor);
while (!mShutdown && (mQueue.Length() == 0)) {
mon.Wait();
}
if (mShutdown) {
// If we are in shutdown leak the rest of the sockets.
for (uint32_t i = 0; i < mQueue.Length(); i++) {
fd = mQueue[i];
// If the ClosingService layer is the first layer above
// PR_NSPR_IO_LAYER we are not going to leak anything, but PR_Close
// will not be called.
PR_Free(fd);
}
mQueue.Clear();
return;
}
fd = mQueue[0];
mQueue.RemoveElementAt(0);
}
// Leave lock before closing socket. It can block for a long time and in
// case we accidentally attach this layer twice this would cause deadlock.
bool tcp = (PR_GetDescType(PR_GetIdentitiesLayer(fd, PR_NSPR_IO_LAYER)) ==
PR_DESC_SOCKET_TCP);
PRIntervalTime closeStarted = PR_IntervalNow();
fd->methods->close(fd);
// Post telemetry.
if (tcp) {
SendPRCloseTelemetry(closeStarted,
Telemetry::PRCLOSE_TCP_BLOCKING_TIME_NORMAL,
Telemetry::PRCLOSE_TCP_BLOCKING_TIME_SHUTDOWN,
Telemetry::PRCLOSE_TCP_BLOCKING_TIME_CONNECTIVITY_CHANGE,
Telemetry::PRCLOSE_TCP_BLOCKING_TIME_LINK_CHANGE,
Telemetry::PRCLOSE_TCP_BLOCKING_TIME_OFFLINE);
} else {
SendPRCloseTelemetry(closeStarted,
Telemetry::PRCLOSE_UDP_BLOCKING_TIME_NORMAL,
Telemetry::PRCLOSE_UDP_BLOCKING_TIME_SHUTDOWN,
Telemetry::PRCLOSE_UDP_BLOCKING_TIME_CONNECTIVITY_CHANGE,
Telemetry::PRCLOSE_UDP_BLOCKING_TIME_LINK_CHANGE,
Telemetry::PRCLOSE_UDP_BLOCKING_TIME_OFFLINE);
}
}
}
void
ClosingService::SendPRCloseTelemetry(PRIntervalTime aStart,
mozilla::Telemetry::ID aIDNormal,
mozilla::Telemetry::ID aIDShutdown,
mozilla::Telemetry::ID aIDConnectivityChange,
mozilla::Telemetry::ID aIDLinkChange,
mozilla::Telemetry::ID aIDOffline)
{
PRIntervalTime now = PR_IntervalNow();
if (gIOService->IsNetTearingDown()) {
Telemetry::Accumulate(aIDShutdown,
PR_IntervalToMilliseconds(now - aStart));
} else if (PR_IntervalToSeconds(now - gIOService->LastConnectivityChange())
< 60) {
Telemetry::Accumulate(aIDConnectivityChange,
PR_IntervalToMilliseconds(now - aStart));
} else if (PR_IntervalToSeconds(now - gIOService->LastNetworkLinkChange())
< 60) {
Telemetry::Accumulate(aIDLinkChange,
PR_IntervalToMilliseconds(now - aStart));
} else if (PR_IntervalToSeconds(now - gIOService->LastOfflineStateChange())
< 60) {
Telemetry::Accumulate(aIDOffline,
PR_IntervalToMilliseconds(now - aStart));
} else {
Telemetry::Accumulate(aIDNormal,
PR_IntervalToMilliseconds(now - aStart));
}
}
} //namwspacw mozilla
} //namespace net

View File

@ -1,71 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef ClosingService_h__
#define ClosingService_h__
#include "nsTArray.h"
#include "nspr.h"
#include "mozilla/Telemetry.h"
#include "mozilla/Monitor.h"
//-----------------------------------------------------------------------------
// ClosingService
//-----------------------------------------------------------------------------
// A helper class carrying call to PR_Close on FD to a separate thread -
// closingThread. This may be a workaround for shutdown blocks that are caused
// by serial calls to close on UDP and TCP sockets.
// This service is started by nsIOService and also the class adds itself as an
// observer to "xpcom-shutdown-threads" notification where we join the thread
// and remove reference.
// During worktime of the thread the class is also self-referenced,
// since observer service might throw the reference away sooner than the thread
// is actually done.
namespace mozilla {
namespace net {
class ClosingService final
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ClosingService)
ClosingService();
// Attaching this layer on tcp or udp sockets PRClose will be send to the
// closingThread.
static nsresult AttachIOLayer(PRFileDesc *aFd);
static void Start();
static void Shutdown();
void PostRequest(PRFileDesc *aFd);
private:
~ClosingService() {}
nsresult StartInternal();
void ShutdownInternal();
void ThreadFunc();
static void ThreadFunc(void *aClosure)
{ static_cast<ClosingService*>(aClosure)->ThreadFunc(); }
void SendPRCloseTelemetry(PRIntervalTime aStart,
mozilla::Telemetry::ID aIDNormal,
mozilla::Telemetry::ID aIDShutdown,
mozilla::Telemetry::ID aIDConnectivityChange,
mozilla::Telemetry::ID aIDLinkChange,
mozilla::Telemetry::ID aIDOffline);
static ClosingService* sInstance;
Atomic<bool> mShutdown;
nsTArray<PRFileDesc *> mQueue;
mozilla::Monitor mMonitor;
PRThread *mThread;
};
} // namespace net
} // namespace mozilla
#endif // ClosingService_h_

View File

@ -193,7 +193,6 @@ UNIFIED_SOURCES += [
'CaptivePortalService.cpp',
'ChannelDiverterChild.cpp',
'ChannelDiverterParent.cpp',
'ClosingService.cpp',
'Dashboard.cpp',
'EventTokenBucket.cpp',
'LoadContextInfo.cpp',

View File

@ -49,7 +49,6 @@
#include "mozilla/Telemetry.h"
#include "mozilla/net/DNS.h"
#include "CaptivePortalService.h"
#include "ClosingService.h"
#include "ReferrerPolicy.h"
#include "nsContentSecurityManager.h"
#include "nsHttpHandler.h"
@ -65,7 +64,6 @@
using namespace mozilla;
using mozilla::net::IsNeckoChild;
using mozilla::net::ClosingService;
using mozilla::net::CaptivePortalService;
using mozilla::net::gHttpHandler;
@ -260,10 +258,6 @@ nsIOService::Init()
InitializeNetworkLinkService();
// Start the closing service. Actual PR_Close() will be carried out on
// a separate "closing" thread. Start the closing servicee here since this
// point is executed only once per session.
ClosingService::Start();
SetOffline(false);
return NS_OK;
@ -1077,9 +1071,6 @@ nsIOService::SetOffline(bool offline)
DebugOnly<nsresult> rv = mSocketTransportService->Shutdown();
NS_ASSERTION(NS_SUCCEEDED(rv), "socket transport service shutdown failed");
}
if (mShutdown) {
ClosingService::Shutdown();
}
}
mSettingOffline = false;

View File

@ -138,7 +138,7 @@ private:
private:
bool mOffline;
bool mOfflineForProfileChange;
mozilla::Atomic<bool, mozilla::Relaxed> mOfflineForProfileChange;
bool mManageLinkStatus;
bool mConnectivity;
// If true, the connectivity state will be mirrored by IOService.offline
@ -150,7 +150,7 @@ private:
bool mSettingOffline;
bool mSetOfflineValue;
bool mShutdown;
mozilla::Atomic<bool, mozilla::Relaxed> mShutdown;
nsCOMPtr<nsPISocketTransportService> mSocketTransportService;
nsCOMPtr<nsPIDNSService> mDNSService;

View File

@ -6,6 +6,7 @@
#include "mozilla/Attributes.h"
#include "mozilla/UniquePtrExtensions.h"
#include "mozilla/UniquePtr.h"
#include "nsIIncrementalDownload.h"
#include "nsIRequestObserver.h"
@ -150,7 +151,7 @@ private:
nsCOMPtr<nsIFile> mDest;
nsCOMPtr<nsIChannel> mChannel;
nsCOMPtr<nsITimer> mTimer;
UniquePtr<char[]> mChunk;
mozilla::UniquePtr<char[]> mChunk;
int32_t mChunkLen;
int32_t mChunkSize;
int32_t mInterval;
@ -712,7 +713,7 @@ nsIncrementalDownload::OnStartRequest(nsIRequest *request,
if (diff < int64_t(mChunkSize))
mChunkSize = uint32_t(diff);
mChunk = MakeUniqueFallible<char[]>(mChunkSize);
mChunk = mozilla::MakeUniqueFallible<char[]>(mChunkSize);
if (!mChunk)
rv = NS_ERROR_OUT_OF_MEMORY;

View File

@ -16,7 +16,6 @@
#include "nsProxyInfo.h"
#include "nsNetCID.h"
#include "nsNetUtil.h"
#include "ClosingService.h"
#include "nsAutoPtr.h"
#include "nsCOMPtr.h"
#include "plstr.h"
@ -1325,9 +1324,6 @@ nsSocketTransport::InitiateSocket()
// Attach network activity monitor
mozilla::net::NetworkActivityMonitor::AttachIOLayer(fd);
// Attach closing service.
ClosingService::AttachIOLayer(fd);
PRStatus status;
// Make the socket non-blocking...

View File

@ -16,6 +16,7 @@
#include "nsError.h"
#include "nsNetCID.h"
#include "nsNetUtil.h"
#include "nsIOService.h"
#include "prnetdb.h"
#include "prio.h"
#include "nsNetAddr.h"
@ -29,7 +30,6 @@
#include "nsIDNSRecord.h"
#include "nsIDNSService.h"
#include "nsICancelable.h"
#include "ClosingService.h"
#ifdef MOZ_WIDGET_GONK
#include "NetStatistics.h"
@ -332,6 +332,10 @@ nsUDPSocket::TryAttach()
if (!gSocketTransportService)
return NS_ERROR_FAILURE;
if (gIOService->IsNetTearingDown()) {
return NS_ERROR_FAILURE;
}
//
// find out if it is going to be ok to attach another socket to the STS.
// if not then we have to wait for the STS to tell us that it is ok.
@ -581,6 +585,10 @@ nsUDPSocket::InitWithAddress(const NetAddr *aAddr, nsIPrincipal *aPrincipal,
{
NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED);
if (gIOService->IsNetTearingDown()) {
return NS_ERROR_FAILURE;
}
bool addressReuse = (aOptionalArgc == 1) ? aAddressReuse : true;
//
@ -657,7 +665,6 @@ nsUDPSocket::InitWithAddress(const NetAddr *aAddr, nsIPrincipal *aPrincipal,
// create proxy via NetworkActivityMonitor
NetworkActivityMonitor::AttachIOLayer(mFD);
ClosingService::AttachIOLayer(mFD);
// wait until AsyncListen is called before polling the socket for
// client connections.

View File

@ -36,7 +36,6 @@
#include "prerr.h"
#include "prerror.h"
#include "NetworkActivityMonitor.h"
#include "ClosingService.h"
using namespace mozilla::net;
@ -145,9 +144,6 @@ void ARTPConnection::MakePortPair(
NetworkActivityMonitor::AttachIOLayer(*rtpSocket);
NetworkActivityMonitor::AttachIOLayer(*rtcpSocket);
ClosingService::AttachIOLayer(*rtpSocket);
ClosingService::AttachIOLayer(*rtcpSocket);
// Reduce the chance of using duplicate port numbers.
srand(time(NULL));
// rand() * 1000 may overflow int type, use long long.

View File

@ -34,7 +34,6 @@
#include "prnetdb.h"
#include "prerr.h"
#include "NetworkActivityMonitor.h"
#include "ClosingService.h"
using namespace mozilla::net;
@ -110,7 +109,6 @@ void ARTPSession::MakeUDPSocket(PRFileDesc **s, unsigned port) {
}
NetworkActivityMonitor::AttachIOLayer(*s);
ClosingService::AttachIOLayer(*s);
PRNetAddr addr;
addr.inet.family = PR_AF_INET;

View File

@ -31,7 +31,6 @@
#include <media/stagefright/MetaData.h>
#include <utils/ByteOrder.h>
#include "ClosingService.h"
#include "NetworkActivityMonitor.h"
using namespace mozilla::net;
@ -65,7 +64,6 @@ ARTPWriter::ARTPWriter(int fd)
}
NetworkActivityMonitor::AttachIOLayer(mSocket);
ClosingService::AttachIOLayer(mSocket);
mRTPAddr.inet.family = PR_AF_INET;

View File

@ -34,7 +34,6 @@
#include "nsCOMPtr.h"
#include "nsString.h"
#include "nsNetCID.h"
#include "ClosingService.h"
#include "nsIServiceManager.h"
#include "nsICryptoHash.h"
@ -284,7 +283,6 @@ void ARTSPConnection::onConnect(const sp<AMessage> &msg) {
}
NetworkActivityMonitor::AttachIOLayer(mSocket);
ClosingService::AttachIOLayer(mSocket);
MakeSocketBlocking(mSocket, false);

View File

@ -27,7 +27,6 @@
#include "prnetdb.h"
#include "prerr.h"
#include "NetworkActivityMonitor.h"
#include "ClosingService.h"
using namespace mozilla::net;
@ -45,7 +44,6 @@ UDPPusher::UDPPusher(const char *filename, unsigned port)
}
NetworkActivityMonitor::AttachIOLayer(mSocket);
ClosingService::AttachIOLayer(mSocket);
PRNetAddr addr;
addr.inet.family = PR_AF_INET;

View File

@ -149,7 +149,6 @@ void nsNotifyAddrListener::checkLink(void)
void nsNotifyAddrListener::OnNetlinkMessage(int aNetlinkSocket)
{
struct nlmsghdr *nlh;
struct rtmsg *route_entry;
// The buffer size below, (4095) was chosen partly based on testing and
// partly on existing sample source code using this size. It needs to be
@ -158,7 +157,6 @@ void nsNotifyAddrListener::OnNetlinkMessage(int aNetlinkSocket)
struct rtattr *attr;
int attr_len;
const struct ifaddrmsg* newifam;
bool link_local;
// inspired by check_pf.c.
nsAutoPtr<char> addr;
@ -181,131 +179,79 @@ void nsNotifyAddrListener::OnNetlinkMessage(int aNetlinkSocket)
break;
}
switch(nlh->nlmsg_type) {
case RTM_DELROUTE:
LOG(("nsNotifyAddrListener::OnNetlinkMessage deleted route"));
case RTM_NEWROUTE:
LOG(("nsNotifyAddrListener::OnNetlinkMessage new/deleted route"));
// Get the route data
route_entry = static_cast<struct rtmsg *>(NLMSG_DATA(nlh));
LOG(("nsNotifyAddrListener::OnNetlinkMessage: new/deleted address\n"));
newifam = reinterpret_cast<struct ifaddrmsg*>(NLMSG_DATA(nlh));
// We are just intrested in main routing table
if (route_entry->rtm_table != RT_TABLE_MAIN)
continue;
if ((route_entry->rtm_family != AF_INET) &&
(route_entry->rtm_family != AF_INET6)) {
continue;
}
attr = (struct rtattr *) RTM_RTA(route_entry);
attr_len = RTM_PAYLOAD(nlh);
link_local = false;
/* Loop through all attributes */
for ( ; RTA_OK(attr, attr_len); attr = RTA_NEXT(attr, attr_len)) {
if (attr->rta_type == RTA_GATEWAY) {
if (route_entry->rtm_family == AF_INET6) {
unsigned char *g = (unsigned char *)
RTA_DATA(attr);
if ((g[0] == 0xFE) && ((g[1] & 0xc0) == 0x80)) {
link_local = true;
break;
}
}
}
}
if (!link_local) {
LOG(("OnNetlinkMessage: route update\n"));
networkChange = true;
} else {
LOG(("OnNetlinkMessage: ignored link-local route update\n"));
}
break;
case RTM_DELADDR:
LOG(("nsNotifyAddrListener::OnNetlinkMessage deleted address"));
case RTM_NEWADDR:
LOG(("nsNotifyAddrListener::OnNetlinkMessage: new/deleted address"
"\n"));
newifam = reinterpret_cast<struct ifaddrmsg*>(NLMSG_DATA(nlh));
if ((newifam->ifa_family != AF_INET) &&
(newifam->ifa_family != AF_INET6)) {
continue;
}
attr = IFA_RTA (newifam);
attr_len = IFA_PAYLOAD (nlh);
for (;attr_len && RTA_OK (attr, attr_len);
attr = RTA_NEXT (attr, attr_len)) {
if (attr->rta_type == IFA_ADDRESS) {
if (newifam->ifa_family == AF_INET) {
struct in_addr* in = (struct in_addr*)RTA_DATA(attr);
addr = (char*)malloc(INET_ADDRSTRLEN);
inet_ntop(AF_INET, in, addr.get(), INET_ADDRSTRLEN);
} else {
struct in6_addr* in = (struct in6_addr*)RTA_DATA(attr);
addr = (char*)malloc(INET6_ADDRSTRLEN);
inet_ntop(AF_INET6, in, addr.get(), INET6_ADDRSTRLEN);
}
} else if (attr->rta_type == IFA_LOCAL) {
if (newifam->ifa_family == AF_INET) {
struct in_addr* in = (struct in_addr*)RTA_DATA(attr);
localaddr = (char*)malloc(INET_ADDRSTRLEN);
inet_ntop(AF_INET, in, localaddr.get(), INET_ADDRSTRLEN);
} else {
struct in6_addr* in = (struct in6_addr*)RTA_DATA(attr);
localaddr = (char*)malloc(INET6_ADDRSTRLEN);
inet_ntop(AF_INET6, in, localaddr.get(), INET6_ADDRSTRLEN);
}
}
}
if (localaddr) {
addr = localaddr;
}
if (!addr) {
continue;
}
if (nlh->nlmsg_type == RTM_NEWADDR) {
LOG(("nsNotifyAddrListener::OnNetlinkMessage: a new address "
"- %s.", addr.get()));
struct ifaddrmsg* ifam;
nsCString addrStr;
addrStr.Assign(addr);
if (mAddressInfo.Get(addrStr, &ifam)) {
LOG(("nsNotifyAddrListener::OnNetlinkMessage: the address "
"already known."));
if (memcmp(ifam, newifam, sizeof(struct ifaddrmsg))) {
LOG(("nsNotifyAddrListener::OnNetlinkMessage: but "
"the address info has been changed."));
networkChange = true;
memcpy(ifam, newifam, sizeof(struct ifaddrmsg));
}
} else {
networkChange = true;
ifam = (struct ifaddrmsg*)malloc(sizeof(struct ifaddrmsg));
memcpy(ifam, newifam, sizeof(struct ifaddrmsg));
mAddressInfo.Put(addrStr,ifam);
}
} else {
LOG(("nsNotifyAddrListener::OnNetlinkMessage: an address "
"has been deleted - %s.", addr.get()));
networkChange = true;
nsCString addrStr;
addrStr.Assign(addr);
mAddressInfo.Remove(addrStr);
}
// clean it up.
localaddr = nullptr;
addr = nullptr;
break;
default:
if ((newifam->ifa_family != AF_INET) &&
(newifam->ifa_family != AF_INET6)) {
continue;
}
attr = IFA_RTA (newifam);
attr_len = IFA_PAYLOAD (nlh);
for (;attr_len && RTA_OK (attr, attr_len);
attr = RTA_NEXT (attr, attr_len)) {
if (attr->rta_type == IFA_ADDRESS) {
if (newifam->ifa_family == AF_INET) {
struct in_addr* in = (struct in_addr*)RTA_DATA(attr);
addr = (char*)malloc(INET_ADDRSTRLEN);
inet_ntop(AF_INET, in, addr.get(), INET_ADDRSTRLEN);
} else {
struct in6_addr* in = (struct in6_addr*)RTA_DATA(attr);
addr = (char*)malloc(INET6_ADDRSTRLEN);
inet_ntop(AF_INET6, in, addr.get(), INET6_ADDRSTRLEN);
}
} else if (attr->rta_type == IFA_LOCAL) {
if (newifam->ifa_family == AF_INET) {
struct in_addr* in = (struct in_addr*)RTA_DATA(attr);
localaddr = (char*)malloc(INET_ADDRSTRLEN);
inet_ntop(AF_INET, in, localaddr.get(), INET_ADDRSTRLEN);
} else {
struct in6_addr* in = (struct in6_addr*)RTA_DATA(attr);
localaddr = (char*)malloc(INET6_ADDRSTRLEN);
inet_ntop(AF_INET6, in, localaddr.get(), INET6_ADDRSTRLEN);
}
}
}
if (localaddr) {
addr = localaddr;
}
if (!addr) {
continue;
}
if (nlh->nlmsg_type == RTM_NEWADDR) {
LOG(("nsNotifyAddrListener::OnNetlinkMessage: a new address "
"- %s.", addr.get()));
struct ifaddrmsg* ifam;
nsCString addrStr;
addrStr.Assign(addr);
if (mAddressInfo.Get(addrStr, &ifam)) {
LOG(("nsNotifyAddrListener::OnNetlinkMessage: the address "
"already known."));
if (memcmp(ifam, newifam, sizeof(struct ifaddrmsg))) {
LOG(("nsNotifyAddrListener::OnNetlinkMessage: but "
"the address info has been changed."));
networkChange = true;
memcpy(ifam, newifam, sizeof(struct ifaddrmsg));
}
} else {
networkChange = true;
ifam = (struct ifaddrmsg*)malloc(sizeof(struct ifaddrmsg));
memcpy(ifam, newifam, sizeof(struct ifaddrmsg));
mAddressInfo.Put(addrStr,ifam);
}
} else {
LOG(("nsNotifyAddrListener::OnNetlinkMessage: an address "
"has been deleted - %s.", addr.get()));
networkChange = true;
nsCString addrStr;
addrStr.Assign(addr);
mAddressInfo.Remove(addrStr);
}
// clean it up.
localaddr = nullptr;
addr = nullptr;
}
if (networkChange && mAllowChangedEvent) {
@ -329,8 +275,7 @@ nsNotifyAddrListener::Run()
memset(&addr, 0, sizeof(addr)); // clear addr
addr.nl_family = AF_NETLINK;
addr.nl_groups = RTMGRP_IPV4_ROUTE | RTMGRP_IPV4_IFADDR |
RTMGRP_IPV6_IFADDR | RTMGRP_IPV6_ROUTE;
addr.nl_groups = RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR;
if (bind(netlinkSocket, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
// failure!

View File

@ -0,0 +1,335 @@
/* 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/. */
/* global Accessibility, Components, Log, ElementNotAccessibleError,
XPCOMUtils */
var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import('resource://gre/modules/Log.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'setInterval',
'resource://gre/modules/Timer.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'clearInterval',
'resource://gre/modules/Timer.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'ElementNotAccessibleError',
'chrome://marionette/content/error.js');
this.EXPORTED_SYMBOLS = ['Accessibility'];
/**
* Accessible states used to check element's state from the accessiblity API
* perspective.
*/
const states = {
unavailable: Ci.nsIAccessibleStates.STATE_UNAVAILABLE,
focusable: Ci.nsIAccessibleStates.STATE_FOCUSABLE,
selectable: Ci.nsIAccessibleStates.STATE_SELECTABLE,
selected: Ci.nsIAccessibleStates.STATE_SELECTED
};
var logger = Log.repository.getLogger('Marionette');
/**
* Component responsible for interacting with platform accessibility API. Its
* methods serve as wrappers for testing content and chrome accessibility as
* well as accessibility of user interactions.
*
* @param {Function} getCapabilies
* Session capabilities getter.
*/
this.Accessibility = function Accessibility(getCapabilies = () => {}) {
// A flag indicating whether the accessibility issue should be logged or cause
// an exception. Default: log to stdout.
Object.defineProperty(this, 'strict', {
configurable: true,
get: function() {
let capabilies = getCapabilies();
return !!capabilies.raisesAccessibilityExceptions;
}
});
// An interface for in-process accessibility clients
// Note: we access it lazily to not enable accessibility when it is not needed
Object.defineProperty(this, 'retrieval', {
configurable: true,
get: function() {
delete this.retrieval;
this.retrieval = Cc[
'@mozilla.org/accessibleRetrieval;1'].getService(
Ci.nsIAccessibleRetrieval);
return this.retrieval;
}
});
};
Accessibility.prototype = {
/**
* Number of attempts to get an accessible object for an element. We attempt
* more than once because accessible tree can be out of sync with the DOM tree
* for a short period of time.
* @type {Number}
*/
GET_ACCESSIBLE_ATTEMPTS: 100,
/**
* An interval between attempts to retrieve an accessible object for an
* element.
* @type {Number} ms
*/
GET_ACCESSIBLE_ATTEMPT_INTERVAL: 10,
/**
* Accessible object roles that support some action
* @type Object
*/
ACTIONABLE_ROLES: new Set([
'pushbutton',
'checkbutton',
'combobox',
'key',
'link',
'menuitem',
'check menu item',
'radio menu item',
'option',
'listbox option',
'listbox rich option',
'check rich option',
'combobox option',
'radiobutton',
'rowheader',
'switch',
'slider',
'spinbutton',
'pagetab',
'entry',
'outlineitem'
]),
/**
* Get an accessible object for a DOM element
* @param nsIDOMElement element
* @param Boolean mustHaveAccessible a flag indicating that the element must
* have an accessible object
* @return nsIAccessible object for the element
*/
getAccessibleObject(element, mustHaveAccessible = false) {
return new Promise((resolve, reject) => {
let acc = this.retrieval.getAccessibleFor(element);
if (acc || !mustHaveAccessible) {
// If accessible object is found, return it. If it is not required,
// also resolve.
resolve(acc);
} else {
// If we require an accessible object, we need to poll for it because
// accessible tree might be out of sync with DOM tree for a short time.
let attempts = this.GET_ACCESSIBLE_ATTEMPTS;
let intervalId = setInterval(() => {
let acc = this.retrieval.getAccessibleFor(element);
if (acc || --attempts <= 0) {
clearInterval(intervalId);
if (acc) { resolve(acc); }
else { reject(); }
}
}, this.GET_ACCESSIBLE_ATTEMPT_INTERVAL);
}
}).catch(() => this.error(
'Element does not have an accessible object', element));
},
/**
* Check if the accessible has a role that supports some action
* @param nsIAccessible object
* @return Boolean an indicator of role being actionable
*/
isActionableRole(accessible) {
return this.ACTIONABLE_ROLES.has(
this.retrieval.getStringRole(accessible.role));
},
/**
* Determine if an accessible has at least one action that it supports
* @param nsIAccessible object
* @return Boolean an indicator of supporting at least one accessible action
*/
hasActionCount(accessible) {
return accessible.actionCount > 0;
},
/**
* Determine if an accessible has a valid name
* @param nsIAccessible object
* @return Boolean an indicator that the element has a non empty valid name
*/
hasValidName(accessible) {
return accessible.name && accessible.name.trim();
},
/**
* Check if an accessible has a set hidden attribute
* @param nsIAccessible object
* @return Boolean an indicator that the element has a hidden accessible
* attribute set to true
*/
hasHiddenAttribute(accessible) {
let hidden = false;
try {
hidden = accessible.attributes.getStringProperty('hidden');
} finally {
// If the property is missing, exception will be thrown.
return hidden && hidden === 'true';
}
},
/**
* Verify if an accessible has a given state
* @param nsIAccessible object
* @param Number stateToMatch the state to match
* @return Boolean accessible has a state
*/
matchState(accessible, stateToMatch) {
let state = {};
accessible.getState(state, {});
return !!(state.value & stateToMatch);
},
/**
* Check if an accessible is hidden from the user of the accessibility API
* @param nsIAccessible object
* @return Boolean an indicator that the element is hidden from the user
*/
isHidden(accessible) {
while (accessible) {
if (this.hasHiddenAttribute(accessible)) {
return true;
}
accessible = accessible.parent;
}
return false;
},
/**
* Send an error message or log the error message in the log
* @param String message
* @param DOMElement element that caused an error
*/
error(message, element) {
if (!message) {
return;
}
if (element) {
let {id, tagName, className} = element;
message += `: id: ${id}, tagName: ${tagName}, className: ${className}`;
}
if (this.strict) {
throw new ElementNotAccessibleError(message);
}
logger.error(message);
},
/**
* Check if the element's visible state corresponds to its accessibility API
* visibility
* @param nsIAccessible object
* @param WebElement corresponding to nsIAccessible object
* @param Boolean visible element's visibility state
*/
checkVisible(accessible, element, visible) {
if (!accessible) {
return;
}
let hiddenAccessibility = this.isHidden(accessible);
let message;
if (visible && hiddenAccessibility) {
message = 'Element is not currently visible via the accessibility API ' +
'and may not be manipulated by it';
} else if (!visible && !hiddenAccessibility) {
message = 'Element is currently only visible via the accessibility API ' +
'and can be manipulated by it';
}
this.error(message, element);
},
/**
* Check if the element's unavailable accessibility state matches the enabled
* state
* @param nsIAccessible object
* @param WebElement corresponding to nsIAccessible object
* @param Boolean enabled element's enabled state
* @param Object container frame and optional ShadowDOM
*/
checkEnabled(accessible, element, enabled, container) {
if (!accessible) {
return;
}
let disabledAccessibility = this.matchState(accessible, states.unavailable);
let explorable = container.frame.document.defaultView.getComputedStyle(
element).getPropertyValue('pointer-events') !== 'none';
let message;
if (!explorable && !disabledAccessibility) {
message = 'Element is enabled but is not explorable via the ' +
'accessibility API';
} else if (enabled && disabledAccessibility) {
message = 'Element is enabled but disabled via the accessibility API';
} else if (!enabled && !disabledAccessibility) {
message = 'Element is disabled but enabled via the accessibility API';
}
this.error(message, element);
},
/**
* Check if it is possible to activate an element with the accessibility API
* @param nsIAccessible object
* @param WebElement corresponding to nsIAccessible object
*/
checkActionable(accessible, element) {
if (!accessible) {
return;
}
let message;
if (!this.hasActionCount(accessible)) {
message = 'Element does not support any accessible actions';
} else if (!this.isActionableRole(accessible)) {
message = 'Element does not have a correct accessibility role ' +
'and may not be manipulated via the accessibility API';
} else if (!this.hasValidName(accessible)) {
message = 'Element is missing an accessible name';
} else if (!this.matchState(accessible, states.focusable)) {
message = 'Element is not focusable via the accessibility API';
}
this.error(message, element);
},
/**
* Check if element's selected state corresponds to its accessibility API
* selected state.
* @param nsIAccessible object
* @param WebElement corresponding to nsIAccessible object
* @param Boolean selected element's selected state
*/
checkSelected(accessible, element, selected) {
if (!accessible) {
return;
}
if (!this.matchState(accessible, states.selectable)) {
// Element is not selectable via the accessibility API
return;
}
let selectedAccessibility = this.matchState(accessible, states.selected);
let message;
if (selected && !selectedAccessibility) {
message =
'Element is selected but not selected via the accessibility API';
} else if (!selected && selectedAccessibility) {
message =
'Element is not selected but selected via the accessibility API';
}
this.error(message, element);
}
};

View File

@ -23,6 +23,7 @@ XPCOMUtils.defineLazyServiceGetter(
this, "cookieManager", "@mozilla.org/cookiemanager;1", "nsICookieManager2");
Cu.import("chrome://marionette/content/actions.js");
Cu.import("chrome://marionette/content/interactions.js");
Cu.import("chrome://marionette/content/elements.js");
Cu.import("chrome://marionette/content/error.js");
Cu.import("chrome://marionette/content/modal.js");
@ -164,6 +165,8 @@ this.GeckoDriver = function(appName, device, stopSignal, emulator) {
"version": Services.appinfo.version,
};
this.interactions = new Interactions(utils, () => this.sessionCapabilities);
this.mm = globalMessageManager;
this.listener = proxy.toListener(() => this.mm, this.sendAsync.bind(this));
@ -1981,10 +1984,9 @@ GeckoDriver.prototype.clickElement = function(cmd, resp) {
switch (this.context) {
case Context.CHROME:
// click atom fails, fall back to click() action
let win = this.getCurrentWindow();
let el = this.curBrowser.elementManager.getKnownElement(id, { frame: win });
el.click();
yield this.interactions.clickElement({ frame: win },
this.curBrowser.elementManager, id)
break;
case Context.CONTENT:
@ -2082,8 +2084,8 @@ GeckoDriver.prototype.isElementDisplayed = function(cmd, resp) {
switch (this.context) {
case Context.CHROME:
let win = this.getCurrentWindow();
let el = this.curBrowser.elementManager.getKnownElement(id, {frame: win});
resp.body.value = utils.isElementDisplayed(el);
resp.body.value = yield this.interactions.isElementDisplayed(
{frame: win}, this.curBrowser.elementManager, id);
break;
case Context.CONTENT:
@ -2130,8 +2132,8 @@ GeckoDriver.prototype.isElementEnabled = function(cmd, resp) {
case Context.CHROME:
// Selenium atom doesn't quite work here
let win = this.getCurrentWindow();
let el = this.curBrowser.elementManager.getKnownElement(id, {frame: win});
resp.body.value = !(!!el.disabled);
resp.body.value = yield this.interactions.isElementEnabled(
{frame: win}, this.curBrowser.elementManager, id);
break;
case Context.CONTENT:
@ -2153,14 +2155,8 @@ GeckoDriver.prototype.isElementSelected = function(cmd, resp) {
case Context.CHROME:
// Selenium atom doesn't quite work here
let win = this.getCurrentWindow();
let el = this.curBrowser.elementManager.getKnownElement(id, { frame: win });
if (typeof el.checked != "undefined") {
resp.body.value = !!el.checked;
} else if (typeof el.selected != "undefined") {
resp.body.value = !!el.selected;
} else {
resp.body.value = true;
}
resp.body.value = yield this.interactions.isElementSelected(
{ frame: win }, this.curBrowser.elementManager, id);
break;
case Context.CONTENT:
@ -2209,15 +2205,8 @@ GeckoDriver.prototype.sendKeysToElement = function(cmd, resp) {
switch (this.context) {
case Context.CHROME:
let win = this.getCurrentWindow();
let el = this.curBrowser.elementManager.getKnownElement(id, { frame: win });
utils.sendKeysToElement(
win,
el,
value,
() => {},
e => { throw e; },
cmd.id,
true /* ignore visibility check */);
yield this.interactions.sendKeysToElement(
{ frame: win }, this.curBrowser.elementManager, id, value, true);
break;
case Context.CONTENT:
@ -2806,9 +2795,6 @@ GeckoDriver.prototype.sendKeysToDialog = function(cmd, resp) {
win,
loginTextbox,
cmd.parameters.value,
() => {},
e => { throw e; },
this.command_id,
true /* ignore visibility check */);
};

View File

@ -5,12 +5,6 @@
let {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("chrome://marionette/content/error.js");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, 'setInterval',
'resource://gre/modules/Timer.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'clearInterval',
'resource://gre/modules/Timer.jsm');
/**
* The ElementManager manages DOM element references and
@ -30,7 +24,6 @@ XPCOMUtils.defineLazyModuleGetter(this, 'clearInterval',
*/
this.EXPORTED_SYMBOLS = [
"Accessibility",
"elements",
"ElementManager",
"CLASS_NAME",
@ -47,8 +40,8 @@ this.EXPORTED_SYMBOLS = [
const DOCUMENT_POSITION_DISCONNECTED = 1;
const uuidGen = Components.classes["@mozilla.org/uuid-generator;1"]
.getService(Components.interfaces.nsIUUIDGenerator);
const uuidGen = Cc["@mozilla.org/uuid-generator;1"]
.getService(Ci.nsIUUIDGenerator);
this.CLASS_NAME = "class name";
this.SELECTOR = "css selector";
@ -61,192 +54,6 @@ this.XPATH = "xpath";
this.ANON= "anon";
this.ANON_ATTRIBUTE = "anon attribute";
this.Accessibility = function Accessibility() {
// A flag indicating whether the accessibility issue should be logged or cause
// an exception. Default: log to stdout.
this.strict = false;
// An interface for in-process accessibility clients
// Note: we access it lazily to not enable accessibility when it is not needed
Object.defineProperty(this, 'accessibleRetrieval', {
configurable: true,
get: function() {
delete this.accessibleRetrieval;
this.accessibleRetrieval = Components.classes[
'@mozilla.org/accessibleRetrieval;1'].getService(
Components.interfaces.nsIAccessibleRetrieval);
return this.accessibleRetrieval;
}
});
};
Accessibility.prototype = {
/**
* Number of attempts to get an accessible object for an element. We attempt
* more than once because accessible tree can be out of sync with the DOM tree
* for a short period of time.
* @type {Number}
*/
GET_ACCESSIBLE_ATTEMPTS: 100,
/**
* An interval between attempts to retrieve an accessible object for an
* element.
* @type {Number} ms
*/
GET_ACCESSIBLE_ATTEMPT_INTERVAL: 10,
/**
* Accessible object roles that support some action
* @type Object
*/
actionableRoles: new Set([
'pushbutton',
'checkbutton',
'combobox',
'key',
'link',
'menuitem',
'check menu item',
'radio menu item',
'option',
'listbox option',
'listbox rich option',
'check rich option',
'combobox option',
'radiobutton',
'rowheader',
'switch',
'slider',
'spinbutton',
'pagetab',
'entry',
'outlineitem'
]),
/**
* Get an accessible object for a DOM element
* @param nsIDOMElement element
* @param Boolean mustHaveAccessible a flag indicating that the element must
* have an accessible object
* @return nsIAccessible object for the element
*/
getAccessibleObject(element, mustHaveAccessible = false) {
return new Promise((resolve, reject) => {
let acc = this.accessibleRetrieval.getAccessibleFor(element);
if (acc || !mustHaveAccessible) {
// If accessible object is found, return it. If it is not required,
// also resolve.
resolve(acc);
} else {
// If we require an accessible object, we need to poll for it because
// accessible tree might be out of sync with DOM tree for a short time.
let attempts = this.GET_ACCESSIBLE_ATTEMPTS;
let intervalId = setInterval(() => {
let acc = this.accessibleRetrieval.getAccessibleFor(element);
if (acc || --attempts <= 0) {
clearInterval(intervalId);
if (acc) { resolve(acc); }
else { reject(); }
}
}, this.GET_ACCESSIBLE_ATTEMPT_INTERVAL);
}
}).catch(() => this.handleErrorMessage(
'Element does not have an accessible object', element));
},
/**
* Check if the accessible has a role that supports some action
* @param nsIAccessible object
* @return Boolean an indicator of role being actionable
*/
isActionableRole(accessible) {
return this.actionableRoles.has(
this.accessibleRetrieval.getStringRole(accessible.role));
},
/**
* Determine if an accessible has at least one action that it supports
* @param nsIAccessible object
* @return Boolean an indicator of supporting at least one accessible action
*/
hasActionCount(accessible) {
return accessible.actionCount > 0;
},
/**
* Determine if an accessible has a valid name
* @param nsIAccessible object
* @return Boolean an indicator that the element has a non empty valid name
*/
hasValidName(accessible) {
return accessible.name && accessible.name.trim();
},
/**
* Check if an accessible has a set hidden attribute
* @param nsIAccessible object
* @return Boolean an indicator that the element has a hidden accessible
* attribute set to true
*/
hasHiddenAttribute(accessible) {
let hidden;
try {
hidden = accessible.attributes.getStringProperty('hidden');
} finally {
// If the property is missing, exception will be thrown.
return hidden && hidden === 'true';
}
},
/**
* Verify if an accessible has a given state
* @param nsIAccessible object
* @param String stateName name of the state to match
* @return Boolean accessible has a state
*/
matchState(accessible, stateName) {
let stateToMatch = Components.interfaces.nsIAccessibleStates[stateName];
let state = {};
accessible.getState(state, {});
return !!(state.value & stateToMatch);
},
/**
* Check if an accessible is hidden from the user of the accessibility API
* @param nsIAccessible object
* @return Boolean an indicator that the element is hidden from the user
*/
isHidden(accessible) {
while (accessible) {
if (this.hasHiddenAttribute(accessible)) {
return true;
}
accessible = accessible.parent;
}
return false;
},
/**
* Send an error message or log the error message in the log
* @param String message
* @param DOMElement element that caused an error
*/
handleErrorMessage(message, element) {
if (!message) {
return;
}
if (element) {
message += ` -> id: ${element.id}, tagName: ${element.tagName}, className: ${element.className}\n`;
}
if (this.strict) {
throw new ElementNotAccessibleError(message);
}
dump(Date.now() + " Marionette: " + message);
}
};
this.ElementManager = function ElementManager(notSupported) {
this.seenItems = {};
this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
@ -291,7 +98,7 @@ ElementManager.prototype = {
}
}
let id = elements.generateUUID();
this.seenItems[id] = Components.utils.getWeakReference(element);
this.seenItems[id] = Cu.getWeakReference(element);
return id;
},
@ -562,7 +369,7 @@ ElementManager.prototype = {
on_success, on_error,
command_id),
100,
Components.interfaces.nsITimer.TYPE_ONE_SHOT);
Ci.nsITimer.TYPE_ONE_SHOT);
}
} else {
if (isArrayLike) {
@ -598,7 +405,7 @@ ElementManager.prototype = {
*/
findByXPath: function EM_findByXPath(root, value, node) {
return root.evaluate(value, node, null,
Components.interfaces.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
},
/**
@ -616,7 +423,7 @@ ElementManager.prototype = {
*/
findByXPathAll: function EM_findByXPathAll(root, value, node) {
let values = root.evaluate(value, node, null,
Components.interfaces.nsIDOMXPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
Ci.nsIDOMXPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
let elements = [];
let element = values.iterateNext();
while (element) {

View File

@ -0,0 +1,317 @@
/* 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/. */
/* global Components, Accessibility, ElementNotVisibleError,
InvalidElementStateError, Interactions */
var {utils: Cu} = Components;
this.EXPORTED_SYMBOLS = ['Interactions'];
Cu.import('chrome://marionette/content/accessibility.js');
Cu.import('chrome://marionette/content/error.js');
/**
* XUL elements that support disabled attribtue.
*/
const DISABLED_ATTRIBUTE_SUPPORTED_XUL = new Set([
'ARROWSCROLLBOX',
'BUTTON',
'CHECKBOX',
'COLORPICKER',
'COMMAND',
'DATEPICKER',
'DESCRIPTION',
'KEY',
'KEYSET',
'LABEL',
'LISTBOX',
'LISTCELL',
'LISTHEAD',
'LISTHEADER',
'LISTITEM',
'MENU',
'MENUITEM',
'MENULIST',
'MENUSEPARATOR',
'PREFERENCE',
'RADIO',
'RADIOGROUP',
'RICHLISTBOX',
'RICHLISTITEM',
'SCALE',
'TAB',
'TABS',
'TEXTBOX',
'TIMEPICKER',
'TOOLBARBUTTON',
'TREE'
]);
/**
* XUL elements that support checked property.
*/
const CHECKED_PROPERTY_SUPPORTED_XUL = new Set([
'BUTTON',
'CHECKBOX',
'LISTITEM',
'TOOLBARBUTTON'
]);
/**
* XUL elements that support selected property.
*/
const SELECTED_PROPERTY_SUPPORTED_XUL = new Set([
'LISTITEM',
'MENU',
'MENUITEM',
'MENUSEPARATOR',
'RADIO',
'RICHLISTITEM',
'TAB'
]);
/**
* This function generates a pair of coordinates relative to the viewport given
* a target element and coordinates relative to that element's top-left corner.
* @param 'x', and 'y' are the relative to the target.
* If they are not specified, then the center of the target is used.
*/
function coordinates(target, x, y) {
let box = target.getBoundingClientRect();
if (typeof x === 'undefined') {
x = box.width / 2;
}
if (typeof y === 'undefined') {
y = box.height / 2;
}
return {
x: box.left + x,
y: box.top + y
};
}
/**
* A collection of interactions available in marionette.
* @type {Object}
*/
this.Interactions = function(utils, getCapabilies) {
this.utils = utils;
this.accessibility = new Accessibility(getCapabilies);
};
Interactions.prototype = {
/**
* Send click event to element.
*
* @param nsIDOMWindow, ShadowRoot container
* The window and an optional shadow root that contains the element
*
* @param ElementManager elementManager
*
* @param String id
* The DOM reference ID
*/
clickElement(container, elementManager, id) {
let el = elementManager.getKnownElement(id, container);
let visible = this.checkVisible(container, el);
if (!visible) {
throw new ElementNotVisibleError('Element is not visible');
}
return this.accessibility.getAccessibleObject(el, true).then(acc => {
this.accessibility.checkVisible(acc, el, visible);
if (this.utils.isElementEnabled(el)) {
this.accessibility.checkEnabled(acc, el, true, container);
this.accessibility.checkActionable(acc, el);
if (this.isXULElement(el)) {
el.click();
} else {
let rects = el.getClientRects();
this.utils.synthesizeMouseAtPoint(rects[0].left + rects[0].width/2,
rects[0].top + rects[0].height/2,
{}, el.ownerDocument.defaultView);
}
} else {
throw new InvalidElementStateError('Element is not enabled');
}
});
},
/**
* Send keys to element
*
* @param nsIDOMWindow, ShadowRoot container
* The window and an optional shadow root that contains the element
*
* @param ElementManager elementManager
*
* @param String id
* The DOM reference ID
*
* @param String?Array value
* Value to send to an element
*
* @param Boolean ignoreVisibility
* A flag to check element visibility
*/
sendKeysToElement(container, elementManager, id, value, ignoreVisibility) {
let el = elementManager.getKnownElement(id, container);
return this.accessibility.getAccessibleObject(el, true).then(acc => {
this.accessibility.checkActionable(acc, el);
this.utils.sendKeysToElement(
container.frame, el, value, ignoreVisibility);
});
},
/**
* Determine the element displayedness of the given web element.
*
* @param nsIDOMWindow, ShadowRoot container
* The window and an optional shadow root that contains the element
*
* @param ElementManager elementManager
*
* @param {WebElement} id
* Reference to web element.
*
* Also performs additional accessibility checks if enabled by session
* capability.
*/
isElementDisplayed(container, elementManager, id) {
let el = elementManager.getKnownElement(id, container);
let displayed = this.utils.isElementDisplayed(el);
return this.accessibility.getAccessibleObject(el).then(acc => {
this.accessibility.checkVisible(acc, el, displayed);
return displayed;
});
},
/**
* Check if element is enabled.
*
* @param nsIDOMWindow, ShadowRoot container
* The window and an optional shadow root that contains the element
*
* @param ElementManager elementManager
*
* @param {WebElement} id
* Reference to web element.
*
* @return {boolean}
* True if enabled, false otherwise.
*/
isElementEnabled(container, elementManager, id) {
let el = elementManager.getKnownElement(id, container);
let enabled = true;
if (this.isXULElement(el)) {
// Check if XUL element supports disabled attribute
if (DISABLED_ATTRIBUTE_SUPPORTED_XUL.has(el.tagName.toUpperCase())) {
let disabled = this.utils.getElementAttribute(el, 'disabled');
if (disabled && disabled === 'true') {
enabled = false;
}
}
} else {
enabled = this.utils.isElementEnabled(el);
}
return this.accessibility.getAccessibleObject(el).then(acc => {
this.accessibility.checkEnabled(acc, el, enabled, container);
return enabled;
});
},
/**
* Determines if the referenced element is selected or not.
*
* This operation only makes sense on input elements of the Checkbox-
* and Radio Button states, or option elements.
*
* @param nsIDOMWindow, ShadowRoot container
* The window and an optional shadow root that contains the element
*
* @param ElementManager elementManager
*
* @param {WebElement} id
* Reference to web element.
*/
isElementSelected(container, elementManager, id) {
let el = elementManager.getKnownElement(id, container);
let selected = true;
if (this.isXULElement(el)) {
let tagName = el.tagName.toUpperCase();
if (CHECKED_PROPERTY_SUPPORTED_XUL.has(tagName)) {
selected = el.checked;
}
if (SELECTED_PROPERTY_SUPPORTED_XUL.has(tagName)) {
selected = el.selected;
}
} else {
selected = this.utils.isElementSelected(el);
}
return this.accessibility.getAccessibleObject(el).then(acc => {
this.accessibility.checkSelected(acc, el, selected);
return selected;
});
},
/**
* This function throws the visibility of the element error if the element is
* not displayed or the given coordinates are not within the viewport.
*
* @param 'x', and 'y' are the coordinates relative to the target.
* If they are not specified, then the center of the target is used.
*/
checkVisible(container, el, x, y) {
// Bug 1094246 - Webdriver's isShown doesn't work with content xul
if (!this.isXULElement(el)) {
//check if the element is visible
let visible = this.utils.isElementDisplayed(el);
if (!visible) {
return false;
}
}
if (el.tagName.toLowerCase() === 'body') {
return true;
}
if (!this.elementInViewport(container, el, x, y)) {
//check if scroll function exist. If so, call it.
if (el.scrollIntoView) {
el.scrollIntoView(false);
if (!this.elementInViewport(container, el)) {
return false;
}
}
else {
return false;
}
}
return true;
},
isXULElement(el) {
return this.utils.getElementAttribute(el, 'namespaceURI').indexOf(
'there.is.only.xul') >= 0;
},
/**
* This function returns true if the given coordinates are in the viewport.
* @param 'x', and 'y' are the coordinates relative to the target.
* If they are not specified, then the center of the target is used.
*/
elementInViewport(container, el, x, y) {
let c = coordinates(el, x, y);
let win = container.frame;
let viewPort = {
top: win.pageYOffset,
left: win.pageXOffset,
bottom: win.pageYOffset + win.innerHeight,
right: win.pageXOffset + win.innerWidth
};
return (viewPort.left <= c.x + win.pageXOffset &&
c.x + win.pageXOffset <= viewPort.right &&
viewPort.top <= c.y + win.pageYOffset &&
c.y + win.pageYOffset <= viewPort.bottom);
}
};

View File

@ -7,6 +7,8 @@ marionette.jar:
content/server.js (server.js)
content/driver.js (driver.js)
content/actions.js (actions.js)
content/interactions.js (interactions.js)
content/accessibility.js (accessibility.js)
content/listener.js (listener.js)
content/elements.js (elements.js)
content/sendkeys.js (sendkeys.js)

View File

@ -18,6 +18,7 @@ Cu.import("chrome://marionette/content/cookies.js");
Cu.import("chrome://marionette/content/elements.js");
Cu.import("chrome://marionette/content/error.js");
Cu.import("chrome://marionette/content/proxy.js");
Cu.import("chrome://marionette/content/interactions.js");
Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
@ -43,7 +44,11 @@ var curContainer = { frame: content, shadowRoot: null };
var isRemoteBrowser = () => curContainer.frame.contentWindow !== null;
var previousContainer = null;
var elementManager = new ElementManager([]);
var accessibility = new Accessibility();
// Holds session capabilities.
var capabilities = {};
var interactions = new Interactions(utils, () => capabilities);
var actions = new ActionChain(utils, checkForInterrupted);
var importedScripts = null;
@ -108,9 +113,8 @@ function registerSelf() {
if (register[0]) {
let {id, remotenessChange} = register[0][0];
let {B2G, raisesAccessibilityExceptions} = register[0][2];
isB2G = B2G;
accessibility.strict = raisesAccessibilityExceptions;
capabilities = register[0][2];
isB2G = capabilities.B2G;
listenerId = id;
if (typeof id != "undefined") {
// check if we're the main process
@ -299,8 +303,8 @@ function waitForReady() {
* current environment, and resets all values
*/
function newSession(msg) {
isB2G = msg.json.B2G;
accessibility.strict = msg.json.raisesAccessibilityExceptions;
capabilities = msg.json;
isB2G = capabilities.B2G;
resetValues();
if (isB2G) {
readyStateTimer.initWithCallback(waitForReady, 100, Ci.nsITimer.TYPE_ONE_SHOT);
@ -938,9 +942,9 @@ function singleTap(id, corx, cory) {
if (!visible) {
throw new ElementNotVisibleError("Element is not currently visible and may not be manipulated");
}
return accessibility.getAccessibleObject(el, true).then(acc => {
checkVisibleAccessibility(acc, el, visible);
checkActionableAccessibility(acc, el);
return interactions.accessibility.getAccessibleObject(el, true).then(acc => {
interactions.accessibility.checkVisible(acc, el, visible);
interactions.accessibility.checkActionable(acc, el);
if (!curContainer.frame.document.createTouch) {
actions.mouseEventsOnly = true;
}
@ -955,108 +959,6 @@ function singleTap(id, corx, cory) {
});
}
/**
* Check if the element's unavailable accessibility state matches the enabled
* state
* @param nsIAccessible object
* @param WebElement corresponding to nsIAccessible object
* @param Boolean enabled element's enabled state
*/
function checkEnabledAccessibility(accesible, element, enabled) {
if (!accesible) {
return;
}
let disabledAccessibility = accessibility.matchState(
accesible, 'STATE_UNAVAILABLE');
let explorable = curContainer.frame.document.defaultView.getComputedStyle(
element, null).getPropertyValue('pointer-events') !== 'none';
let message;
if (!explorable && !disabledAccessibility) {
message = 'Element is enabled but is not explorable via the ' +
'accessibility API';
} else if (enabled && disabledAccessibility) {
message = 'Element is enabled but disabled via the accessibility API';
} else if (!enabled && !disabledAccessibility) {
message = 'Element is disabled but enabled via the accessibility API';
}
accessibility.handleErrorMessage(message, element);
}
/**
* Check if the element's visible state corresponds to its accessibility API
* visibility
* @param nsIAccessible object
* @param WebElement corresponding to nsIAccessible object
* @param Boolean visible element's visibility state
*/
function checkVisibleAccessibility(accesible, element, visible) {
if (!accesible) {
return;
}
let hiddenAccessibility = accessibility.isHidden(accesible);
let message;
if (visible && hiddenAccessibility) {
message = 'Element is not currently visible via the accessibility API ' +
'and may not be manipulated by it';
} else if (!visible && !hiddenAccessibility) {
message = 'Element is currently only visible via the accessibility API ' +
'and can be manipulated by it';
}
accessibility.handleErrorMessage(message, element);
}
/**
* Check if it is possible to activate an element with the accessibility API
* @param nsIAccessible object
* @param WebElement corresponding to nsIAccessible object
*/
function checkActionableAccessibility(accesible, element) {
if (!accesible) {
return;
}
let message;
if (!accessibility.hasActionCount(accesible)) {
message = 'Element does not support any accessible actions';
} else if (!accessibility.isActionableRole(accesible)) {
message = 'Element does not have a correct accessibility role ' +
'and may not be manipulated via the accessibility API';
} else if (!accessibility.hasValidName(accesible)) {
message = 'Element is missing an accesible name';
} else if (!accessibility.matchState(accesible, 'STATE_FOCUSABLE')) {
message = 'Element is not focusable via the accessibility API';
}
accessibility.handleErrorMessage(message, element);
}
/**
* Check if element's selected state corresponds to its accessibility API
* selected state.
* @param nsIAccessible object
* @param WebElement corresponding to nsIAccessible object
* @param Boolean selected element's selected state
*/
function checkSelectedAccessibility(accessible, element, selected) {
if (!accessible) {
return;
}
if (!accessibility.matchState(accessible, 'STATE_SELECTABLE')) {
// Element is not selectable via the accessibility API
return;
}
let selectedAccessibility = accessibility.matchState(
accessible, 'STATE_SELECTED');
let message;
if (selected && !selectedAccessibility) {
message = 'Element is selected but not selected via the accessibility API';
} else if (!selected && selectedAccessibility) {
message = 'Element is not selected but selected via the accessibility API';
}
accessibility.handleErrorMessage(message, element);
}
/**
* Function to create a touch based on the element
* corx and cory are relative to the viewport, id is the touchId
@ -1460,24 +1362,7 @@ function getActiveElement() {
* Reference to the web element to click.
*/
function clickElement(id) {
let el = elementManager.getKnownElement(id, curContainer);
let visible = checkVisible(el);
if (!visible) {
throw new ElementNotVisibleError("Element is not visible");
}
return accessibility.getAccessibleObject(el, true).then(acc => {
checkVisibleAccessibility(acc, el, visible);
if (utils.isElementEnabled(el)) {
checkEnabledAccessibility(acc, el, true);
checkActionableAccessibility(acc, el);
let rects = el.getClientRects();
utils.synthesizeMouseAtPoint(rects[0].left + rects[0].width/2,
rects[0].top + rects[0].height/2,
{}, el.ownerDocument.defaultView);
} else {
throw new InvalidElementStateError("Element is not Enabled");
}
});
return interactions.clickElement(curContainer, elementManager, id);
}
/**
@ -1531,12 +1416,7 @@ function getElementTagName(id) {
* capability.
*/
function isElementDisplayed(id) {
let el = elementManager.getKnownElement(id, curContainer);
let displayed = utils.isElementDisplayed(el);
return accessibility.getAccessibleObject(el).then(acc => {
checkVisibleAccessibility(acc, el, displayed);
return displayed;
});
return interactions.isElementDisplayed(curContainer, elementManager, id);
}
/**
@ -1587,12 +1467,7 @@ function getElementRect(id) {
* True if enabled, false otherwise.
*/
function isElementEnabled(id) {
let el = elementManager.getKnownElement(id, curContainer);
let enabled = utils.isElementEnabled(el);
return accessibility.getAccessibleObject(el).then(acc => {
checkEnabledAccessibility(acc, el, enabled);
return enabled;
});
return interactions.isElementEnabled(curContainer, elementManager, id);
}
/**
@ -1602,12 +1477,7 @@ function isElementEnabled(id) {
* and Radio Button states, or option elements.
*/
function isElementSelected(id) {
let el = elementManager.getKnownElement(id, curContainer);
let selected = utils.isElementSelected(el);
return accessibility.getAccessibleObject(el).then(acc => {
checkSelectedAccessibility(acc, el, selected);
return selected;
});
return interactions.isElementSelected(curContainer, elementManager, id);
}
/**
@ -1616,28 +1486,22 @@ function isElementSelected(id) {
function sendKeysToElement(msg) {
let command_id = msg.json.command_id;
let val = msg.json.value;
let el;
let id = msg.json.id;
let el = elementManager.getKnownElement(id, curContainer);
return Promise.resolve(elementManager.getKnownElement(msg.json.id, curContainer))
.then(knownEl => {
el = knownEl;
// Element should be actionable from the accessibility standpoint to be able
// to send keys to it.
return accessibility.getAccessibleObject(el, true)
}).then(acc => {
checkActionableAccessibility(acc, el);
if (el.type == "file") {
let p = val.join("");
if (el.type == "file") {
let p = val.join("");
fileInputElement = el;
// In e10s, we can only construct File objects in the parent process,
// so pass the filename to driver.js, which in turn passes them back
// to this frame script in receiveFiles.
sendSyncMessage("Marionette:getFiles",
{value: p, command_id: command_id});
} else {
utils.sendKeysToElement(curContainer.frame, el, val, sendOk, sendError, command_id);
}
}).catch(e => sendError(e, command_id));
} else {
interactions.sendKeysToElement(curContainer, elementManager, id, val)
.then(() => sendOk(command_id))
.catch(e => sendError(e, command_id));
}
}
/**

View File

@ -144,7 +144,7 @@ function focusElement(el) {
el.focus();
}
function sendKeysToElement(document, element, keysToSend, successCallback, errorCallback, command_id, ignoreVisibility) {
function sendKeysToElement(document, element, keysToSend, ignoreVisibility) {
if (ignoreVisibility || checkVisible(element)) {
focusElement(element);
@ -159,9 +159,7 @@ function sendKeysToElement(document, element, keysToSend, successCallback, error
var c = value.charAt(i);
sendSingleKey(c, modifiers, document);
}
successCallback(command_id);
} else {
errorCallback(new ElementNotVisibleError("Element is not visible"), command_id);
throw new ElementNotVisibleError("Element is not visible");
}
};

View File

@ -26579,6 +26579,10 @@
"path": "vibration/silent-ignore.html",
"url": "/vibration/silent-ignore.html"
},
{
"path": "web-animations/animation/constructor.html",
"url": "/web-animations/animation/constructor.html"
},
{
"path": "web-animations/animation-node/animation-node-after.html",
"url": "/web-animations/animation-node/animation-node-after.html"

View File

@ -0,0 +1,13 @@
[constructor.html]
type: testharness
[Animation can be constructed with null effect and null timeline]
expected: FAIL
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1049975
[Animation can be constructed with null effect and non-null timeline]
expected: FAIL
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1049975
[Animation can be constructed with non-null effect and null timeline]
expected: FAIL
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1096776

View File

@ -0,0 +1,60 @@
<!DOCTYPE html>
<meta charset=utf-8>
<title>Animation constructor tests</title>
<link rel="help" href="http://w3c.github.io/web-animations/#dom-animation-animation">
<link rel="author" title="Hiroyuki Ikezoe" href="mailto:hiikezoe@mozilla-japan.org">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../testcommon.js"></script>
<link rel="stylesheet" href="/resources/testharness.css">
<body>
<div id="log"></div>
<div id="target"></div>
<script>
"use strict";
var gTarget = document.getElementById("target");
var gEffect = new KeyframeEffectReadOnly(gTarget, { opacity: [0, 1] });
var gTestArguments = [
{
effect: null,
timeline: null,
description: "with null effect and null timeline"
},
{
effect: null,
timeline: document.timeline,
description: "with null effect and non-null timeline"
},
{
effect: gEffect,
timeline: null,
description: "with non-null effect and null timeline"
},
{
effect: gEffect,
timeline: document.timeline,
description: "with non-null effect and non-null timeline"
},
];
gTestArguments.forEach(function(args) {
test(function(t) {
var animation = new Animation(args.effect, args.timeline);
assert_not_equals(animation, null,
"An animation sohuld be created");
assert_equals(animation.effect, args.effect,
"Animation returns the same effect passed to " +
"the Constructor");
assert_equals(animation.timeline, args.timeline,
"Animation returns the same timeline passed to " +
"the Constructor");
assert_equals(animation.playState, "idle",
"Animation.playState should be initially 'idle'");
}, "Animation can be constructed " + args.description);
});
</script>
</body>

View File

@ -6032,11 +6032,22 @@
"description": "The time (in milliseconds) that it took a 'reconfigure thread' request to go round trip."
},
"MEDIA_RUST_MP4PARSE_SUCCESS": {
"alert_emails": ["giles@mozilla.com", "kinetik@flim.org"],
"expires_in_version": "50",
"kind": "boolean",
"bug_numbers": [1220885],
"description": "(Bug 1220885) Whether the rust mp4 demuxer successfully parsed a stream segment.",
"cpp_guard": "MOZ_RUST_MP4PARSE"
},
"MEDIA_RUST_MP4PARSE_ERROR_CODE": {
"alert_emails": ["giles@mozilla.com", "kinetik@flim.org"],
"expires_in_version": "50",
"kind": "enumerated",
"n_values": 32,
"bug_numbers": [1238420],
"description": "The error code reported when an MP4 parse attempt has failed.",
"cpp_guard": "MOZ_RUST_MP4PARSE"
},
"MEDIA_RUST_MP4PARSE_TRACK_MATCH_AUDIO": {
"alert_emails": ["giles@mozilla.com", "kinetik@flim.org"],
"expires_in_version": "50",

View File

@ -64,7 +64,7 @@ PluginWidgetProxy::Create(nsIWidget* aParent,
BaseCreate(aParent, aInitData);
mBounds = aRect.ToUnknownRect();
mBounds = aRect;
mEnabled = true;
mVisible = true;

View File

@ -30,11 +30,11 @@ using namespace mozilla::layers;
using namespace mozilla::widget;
static void
InvalidateRegion(nsIWidget* aWidget, const nsIntRegion& aRegion)
InvalidateRegion(nsIWidget* aWidget, const LayoutDeviceIntRegion& aRegion)
{
nsIntRegionRectIterator it(aRegion);
while(const nsIntRect* r = it.Next()) {
aWidget->Invalidate(LayoutDeviceIntRect::FromUnknownRect(*r));
LayoutDeviceIntRegion::RectIterator it(aRegion);
while(const LayoutDeviceIntRect* r = it.Next()) {
aWidget->Invalidate(*r);
}
}
@ -107,7 +107,7 @@ PuppetWidget::Create(nsIWidget* aParent,
BaseCreate(nullptr, aInitData);
mBounds = aRect.ToUnknownRect();
mBounds = aRect;
mEnabled = true;
mVisible = true;
@ -197,7 +197,7 @@ PuppetWidget::Show(bool aState)
if (!wasVisible && mVisible) {
Resize(mBounds.width, mBounds.height, false);
Invalidate(LayoutDeviceIntRect::FromUnknownRect(mBounds));
Invalidate(mBounds);
}
return NS_OK;
@ -208,8 +208,9 @@ PuppetWidget::Resize(double aWidth,
double aHeight,
bool aRepaint)
{
nsIntRect oldBounds = mBounds;
mBounds.SizeTo(nsIntSize(NSToIntRound(aWidth), NSToIntRound(aHeight)));
LayoutDeviceIntRect oldBounds = mBounds;
mBounds.SizeTo(LayoutDeviceIntSize(NSToIntRound(aWidth),
NSToIntRound(aHeight)));
if (mChild) {
return mChild->Resize(aWidth, aHeight, aRepaint);
@ -218,8 +219,8 @@ PuppetWidget::Resize(double aWidth,
// XXX: roc says that |aRepaint| dictates whether or not to
// invalidate the expanded area
if (oldBounds.Size() < mBounds.Size() && aRepaint) {
nsIntRegion dirty(mBounds);
dirty.Sub(dirty, oldBounds);
LayoutDeviceIntRegion dirty(mBounds);
dirty.Sub(dirty, oldBounds);
InvalidateRegion(this, dirty);
}
@ -1240,7 +1241,7 @@ PuppetWidget::GetWindowPosition()
NS_METHOD
PuppetWidget::GetScreenBounds(LayoutDeviceIntRect& aRect) {
aRect.MoveTo(WidgetToScreenOffset());
aRect.SizeTo(LayoutDeviceIntSize::FromUnknownSize(mBounds.Size()));
aRect.SizeTo(mBounds.Size());
return NS_OK;
}

View File

@ -1075,7 +1075,7 @@ nsWindow::Create(nsIWidget* aParent,
}
}
mBounds = aRect.ToUnknownRect();
mBounds = aRect;
// for toplevel windows, bounds are fixed to full screen size
if (!parent) {

View File

@ -535,11 +535,6 @@ public:
CGFloat DevPixelsToCocoaPoints(int32_t aPixels) const {
return nsCocoaUtils::DevPixelsToCocoaPoints(aPixels, BackingScaleFactor());
}
// XXX: all calls to this function should eventually be replaced with calls
// to DevPixelsToCocoaPoints().
NSRect UntypedDevPixelsToCocoaPoints(const nsIntRect& aRect) const {
return nsCocoaUtils::UntypedDevPixelsToCocoaPoints(aRect, BackingScaleFactor());
}
NSRect DevPixelsToCocoaPoints(const LayoutDeviceIntRect& aRect) const {
return nsCocoaUtils::DevPixelsToCocoaPoints(aRect, BackingScaleFactor());
}

View File

@ -496,7 +496,7 @@ nsresult nsChildView::Create(nsIWidget* aParent,
gChildViewMethodsSwizzled = true;
}
mBounds = aRect.ToUnknownRect();
mBounds = aRect;
// Ensure that the toolkit is created.
nsToolkit::GetToolkit();
@ -522,8 +522,7 @@ nsresult nsChildView::Create(nsIWidget* aParent,
// create our parallel NSView and hook it up to our parent. Recall
// that NS_NATIVE_WIDGET is the NSView.
CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mParentView);
NSRect r = nsCocoaUtils::DevPixelsToCocoaPoints(
LayoutDeviceIntRect::FromUnknownRect(mBounds), scaleFactor);
NSRect r = nsCocoaUtils::DevPixelsToCocoaPoints(mBounds, scaleFactor);
mView = [(NSView<mozView>*)CreateCocoaView(r) retain];
if (!mView) {
return NS_ERROR_FAILURE;
@ -929,9 +928,7 @@ NS_IMETHODIMP nsChildView::SetCursor(imgIContainer* aCursor,
// Get this component dimension
NS_IMETHODIMP nsChildView::GetBounds(LayoutDeviceIntRect& aRect)
{
aRect = !mView
? LayoutDeviceIntRect::FromUnknownRect(mBounds)
: CocoaPointsToDevPixels([mView frame]);
aRect = !mView ? mBounds : CocoaPointsToDevPixels([mView frame]);
return NS_OK;
}
@ -1023,7 +1020,7 @@ NS_IMETHODIMP nsChildView::Move(double aX, double aY)
mBounds.y = y;
ManipulateViewWithoutNeedingDisplay(mView, ^{
[mView setFrame:UntypedDevPixelsToCocoaPoints(mBounds)];
[mView setFrame:DevPixelsToCocoaPoints(mBounds)];
});
NotifyRollupGeometryChange();
@ -1048,7 +1045,7 @@ NS_IMETHODIMP nsChildView::Resize(double aWidth, double aHeight, bool aRepaint)
mBounds.height = height;
ManipulateViewWithoutNeedingDisplay(mView, ^{
[mView setFrame:UntypedDevPixelsToCocoaPoints(mBounds)];
[mView setFrame:DevPixelsToCocoaPoints(mBounds)];
});
if (mVisible && aRepaint)
@ -1087,7 +1084,7 @@ NS_IMETHODIMP nsChildView::Resize(double aX, double aY,
}
ManipulateViewWithoutNeedingDisplay(mView, ^{
[mView setFrame:UntypedDevPixelsToCocoaPoints(mBounds)];
[mView setFrame:DevPixelsToCocoaPoints(mBounds)];
});
if (mVisible && aRepaint)
@ -2650,8 +2647,7 @@ nsChildView::StartRemoteDrawingInRegion(LayoutDeviceIntRegion& aInvalidRegion)
}
LayoutDeviceIntRegion dirtyRegion(aInvalidRegion);
LayoutDeviceIntSize renderSize =
LayoutDeviceIntSize::FromUnknownSize(mBounds.Size());
LayoutDeviceIntSize renderSize = mBounds.Size();
if (!mBasicCompositorImage) {
mBasicCompositorImage = new RectTextureImage(mGLPresenter->gl());
@ -2662,7 +2658,7 @@ nsChildView::StartRemoteDrawingInRegion(LayoutDeviceIntRegion& aInvalidRegion)
if (!drawTarget) {
// Composite unchanged textures.
DoRemoteComposition(LayoutDeviceIntRect::FromUnknownRect(mBounds));
DoRemoteComposition(mBounds);
return nullptr;
}
@ -2675,7 +2671,7 @@ void
nsChildView::EndRemoteDrawing()
{
mBasicCompositorImage->EndUpdate(true);
DoRemoteComposition(LayoutDeviceIntRect::FromUnknownRect(mBounds));
DoRemoteComposition(mBounds);
}
void

View File

@ -184,17 +184,6 @@ public:
(CGFloat)aPt.y / aBackingScale);
}
// XXX: all calls to this function should eventually be replaced with calls
// to DevPixelsToCocoaPoints().
static NSRect
UntypedDevPixelsToCocoaPoints(const nsIntRect& aRect, CGFloat aBackingScale)
{
return NSMakeRect((CGFloat)aRect.x / aBackingScale,
(CGFloat)aRect.y / aBackingScale,
(CGFloat)aRect.width / aBackingScale,
(CGFloat)aRect.height / aBackingScale);
}
static NSRect
DevPixelsToCocoaPoints(const LayoutDeviceIntRect& aRect,
CGFloat aBackingScale)

View File

@ -1621,8 +1621,8 @@ nsCocoaWindow::UpdateBounds()
if (mWindow) {
frame = [mWindow frame];
}
mBounds = nsCocoaUtils::CocoaRectToGeckoRectDevPix(
frame, BackingScaleFactor()).ToUnknownRect();
mBounds =
nsCocoaUtils::CocoaRectToGeckoRectDevPix(frame, BackingScaleFactor());
}
NS_IMETHODIMP nsCocoaWindow::GetScreenBounds(LayoutDeviceIntRect &aRect)
@ -1631,10 +1631,10 @@ NS_IMETHODIMP nsCocoaWindow::GetScreenBounds(LayoutDeviceIntRect &aRect)
#ifdef DEBUG
LayoutDeviceIntRect r = nsCocoaUtils::CocoaRectToGeckoRectDevPix([mWindow frame], BackingScaleFactor());
NS_ASSERTION(mWindow && mBounds == r.ToUnknownRect(), "mBounds out of sync!");
NS_ASSERTION(mWindow && mBounds == r, "mBounds out of sync!");
#endif
aRect = LayoutDeviceIntRect::FromUnknownRect(mBounds);
aRect = mBounds;
return NS_OK;
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
@ -2025,8 +2025,7 @@ LayoutDeviceIntPoint nsCocoaWindow::GetClientOffset()
LayoutDeviceIntRect clientRect;
GetClientBounds(clientRect);
return clientRect.TopLeft() -
LayoutDeviceIntPoint::FromUnknownPoint(mBounds.TopLeft());
return clientRect.TopLeft() - mBounds.TopLeft();
NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(LayoutDeviceIntPoint(0, 0));
}

View File

@ -344,13 +344,13 @@ nsWindow::Create(nsIWidget* aParent,
mScreen = static_cast<nsScreenGonk*>(screen.get());
mBounds = aRect.ToUnknownRect();
mBounds = aRect;
mParent = (nsWindow *)aParent;
mVisible = false;
if (!aParent) {
mBounds = mScreen->GetRect().ToUnknownRect();
mBounds = mScreen->GetRect();
}
mComposer2D = HwcComposer2D::GetInstance();
@ -448,14 +448,14 @@ nsWindow::Resize(double aX,
double aHeight,
bool aRepaint)
{
mBounds = nsIntRect(NSToIntRound(aX), NSToIntRound(aY),
NSToIntRound(aWidth), NSToIntRound(aHeight));
mBounds = LayoutDeviceIntRect(NSToIntRound(aX), NSToIntRound(aY),
NSToIntRound(aWidth), NSToIntRound(aHeight));
if (mWidgetListener) {
mWidgetListener->WindowResized(this, mBounds.width, mBounds.height);
}
if (aRepaint) {
Invalidate(LayoutDeviceIntRect::FromUnknownRect(mBounds));
Invalidate(mBounds);
}
return NS_OK;
@ -726,7 +726,7 @@ nsWindow::BringToTop()
mWidgetListener->WindowActivated();
}
Invalidate(LayoutDeviceIntRect::FromUnknownRect(mBounds));
Invalidate(mBounds);
}
void

View File

@ -1497,7 +1497,7 @@ nsWindow::GetScreenBounds(LayoutDeviceIntRect& aRect)
// bounds (bug 581863). gdk_window_get_frame_extents would give the
// frame bounds, but mBounds.Size() is returned here for consistency
// with Resize.
aRect.SizeTo(LayoutDeviceIntSize::FromUnknownSize(mBounds.Size()));
aRect.SizeTo(mBounds.Size());
LOG(("GetScreenBounds %d,%d | %dx%d\n",
aRect.x, aRect.y, aRect.width, aRect.height));
return NS_OK;
@ -2419,7 +2419,7 @@ nsWindow::OnConfigureEvent(GtkWidget *aWidget, GdkEventConfigure *aEvent)
return FALSE;
}
mBounds.MoveTo(screenBounds.TopLeft().ToUnknownPoint());
mBounds.MoveTo(screenBounds.TopLeft());
// XXX mozilla will invalidate the entire window after this move
// complete. wtf?
@ -2450,7 +2450,7 @@ nsWindow::OnSizeAllocate(GtkAllocation *aAllocation)
(void *)this, aAllocation->x, aAllocation->y,
aAllocation->width, aAllocation->height));
nsIntSize size = GdkRectToDevicePixels(*aAllocation).Size();
LayoutDeviceIntSize size = GdkRectToDevicePixels(*aAllocation).Size();
if (mBounds.Size() == size)
return;
@ -3542,7 +3542,7 @@ nsWindow::Create(nsIWidget* aParent,
CommonCreate(aParent, listenForResizes);
// save our bounds
mBounds = aRect.ToUnknownRect();
mBounds = aRect;
ConstrainSize(&mBounds.width, &mBounds.height);
// figure out our parent window
@ -4035,7 +4035,7 @@ nsWindow::NativeResize()
}
GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mBounds.Size());
LOG(("nsWindow::NativeResize [%p] %d %d\n", (void *)this,
size.width, size.height));
@ -4301,14 +4301,12 @@ nsWindow::ConfigureChildren(const nsTArray<Configuration>& aConfigurations)
nsWindow* w = static_cast<nsWindow*>(configuration.mChild.get());
NS_ASSERTION(w->GetParent() == this,
"Configured widget is not a child");
LayoutDeviceIntRect wBounds =
LayoutDeviceIntRect::FromUnknownRect(w->mBounds);
w->SetWindowClipRegion(configuration.mClipRegion, true);
if (wBounds.Size() != configuration.mBounds.Size()) {
if (w->mBounds.Size() != configuration.mBounds.Size()) {
w->Resize(configuration.mBounds.x, configuration.mBounds.y,
configuration.mBounds.width, configuration.mBounds.height,
true);
} else if (wBounds.TopLeft() != configuration.mBounds.TopLeft()) {
} else if (w->mBounds.TopLeft() != configuration.mBounds.TopLeft()) {
w->Move(configuration.mBounds.x, configuration.mBounds.y);
}
w->SetWindowClipRegion(configuration.mClipRegion, false);
@ -6702,7 +6700,7 @@ nsWindow::DevicePixelsToGdkCoordRoundDown(int pixels) {
}
GdkPoint
nsWindow::DevicePixelsToGdkPointRoundDown(nsIntPoint point) {
nsWindow::DevicePixelsToGdkPointRoundDown(LayoutDeviceIntPoint point) {
gint scale = GdkScaleFactor();
return { point.x / scale, point.y / scale };
}
@ -6718,7 +6716,7 @@ nsWindow::DevicePixelsToGdkRectRoundOut(LayoutDeviceIntRect rect) {
}
GdkRectangle
nsWindow::DevicePixelsToGdkSizeRoundUp(nsIntSize pixelSize) {
nsWindow::DevicePixelsToGdkSizeRoundUp(LayoutDeviceIntSize pixelSize) {
gint scale = GdkScaleFactor();
gint width = (pixelSize.width + scale - 1) / scale;
gint height = (pixelSize.height + scale - 1) / scale;
@ -6744,13 +6742,13 @@ nsWindow::GdkPointToDevicePixels(GdkPoint point) {
point.y * scale);
}
nsIntRect
LayoutDeviceIntRect
nsWindow::GdkRectToDevicePixels(GdkRectangle rect) {
gint scale = GdkScaleFactor();
return nsIntRect(rect.x * scale,
rect.y * scale,
rect.width * scale,
rect.height * scale);
return LayoutDeviceIntRect(rect.x * scale,
rect.y * scale,
rect.width * scale,
rect.height * scale);
}
nsresult

View File

@ -340,14 +340,14 @@ public:
// To GDK
gint DevicePixelsToGdkCoordRoundUp(int pixels);
gint DevicePixelsToGdkCoordRoundDown(int pixels);
GdkPoint DevicePixelsToGdkPointRoundDown(nsIntPoint point);
GdkRectangle DevicePixelsToGdkSizeRoundUp(nsIntSize pixelSize);
GdkPoint DevicePixelsToGdkPointRoundDown(LayoutDeviceIntPoint point);
GdkRectangle DevicePixelsToGdkSizeRoundUp(LayoutDeviceIntSize pixelSize);
// From GDK
int GdkCoordToDevicePixels(gint coord);
LayoutDeviceIntPoint GdkPointToDevicePixels(GdkPoint point);
LayoutDeviceIntPoint GdkEventCoordsToDevicePixels(gdouble x, gdouble y);
nsIntRect GdkRectToDevicePixels(GdkRectangle rect);
LayoutDeviceIntRect GdkRectToDevicePixels(GdkRectangle rect);
protected:
virtual ~nsWindow();

View File

@ -1443,7 +1443,7 @@ NS_METHOD nsBaseWidget::GetClientBounds(LayoutDeviceIntRect &aRect)
**/
NS_METHOD nsBaseWidget::GetBounds(LayoutDeviceIntRect &aRect)
{
aRect = LayoutDeviceIntRect::FromUnknownRect(mBounds);
aRect = mBounds;
return NS_OK;
}

View File

@ -515,7 +515,7 @@ protected:
RefPtr<TextEventDispatcher> mTextEventDispatcher;
nsCursor mCursor;
nsBorderStyle mBorderStyle;
nsIntRect mBounds;
LayoutDeviceIntRect mBounds;
LayoutDeviceIntRect* mOriginalBounds;
// When this pointer is null, the widget is not clipped
mozilla::UniquePtr<LayoutDeviceIntRect[]> mClipRects;

View File

@ -151,7 +151,7 @@ nsWindow::Create(nsIWidget* aParent,
mParent = (nsWindow *)aParent;
// save our bounds
mBounds = aRect.ToUnknownRect();
mBounds = aRect;
// find native parent
MozQWidget *parent = nullptr;
@ -466,8 +466,7 @@ nsWindow::Resize(double aWidth, double aHeight, bool aRepaint)
// synthesize a resize event if this isn't a toplevel
if (mIsTopLevel || mListenForResizes) {
nsEventStatus status;
DispatchResizeEvent(LayoutDeviceIntRect::FromUnknownRect(mBounds),
status);
DispatchResizeEvent(mBounds, status);
}
NotifyRollupGeometryChange();
@ -530,8 +529,7 @@ nsWindow::Resize(double aX, double aY, double aWidth, double aHeight,
if (mIsTopLevel || mListenForResizes) {
// synthesize a resize event
nsEventStatus status;
DispatchResizeEvent(LayoutDeviceIntRect::FromUnknownRect(mBounds),
status);
DispatchResizeEvent(mBounds, status);
}
if (aRepaint) {
@ -603,13 +601,11 @@ nsWindow::ConfigureChildren(const nsTArray<nsIWidget::Configuration>& aConfigura
NS_ASSERTION(w->GetParent() == this,
"Configured widget is not a child");
LayoutDeviceIntRect wBounds =
LayoutDeviceIntRect::FromUnknownRect(w->mBounds);
if (wBounds.Size() != configuration.mBounds.Size()) {
if (w->mBounds.Size() != configuration.mBounds.Size()) {
w->Resize(configuration.mBounds.x, configuration.mBounds.y,
configuration.mBounds.width, configuration.mBounds.height,
true);
} else if (wBounds.TopLeft() != configuration.mBounds.TopLeft()) {
} else if (w->mBounds.TopLeft() != configuration.mBounds.TopLeft()) {
w->Move(configuration.mBounds.x, configuration.mBounds.y);
}
}
@ -1503,8 +1499,7 @@ void find_first_visible_parent(QWindow* aItem, QWindow*& aVisibleItem)
NS_IMETHODIMP
nsWindow::GetScreenBounds(LayoutDeviceIntRect& aRect)
{
aRect = LayoutDeviceIntRect(LayoutDeviceIntPoint(0, 0),
LayoutDeviceIntSize::FromUnknownSize(mBounds.Size()));
aRect = LayoutDeviceIntRect(LayoutDeviceIntPoint(0, 0), mBounds.Size());
if (mIsTopLevel) {
QPoint pos = mWidget->position();
aRect.MoveTo(pos.x(), pos.y());

View File

@ -45,7 +45,7 @@ public:
NS_DECL_NSISCREENMANAGER
static nsIntRect GetBounds();
static LayoutDeviceIntRect GetBounds();
private:
virtual ~UIKitScreenManager () {}

View File

@ -9,7 +9,7 @@
#include "nsScreenManager.h"
#include "nsAppShell.h"
static nsIntRect gScreenBounds;
static LayoutDeviceIntRect gScreenBounds;
static bool gScreenBoundsSet = false;
UIKitScreen::UIKitScreen(UIScreen* aScreen)
@ -83,7 +83,7 @@ UIKitScreenManager::UIKitScreenManager()
{
}
nsIntRect
LayoutDeviceIntRect
UIKitScreenManager::GetBounds()
{
if (!gScreenBoundsSet) {

View File

@ -51,7 +51,7 @@ UIKitPointsToDevPixels(CGPoint aPoint, CGFloat aBackingScale)
}
static CGRect
DevPixelsToUIKitPoints(const nsIntRect& aRect, CGFloat aBackingScale)
DevPixelsToUIKitPoints(const LayoutDeviceIntRect& aRect, CGFloat aBackingScale)
{
return CGRectMake((CGFloat)aRect.x / aBackingScale,
(CGFloat)aRect.y / aBackingScale,
@ -493,7 +493,7 @@ nsWindow::Create(nsIWidget* aParent,
mBounds.height = cgRect.size.height;
}
} else {
mBounds = aRect.ToUnknownRect();
mBounds = aRect;
}
ALOG("nsWindow[%p]::Create bounds: %d %d %d %d", (void*)this,

View File

@ -4099,6 +4099,7 @@ TSFTextStore::RecordCompositionStartAction(ITfCompositionView* aComposition,
}
lockedContent.StartComposition(aComposition, *action, aPreserveSelection);
action->mData = mComposition.mString;
MOZ_LOG(sTextStoreLog, LogLevel::Info,
("TSF: 0x%p TSFTextStore::RecordCompositionStartAction() succeeded: "
@ -4137,6 +4138,33 @@ TSFTextStore::RecordCompositionEndAction()
}
lockedContent.EndComposition(*action);
// If this composition was restart but the composition doesn't modify
// anything, we should remove the pending composition for preventing to
// dispatch redundant composition events.
for (size_t i = mPendingActions.Length(), j = 1; i > 0; --i, ++j) {
PendingAction& pendingAction = mPendingActions[i - 1];
if (pendingAction.mType == PendingAction::COMPOSITION_START) {
if (pendingAction.mData != action->mData) {
break;
}
// When only setting selection is necessary, we should append it.
if (pendingAction.mAdjustSelection) {
PendingAction* setSelection = mPendingActions.AppendElement();
setSelection->mType = PendingAction::SET_SELECTION;
setSelection->mSelectionStart = pendingAction.mSelectionStart;
setSelection->mSelectionLength = pendingAction.mSelectionLength;
setSelection->mSelectionReversed = false;
}
// Remove the redundant pending composition.
mPendingActions.RemoveElementsAt(i - 1, j);
MOZ_LOG(sTextStoreLog, LogLevel::Info,
("TSF: 0x%p TSFTextStore::RecordCompositionEndAction(), "
"succeeded, but the composition was canceled due to redundant",
this));
return S_OK;
}
}
MOZ_LOG(sTextStoreLog, LogLevel::Info,
("TSF: 0x%p TSFTextStore::RecordCompositionEndAction(), succeeded",
this));

View File

@ -518,7 +518,7 @@ protected:
// For compositionstart and selectionset
LONG mSelectionStart;
LONG mSelectionLength;
// For compositionupdate and compositionend
// For compositionstart, compositionupdate and compositionend
nsString mData;
// For compositionupdate
RefPtr<TextRangeArray> mRanges;

View File

@ -189,6 +189,7 @@
#include "mozilla/layers/APZCTreeManager.h"
#include "mozilla/layers/InputAPZContext.h"
#include "ClientLayerManager.h"
#include "InputData.h"
#include "mozilla/Telemetry.h"
@ -547,7 +548,7 @@ nsWindow::Create(nsIWidget* aParent,
nullptr : aParent;
mIsTopWidgetWindow = (nullptr == baseParent);
mBounds = aRect.ToUnknownRect();
mBounds = aRect;
// Ensure that the toolkit is created.
nsToolkit::GetToolkit();
@ -1410,6 +1411,20 @@ nsWindow::SetSizeConstraints(const SizeConstraints& aConstraints)
c.mMinSize.width = std::max(int32_t(::GetSystemMetrics(SM_CXMINTRACK)), c.mMinSize.width);
c.mMinSize.height = std::max(int32_t(::GetSystemMetrics(SM_CYMINTRACK)), c.mMinSize.height);
}
ClientLayerManager *clientLayerManager =
(GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_CLIENT)
? static_cast<ClientLayerManager*>(GetLayerManager())
: nullptr;
if (clientLayerManager) {
int32_t maxSize = clientLayerManager->GetMaxTextureSize();
// We can't make ThebesLayers bigger than this anyway.. no point it letting
// a window grow bigger as we won't be able to draw content there in
// general.
c.mMaxSize.width = std::min(c.mMaxSize.width, maxSize);
c.mMaxSize.height = std::min(c.mMaxSize.height, maxSize);
}
mSizeConstraintsScale = GetDefaultScale().scale;
nsBaseWidget::SetSizeConstraints(c);
@ -2015,7 +2030,7 @@ NS_METHOD nsWindow::GetBounds(LayoutDeviceIntRect& aRect)
aRect.x = r.left;
aRect.y = r.top;
} else {
aRect = LayoutDeviceIntRect::FromUnknownRect(mBounds);
aRect = mBounds;
}
return NS_OK;
}
@ -2051,7 +2066,7 @@ NS_METHOD nsWindow::GetScreenBounds(LayoutDeviceIntRect& aRect)
aRect.x = r.left;
aRect.y = r.top;
} else {
aRect = LayoutDeviceIntRect::FromUnknownRect(mBounds);
aRect = mBounds;
}
return NS_OK;
}

View File

@ -585,7 +585,7 @@ protected:
HDC mPaintDC; // only set during painting
HDC mCompositeDC; // only set during StartRemoteDrawing
nsIntRect mLastPaintBounds;
LayoutDeviceIntRect mLastPaintBounds;
// Used for displayport suppression during window resize
enum ResizeState {

View File

@ -274,33 +274,6 @@ PLDHashTable::Hash2(PLDHashNumber aHash,
// uses the high order bits of mKeyHash, so this least-significant reservation
// should not hurt the hash function's effectiveness much.
/* static */ MOZ_ALWAYS_INLINE bool
PLDHashTable::EntryIsFree(PLDHashEntryHdr* aEntry)
{
return aEntry->mKeyHash == 0;
}
/* static */ MOZ_ALWAYS_INLINE bool
PLDHashTable::EntryIsRemoved(PLDHashEntryHdr* aEntry)
{
return aEntry->mKeyHash == 1;
}
/* static */ MOZ_ALWAYS_INLINE bool
PLDHashTable::EntryIsLive(PLDHashEntryHdr* aEntry)
{
return aEntry->mKeyHash >= 2;
}
/* static */ MOZ_ALWAYS_INLINE void
PLDHashTable::MarkEntryFree(PLDHashEntryHdr* aEntry)
{
aEntry->mKeyHash = 0;
}
/* static */ MOZ_ALWAYS_INLINE void
PLDHashTable::MarkEntryRemoved(PLDHashEntryHdr* aEntry)
{
aEntry->mKeyHash = 1;
}
// Match an entry's mKeyHash against an unstored one computed from a key.
/* static */ bool
PLDHashTable::MatchEntryKeyhash(PLDHashEntryHdr* aEntry, PLDHashNumber aKeyHash)
@ -783,12 +756,6 @@ PLDHashTable::Iterator::~Iterator()
}
}
bool
PLDHashTable::Iterator::Done() const
{
return mNexts == mNextsLimit;
}
MOZ_ALWAYS_INLINE bool
PLDHashTable::Iterator::IsOnNonLiveEntry() const
{
@ -805,16 +772,6 @@ PLDHashTable::Iterator::MoveToNextEntry()
}
}
PLDHashEntryHdr*
PLDHashTable::Iterator::Get() const
{
MOZ_ASSERT(!Done());
PLDHashEntryHdr* entry = reinterpret_cast<PLDHashEntryHdr*>(mCurrent);
MOZ_ASSERT(EntryIsLive(entry));
return entry;
}
void
PLDHashTable::Iterator::Next()
{

View File

@ -450,9 +450,21 @@ public:
Iterator(Iterator&& aOther);
~Iterator();
bool Done() const; // Have we finished?
PLDHashEntryHdr* Get() const; // Get the current entry.
void Next(); // Advance to the next entry.
// Have we finished?
bool Done() const { return mNexts == mNextsLimit; }
// Get the current entry.
PLDHashEntryHdr* Get() const
{
MOZ_ASSERT(!Done());
PLDHashEntryHdr* entry = reinterpret_cast<PLDHashEntryHdr*>(mCurrent);
MOZ_ASSERT(EntryIsLive(entry));
return entry;
}
// Advance to the next entry.
void Next();
// Remove the current entry. Must only be called once per entry, and Get()
// must not be called on that entry afterwards.
@ -498,11 +510,27 @@ private:
static const PLDHashNumber kCollisionFlag = 1;
static bool EntryIsFree(PLDHashEntryHdr* aEntry);
static bool EntryIsRemoved(PLDHashEntryHdr* aEntry);
static bool EntryIsLive(PLDHashEntryHdr* aEntry);
static void MarkEntryFree(PLDHashEntryHdr* aEntry);
static void MarkEntryRemoved(PLDHashEntryHdr* aEntry);
static bool EntryIsFree(PLDHashEntryHdr* aEntry)
{
return aEntry->mKeyHash == 0;
}
static bool EntryIsRemoved(PLDHashEntryHdr* aEntry)
{
return aEntry->mKeyHash == 1;
}
static bool EntryIsLive(PLDHashEntryHdr* aEntry)
{
return aEntry->mKeyHash >= 2;
}
static void MarkEntryFree(PLDHashEntryHdr* aEntry)
{
aEntry->mKeyHash = 0;
}
static void MarkEntryRemoved(PLDHashEntryHdr* aEntry)
{
aEntry->mKeyHash = 1;
}
PLDHashNumber Hash1(PLDHashNumber aHash0);
void Hash2(PLDHashNumber aHash, uint32_t& aHash2Out, uint32_t& aSizeMaskOut);

View File

@ -13,6 +13,7 @@
#include "mozilla/Attributes.h"
#include "mozilla/BinarySearch.h"
#include "mozilla/fallible.h"
#include "mozilla/InitializerList.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/Move.h"
@ -867,6 +868,7 @@ public:
// |const nsTArray_Impl<E, OtherAlloc>&|.
explicit nsTArray_Impl(const self_type& aOther) { AppendElements(aOther); }
explicit nsTArray_Impl(std::initializer_list<E> aIL) { AppendElements(aIL.begin(), aIL.size()); }
// Allow converting to a const array with a different kind of allocator,
// Since the allocator doesn't matter for const arrays
template<typename Allocator>
@ -2101,6 +2103,7 @@ public:
explicit nsTArray(size_type aCapacity) : base_type(aCapacity) {}
explicit nsTArray(const nsTArray& aOther) : base_type(aOther) {}
MOZ_IMPLICIT nsTArray(nsTArray&& aOther) : base_type(mozilla::Move(aOther)) {}
MOZ_IMPLICIT nsTArray(std::initializer_list<E> aIL) : base_type(aIL) {}
template<class Allocator>
explicit nsTArray(const nsTArray_Impl<E, Allocator>& aOther)