зеркало из https://github.com/mozilla/gecko-dev.git
Merge b2g-inbound to m-c.
This commit is contained in:
Коммит
4ee712cb39
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"config_version": 2,
|
||||
"tooltool_manifest": "releng-emulator-ics.tt",
|
||||
"mock_target": "mozilla-centos6-x86_64",
|
||||
"mock_packages": ["ccache", "make", "bison", "flex", "gcc", "g++", "mpfr", "zlib-devel", "ncurses-devel", "zip", "autoconf213", "glibc-static", "perl-Digest-SHA", "wget", "alsa-lib", "atk", "cairo", "dbus-glib", "fontconfig", "freetype", "glib2", "gtk2", "libXRender", "libXt", "pango", "mozilla-python27-mercurial", "openssh-clients", "nss-devel", "glibc-devel.i686", "libstdc++.i686", "zlib-devel.i686", "ncurses-devel.i686", "libX11-devel.i686", "mesa-libGL-devel.i686", "mesa-libGL-devel", "libX11-devel", "git"],
|
||||
"mock_files": [["/home/cltbld/.ssh", "/home/mock_mozilla/.ssh"]],
|
||||
"build_targets": ["droid", "package-emulator", "package-tests"],
|
||||
"upload_files": [
|
||||
"{workdir}/out/target/product/generic/*.tar.bz2",
|
||||
"{workdir}/out/target/product/generic/tests/*.zip",
|
||||
"{workdir}/out/emulator.tar.gz",
|
||||
"{objdir}/dist/b2g-*.crashreporter-symbols.zip",
|
||||
"{workdir}/sources.xml"
|
||||
],
|
||||
"upload_platform": "emulator-ics",
|
||||
"gecko_l10n_root": "http://hg.mozilla.org/l10n-central",
|
||||
"gaia": {
|
||||
"l10n": {
|
||||
"vcs": "hgtool",
|
||||
"root": "http://hg.mozilla.org/gaia-l10n"
|
||||
}
|
||||
},
|
||||
"b2g_manifest": "emulator.xml",
|
||||
"b2g_manifest_branch": "master"
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
[
|
||||
]
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"revision": "43bc58df001f2b870fd2d1eb80723cfdccdc7b67",
|
||||
"revision": "c9f88878f616a439db6c6be66378af79249f7528",
|
||||
"repo_path": "/integration/gaia-central"
|
||||
}
|
||||
|
|
|
@ -30,99 +30,293 @@ NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper)
|
|||
NS_IMPL_ADDREF_INHERITED(MediaRecorder, nsDOMEventTargetHelper)
|
||||
NS_IMPL_RELEASE_INHERITED(MediaRecorder, nsDOMEventTargetHelper)
|
||||
|
||||
// This task is used for triggering the creating blob object and it runs at main thread.
|
||||
class MediaRecorder::PushBlobTask : public nsRunnable
|
||||
/**
|
||||
* Session is an object to represent a single recording event.
|
||||
* In original design, all recording context is stored in MediaRecorder, which causes
|
||||
* a problem if someone calls MediaRecoder::Stop and MedaiRecorder::Start quickly.
|
||||
* To prevent blocking main thread, media encoding is executed in a second thread,
|
||||
* named as Read Thread. For the same reason, we do not wait Read Thread shutdown in
|
||||
* MediaRecorder::Stop. If someone call MediaRecoder::Start before Read Thread shutdown,
|
||||
* the same recording context in MediaRecoder might be access by two Reading Threads,
|
||||
* which cause a problem.
|
||||
* In the new design, we put recording context into Session object, including Read
|
||||
* Thread. Each Session has its own recording context and Read Thread, problem is been
|
||||
* resolved.
|
||||
*
|
||||
* Life cycle of a Session object.
|
||||
* 1) Initialization Stage (in main thread)
|
||||
* Setup media streams in MSG, and bind MediaEncoder with Source Stream.
|
||||
* Resource allocation, such as encoded data cache buffer and MediaEncoder.
|
||||
* Create read thread.
|
||||
* Automatically switch to Extract stage in the end of this stage.
|
||||
* 2) Extract Stage (in Read Thread)
|
||||
* Pull encoded A/V frames from MediaEncoder, dispatch to OnDataAvailable handler.
|
||||
* Unless a client calls Session::Stop, Session object keeps stay in this stage.
|
||||
* 3) Destroy Stage (in main thread)
|
||||
* Switch from Extract stage to Destroy stage by calling Session::Stop.
|
||||
* Release session resource and remove associated streams from MSG.
|
||||
*
|
||||
* Lifetime of a Session object.
|
||||
* 1) MediaRecorder creates a Session in MediaRecorder::Start function.
|
||||
* 2) A Session is destroyed in DestroyRunnable after MediaRecorder::Stop being called
|
||||
* _and_ all encoded media data been passed to OnDataAvailable handler.
|
||||
*/
|
||||
class MediaRecorder::Session
|
||||
{
|
||||
public:
|
||||
PushBlobTask(MediaRecorder* recorder)
|
||||
: mRecorder(recorder) {}
|
||||
|
||||
NS_IMETHODIMP Run()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
nsresult rv = mRecorder->CreateAndDispatchBlobEvent();
|
||||
if (NS_FAILED(rv)) {
|
||||
mRecorder->NotifyError(rv);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
MediaRecorder* mRecorder;
|
||||
};
|
||||
|
||||
// This task is for firing the error message from encoder and it runs in main thread
|
||||
class MediaRecorder::PushErrorMessageTask : public nsRunnable
|
||||
{
|
||||
public:
|
||||
PushErrorMessageTask(MediaRecorder* recorder, nsresult aError)
|
||||
: mRecorder(recorder),
|
||||
mError(aError) { }
|
||||
|
||||
NS_IMETHODIMP Run()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
mRecorder->NotifyError(mError);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
MediaRecorder* mRecorder;
|
||||
nsresult mError;
|
||||
};
|
||||
|
||||
// This class is used for avoiding abort by directly use the NS_NewRunnableMethod and invoke ExtractEncodedData function
|
||||
// The abort is checking if the destructor runs at main thread during the cycle-collect step in nsDOMEventTargetHelper
|
||||
class MediaRecorder::ExtractEncodedDataTask : public nsRunnable
|
||||
{
|
||||
public:
|
||||
ExtractEncodedDataTask(MediaRecorder* aRecorder, MediaEncoder* aEncoder)
|
||||
: mRecorder(aRecorder),
|
||||
mEncoder(aEncoder) {}
|
||||
|
||||
class ReleaseEncoderThreadTask : public nsRunnable
|
||||
// Main thread task.
|
||||
// Create a blob event and send back to client.
|
||||
class PushBlobRunnable : public nsRunnable
|
||||
{
|
||||
public:
|
||||
ReleaseEncoderThreadTask(already_AddRefed<MediaRecorder> recorder)
|
||||
: mRecorder(recorder) {}
|
||||
PushBlobRunnable(Session* aSession)
|
||||
: mSession(aSession)
|
||||
{ }
|
||||
|
||||
NS_IMETHODIMP Run()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
mRecorder->mReadThread->Shutdown();
|
||||
mRecorder->mReadThread = nullptr;
|
||||
|
||||
// Setting mState to Inactive here is for the case where SourceStream
|
||||
// ends itself, thus the recorder should stop itself too.
|
||||
mRecorder->mState = RecordingState::Inactive;
|
||||
mRecorder->DispatchSimpleEvent(NS_LITERAL_STRING("stop"));
|
||||
MediaRecorder *recorder = mSession->mRecorder;
|
||||
nsresult rv = recorder->CreateAndDispatchBlobEvent(mSession);
|
||||
if (NS_FAILED(rv)) {
|
||||
recorder->NotifyError(rv);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
nsRefPtr<MediaRecorder> mRecorder;
|
||||
Session *mSession;
|
||||
};
|
||||
|
||||
NS_IMETHODIMP Run()
|
||||
// Record thread task.
|
||||
// Fetch encoded Audio/Video data from MediaEncoder.
|
||||
class ExtractRunnable : public nsRunnable
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
mRecorder->ExtractEncodedData();
|
||||
NS_DispatchToMainThread(new ReleaseEncoderThreadTask(mRecorder.forget()));
|
||||
return NS_OK;
|
||||
public:
|
||||
ExtractRunnable(Session *aSession)
|
||||
: mSession(aSession) {}
|
||||
|
||||
NS_IMETHODIMP Run()
|
||||
{
|
||||
MOZ_ASSERT(NS_GetCurrentThread() == mSession->mReadThread);
|
||||
|
||||
mSession->Extract();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
Session *mSession;
|
||||
};
|
||||
|
||||
// Main thread task.
|
||||
// To delete RecordingSession object.
|
||||
class DestroyRunnable : public nsRunnable
|
||||
{
|
||||
public:
|
||||
DestroyRunnable(Session *aSession)
|
||||
: mSession(aSession) {}
|
||||
|
||||
NS_IMETHODIMP Run()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread() && mSession.get());
|
||||
MediaRecorder *recorder = mSession->mRecorder;
|
||||
|
||||
// If MediaRecoder is not in Inactive mode, call MediaRecoder::Stop
|
||||
// and dispatch DestroyRunnable again.
|
||||
if (recorder->mState != RecordingState::Inactive) {
|
||||
ErrorResult result;
|
||||
recorder->Stop(result);
|
||||
NS_DispatchToMainThread(new DestroyRunnable(mSession.forget()));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Dispatch stop event and clear MIME type.
|
||||
recorder->DispatchSimpleEvent(NS_LITERAL_STRING("stop"));
|
||||
recorder->SetMimeType(NS_LITERAL_STRING(""));
|
||||
|
||||
// Delete session object.
|
||||
mSession = nullptr;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
nsAutoPtr<Session> mSession;
|
||||
};
|
||||
|
||||
friend class PushBlobRunnable;
|
||||
friend class ExtractRunnable;
|
||||
friend class DestroyRunnable;
|
||||
|
||||
public:
|
||||
Session(MediaRecorder* aRecorder, int32_t aTimeSlice)
|
||||
: mRecorder(aRecorder),
|
||||
mTimeSlice(aTimeSlice)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
mEncodedBufferCache = new EncodedBufferCache(MAX_ALLOW_MEMORY_BUFFER);
|
||||
}
|
||||
|
||||
// Only DestroyRunnable is allowed to delete Session object.
|
||||
~Session()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (mInputPort.get()) {
|
||||
mInputPort->Destroy();
|
||||
}
|
||||
|
||||
if (mTrackUnionStream.get()) {
|
||||
mTrackUnionStream->Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
SetupStreams();
|
||||
|
||||
// Create a thread to read encode media data from MediaEncoder.
|
||||
if (!mReadThread) {
|
||||
nsresult rv = NS_NewNamedThread("Media Encoder", getter_AddRefs(mReadThread));
|
||||
if (NS_FAILED(rv)) {
|
||||
mRecorder->NotifyError(rv);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
mReadThread->Dispatch(new ExtractRunnable(this), NS_DISPATCH_NORMAL);
|
||||
}
|
||||
|
||||
void Stop()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
// Shutdown mEncoder to stop Session::Extract
|
||||
if (mInputPort.get())
|
||||
{
|
||||
mInputPort->Destroy();
|
||||
mInputPort = nullptr;
|
||||
}
|
||||
|
||||
if (mTrackUnionStream.get())
|
||||
{
|
||||
mTrackUnionStream->Destroy();
|
||||
mTrackUnionStream = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Pause()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread() && mTrackUnionStream);
|
||||
|
||||
mTrackUnionStream->ChangeExplicitBlockerCount(-1);
|
||||
}
|
||||
|
||||
void Resume()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread() && mTrackUnionStream);
|
||||
|
||||
mTrackUnionStream->ChangeExplicitBlockerCount(1);
|
||||
}
|
||||
|
||||
already_AddRefed<nsIDOMBlob> GetEncodedData()
|
||||
{
|
||||
nsString mimeType;
|
||||
mRecorder->GetMimeType(mimeType);
|
||||
return mEncodedBufferCache->ExtractBlob(mimeType);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
// Pull encoded meida data from MediaEncoder and put into EncodedBufferCache.
|
||||
// Destroy this session object in the end of this function.
|
||||
void Extract()
|
||||
{
|
||||
MOZ_ASSERT(NS_GetCurrentThread() == mReadThread);
|
||||
|
||||
TimeStamp lastBlobTimeStamp = TimeStamp::Now();
|
||||
// Whether push encoded data back to onDataAvailable automatically.
|
||||
const bool pushBlob = (mTimeSlice > 0) ? true : false;
|
||||
|
||||
do {
|
||||
// Pull encoded media data from MediaEncoder
|
||||
nsTArray<nsTArray<uint8_t> > encodedBuf;
|
||||
nsString mimeType;
|
||||
mEncoder->GetEncodedData(&encodedBuf, mimeType);
|
||||
|
||||
mRecorder->SetMimeType(mimeType);
|
||||
|
||||
// Append pulled data into cache buffer.
|
||||
for (uint32_t i = 0; i < encodedBuf.Length(); i++) {
|
||||
mEncodedBufferCache->AppendBuffer(encodedBuf[i]);
|
||||
}
|
||||
|
||||
if (pushBlob) {
|
||||
if ((TimeStamp::Now() - lastBlobTimeStamp).ToMilliseconds() > mTimeSlice) {
|
||||
NS_DispatchToMainThread(new PushBlobRunnable(this));
|
||||
lastBlobTimeStamp = TimeStamp::Now();
|
||||
}
|
||||
}
|
||||
} while (!mEncoder->IsShutdown());
|
||||
|
||||
// Flush out remainding encoded data.
|
||||
NS_DispatchToMainThread(new PushBlobRunnable(this));
|
||||
|
||||
// Destroy this session object in main thread.
|
||||
NS_DispatchToMainThread(new DestroyRunnable(this));
|
||||
}
|
||||
|
||||
// Bind media source with MediaEncoder to receive raw media data.
|
||||
void SetupStreams()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
MediaStreamGraph* gm = mRecorder->mStream->GetStream()->Graph();
|
||||
mTrackUnionStream = gm->CreateTrackUnionStream(nullptr);
|
||||
MOZ_ASSERT(mTrackUnionStream, "CreateTrackUnionStream failed");
|
||||
|
||||
mTrackUnionStream->SetAutofinish(true);
|
||||
|
||||
mInputPort = mTrackUnionStream->AllocateInputPort(mRecorder->mStream->GetStream(), MediaInputPort::FLAG_BLOCK_OUTPUT);
|
||||
|
||||
// Allocate encoder and bind with union stream.
|
||||
mEncoder = MediaEncoder::CreateEncoder(NS_LITERAL_STRING(""));
|
||||
MOZ_ASSERT(mEncoder, "CreateEncoder failed");
|
||||
|
||||
if (mEncoder) {
|
||||
mTrackUnionStream->AddListener(mEncoder);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// Hold a reference to MediaRecoder to make sure MediaRecoder be
|
||||
// destroyed after all session object dead.
|
||||
nsRefPtr<MediaRecorder> mRecorder;
|
||||
nsRefPtr<MediaEncoder> mEncoder;
|
||||
|
||||
// Pause/ Resume controller.
|
||||
nsRefPtr<ProcessedMediaStream> mTrackUnionStream;
|
||||
nsRefPtr<MediaInputPort> mInputPort;
|
||||
|
||||
// Runnable thread for read data from MediaEncode.
|
||||
nsCOMPtr<nsIThread> mReadThread;
|
||||
// MediaEncoder pipeline.
|
||||
nsRefPtr<MediaEncoder> mEncoder;
|
||||
// A buffer to cache encoded meda data.
|
||||
nsAutoPtr<EncodedBufferCache> mEncodedBufferCache;
|
||||
// The interval of passing encoded data from EncodedBufferCache to onDataAvailable
|
||||
// handler. "mTimeSlice < 0" means Session object does not push encoded data to
|
||||
// onDataAvailable, instead, it passive wait the client side pull encoded data
|
||||
// by calling requestData API.
|
||||
const int32_t mTimeSlice;
|
||||
};
|
||||
|
||||
MediaRecorder::~MediaRecorder()
|
||||
{
|
||||
if (mStreamPort) {
|
||||
mStreamPort->Destroy();
|
||||
}
|
||||
if (mTrackUnionStream) {
|
||||
mTrackUnionStream->Destroy();
|
||||
}
|
||||
MOZ_ASSERT(mSession == nullptr);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -134,31 +328,26 @@ MediaRecorder::Init(nsPIDOMWindow* aOwnerWindow)
|
|||
}
|
||||
|
||||
MediaRecorder::MediaRecorder(DOMMediaStream& aStream)
|
||||
: mTimeSlice(0),
|
||||
mState(RecordingState::Inactive)
|
||||
: mState(RecordingState::Inactive),
|
||||
mSession(nullptr),
|
||||
mMutex("Session.Data.Mutex")
|
||||
{
|
||||
mStream = &aStream;
|
||||
SetIsDOMBinding();
|
||||
}
|
||||
|
||||
void
|
||||
MediaRecorder::ExtractEncodedData()
|
||||
MediaRecorder::SetMimeType(const nsString &aMimeType)
|
||||
{
|
||||
TimeStamp lastBlobTimeStamp = TimeStamp::Now();
|
||||
do {
|
||||
nsTArray<nsTArray<uint8_t> > outputBufs;
|
||||
mEncoder->GetEncodedData(&outputBufs, mMimeType);
|
||||
for (uint32_t i = 0; i < outputBufs.Length(); i++) {
|
||||
mEncodedBufferCache->AppendBuffer(outputBufs[i]);
|
||||
}
|
||||
MutexAutoLock lock(mMutex);
|
||||
mMimeType = aMimeType;
|
||||
}
|
||||
|
||||
if (mTimeSlice > 0 && (TimeStamp::Now() - lastBlobTimeStamp).ToMilliseconds() > mTimeSlice) {
|
||||
NS_DispatchToMainThread(new PushBlobTask(this));
|
||||
lastBlobTimeStamp = TimeStamp::Now();
|
||||
}
|
||||
} while (!mEncoder->IsShutdown());
|
||||
|
||||
NS_DispatchToMainThread(new PushBlobTask(this));
|
||||
void
|
||||
MediaRecorder::GetMimeType(nsString &aMimeType)
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
aMimeType = mMimeType;
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -174,53 +363,25 @@ MediaRecorder::Start(const Optional<int32_t>& aTimeSlice, ErrorResult& aResult)
|
|||
return;
|
||||
}
|
||||
|
||||
int32_t timeSlice = 0;
|
||||
if (aTimeSlice.WasPassed()) {
|
||||
if (aTimeSlice.Value() < 0) {
|
||||
aResult.Throw(NS_ERROR_INVALID_ARG);
|
||||
return;
|
||||
}
|
||||
mTimeSlice = aTimeSlice.Value();
|
||||
} else {
|
||||
mTimeSlice = 0;
|
||||
}
|
||||
|
||||
// Create a TrackUnionStream to support Pause/Resume by using ChangeExplicitBlockerCount
|
||||
MediaStreamGraph* gm = mStream->GetStream()->Graph();
|
||||
mTrackUnionStream = gm->CreateTrackUnionStream(nullptr);
|
||||
MOZ_ASSERT(mTrackUnionStream, "CreateTrackUnionStream failed");
|
||||
timeSlice = aTimeSlice.Value();
|
||||
}
|
||||
|
||||
if (!CheckPrincipal()) {
|
||||
aResult.Throw(NS_ERROR_DOM_SECURITY_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mEncodedBufferCache == nullptr) {
|
||||
mEncodedBufferCache = new EncodedBufferCache(MAX_ALLOW_MEMORY_BUFFER);
|
||||
}
|
||||
|
||||
mEncoder = MediaEncoder::CreateEncoder(NS_LITERAL_STRING(""));
|
||||
MOZ_ASSERT(mEncoder, "CreateEncoder failed");
|
||||
|
||||
mTrackUnionStream->SetAutofinish(true);
|
||||
mStreamPort = mTrackUnionStream->AllocateInputPort(mStream->GetStream(), MediaInputPort::FLAG_BLOCK_OUTPUT);
|
||||
|
||||
if (mEncoder) {
|
||||
mTrackUnionStream->AddListener(mEncoder);
|
||||
} else {
|
||||
aResult.Throw(NS_ERROR_DOM_ABORT_ERR);
|
||||
}
|
||||
|
||||
if (!mReadThread) {
|
||||
nsresult rv = NS_NewNamedThread("Media Encoder",
|
||||
getter_AddRefs(mReadThread));
|
||||
if (NS_FAILED(rv)) {
|
||||
aResult.Throw(rv);
|
||||
return;
|
||||
}
|
||||
nsRefPtr<ExtractEncodedDataTask> event = new ExtractEncodedDataTask(this, mEncoder);
|
||||
mReadThread->Dispatch(event, NS_DISPATCH_NORMAL);
|
||||
mState = RecordingState::Recording;
|
||||
}
|
||||
mState = RecordingState::Recording;
|
||||
// Start a session
|
||||
mSession = new Session(this, timeSlice);
|
||||
mSession->Start();
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -230,13 +391,11 @@ MediaRecorder::Stop(ErrorResult& aResult)
|
|||
aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
mSession->Stop();
|
||||
mSession = nullptr;
|
||||
|
||||
mState = RecordingState::Inactive;
|
||||
|
||||
mStreamPort->Destroy();
|
||||
mStreamPort = nullptr;
|
||||
|
||||
mTrackUnionStream->Destroy();
|
||||
mTrackUnionStream = nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -246,9 +405,14 @@ MediaRecorder::Pause(ErrorResult& aResult)
|
|||
aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return;
|
||||
}
|
||||
mTrackUnionStream->ChangeExplicitBlockerCount(-1);
|
||||
|
||||
mState = RecordingState::Paused;
|
||||
|
||||
MOZ_ASSERT(mSession != nullptr);
|
||||
if (mSession) {
|
||||
mSession->Pause();
|
||||
mState = RecordingState::Paused;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -258,8 +422,12 @@ MediaRecorder::Resume(ErrorResult& aResult)
|
|||
aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return;
|
||||
}
|
||||
mTrackUnionStream->ChangeExplicitBlockerCount(1);
|
||||
mState = RecordingState::Recording;
|
||||
|
||||
MOZ_ASSERT(mSession != nullptr);
|
||||
if (mSession) {
|
||||
mSession->Resume();
|
||||
mState = RecordingState::Recording;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -269,8 +437,11 @@ MediaRecorder::RequestData(ErrorResult& aResult)
|
|||
aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return;
|
||||
}
|
||||
NS_DispatchToMainThread(NS_NewRunnableMethod(this, &MediaRecorder::CreateAndDispatchBlobEvent),
|
||||
NS_DISPATCH_NORMAL);
|
||||
|
||||
NS_DispatchToMainThread(
|
||||
NS_NewRunnableMethodWithArg<Session *>(this,
|
||||
&MediaRecorder::CreateAndDispatchBlobEvent, mSession),
|
||||
NS_DISPATCH_NORMAL);
|
||||
}
|
||||
|
||||
JSObject*
|
||||
|
@ -301,7 +472,7 @@ MediaRecorder::Constructor(const GlobalObject& aGlobal,
|
|||
}
|
||||
|
||||
nsresult
|
||||
MediaRecorder::CreateAndDispatchBlobEvent()
|
||||
MediaRecorder::CreateAndDispatchBlobEvent(Session *aSession)
|
||||
{
|
||||
NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
|
||||
|
||||
|
@ -313,7 +484,7 @@ MediaRecorder::CreateAndDispatchBlobEvent()
|
|||
BlobEventInitInitializer init;
|
||||
init.mBubbles = false;
|
||||
init.mCancelable = false;
|
||||
init.mData = mEncodedBufferCache->ExtractBlob(mMimeType);
|
||||
init.mData = aSession->GetEncodedData();
|
||||
nsRefPtr<BlobEvent> event =
|
||||
BlobEvent::Constructor(this,
|
||||
NS_LITERAL_STRING("dataavailable"),
|
||||
|
|
|
@ -36,9 +36,8 @@ namespace dom {
|
|||
|
||||
class MediaRecorder : public nsDOMEventTargetHelper
|
||||
{
|
||||
class ExtractEncodedDataTask;
|
||||
class PushBlobTask;
|
||||
class PushErrorMessageTask;
|
||||
class Session;
|
||||
|
||||
public:
|
||||
MediaRecorder(DOMMediaStream&);
|
||||
virtual ~MediaRecorder();
|
||||
|
@ -71,7 +70,7 @@ public:
|
|||
// The current state of the MediaRecorder object.
|
||||
RecordingState State() const { return mState; }
|
||||
// Return the current encoding MIME type selected by the MediaEncoder.
|
||||
void GetMimeType(nsString &aMimeType) { aMimeType = mMimeType; }
|
||||
void GetMimeType(nsString &aMimeType);
|
||||
|
||||
static already_AddRefed<MediaRecorder>
|
||||
Constructor(const GlobalObject& aGlobal,
|
||||
|
@ -83,45 +82,33 @@ public:
|
|||
IMPL_EVENT_HANDLER(stop)
|
||||
IMPL_EVENT_HANDLER(warning)
|
||||
|
||||
friend class ExtractEncodedData;
|
||||
|
||||
protected:
|
||||
void Init(nsPIDOMWindow* aOwnerWindow);
|
||||
// Copy encoded data from encoder to EncodedBufferCache. This function runs in the Media Encoder Thread.
|
||||
void ExtractEncodedData();
|
||||
|
||||
MediaRecorder& operator = (const MediaRecorder& x) MOZ_DELETE;
|
||||
// Create dataavailable event with Blob data and it runs in main thread
|
||||
nsresult CreateAndDispatchBlobEvent();
|
||||
nsresult CreateAndDispatchBlobEvent(Session *session);
|
||||
// Creating a simple event to notify UA simple event.
|
||||
void DispatchSimpleEvent(const nsAString & aStr);
|
||||
// Creating a error event with message.
|
||||
void NotifyError(nsresult aRv);
|
||||
// Check if the recorder's principal is the subsume of mediaStream
|
||||
bool CheckPrincipal();
|
||||
// Set encoded MIME type.
|
||||
void SetMimeType(const nsString &aMimeType);
|
||||
|
||||
MediaRecorder(const MediaRecorder& x) MOZ_DELETE; // prevent bad usage
|
||||
|
||||
|
||||
// Runnable thread for read data from mediaEncoder. It starts at MediaRecorder::Start() and stops at MediaRecorder::Stop().
|
||||
nsCOMPtr<nsIThread> mReadThread;
|
||||
// The MediaEncoder object initializes on start() and destroys in ~MediaRecorder.
|
||||
nsRefPtr<MediaEncoder> mEncoder;
|
||||
// MediaStream passed from js context
|
||||
nsRefPtr<DOMMediaStream> mStream;
|
||||
// This media stream is used for notifying raw data to encoder and can be blocked.
|
||||
nsRefPtr<ProcessedMediaStream> mTrackUnionStream;
|
||||
// This is used for destroing the inputport when destroy the mediaRecorder
|
||||
nsRefPtr<MediaInputPort> mStreamPort;
|
||||
// This object creates on start() and destroys in ~MediaRecorder.
|
||||
nsAutoPtr<EncodedBufferCache> mEncodedBufferCache;
|
||||
// It specifies the container format as well as the audio and video capture formats.
|
||||
nsString mMimeType;
|
||||
// The interval of timer passed from Start(). On every mTimeSlice milliseconds, if there are buffers store in the EncodedBufferCache,
|
||||
// a dataavailable event will be fired.
|
||||
int32_t mTimeSlice;
|
||||
// The current state of the MediaRecorder object.
|
||||
RecordingState mState;
|
||||
// Current recording session.
|
||||
Session *mSession;
|
||||
// Thread safe for mMimeType.
|
||||
Mutex mMutex;
|
||||
// It specifies the container format as well as the audio and video capture formats.
|
||||
nsString mMimeType;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -128,6 +128,7 @@ MOCHITEST_FILES = \
|
|||
test_mediarecorder_record_no_timeslice.html \
|
||||
test_mediarecorder_reload_crash.html \
|
||||
test_mediarecorder_record_immediate_stop.html \
|
||||
test_mediarecorder_record_session.html \
|
||||
test_media_selection.html \
|
||||
test_playback.html \
|
||||
test_seekLies.html \
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=909670
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for Media Recoder recording session</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
<script type="text/javascript" src="manifest.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
var manager = new MediaTestManager;
|
||||
|
||||
function startTest(test, token) {
|
||||
var element = document.createElement('audio');
|
||||
|
||||
element.token = token;
|
||||
manager.started(token);
|
||||
|
||||
element.src = test.name;
|
||||
element.test = test;
|
||||
element.stream = element.mozCaptureStream();
|
||||
|
||||
var mStopCount = 0;
|
||||
// Start and stop recording session three times continuously.
|
||||
var mExpectStopCount = 3;
|
||||
var mediaRecorder = new MediaRecorder(element.stream);
|
||||
|
||||
// Stop callback.
|
||||
// Suppose to receive mExpectStopCount
|
||||
mediaRecorder.onstop = function stopCallback() {
|
||||
mStopCount++;
|
||||
|
||||
info("MediaRecorder.onstop callback: (" + mStopCount + ")");
|
||||
|
||||
if (mExpectStopCount === mStopCount)
|
||||
{
|
||||
manager.finished(token);
|
||||
}
|
||||
}
|
||||
|
||||
// data avaliable.
|
||||
mediaRecorder.ondataavailable = function(evt) {}
|
||||
|
||||
mediaRecorder.onerror = function(err) {
|
||||
ok(false, 'Unexpected error fired with:' + err);
|
||||
}
|
||||
|
||||
mediaRecorder.onwarning = function() {
|
||||
ok(false, 'Unexpected warning fired');
|
||||
}
|
||||
|
||||
element.oncanplaythrough = function () {
|
||||
for (var i = 0; i < mExpectStopCount; i++) {
|
||||
mediaRecorder.start(1000);
|
||||
mediaRecorder.stop();
|
||||
}
|
||||
}
|
||||
|
||||
element.play();
|
||||
}
|
||||
|
||||
manager.runTests(gMediaRecorderTests, startTest);
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -109,7 +109,7 @@ function newIncomingParcel(fakeParcelSize, response, request, data) {
|
|||
++writeIndex;
|
||||
}
|
||||
|
||||
function writeUint32(value) {
|
||||
function writeInt32(value) {
|
||||
writeUint8(value & 0xff);
|
||||
writeUint8((value >> 8) & 0xff);
|
||||
writeUint8((value >> 16) & 0xff);
|
||||
|
@ -128,8 +128,8 @@ function newIncomingParcel(fakeParcelSize, response, request, data) {
|
|||
}
|
||||
writeParcelSize(fakeParcelSize);
|
||||
|
||||
writeUint32(response);
|
||||
writeUint32(request);
|
||||
writeInt32(response);
|
||||
writeInt32(request);
|
||||
|
||||
// write parcel data
|
||||
for (let ii = 0; ii < data.length; ++ii) {
|
||||
|
|
|
@ -42,10 +42,10 @@ add_test(function test_change_call_barring_password() {
|
|||
function do_test(facility, pin, newPin) {
|
||||
buf.sendParcel = function fakeSendParcel () {
|
||||
// Request Type.
|
||||
do_check_eq(this.readUint32(), REQUEST_CHANGE_BARRING_PASSWORD);
|
||||
do_check_eq(this.readInt32(), REQUEST_CHANGE_BARRING_PASSWORD);
|
||||
|
||||
// Token : we don't care.
|
||||
this.readUint32();
|
||||
this.readInt32();
|
||||
|
||||
let parcel = this.readStringList();
|
||||
do_check_eq(parcel.length, 3);
|
||||
|
|
|
@ -51,7 +51,7 @@ add_test_incoming_parcel(null,
|
|||
function test_normal_parcel_handling(worker) {
|
||||
do_check_throws(function normal_handler() {
|
||||
// reads exactly the same size, should not throw anything.
|
||||
worker.Buf.readUint32();
|
||||
worker.Buf.readInt32();
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
|
@ -31,7 +31,7 @@ function newWorkerWithParcel(parcelBuf) {
|
|||
return buf[index++];
|
||||
};
|
||||
|
||||
worker.Buf.readUint32 = function () {
|
||||
worker.Buf.readInt32 = function () {
|
||||
return buf[index++];
|
||||
};
|
||||
|
||||
|
|
|
@ -106,7 +106,7 @@ add_test(function test_queryCallForwardStatus_unconditional() {
|
|||
});
|
||||
};
|
||||
|
||||
worker.Buf.readUint32 = function fakeReadUint32() {
|
||||
worker.Buf.readInt32 = function fakeReadUint32() {
|
||||
return worker.Buf.int32Array.pop();
|
||||
};
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ add_test(function test_queryCLIP_provisioned() {
|
|||
let workerHelper = _getWorker();
|
||||
let worker = workerHelper.worker;
|
||||
|
||||
worker.Buf.readUint32 = function fakeReadUint32() {
|
||||
worker.Buf.readInt32 = function fakeReadUint32() {
|
||||
return worker.Buf.int32Array.pop();
|
||||
};
|
||||
|
||||
|
@ -58,7 +58,7 @@ add_test(function test_getCLIP_error_generic_failure_invalid_length() {
|
|||
let workerHelper = _getWorker();
|
||||
let worker = workerHelper.worker;
|
||||
|
||||
worker.Buf.readUint32 = function fakeReadUint32() {
|
||||
worker.Buf.readInt32 = function fakeReadUint32() {
|
||||
return worker.Buf.int32Array.pop();
|
||||
};
|
||||
|
||||
|
|
|
@ -85,7 +85,7 @@ add_test(function test_getCLIR_n0_m1() {
|
|||
let workerHelper = _getWorker();
|
||||
let worker = workerHelper.worker;
|
||||
|
||||
worker.Buf.readUint32 = function fakeReadUint32() {
|
||||
worker.Buf.readInt32 = function fakeReadUint32() {
|
||||
return worker.Buf.int32Array.pop();
|
||||
};
|
||||
|
||||
|
@ -117,7 +117,7 @@ add_test(function test_getCLIR_error_generic_failure_invalid_length() {
|
|||
let workerHelper = _getWorker();
|
||||
let worker = workerHelper.worker;
|
||||
|
||||
worker.Buf.readUint32 = function fakeReadUint32() {
|
||||
worker.Buf.readInt32 = function fakeReadUint32() {
|
||||
return worker.Buf.int32Array.pop();
|
||||
};
|
||||
|
||||
|
|
|
@ -74,7 +74,7 @@ add_test(function test_queryCallWaiting_success_enabled_true() {
|
|||
let workerHelper = _getWorker();
|
||||
let worker = workerHelper.worker;
|
||||
|
||||
worker.Buf.readUint32 = function fakeReadUint32() {
|
||||
worker.Buf.readInt32 = function fakeReadUint32() {
|
||||
return worker.Buf.int32Array.pop();
|
||||
};
|
||||
|
||||
|
@ -104,7 +104,7 @@ add_test(function test_queryCallWaiting_success_enabled_false() {
|
|||
let workerHelper = _getWorker();
|
||||
let worker = workerHelper.worker;
|
||||
|
||||
worker.Buf.readUint32 = function fakeReadUint32() {
|
||||
worker.Buf.readInt32 = function fakeReadUint32() {
|
||||
return worker.Buf.int32Array.pop();
|
||||
};
|
||||
|
||||
|
|
|
@ -492,13 +492,13 @@ add_test(function test_icc_get_card_lock_state_fdn() {
|
|||
|
||||
buf.sendParcel = function () {
|
||||
// Request Type.
|
||||
do_check_eq(this.readUint32(), REQUEST_QUERY_FACILITY_LOCK)
|
||||
do_check_eq(this.readInt32(), REQUEST_QUERY_FACILITY_LOCK)
|
||||
|
||||
// Token : we don't care.
|
||||
this.readUint32();
|
||||
this.readInt32();
|
||||
|
||||
// String Array Length.
|
||||
do_check_eq(this.readUint32(), worker.RILQUIRKS_V5_LEGACY ? 3 : 4);
|
||||
do_check_eq(this.readInt32(), worker.RILQUIRKS_V5_LEGACY ? 3 : 4);
|
||||
|
||||
// Facility.
|
||||
do_check_eq(this.readString(), ICC_CB_FACILITY_FDN);
|
||||
|
@ -513,7 +513,7 @@ add_test(function test_icc_get_card_lock_state_fdn() {
|
|||
|
||||
if (!worker.RILQUIRKS_V5_LEGACY) {
|
||||
// AID. Ignore because it's from modem.
|
||||
this.readUint32();
|
||||
this.readInt32();
|
||||
}
|
||||
|
||||
run_next_test();
|
||||
|
@ -759,7 +759,7 @@ add_test(function test_read_pbr() {
|
|||
];
|
||||
|
||||
// Write data size
|
||||
buf.writeUint32(pbr_1.length * 2);
|
||||
buf.writeInt32(pbr_1.length * 2);
|
||||
|
||||
// Write pbr
|
||||
for (let i = 0; i < pbr_1.length; i++) {
|
||||
|
@ -820,7 +820,7 @@ add_test(function test_read_email() {
|
|||
0x6F, 0x6D, 0x02, 0x23];
|
||||
|
||||
// Write data size
|
||||
buf.writeUint32(email_1.length * 2);
|
||||
buf.writeInt32(email_1.length * 2);
|
||||
|
||||
// Write email
|
||||
for (let i = 0; i < email_1.length; i++) {
|
||||
|
@ -888,32 +888,32 @@ add_test(function test_update_email() {
|
|||
count++;
|
||||
|
||||
// Request Type.
|
||||
do_check_eq(this.readUint32(), REQUEST_SIM_IO);
|
||||
do_check_eq(this.readInt32(), REQUEST_SIM_IO);
|
||||
|
||||
// Token : we don't care
|
||||
this.readUint32();
|
||||
this.readInt32();
|
||||
|
||||
// command.
|
||||
do_check_eq(this.readUint32(), ICC_COMMAND_UPDATE_RECORD);
|
||||
do_check_eq(this.readInt32(), ICC_COMMAND_UPDATE_RECORD);
|
||||
|
||||
// fileId.
|
||||
do_check_eq(this.readUint32(), fileId);
|
||||
do_check_eq(this.readInt32(), fileId);
|
||||
|
||||
// pathId.
|
||||
do_check_eq(this.readString(),
|
||||
EF_PATH_MF_SIM + EF_PATH_DF_TELECOM + EF_PATH_DF_PHONEBOOK);
|
||||
|
||||
// p1.
|
||||
do_check_eq(this.readUint32(), recordNumber);
|
||||
do_check_eq(this.readInt32(), recordNumber);
|
||||
|
||||
// p2.
|
||||
do_check_eq(this.readUint32(), READ_RECORD_ABSOLUTE_MODE);
|
||||
do_check_eq(this.readInt32(), READ_RECORD_ABSOLUTE_MODE);
|
||||
|
||||
// p3.
|
||||
do_check_eq(this.readUint32(), recordSize);
|
||||
do_check_eq(this.readInt32(), recordSize);
|
||||
|
||||
// data.
|
||||
let strLen = this.readUint32();
|
||||
let strLen = this.readInt32();
|
||||
let email;
|
||||
if (pbr.email.fileType === ICC_USIM_TYPE1_TAG) {
|
||||
email = pduHelper.read8BitUnpackedToString(recordSize);
|
||||
|
@ -930,7 +930,7 @@ add_test(function test_update_email() {
|
|||
|
||||
if (!worker.RILQUIRKS_V5_LEGACY) {
|
||||
// AID. Ignore because it's from modem.
|
||||
this.readUint32();
|
||||
this.readInt32();
|
||||
}
|
||||
|
||||
if (count == NUM_TESTS) {
|
||||
|
@ -962,7 +962,7 @@ add_test(function test_read_anr() {
|
|||
0x54, 0xF6, 0xFF, 0xFF];
|
||||
|
||||
// Write data size
|
||||
buf.writeUint32(anr_1.length * 2);
|
||||
buf.writeInt32(anr_1.length * 2);
|
||||
|
||||
// Write anr
|
||||
for (let i = 0; i < anr_1.length; i++) {
|
||||
|
@ -1029,32 +1029,32 @@ add_test(function test_update_anr() {
|
|||
count++;
|
||||
|
||||
// Request Type.
|
||||
do_check_eq(this.readUint32(), REQUEST_SIM_IO);
|
||||
do_check_eq(this.readInt32(), REQUEST_SIM_IO);
|
||||
|
||||
// Token : we don't care
|
||||
this.readUint32();
|
||||
this.readInt32();
|
||||
|
||||
// command.
|
||||
do_check_eq(this.readUint32(), ICC_COMMAND_UPDATE_RECORD);
|
||||
do_check_eq(this.readInt32(), ICC_COMMAND_UPDATE_RECORD);
|
||||
|
||||
// fileId.
|
||||
do_check_eq(this.readUint32(), fileId);
|
||||
do_check_eq(this.readInt32(), fileId);
|
||||
|
||||
// pathId.
|
||||
do_check_eq(this.readString(),
|
||||
EF_PATH_MF_SIM + EF_PATH_DF_TELECOM + EF_PATH_DF_PHONEBOOK);
|
||||
|
||||
// p1.
|
||||
do_check_eq(this.readUint32(), recordNumber);
|
||||
do_check_eq(this.readInt32(), recordNumber);
|
||||
|
||||
// p2.
|
||||
do_check_eq(this.readUint32(), READ_RECORD_ABSOLUTE_MODE);
|
||||
do_check_eq(this.readInt32(), READ_RECORD_ABSOLUTE_MODE);
|
||||
|
||||
// p3.
|
||||
do_check_eq(this.readUint32(), recordSize);
|
||||
do_check_eq(this.readInt32(), recordSize);
|
||||
|
||||
// data.
|
||||
let strLen = this.readUint32();
|
||||
let strLen = this.readInt32();
|
||||
// EF_AAS, ignore.
|
||||
pduHelper.readHexOctet();
|
||||
do_check_eq(pduHelper.readNumberWithLength(), expectedANR);
|
||||
|
@ -1073,7 +1073,7 @@ add_test(function test_update_anr() {
|
|||
|
||||
if (!worker.RILQUIRKS_V5_LEGACY) {
|
||||
// AID. Ignore because it's from modem.
|
||||
this.readUint32();
|
||||
this.readInt32();
|
||||
}
|
||||
|
||||
if (count == NUM_TESTS) {
|
||||
|
@ -1103,7 +1103,7 @@ add_test(function test_read_iap() {
|
|||
let iap_1 = [0x01, 0x02];
|
||||
|
||||
// Write data size/
|
||||
buf.writeUint32(iap_1.length * 2);
|
||||
buf.writeInt32(iap_1.length * 2);
|
||||
|
||||
// Write iap.
|
||||
for (let i = 0; i < iap_1.length; i++) {
|
||||
|
@ -1172,32 +1172,32 @@ add_test(function test_update_iap() {
|
|||
function do_test(expectedIAP) {
|
||||
buf.sendParcel = function () {
|
||||
// Request Type.
|
||||
do_check_eq(this.readUint32(), REQUEST_SIM_IO);
|
||||
do_check_eq(this.readInt32(), REQUEST_SIM_IO);
|
||||
|
||||
// Token : we don't care
|
||||
this.readUint32();
|
||||
this.readInt32();
|
||||
|
||||
// command.
|
||||
do_check_eq(this.readUint32(), ICC_COMMAND_UPDATE_RECORD);
|
||||
do_check_eq(this.readInt32(), ICC_COMMAND_UPDATE_RECORD);
|
||||
|
||||
// fileId.
|
||||
do_check_eq(this.readUint32(), fileId);
|
||||
do_check_eq(this.readInt32(), fileId);
|
||||
|
||||
// pathId.
|
||||
do_check_eq(this.readString(),
|
||||
EF_PATH_MF_SIM + EF_PATH_DF_TELECOM + EF_PATH_DF_PHONEBOOK);
|
||||
|
||||
// p1.
|
||||
do_check_eq(this.readUint32(), recordNumber);
|
||||
do_check_eq(this.readInt32(), recordNumber);
|
||||
|
||||
// p2.
|
||||
do_check_eq(this.readUint32(), READ_RECORD_ABSOLUTE_MODE);
|
||||
do_check_eq(this.readInt32(), READ_RECORD_ABSOLUTE_MODE);
|
||||
|
||||
// p3.
|
||||
do_check_eq(this.readUint32(), recordSize);
|
||||
do_check_eq(this.readInt32(), recordSize);
|
||||
|
||||
// data.
|
||||
let strLen = this.readUint32();
|
||||
let strLen = this.readInt32();
|
||||
for (let i = 0; i < recordSize; i++) {
|
||||
do_check_eq(expectedIAP[i], pduHelper.readHexOctet());
|
||||
}
|
||||
|
@ -1208,7 +1208,7 @@ add_test(function test_update_iap() {
|
|||
|
||||
if (!worker.RILQUIRKS_V5_LEGACY) {
|
||||
// AID. Ignore because it's from modem.
|
||||
this.readUint32();
|
||||
this.readInt32();
|
||||
}
|
||||
|
||||
run_next_test();
|
||||
|
@ -1246,28 +1246,28 @@ add_test(function test_update_adn_like() {
|
|||
|
||||
buf.sendParcel = function () {
|
||||
// Request Type.
|
||||
do_check_eq(this.readUint32(), REQUEST_SIM_IO);
|
||||
do_check_eq(this.readInt32(), REQUEST_SIM_IO);
|
||||
|
||||
// Token : we don't care
|
||||
this.readUint32();
|
||||
this.readInt32();
|
||||
|
||||
// command.
|
||||
do_check_eq(this.readUint32(), ICC_COMMAND_UPDATE_RECORD);
|
||||
do_check_eq(this.readInt32(), ICC_COMMAND_UPDATE_RECORD);
|
||||
|
||||
// fileId.
|
||||
do_check_eq(this.readUint32(), fileId);
|
||||
do_check_eq(this.readInt32(), fileId);
|
||||
|
||||
// pathId.
|
||||
do_check_eq(this.readString(), EF_PATH_MF_SIM + EF_PATH_DF_TELECOM);
|
||||
|
||||
// p1.
|
||||
do_check_eq(this.readUint32(), 1);
|
||||
do_check_eq(this.readInt32(), 1);
|
||||
|
||||
// p2.
|
||||
do_check_eq(this.readUint32(), READ_RECORD_ABSOLUTE_MODE);
|
||||
do_check_eq(this.readInt32(), READ_RECORD_ABSOLUTE_MODE);
|
||||
|
||||
// p3.
|
||||
do_check_eq(this.readUint32(), 0x20);
|
||||
do_check_eq(this.readInt32(), 0x20);
|
||||
|
||||
// data.
|
||||
let contact = pdu.readAlphaIdDiallingNumber(0x20);
|
||||
|
@ -1283,7 +1283,7 @@ add_test(function test_update_adn_like() {
|
|||
|
||||
if (!worker.RILQUIRKS_V5_LEGACY) {
|
||||
// AID. Ignore because it's from modem.
|
||||
this.readUint32();
|
||||
this.readInt32();
|
||||
}
|
||||
|
||||
if (fileId == ICC_EF_FDN) {
|
||||
|
@ -1313,7 +1313,7 @@ add_test(function test_find_free_record_id() {
|
|||
|
||||
function writeRecord (record) {
|
||||
// Write data size
|
||||
buf.writeUint32(record.length * 2);
|
||||
buf.writeInt32(record.length * 2);
|
||||
|
||||
for (let i = 0; i < record.length; i++) {
|
||||
pduHelper.writeHexOctet(record[i]);
|
||||
|
@ -1733,10 +1733,10 @@ add_test(function test_set_icc_card_lock_facility_lock() {
|
|||
function do_test(aLock, aPassword, aEnabled) {
|
||||
buf.sendParcel = function fakeSendParcel () {
|
||||
// Request Type.
|
||||
do_check_eq(this.readUint32(), REQUEST_SET_FACILITY_LOCK);
|
||||
do_check_eq(this.readInt32(), REQUEST_SET_FACILITY_LOCK);
|
||||
|
||||
// Token : we don't care
|
||||
this.readUint32();
|
||||
this.readInt32();
|
||||
|
||||
let parcel = this.readStringList();
|
||||
do_check_eq(parcel.length, 5);
|
||||
|
@ -1783,14 +1783,14 @@ add_test(function test_unlock_card_lock_corporateLocked() {
|
|||
function do_test(aLock, aPassword) {
|
||||
buf.sendParcel = function fakeSendParcel () {
|
||||
// Request Type.
|
||||
do_check_eq(this.readUint32(), REQUEST_ENTER_NETWORK_DEPERSONALIZATION_CODE);
|
||||
do_check_eq(this.readInt32(), REQUEST_ENTER_NETWORK_DEPERSONALIZATION_CODE);
|
||||
|
||||
// Token : we don't care
|
||||
this.readUint32();
|
||||
this.readInt32();
|
||||
|
||||
let lockType = GECKO_PERSO_LOCK_TO_CARD_PERSO_LOCK[aLock];
|
||||
// Lock Type
|
||||
do_check_eq(this.readUint32(), lockType);
|
||||
do_check_eq(this.readInt32(), lockType);
|
||||
|
||||
// Pin/Puk.
|
||||
do_check_eq(this.readString(), aPassword);
|
||||
|
@ -1867,7 +1867,7 @@ add_test(function test_reading_ad_and_parsing_mcc_mnc() {
|
|||
}
|
||||
|
||||
// Write data size
|
||||
buf.writeUint32(ad.length * 2);
|
||||
buf.writeInt32(ad.length * 2);
|
||||
|
||||
// Write data
|
||||
for (let i = 0; i < ad.length; i++) {
|
||||
|
|
|
@ -435,7 +435,7 @@ add_test(function test_sendMMI_call_forwarding_interrogation() {
|
|||
let workerhelper = getWorker();
|
||||
let worker = workerhelper.worker;
|
||||
|
||||
worker.Buf.readUint32 = function fakeReadUint32() {
|
||||
worker.Buf.readInt32 = function fakeReadUint32() {
|
||||
return worker.Buf.int32Array.pop();
|
||||
};
|
||||
|
||||
|
@ -477,7 +477,7 @@ add_test(function test_sendMMI_call_forwarding_interrogation_no_rules() {
|
|||
let workerhelper = getWorker();
|
||||
let worker = workerhelper.worker;
|
||||
|
||||
worker.Buf.readUint32 = function fakeReadUint32() {
|
||||
worker.Buf.readInt32 = function fakeReadUint32() {
|
||||
return 0;
|
||||
};
|
||||
|
||||
|
@ -771,7 +771,7 @@ add_test(function test_sendMMI_call_barring_BAIC_interrogation_voice() {
|
|||
let workerhelper = getWorker();
|
||||
let worker = workerhelper.worker;
|
||||
|
||||
worker.Buf.readUint32List = function fakeReadUint32List() {
|
||||
worker.Buf.readInt32List = function fakeReadUint32List() {
|
||||
return [1];
|
||||
};
|
||||
|
||||
|
@ -954,7 +954,7 @@ add_test(function test_sendMMI_call_waiting_interrogation() {
|
|||
let workerhelper = getWorker();
|
||||
let worker = workerhelper.worker;
|
||||
|
||||
worker.Buf.readUint32 = function fakeReadUint32() {
|
||||
worker.Buf.readInt32 = function fakeReadUint32() {
|
||||
return worker.Buf.int32Array.pop();
|
||||
};
|
||||
|
||||
|
|
|
@ -82,7 +82,7 @@ add_test(function test_read_cdmahome() {
|
|||
let cdmaHome = [0xc1, 0x34, 0xff, 0xff, 0x00];
|
||||
|
||||
// Write data size
|
||||
buf.writeUint32(cdmaHome.length * 2);
|
||||
buf.writeInt32(cdmaHome.length * 2);
|
||||
|
||||
// Write cdma home file.
|
||||
for (let i = 0; i < cdmaHome.length; i++) {
|
||||
|
@ -128,7 +128,7 @@ add_test(function test_read_cdmaspn() {
|
|||
function testReadSpn(file, expectedSpn, expectedDisplayCondition) {
|
||||
io.loadTransparentEF = function fakeLoadTransparentEF(options) {
|
||||
// Write data size
|
||||
buf.writeUint32(file.length * 2);
|
||||
buf.writeInt32(file.length * 2);
|
||||
|
||||
// Write file.
|
||||
for (let i = 0; i < file.length; i++) {
|
||||
|
|
|
@ -92,16 +92,16 @@ add_test(function test_stk_terminal_response() {
|
|||
|
||||
buf.sendParcel = function () {
|
||||
// Type
|
||||
do_check_eq(this.readUint32(), REQUEST_STK_SEND_TERMINAL_RESPONSE);
|
||||
do_check_eq(this.readInt32(), REQUEST_STK_SEND_TERMINAL_RESPONSE);
|
||||
|
||||
// Token : we don't care
|
||||
this.readUint32();
|
||||
this.readInt32();
|
||||
|
||||
// Data Size, 44 = 2 * (TLV_COMMAND_DETAILS_SIZE(5) +
|
||||
// TLV_DEVICE_ID_SIZE(4) +
|
||||
// TLV_RESULT_SIZE(3) +
|
||||
// TEXT LENGTH(10))
|
||||
do_check_eq(this.readUint32(), 44);
|
||||
do_check_eq(this.readInt32(), 44);
|
||||
|
||||
// Command Details, Type-Length-Value
|
||||
do_check_eq(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_COMMAND_DETAILS |
|
||||
|
@ -748,16 +748,16 @@ add_test(function test_stk_event_download_location_status() {
|
|||
|
||||
buf.sendParcel = function () {
|
||||
// Type
|
||||
do_check_eq(this.readUint32(), REQUEST_STK_SEND_ENVELOPE_COMMAND);
|
||||
do_check_eq(this.readInt32(), REQUEST_STK_SEND_ENVELOPE_COMMAND);
|
||||
|
||||
// Token : we don't care
|
||||
this.readUint32();
|
||||
this.readInt32();
|
||||
|
||||
// Data Size, 42 = 2 * (2 + TLV_DEVICE_ID_SIZE(4) +
|
||||
// TLV_EVENT_LIST_SIZE(3) +
|
||||
// TLV_LOCATION_STATUS_SIZE(3) +
|
||||
// TLV_LOCATION_INFO_GSM_SIZE(9))
|
||||
do_check_eq(this.readUint32(), 42);
|
||||
do_check_eq(this.readInt32(), 42);
|
||||
|
||||
// BER tag
|
||||
do_check_eq(pduHelper.readHexOctet(), BER_EVENT_DOWNLOAD_TAG);
|
||||
|
@ -830,15 +830,15 @@ add_test(function test_stk_event_download_language_selection() {
|
|||
|
||||
buf.sendParcel = function () {
|
||||
// Type
|
||||
do_check_eq(this.readUint32(), REQUEST_STK_SEND_ENVELOPE_COMMAND);
|
||||
do_check_eq(this.readInt32(), REQUEST_STK_SEND_ENVELOPE_COMMAND);
|
||||
|
||||
// Token : we don't care
|
||||
this.readUint32();
|
||||
this.readInt32();
|
||||
|
||||
// Data Size, 26 = 2 * (2 + TLV_DEVICE_ID_SIZE(4) +
|
||||
// TLV_EVENT_LIST_SIZE(3) +
|
||||
// TLV_LANGUAGE(4))
|
||||
do_check_eq(this.readUint32(), 26);
|
||||
do_check_eq(this.readInt32(), 26);
|
||||
|
||||
// BER tag
|
||||
do_check_eq(pduHelper.readHexOctet(), BER_EVENT_DOWNLOAD_TAG);
|
||||
|
@ -888,13 +888,13 @@ add_test(function test_stk_event_download_user_activity() {
|
|||
|
||||
buf.sendParcel = function () {
|
||||
// Type
|
||||
do_check_eq(this.readUint32(), REQUEST_STK_SEND_ENVELOPE_COMMAND);
|
||||
do_check_eq(this.readInt32(), REQUEST_STK_SEND_ENVELOPE_COMMAND);
|
||||
|
||||
// Token : we don't care
|
||||
this.readUint32();
|
||||
this.readInt32();
|
||||
|
||||
// Data Size, 18 = 2 * (2 + TLV_DEVICE_ID_SIZE(4) + TLV_EVENT_LIST_SIZE(3))
|
||||
do_check_eq(this.readUint32(), 18);
|
||||
do_check_eq(this.readInt32(), 18);
|
||||
|
||||
// BER tag
|
||||
do_check_eq(pduHelper.readHexOctet(), BER_EVENT_DOWNLOAD_TAG);
|
||||
|
@ -936,13 +936,13 @@ add_test(function test_stk_event_download_idle_screen_available() {
|
|||
|
||||
buf.sendParcel = function () {
|
||||
// Type
|
||||
do_check_eq(this.readUint32(), REQUEST_STK_SEND_ENVELOPE_COMMAND);
|
||||
do_check_eq(this.readInt32(), REQUEST_STK_SEND_ENVELOPE_COMMAND);
|
||||
|
||||
// Token : we don't care
|
||||
this.readUint32();
|
||||
this.readInt32();
|
||||
|
||||
// Data Size, 18 = 2 * (2 + TLV_DEVICE_ID_SIZE(4) + TLV_EVENT_LIST_SIZE(3))
|
||||
do_check_eq(this.readUint32(), 18);
|
||||
do_check_eq(this.readInt32(), 18);
|
||||
|
||||
// BER tag
|
||||
do_check_eq(pduHelper.readHexOctet(), BER_EVENT_DOWNLOAD_TAG);
|
||||
|
|
|
@ -72,7 +72,7 @@ add_test(function test_queryVoicePrivacyMode_success_enabled_true() {
|
|||
let workerHelper = _getWorker();
|
||||
let worker = workerHelper.worker;
|
||||
|
||||
worker.Buf.readUint32List = function fakeReadUint32List() {
|
||||
worker.Buf.readInt32List = function fakeReadUint32List() {
|
||||
return [1];
|
||||
};
|
||||
|
||||
|
@ -95,7 +95,7 @@ add_test(function test_queryVoicePrivacyMode_success_enabled_false() {
|
|||
let workerHelper = _getWorker();
|
||||
let worker = workerHelper.worker;
|
||||
|
||||
worker.Buf.readUint32List = function fakeReadUint32List() {
|
||||
worker.Buf.readInt32List = function fakeReadUint32List() {
|
||||
return [0];
|
||||
};
|
||||
|
||||
|
|
|
@ -262,22 +262,22 @@ let Buf = {
|
|||
return this.readUint8() | this.readUint8() << 8;
|
||||
},
|
||||
|
||||
readUint32: function readUint32() {
|
||||
readInt32: function readInt32() {
|
||||
return this.readUint8() | this.readUint8() << 8 |
|
||||
this.readUint8() << 16 | this.readUint8() << 24;
|
||||
},
|
||||
|
||||
readUint32List: function readUint32List() {
|
||||
let length = this.readUint32();
|
||||
readInt32List: function readInt32List() {
|
||||
let length = this.readInt32();
|
||||
let ints = [];
|
||||
for (let i = 0; i < length; i++) {
|
||||
ints.push(this.readUint32());
|
||||
ints.push(this.readInt32());
|
||||
}
|
||||
return ints;
|
||||
},
|
||||
|
||||
readString: function readString() {
|
||||
let string_len = this.readUint32();
|
||||
let string_len = this.readInt32();
|
||||
if (string_len < 0 || string_len >= this.INT32_MAX) {
|
||||
return null;
|
||||
}
|
||||
|
@ -293,7 +293,7 @@ let Buf = {
|
|||
},
|
||||
|
||||
readStringList: function readStringList() {
|
||||
let num_strings = this.readUint32();
|
||||
let num_strings = this.readInt32();
|
||||
let strings = [];
|
||||
for (let i = 0; i < num_strings; i++) {
|
||||
strings.push(this.readString());
|
||||
|
@ -349,7 +349,7 @@ let Buf = {
|
|||
this.writeUint8((value >> 8) & 0xff);
|
||||
},
|
||||
|
||||
writeUint32: function writeUint32(value) {
|
||||
writeInt32: function writeInt32(value) {
|
||||
this.writeUint8(value & 0xff);
|
||||
this.writeUint8((value >> 8) & 0xff);
|
||||
this.writeUint8((value >> 16) & 0xff);
|
||||
|
@ -358,10 +358,10 @@ let Buf = {
|
|||
|
||||
writeString: function writeString(value) {
|
||||
if (value == null) {
|
||||
this.writeUint32(-1);
|
||||
this.writeInt32(-1);
|
||||
return;
|
||||
}
|
||||
this.writeUint32(value.length);
|
||||
this.writeInt32(value.length);
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
this.writeUint16(value.charCodeAt(i));
|
||||
}
|
||||
|
@ -372,7 +372,7 @@ let Buf = {
|
|||
},
|
||||
|
||||
writeStringList: function writeStringList(strings) {
|
||||
this.writeUint32(strings.length);
|
||||
this.writeInt32(strings.length);
|
||||
for (let i = 0; i < strings.length; i++) {
|
||||
this.writeString(strings[i]);
|
||||
}
|
||||
|
@ -587,7 +587,7 @@ let Buf = {
|
|||
* may call multiple read functions to extract data from the incoming buffer.
|
||||
*/
|
||||
//processParcel: function processParcel() {
|
||||
// let something = this.readUint32();
|
||||
// let something = this.readInt32();
|
||||
// ...
|
||||
//},
|
||||
|
||||
|
|
|
@ -3337,11 +3337,11 @@ WifiWorker.prototype = {
|
|||
this.queueRequest(false, function(data) {
|
||||
if (this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED] ||
|
||||
WifiManager.tetheringState != "UNINITIALIZED") {
|
||||
this.disconnectedByWifi = true;
|
||||
this.setWifiApEnabled(false, this.notifyTetheringOff.bind(this));
|
||||
} else {
|
||||
this.requestDone();
|
||||
}
|
||||
this.disconnectedByWifi = true;
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
|
@ -3366,11 +3366,11 @@ WifiWorker.prototype = {
|
|||
if (enabled) {
|
||||
this.queueRequest(false, function(data) {
|
||||
if (WifiManager.enabled || WifiManager.state != "UNINITIALIZED") {
|
||||
this.disconnectedByWifiTethering = true;
|
||||
this.setWifiEnabled(false, this._setWifiEnabledCallback.bind(this));
|
||||
} else {
|
||||
this.requestDone();
|
||||
}
|
||||
this.disconnectedByWifiTethering = true;
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
|
|
|
@ -189,7 +189,6 @@
|
|||
|
||||
"content/html/content/test/test_iframe_sandbox_plugins.html":"plugins not supported",
|
||||
"content/html/content/test/test_object_plugin_nav.html":"plugins not supported",
|
||||
"content/html/document/test/test_bug199692.html":"needs popup to be sized to 600*600",
|
||||
"content/html/document/test/test_bug741266.html":"needs control of popup window size",
|
||||
"docshell/test/navigation/test_popup-navigates-children.html":"Needs multiple window.open support, also uses docshelltreenode",
|
||||
"docshell/test/test_bug590573.html":"queryinterfaces into webnavigation, might suffer from something similar as bug 823022",
|
||||
|
@ -208,7 +207,6 @@
|
|||
"content/base/test/csp/test_CSP_bug916446.html":"observer not working",
|
||||
|
||||
"content/base/test/test_CrossSiteXHR_origin.html":"https not working, bug 907770",
|
||||
"content/base/test/test_bug326337.html":"popup windows don't have specialpowers installed, could be solved with sendmessage/receivemessage",
|
||||
"content/base/test/test_plugin_freezing.html":"",
|
||||
"content/base/test/test_bug466409.html":"",
|
||||
"content/base/test/test_bug482935.html":"",
|
||||
|
|
|
@ -14,7 +14,20 @@ let container = homescreen.contentWindow.document.getElementById('test-container
|
|||
|
||||
function openWindow(aEvent) {
|
||||
var popupIframe = aEvent.detail.frameElement;
|
||||
popupIframe.setAttribute('style', 'position: absolute; left: 0; top: 300px; background: white; ');
|
||||
popupIframe.setAttribute('style', 'position: absolute; left: 0; top: 0px; background: white;');
|
||||
|
||||
// This is to size the iframe to what is requested in the window.open call,
|
||||
// e.g. window.open("", "", "width=600,height=600");
|
||||
if (aEvent.detail.features.indexOf('width') != -1) {
|
||||
let width = aEvent.detail.features.substr(aEvent.detail.features.indexOf('width')+6);
|
||||
width = width.substr(0,width.indexOf(',') == -1 ? width.length : width.indexOf(','));
|
||||
popupIframe.style.width = width + 'px';
|
||||
}
|
||||
if (aEvent.detail.features.indexOf('height') != -1) {
|
||||
let height = aEvent.detail.features.substr(aEvent.detail.features.indexOf('height')+7);
|
||||
height = height.substr(0, height.indexOf(',') == -1 ? height.length : height.indexOf(','));
|
||||
popupIframe.style.height = height + 'px';
|
||||
}
|
||||
|
||||
popupIframe.addEventListener('mozbrowserclose', function(e) {
|
||||
container.parentNode.removeChild(popupIframe);
|
||||
|
@ -26,6 +39,11 @@ function openWindow(aEvent) {
|
|||
|
||||
popupIframe.addEventListener('mozbrowserloadstart', function(e) {
|
||||
popupIframe.focus();
|
||||
let mm = popupIframe.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager;
|
||||
mm.loadFrameScript(CHILD_LOGGER_SCRIPT, true);
|
||||
mm.loadFrameScript(CHILD_SCRIPT_API, true);
|
||||
mm.loadFrameScript(CHILD_SCRIPT, true);
|
||||
mm.loadFrameScript('data:,attachSpecialPowersToWindow%28content%29%3B', true);
|
||||
});
|
||||
|
||||
container.parentNode.appendChild(popupIframe);
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
#
|
||||
# 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/.
|
||||
|
@ -887,8 +886,13 @@ class Mochitest(MochitestUtilsMixin):
|
|||
# create mozrunner instance and start the system under test process
|
||||
self.lastTestSeen = self.test_name
|
||||
startTime = datetime.now()
|
||||
runner_cls = mozrunner.runners.get(mozinfo.info.get('appname', 'firefox'),
|
||||
mozrunner.Runner)
|
||||
|
||||
# b2g desktop requires FirefoxRunner even though appname is b2g
|
||||
if mozinfo.info.get('appname') == 'b2g' and mozinfo.info.get('toolkit') != 'gonk':
|
||||
runner_cls = mozrunner.FirefoxRunner
|
||||
else:
|
||||
runner_cls = mozrunner.runners.get(mozinfo.info.get('appname', 'firefox'),
|
||||
mozrunner.Runner)
|
||||
runner = runner_cls(profile=self.profile,
|
||||
binary=cmd,
|
||||
cmdargs=args,
|
||||
|
|
|
@ -18,7 +18,6 @@ except ImportError:
|
|||
here = os.path.abspath(os.path.dirname(__file__))
|
||||
sys.path.insert(0, here)
|
||||
|
||||
from b2gautomation import B2GDesktopAutomation
|
||||
from runtests import Mochitest
|
||||
from runtests import MochitestUtilsMixin
|
||||
from runtests import MochitestOptions
|
||||
|
@ -225,9 +224,9 @@ class B2GDeviceMochitest(B2GMochitest):
|
|||
|
||||
class B2GDesktopMochitest(B2GMochitest, Mochitest):
|
||||
|
||||
def __init__(self, automation, marionette, profile_data_dir):
|
||||
def __init__(self, marionette, profile_data_dir):
|
||||
B2GMochitest.__init__(self, marionette, out_of_process=False, profile_data_dir=profile_data_dir)
|
||||
Mochitest.__init__(self, automation)
|
||||
Mochitest.__init__(self)
|
||||
|
||||
def runMarionetteScript(self, marionette, test_script, test_script_args):
|
||||
assert(marionette.wait_for_port())
|
||||
|
@ -337,8 +336,6 @@ def run_remote_mochitests(parser, options):
|
|||
sys.exit(retVal)
|
||||
|
||||
def run_desktop_mochitests(parser, options):
|
||||
automation = B2GDesktopAutomation()
|
||||
|
||||
# create our Marionette instance
|
||||
kwargs = {}
|
||||
if options.marionette:
|
||||
|
@ -346,9 +343,7 @@ def run_desktop_mochitests(parser, options):
|
|||
kwargs['host'] = host
|
||||
kwargs['port'] = int(port)
|
||||
marionette = Marionette.getMarionetteOrExit(**kwargs)
|
||||
automation.marionette = marionette
|
||||
|
||||
mochitest = B2GDesktopMochitest(automation, marionette, options.profile_data_dir)
|
||||
mochitest = B2GDesktopMochitest(marionette, options.profile_data_dir)
|
||||
|
||||
# b2g desktop builds don't always have a b2g-bin file
|
||||
if options.app[-4:] == '-bin':
|
||||
|
@ -361,10 +356,6 @@ def run_desktop_mochitests(parser, options):
|
|||
if options.desktop and not options.profile:
|
||||
raise Exception("must specify --profile when specifying --desktop")
|
||||
|
||||
automation.setServerInfo(options.webServer,
|
||||
options.httpPort,
|
||||
options.sslPort,
|
||||
options.webSocketPort)
|
||||
sys.exit(mochitest.runTests(options, onLaunch=mochitest.startTests))
|
||||
|
||||
def main():
|
||||
|
|
Загрузка…
Ссылка в новой задаче