diff --git a/.gitignore b/.gitignore index 93b5ed7c..5aad14bc 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ Thumbs.db .idea/ *Makefile* *qmake.stash +.qtc_clangd diff --git a/ChromiumBasedEditors/lib/ascdocumentscore.pri b/ChromiumBasedEditors/lib/ascdocumentscore.pri index fb5205f8..3f52728a 100644 --- a/ChromiumBasedEditors/lib/ascdocumentscore.pri +++ b/ChromiumBasedEditors/lib/ascdocumentscore.pri @@ -121,6 +121,9 @@ SOURCES += \ $$PWD/src/keychain.cpp \ $$PWD/src/filelocker.cpp +HEADERS += \ + $$PWD/src/cefwrapper/external_process.h + SOURCES += \ $$CORE_ROOT_DIR/Common/OfficeFileFormatChecker2.cpp \ $$CORE_ROOT_DIR/Common/3dParty/pole/pole.cpp \ diff --git a/ChromiumBasedEditors/lib/src/cefwrapper/client_renderer_wrapper.cpp b/ChromiumBasedEditors/lib/src/cefwrapper/client_renderer_wrapper.cpp index 7539c4cb..fa16e6f9 100644 --- a/ChromiumBasedEditors/lib/src/cefwrapper/client_renderer_wrapper.cpp +++ b/ChromiumBasedEditors/lib/src/cefwrapper/client_renderer_wrapper.cpp @@ -29,6 +29,7 @@ * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode * */ +#include "./external_process.h" #include "./client_renderer.h" @@ -592,7 +593,7 @@ namespace asc_client_renderer IMPLEMENT_REFCOUNTING(CLocalFileConvertV8Handler); }; - class CAscEditorNativeV8Handler : public CefV8Handler, public CAIToolsHelper + class CAscEditorNativeV8Handler : public CefV8Handler, public CAIToolsHelper, public NSProcesses::CProcessRunnerCallback { class CSavedPageInfo { @@ -717,6 +718,10 @@ namespace asc_client_renderer bool m_bIsMacrosesSupport; bool m_bIsPluginsSupport; + // External processes + CefRefPtr m_frame; + NSProcesses::CProcessManager* m_external_processes; + CAscEditorNativeV8Handler(const std::wstring& sUrl) { m_etType = AscEditorType::etUndefined; @@ -767,6 +772,8 @@ namespace asc_client_renderer m_bEditorsCloudFeaturesCheck = false; m_arCloudFeaturesBlackList.push_back("personal.onlyoffice.com"); + + m_external_processes = NULL; } void CheckDefaults() @@ -811,6 +818,9 @@ namespace asc_client_renderer virtual ~CAscEditorNativeV8Handler() { + RELEASEOBJECT(m_external_processes); + m_frame.reset(); + if (m_pAES_Key) NSOpenSSL::openssl_free(m_pAES_Key); NSBase::Release(m_pLocalApplicationFonts); @@ -939,6 +949,33 @@ return undefined; \n\ return true; } + virtual void process_callback(const int& id, const NSProcesses::StreamType& type, const std::string& message) OVERRIDE + { + if (m_frame && m_external_processes) + { + std::string sMessageDst = message; + NSStringUtils::string_replaceA(sMessageDst, "\\", "\\\\"); + NSStringUtils::string_replaceA(sMessageDst, "\"", "\\\""); + NSStringUtils::string_replaceA(sMessageDst, "\r", ""); + NSStringUtils::string_replaceA(sMessageDst, "\n", ""); + + std::string sCode = "(function(){\n\ +if (!window._external_process_callback) return;\n\ +if (!window._external_process_callback[" + std::to_string(id) + "]) return;\n\ +window._external_process_callback[" + std::to_string(id) + "]._onprocess(" + std::to_string((int)type) + ", \"" + sMessageDst + "\");\n\ +})();"; + + CefPostTask(TID_RENDERER, base::BindOnce([](CefRefPtr frame, const std::string& code) { + if (frame) { + frame->ExecuteJavaScript( + code, + frame->GetURL(), 0); + } + }, + m_frame, sCode)); + } + } + virtual bool Execute(const CefString& sMessageName, CefRefPtr object, const CefV8ValueList& arguments, CefRefPtr& retval, CefString& exception) OVERRIDE { std::string name = sMessageName.ToString(); @@ -2425,6 +2462,35 @@ window.AscDesktopEditor.attachEvent=function(name,callback){if(undefined===windo window.AscDesktopEditor.LocalFileTemplates=function(e){window.__lang_checker_templates__=e||\"\",window.__resize_checker_templates__||(window.__resize_checker_templates__=!0,window.addEventListener(\"resize\",function(){window.AscDesktopEditor._LocalFileTemplates(window.__lang_checker_templates__,(100*window.devicePixelRatio)>>0)})),window.AscDesktopEditor._LocalFileTemplates(window.__lang_checker_templates__,(100*window.devicePixelRatio)>>0)};"; _frame->ExecuteJavaScript(sCodeInitJS, _frame->GetURL(), 0); + + std::string sUrl = _frame->GetURL().ToString(); + if (0 == sUrl.find("file:///") || 0 == sUrl.find("onlyoffice://")) + { + std::string sCode = "function ExternalProcess(command)\n\ +{\n\ +this.command = command;this.id = -1;\n\ +this.start = function() {\n\ +this.id = AscDesktopEditor._createProcess(this.command);\n\ +window._external_process_callback[this.id] = this;\n\ +};\n\ +this.end = function() {\n\ +if (window._external_process_callback[this.id])\n\ +delete window._external_process_callback[this.id];\n\ +AscDesktopEditor._endProcess(this.id);\n\ +};\n\ +this._onprocess = function(type, message) {\n\ +if (2 === type && window._external_process_callback[this.id])\n\ +delete window._external_process_callback[this.id];\n\ +if (this.onprocess)\n\ +this.onprocess(type, message);\n\ +};\n\ +if (!window._external_process_callback) {\n\ +window._external_process_callback = {};\n\ +}\n\ +}"; + + _frame->ExecuteJavaScript(sCode, _frame->GetURL(), 0); + } } return true; @@ -4505,6 +4571,24 @@ window.AscDesktopEditor.CallInFrame(\"" + return true; } + else if (name == "_createProcess") + { + if (!m_external_processes) + m_external_processes = new NSProcesses::CProcessManager(this); + + std::string command = arguments[0]->GetStringValue().ToString(); + int process_id = m_external_processes->Start(command); + + retval = CefV8Value::CreateInt(process_id); + return true; + } + else if (name == "_endProcess") + { + int process_id = arguments[0]->GetIntValue(); + if (m_external_processes) + m_external_processes->End(process_id); + return true; + } // Function does not exist. return false; @@ -5097,6 +5181,17 @@ if (targetElem) { targetElem.dispatchEvent(event); }})();"; message_router_ = CefMessageRouterRendererSide::Create(config); } + virtual void OnBrowserCreated(CefRefPtr app, + CefRefPtr browser, + CefRefPtr extra_info) OVERRIDE + { + } + + virtual void OnBrowserDestroyed(CefRefPtr app, + CefRefPtr browser) OVERRIDE + { + } + virtual void OnContextCreated(CefRefPtr app, CefRefPtr browser, CefRefPtr frame, CefRefPtr context) OVERRIDE { message_router_->OnContextCreated(browser, frame, context); @@ -5119,7 +5214,7 @@ if (targetElem) { targetElem.dispatchEvent(event); }})();"; CefRefPtr handler = pWrapper; -#define EXTEND_METHODS_COUNT 190 +#define EXTEND_METHODS_COUNT 192 const char* methods[EXTEND_METHODS_COUNT] = { "Copy", "Paste", @@ -5384,6 +5479,9 @@ if (targetElem) { targetElem.dispatchEvent(event); }})();"; "_convertFileExternal", "_onConvertFileExternal", + "_createProcess", + "_endProcess", + NULL}; ExtendObject(obj, handler, methods); @@ -5453,6 +5551,8 @@ return this.split(str).join(newStr);\ #else browser->SendProcessMessage(PID_BROWSER, message); #endif + + pWrapper->m_frame = curFrame; } virtual void OnContextReleased(CefRefPtr app, CefRefPtr browser, CefRefPtr frame, CefRefPtr context) OVERRIDE diff --git a/ChromiumBasedEditors/lib/src/cefwrapper/external_process.h b/ChromiumBasedEditors/lib/src/cefwrapper/external_process.h new file mode 100644 index 00000000..4d600b98 --- /dev/null +++ b/ChromiumBasedEditors/lib/src/cefwrapper/external_process.h @@ -0,0 +1,211 @@ +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#endif + +#include +#include +#include +#include +#include + +#include "../../../../../core/DesktopEditor/common/File.h" +#include "../../../../../core/DesktopEditor/graphics/TemporaryCS.h" + +namespace NSProcesses +{ + enum class StreamType + { + StdOut, + StdErr, + Stop + }; + + class CProcessRunnerCallback + { + public: + CProcessRunnerCallback(){} + virtual ~CProcessRunnerCallback(){} + + virtual void process_callback(const int& id, const StreamType& type, const std::string& message) = 0; + }; + + class CProcessRunner + { + public: + CProcessRunner(const int& id, const std::string& command, CProcessRunnerCallback* cb) + : m_command(command), m_callback(cb), m_running(false), m_id(id) + { + } + + ~CProcessRunner() + { + stop(); + } + + void start() + { + if (m_running) + return; + + m_running = true; + + m_worker = std::thread([this]() { + run(); + }); + } + + void stop() + { + if (!m_running) + return; + + m_running = false; + if (m_proc.valid() && m_proc.running()) + m_proc.terminate(); + + if (m_worker.joinable()) + m_worker.join(); + } + + int get_id() + { + return m_id; + } + + private: + void run() + { + boost::process::ipstream out; + boost::process::ipstream err; + + try + { + #ifdef _WIN32 + std::wstring commandW = UTF8_TO_U(m_command); + m_proc = boost::process::child(commandW, boost::process::std_out > out, boost::process::std_err > err); + #else + m_proc = boost::process::child(m_command, boost::process::std_out > out, boost::process::std_err > err); + #endif + } + catch (const std::exception& ex) + { + m_callback->process_callback(m_id, StreamType::StdErr, std::string("Failed to start process: ") + ex.what() + "\n"); + return; + } + + std::thread t_out([this, &out]() { + std::string line; + while (m_running && std::getline(out, line)) + { + m_callback->process_callback(m_id, StreamType::StdOut, line); + } + }); + + std::thread t_err([this, &err]() { + std::string line; + while (m_running && std::getline(err, line)) + { + m_callback->process_callback(m_id, StreamType::StdErr, line); + } + }); + + if (m_proc.running()) + m_proc.wait(); + + m_callback->process_callback(m_id, StreamType::Stop, ""); + + t_out.join(); + t_err.join(); + } + + private: + std::string m_command; + CProcessRunnerCallback* m_callback; + std::thread m_worker; + std::atomic m_running; + boost::process::child m_proc; + int m_id; + }; + + class CProcessManager : public CProcessRunnerCallback + { + private: + std::vector m_processes; + int m_counter; + bool m_enable_callback; + CProcessRunnerCallback* m_callback; + + NSCriticalSection::CRITICAL_SECTION m_cs; + + public: + CProcessManager(CProcessRunnerCallback* cb) + { + m_cs.InitializeCriticalSection(); + m_counter = 1; + m_enable_callback = true; + m_callback = cb; + } + ~CProcessManager() + { + StopAll(); + m_cs.DeleteCriticalSection(); + } + + int Start(const std::string& command) + { + m_cs.Enter(); + int cur_id = m_counter++; + CProcessRunner* runner = new CProcessRunner(cur_id, command, this); + m_processes.push_back(runner); + m_cs.Leave(); + runner->start(); + return cur_id; + } + + void End(const int& id) + { + CTemporaryCS oCS(&m_cs); + + for (std::vector::iterator iter = m_processes.begin(); iter != m_processes.end(); iter++) + { + CProcessRunner* runner = *iter; + if (runner->get_id() == id) + { + delete runner; + m_processes.erase(iter); + return; + } + } + } + + void StopAll() + { + CTemporaryCS oCS(&m_cs); + m_enable_callback = false; + for (std::vector::iterator iter = m_processes.begin(); iter != m_processes.end(); iter++) + { + CProcessRunner* runner = *iter; + delete runner; + } + m_processes.clear(); + } + + void DisableCallbacks() + { + CTemporaryCS oCS(&m_cs); + m_enable_callback = false; + } + + virtual void process_callback(const int& id, const StreamType& type, const std::string& message) + { + CTemporaryCS oCS(&m_cs); + + if (!m_enable_callback) + return; + + m_callback->process_callback(id, type, message); + } + }; +}