diff --git a/browser_patches/firefox/BUILD_NUMBER b/browser_patches/firefox/BUILD_NUMBER index 806beecbf3..61e24b86d6 100644 --- a/browser_patches/firefox/BUILD_NUMBER +++ b/browser_patches/firefox/BUILD_NUMBER @@ -1,2 +1,2 @@ -1146 -Changed: lushnikov@chromium.org Thu 30 Jul 2020 11:44:10 PM PDT +1147 +Changed: dgozman@gmail.com Mon Aug 3 13:14:27 PDT 2020 diff --git a/browser_patches/firefox/juggler/components/juggler.js b/browser_patches/firefox/juggler/components/juggler.js index 30c310925f..d8f8737022 100644 --- a/browser_patches/firefox/juggler/components/juggler.js +++ b/browser_patches/firefox/juggler/components/juggler.js @@ -32,9 +32,9 @@ CommandLineHandler.prototype = { /* nsICommandLineHandler */ handle: async function(cmdLine) { const jugglerFlag = cmdLine.handleFlagWithParam("juggler", false); - if (!jugglerFlag || isNaN(jugglerFlag)) + const jugglerPipeFlag = cmdLine.handleFlag("juggler-pipe", false); + if (!jugglerPipeFlag && (!jugglerFlag || isNaN(jugglerFlag))) return; - const port = parseInt(jugglerFlag, 10); const silent = cmdLine.preventDefault; if (silent) Services.startup.enterLastWindowClosingSurvivalArea(); @@ -42,38 +42,64 @@ CommandLineHandler.prototype = { const targetRegistry = new TargetRegistry(); new NetworkObserver(targetRegistry); - const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm"); - const WebSocketServer = require('devtools/server/socket/websocket-server'); - this._server = Cc["@mozilla.org/network/server-socket;1"].createInstance(Ci.nsIServerSocket); - this._server.initSpecialConnection(port, Ci.nsIServerSocket.KeepWhenOffline | Ci.nsIServerSocket.LoopbackOnly, 4); - - const token = helper.generateId(); + const loadFrameScript = () => { + Services.mm.loadFrameScript(FRAME_SCRIPT, true /* aAllowDelayedLoad */); + if (Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo).isHeadless) { + const styleSheetService = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Components.interfaces.nsIStyleSheetService); + const ioService = Cc["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService); + const uri = ioService.newURI('chrome://juggler/content/content/hidden-scrollbars.css', null, null); + styleSheetService.loadAndRegisterSheet(uri, styleSheetService.AGENT_SHEET); + } + }; // Force create hidden window here, otherwise its creation later closes the web socket! Services.appShell.hiddenDOMWindow; - this._server.asyncListen({ - onSocketAccepted: async(socket, transport) => { - const input = transport.openInputStream(0, 0, 0); - const output = transport.openOutputStream(0, 0, 0); - const webSocket = await WebSocketServer.accept(transport, input, output, "/" + token); - const dispatcher = new Dispatcher(webSocket); - const browserHandler = new BrowserHandler(dispatcher.rootSession(), dispatcher, targetRegistry, () => { - if (silent) - Services.startup.exitLastWindowClosingSurvivalArea(); - }); - dispatcher.rootSession().registerHandler('Browser', browserHandler); - } - }); - - Services.mm.loadFrameScript(FRAME_SCRIPT, true /* aAllowDelayedLoad */); - if (Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo).isHeadless) { - const styleSheetService = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Components.interfaces.nsIStyleSheetService); - const ioService = Cc["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService); - const uri = ioService.newURI('chrome://juggler/content/content/hidden-scrollbars.css', null, null); - styleSheetService.loadAndRegisterSheet(uri, styleSheetService.AGENT_SHEET); + if (jugglerFlag) { + const port = parseInt(jugglerFlag, 10); + const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm"); + const WebSocketServer = require('devtools/server/socket/websocket-server'); + this._server = Cc["@mozilla.org/network/server-socket;1"].createInstance(Ci.nsIServerSocket); + this._server.initSpecialConnection(port, Ci.nsIServerSocket.KeepWhenOffline | Ci.nsIServerSocket.LoopbackOnly, 4); + const token = helper.generateId(); + this._server.asyncListen({ + onSocketAccepted: async(socket, transport) => { + const input = transport.openInputStream(0, 0, 0); + const output = transport.openOutputStream(0, 0, 0); + const webSocket = await WebSocketServer.accept(transport, input, output, "/" + token); + const dispatcher = new Dispatcher(webSocket); + const browserHandler = new BrowserHandler(dispatcher.rootSession(), dispatcher, targetRegistry, () => { + if (silent) + Services.startup.exitLastWindowClosingSurvivalArea(); + }); + dispatcher.rootSession().registerHandler('Browser', browserHandler); + } + }); + loadFrameScript(); + dump(`Juggler listening on ws://127.0.0.1:${this._server.port}/${token}\n`); + } else if (jugglerPipeFlag) { + const pipe = Cc['@mozilla.org/juggler/remotedebuggingpipe;1'].getService(Ci.nsIRemoteDebuggingPipe); + const connection = { + QueryInterface: ChromeUtils.generateQI([Ci.nsIRemoteDebuggingPipeClient]), + receiveMessage(message) { + if (this.onmessage) + this.onmessage({ data: message }); + }, + send(message) { + pipe.sendMessage(message); + }, + }; + pipe.init(connection); + const dispatcher = new Dispatcher(connection); + const browserHandler = new BrowserHandler(dispatcher.rootSession(), dispatcher, targetRegistry, () => { + if (silent) + Services.startup.exitLastWindowClosingSurvivalArea(); + pipe.stop(); + }); + dispatcher.rootSession().registerHandler('Browser', browserHandler); + loadFrameScript(); + dump(`Juggler listening to the pipe\n`); } - dump(`Juggler listening on ws://127.0.0.1:${this._server.port}/${token}\n`); }, QueryInterface: ChromeUtils.generateQI([ Ci.nsICommandLineHandler ]), diff --git a/browser_patches/firefox/juggler/moz.build b/browser_patches/firefox/juggler/moz.build index efc74e5767..1f04b67441 100644 --- a/browser_patches/firefox/juggler/moz.build +++ b/browser_patches/firefox/juggler/moz.build @@ -2,7 +2,7 @@ # 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/. -DIRS += ["components", "screencast"] +DIRS += ["components", "screencast", "pipe"] JAR_MANIFESTS += ["jar.mn"] #JS_PREFERENCE_FILES += ["prefs/marionette.js"] diff --git a/browser_patches/firefox/juggler/pipe/components.conf b/browser_patches/firefox/juggler/pipe/components.conf new file mode 100644 index 0000000000..db13a00ba7 --- /dev/null +++ b/browser_patches/firefox/juggler/pipe/components.conf @@ -0,0 +1,15 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +Classes = [ + { + 'cid': '{d69ecefe-3df7-4d11-9dc7-f604edb96da2}', + 'contract_ids': ['@mozilla.org/juggler/remotedebuggingpipe;1'], + 'type': 'nsIRemoteDebuggingPipe', + 'constructor': 'mozilla::nsRemoteDebuggingPipe::GetSingleton', + 'headers': ['/juggler/pipe/nsRemoteDebuggingPipe.h'], + }, +] diff --git a/browser_patches/firefox/juggler/pipe/moz.build b/browser_patches/firefox/juggler/pipe/moz.build new file mode 100644 index 0000000000..b56c697881 --- /dev/null +++ b/browser_patches/firefox/juggler/pipe/moz.build @@ -0,0 +1,24 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +XPIDL_SOURCES += [ + 'nsIRemoteDebuggingPipe.idl', +] + +XPIDL_MODULE = 'jugglerpipe' + +SOURCES += [ + 'nsRemoteDebuggingPipe.cpp', +] + +XPCOM_MANIFESTS += [ + 'components.conf', +] + +LOCAL_INCLUDES += [ +] + +FINAL_LIBRARY = 'xul' diff --git a/browser_patches/firefox/juggler/pipe/nsIRemoteDebuggingPipe.idl b/browser_patches/firefox/juggler/pipe/nsIRemoteDebuggingPipe.idl new file mode 100644 index 0000000000..ab28b21674 --- /dev/null +++ b/browser_patches/firefox/juggler/pipe/nsIRemoteDebuggingPipe.idl @@ -0,0 +1,19 @@ +/* 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 "nsISupports.idl" + +[scriptable, uuid(7910c231-971a-4653-abdc-a8599a986c4c)] +interface nsIRemoteDebuggingPipeClient : nsISupports +{ + void receiveMessage(in AString message); +}; + +[scriptable, uuid(b7bfb66b-fd46-4aa2-b4ad-396177186d94)] +interface nsIRemoteDebuggingPipe : nsISupports +{ + void init(in nsIRemoteDebuggingPipeClient client); + void sendMessage(in AString message); + void stop(); +}; diff --git a/browser_patches/firefox/juggler/pipe/nsRemoteDebuggingPipe.cpp b/browser_patches/firefox/juggler/pipe/nsRemoteDebuggingPipe.cpp new file mode 100644 index 0000000000..47e78365a8 --- /dev/null +++ b/browser_patches/firefox/juggler/pipe/nsRemoteDebuggingPipe.cpp @@ -0,0 +1,204 @@ +/* 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 diff --git a/browser_patches/firefox/juggler/pipe/nsRemoteDebuggingPipe.h b/browser_patches/firefox/juggler/pipe/nsRemoteDebuggingPipe.h new file mode 100644 index 0000000000..422758cdc7 --- /dev/null +++ b/browser_patches/firefox/juggler/pipe/nsRemoteDebuggingPipe.h @@ -0,0 +1,33 @@ +/* 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/. */ + +#pragma once + +#include +#include "nsCOMPtr.h" +#include "nsIRemoteDebuggingPipe.h" +#include "nsThread.h" + +namespace mozilla { + +class nsRemoteDebuggingPipe final : public nsIRemoteDebuggingPipe { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIREMOTEDEBUGGINGPIPE + + static already_AddRefed GetSingleton(); + nsRemoteDebuggingPipe(); + + private: + void ReaderLoop(); + void ReceiveMessage(const nsCString& aMessage); + ~nsRemoteDebuggingPipe(); + + RefPtr mClient; + nsCOMPtr mReaderThread; + nsCOMPtr mWriterThread; + std::atomic m_terminated { false }; +}; + +} // namespace mozilla