/* 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 "nsRemoteDebuggingPipe.h" #include #if defined(OS_WIN) #include #else #include #include #endif #include "mozilla/StaticPtr.h" #include "nsISupportsPrimitives.h" #include "nsThreadUtils.h" namespace mozilla { NS_IMPL_ISUPPORTS(nsRemoteDebuggingPipe, nsIRemoteDebuggingPipe) namespace { StaticRefPtr gPipe; const int readFD = 3; const int writeFD = 4; const size_t kWritePacketSize = 1 << 16; #if defined(OS_WIN) HANDLE readHandle; HANDLE writeHandle; #endif size_t ReadBytes(void* buffer, size_t size, bool exact_size) { size_t bytesRead = 0; while (bytesRead < size) { #if defined(OS_WIN) DWORD sizeRead = 0; bool hadError = !ReadFile(readHandle, static_cast(buffer) + bytesRead, size - bytesRead, &sizeRead, nullptr); #else int sizeRead = read(readFD, static_cast(buffer) + bytesRead, size - bytesRead); if (sizeRead < 0 && errno == EINTR) continue; bool hadError = sizeRead <= 0; #endif if (hadError) { return 0; } bytesRead += sizeRead; if (!exact_size) break; } return bytesRead; } void WriteBytes(const char* bytes, size_t size) { size_t totalWritten = 0; while (totalWritten < size) { size_t length = size - totalWritten; if (length > kWritePacketSize) length = kWritePacketSize; #if defined(OS_WIN) DWORD bytesWritten = 0; bool hadError = !WriteFile(writeHandle, bytes + totalWritten, static_cast(length), &bytesWritten, nullptr); #else int bytesWritten = write(writeFD, bytes + totalWritten, length); if (bytesWritten < 0 && errno == EINTR) continue; bool hadError = bytesWritten <= 0; #endif if (hadError) return; totalWritten += bytesWritten; } } } // namespace // static already_AddRefed nsRemoteDebuggingPipe::GetSingleton() { if (!gPipe) { gPipe = new nsRemoteDebuggingPipe(); } return do_AddRef(gPipe); } nsRemoteDebuggingPipe::nsRemoteDebuggingPipe() = default; nsRemoteDebuggingPipe::~nsRemoteDebuggingPipe() = default; nsresult nsRemoteDebuggingPipe::Init(nsIRemoteDebuggingPipeClient* aClient) { MOZ_RELEASE_ASSERT(NS_IsMainThread(), "Remote debugging pipe must be used on the Main thread."); if (mClient) { return NS_ERROR_FAILURE; } mClient = aClient; MOZ_ALWAYS_SUCCEEDS(NS_NewNamedThread("Pipe Reader", getter_AddRefs(mReaderThread))); MOZ_ALWAYS_SUCCEEDS(NS_NewNamedThread("Pipe Writer", getter_AddRefs(mWriterThread))); #if defined(OS_WIN) readHandle = reinterpret_cast(_get_osfhandle(readFD)); writeHandle = reinterpret_cast(_get_osfhandle(writeFD)); #endif MOZ_ALWAYS_SUCCEEDS(mReaderThread->Dispatch(NewRunnableMethod( "nsRemoteDebuggingPipe::ReaderLoop", this, &nsRemoteDebuggingPipe::ReaderLoop), nsIThread::DISPATCH_NORMAL)); return NS_OK; } nsresult nsRemoteDebuggingPipe::Stop() { MOZ_RELEASE_ASSERT(NS_IsMainThread(), "Remote debugging pipe must be used on the Main thread."); if (!mClient) { return NS_ERROR_FAILURE; } m_terminated = true; mClient = nullptr; // Cancel pending synchronous read. #if defined(OS_WIN) CancelIoEx(readHandle, nullptr); CloseHandle(readHandle); #else shutdown(readFD, SHUT_RDWR); #endif mReaderThread->Shutdown(); mReaderThread = nullptr; mWriterThread->AsyncShutdown(); mWriterThread = nullptr; return NS_OK; } void nsRemoteDebuggingPipe::ReaderLoop() { const size_t bufSize = 256 * 1024; std::vector buffer; buffer.resize(bufSize); std::vector line; while (!m_terminated) { size_t size = ReadBytes(buffer.data(), bufSize, false); if (!size) break; size_t start = 0; size_t end = line.size(); line.insert(line.end(), buffer.begin(), buffer.begin() + size); while (true) { for (; end < line.size(); ++end) { if (line[end] == '\0') { break; } } if (end == line.size()) { break; } if (end > start) { nsCString message; message.Append(line.data() + start, end - start); nsCOMPtr runnable = NewRunnableMethod( "nsRemoteDebuggingPipe::ReceiveMessage", this, &nsRemoteDebuggingPipe::ReceiveMessage, std::move(message)); NS_DispatchToMainThread(runnable.forget()); } ++end; start = end; } if (start != 0 && start < line.size()) { memmove(line.data(), line.data() + start, line.size() - start); } line.resize(line.size() - start); } } void nsRemoteDebuggingPipe::ReceiveMessage(const nsCString& aMessage) { MOZ_RELEASE_ASSERT(NS_IsMainThread(), "Remote debugging pipe must be used on the Main thread."); if (mClient) { NS_ConvertUTF8toUTF16 utf16(aMessage); mClient->ReceiveMessage(utf16); } } nsresult nsRemoteDebuggingPipe::SendMessage(const nsAString& aMessage) { MOZ_RELEASE_ASSERT(NS_IsMainThread(), "Remote debugging pipe must be used on the Main thread."); if (!mClient) { return NS_ERROR_FAILURE; } NS_ConvertUTF16toUTF8 utf8(aMessage); nsCOMPtr runnable = NS_NewRunnableFunction( "nsRemoteDebuggingPipe::SendMessage", [message = std::move(utf8)] { const nsCString& flat = PromiseFlatCString(message); WriteBytes(flat.Data(), flat.Length()); WriteBytes("\0", 1); }); MOZ_ALWAYS_SUCCEEDS(mWriterThread->Dispatch(runnable.forget(), nsIThread::DISPATCH_NORMAL)); return NS_OK; } } // namespace mozilla