From d903ecbfcab8b14cd87fd61a0f3fa38a82bd744e Mon Sep 17 00:00:00 2001 From: bablosoft Date: Tue, 12 Jul 2016 04:27:33 +0300 Subject: [PATCH] Version 12.3.0 --- ChromeWorker/ChromeWorker.pro | 10 +- ChromeWorker/browserdata.h | 6 + ChromeWorker/commandparser.cpp | 25 ++ ChromeWorker/commandparser.h | 3 + ChromeWorker/curlresourcehandler.cpp | 12 + ChromeWorker/curlresourcehandler.h | 2 + ChromeWorker/devtoolshandler.cpp | 84 ++++- ChromeWorker/devtoolshandler.h | 17 +- ChromeWorker/handlersmanager.cpp | 219 ++++++++++++ ChromeWorker/handlersmanager.h | 68 ++++ .../html/toolbox/expressioneditor.css | 5 + ChromeWorker/html/toolbox/expressioneditor.js | 4 +- ChromeWorker/html/toolbox/index.html | 81 ++++- ChromeWorker/html/toolbox/style.css | 2 +- ChromeWorker/html/toolbox/translate.js | 7 +- ChromeWorker/main.cpp | 2 + ChromeWorker/mainapp.cpp | 317 +++++++++--------- ChromeWorker/mainapp.h | 14 +- ChromeWorker/mainhandler.cpp | 113 ++++++- ChromeWorker/mainhandler.h | 22 +- ChromeWorker/multithreading.h | 8 +- ChromeWorker/refcountpublic.h | 18 + Engine/ibrowser.h | 2 + Engine/irecordprocesscommunication.h | 1 + Engine/qtresourcecontroller.cpp | 1 + Engine/recordprocesscommunication.cpp | 6 + Engine/recordprocesscommunication.h | 1 + Engine/scripts/engine/common/helpers.js | 15 + Engine/scripts/engine/worker/worker.js | 18 + Engine/subprocessbrowser.cpp | 34 +- Engine/subprocessbrowser.h | 4 + Engine/text/apilist.txt | 2 + Engine/translate/ru.qm | Bin 62469 -> 62611 bytes Engine/versioninfo.cpp | 2 +- Modules/ReCaptcha2/js/captcha2_code.js | 73 +++- Modules/ReCaptcha2/js/engine.js | 70 ++-- Modules/ReCaptcha2/js/manifest.json | 2 +- Studio/mainwindow.cpp | 4 + 38 files changed, 1029 insertions(+), 245 deletions(-) create mode 100644 ChromeWorker/handlersmanager.cpp create mode 100644 ChromeWorker/handlersmanager.h diff --git a/ChromeWorker/ChromeWorker.pro b/ChromeWorker/ChromeWorker.pro index cee5ef4..6d63fe3 100644 --- a/ChromeWorker/ChromeWorker.pro +++ b/ChromeWorker/ChromeWorker.pro @@ -43,15 +43,16 @@ SOURCES += main.cpp \ replaceall.cpp \ convertencoding.cpp \ fixcontentcharset.cpp \ - extract_resources.cpp + extract_resources.cpp \ + handlersmanager.cpp INCLUDEPATH += $(BAS_PATH_WORKER)/include -LIBS += -L$(BAS_PATH_WORKER)/lib -llibiconv -llibcef -lcef_sandbox -llibcef_dll_wrapper -lAdvapi32 -luser32 -lPsapi -lshell32 -lDbgHelp -lgdi32 -llibcurl -llibeay32 -lssleay32 -lnetwork-uri +LIBS += -L$(BAS_PATH_WORKER)/lib -llibiconv -llibcef -llibcef_dll_wrapper -lAdvapi32 -luser32 -lPsapi -lshell32 -lDbgHelp -lgdi32 -llibcurl -llibeay32 -lssleay32 -lnetwork-uri QMAKE_CXXFLAGS_RELEASE += /MT -QMAKE_CXXFLAGS_DEBUG += /MTd /FS +QMAKE_CXXFLAGS_DEBUG += /MTd HEADERS += \ mainapp.h \ @@ -97,7 +98,8 @@ HEADERS += \ replaceall.h \ convertencoding.h \ fixcontentcharset.h \ - extract_resources.h + extract_resources.h \ + handlersmanager.h INCLUDEPATH += xml json png diff --git a/ChromeWorker/browserdata.h b/ChromeWorker/browserdata.h index 510bfa4..fff898b 100644 --- a/ChromeWorker/browserdata.h +++ b/ChromeWorker/browserdata.h @@ -36,6 +36,12 @@ class BrowserData InspectResult _Inspect; ModulesDataList _ModulesData; + //Dialogs + std::string _PromptResult; + std::string _HttpAuthLogin; + std::string _HttpAuthPassword; + + //Reset std::atomic_bool IsReset; std::atomic_bool IsAboutBlankLoaded; diff --git a/ChromeWorker/commandparser.cpp b/ChromeWorker/commandparser.cpp index ae041e0..38c1413 100644 --- a/ChromeWorker/commandparser.cpp +++ b/ChromeWorker/commandparser.cpp @@ -1,6 +1,7 @@ #include "commandparser.h" #include "rapidxml.hpp" #include "log.h" +#include "split.h" CommandParser::CommandParser() { @@ -125,6 +126,30 @@ void CommandParser::Parse(const std::string& Xml) f(value); } + CommandNode = MessagesNode->first_node("SetPromptResult"); + if(CommandNode) + { + std::string value = CommandNode->value(); + worker_log("EventSetPromptResult"); + for(auto f:EventSetPromptResult) + f(value); + } + + CommandNode = MessagesNode->first_node("SetHttpAuthResult"); + if(CommandNode) + { + std::string value = CommandNode->value(); + std::vector s = split(value,':'); + if(s.size() == 2) + { + std::string login = s[0]; + std::string password = s[1]; + worker_log("EventSetHttpAuthResult"); + for(auto f:EventSetHttpAuthResult) + f(login,password); + } + } + CommandNode = MessagesNode->first_node("GetCookiesForUrl"); if(CommandNode) { diff --git a/ChromeWorker/commandparser.h b/ChromeWorker/commandparser.h index 2bcb9d3..bb4c1fb 100644 --- a/ChromeWorker/commandparser.h +++ b/ChromeWorker/commandparser.h @@ -21,6 +21,9 @@ class CommandParser std::vector > EventAddHeader; std::vector > EventSetUserAgent; std::vector > EventSetOpenFileName; + std::vector > EventSetPromptResult; + std::vector > EventSetHttpAuthResult; + std::vector > EventGetUrl; std::vector > EventResize; std::vector > EventSetWindow; diff --git a/ChromeWorker/curlresourcehandler.cpp b/ChromeWorker/curlresourcehandler.cpp index 5520144..15fa04c 100644 --- a/ChromeWorker/curlresourcehandler.cpp +++ b/ChromeWorker/curlresourcehandler.cpp @@ -191,6 +191,12 @@ void CurlThreadFunction(CurlResourceHandler::CurlThreadDataClass * Data) curl_easy_setopt(curl_handle,CURLOPT_PROXYUSERPWD,Data->ProxyAuth.c_str()); } + if(!Data->HttpAuthLogin.empty() && !Data->HttpAuthPassword.empty()) + { + curl_easy_setopt(curl_handle,CURLOPT_USERNAME,Data->HttpAuthLogin.c_str()); + curl_easy_setopt(curl_handle,CURLOPT_PASSWORD,Data->HttpAuthPassword.c_str()); + } + //if(write_logs) //{ //curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 1L); @@ -300,6 +306,12 @@ bool CurlResourceHandler::ProcessRequest(CefRefPtr request, CefRefPt CurlThreadData.ProxyAuth = _BrowserData->_Proxy.AuthToString(); } + { + LOCK_HTTP_AUTH + CurlThreadData.HttpAuthLogin = _BrowserData->_HttpAuthLogin; + CurlThreadData.HttpAuthPassword = _BrowserData->_HttpAuthPassword; + } + CefRequest::HeaderMap ReqestHeaderMap; request->GetHeaderMap(ReqestHeaderMap); diff --git a/ChromeWorker/curlresourcehandler.h b/ChromeWorker/curlresourcehandler.h index 18296a0..46be00d 100644 --- a/ChromeWorker/curlresourcehandler.h +++ b/ChromeWorker/curlresourcehandler.h @@ -51,6 +51,8 @@ class CurlResourceHandler : public CefResourceHandler std::vector PostData; std::string Proxy; std::string ProxyAuth; + std::string HttpAuthLogin; + std::string HttpAuthPassword; /* Sync */ std::atomic_bool StopRequest = false; diff --git a/ChromeWorker/devtoolshandler.cpp b/ChromeWorker/devtoolshandler.cpp index 3ae8ce4..9f79886 100644 --- a/ChromeWorker/devtoolshandler.cpp +++ b/ChromeWorker/devtoolshandler.cpp @@ -4,6 +4,8 @@ DevToolsHandler::DevToolsHandler() { OpenAfterClose = false; + DoClose = false; + DevToolsBrowserId = -1; } void DevToolsHandler::SetData(BrowserData *Data) @@ -11,6 +13,18 @@ void DevToolsHandler::SetData(BrowserData *Data) this->Data = Data; } +void DevToolsHandler::SetLayout(MainLayout *Layout) +{ + this->Layout = Layout; +} + + +void DevToolsHandler::SetHandlersManager(HandlersManager *_HandlersManager) +{ + this->_HandlersManager = _HandlersManager; + this->_HandlersManager->EventNeedToCloseDevTools.push_back(std::bind(&DevToolsHandler::CloseDevTools,this)); +} + void DevToolsHandler::Timer() { if(OpenAfterClose) @@ -24,18 +38,80 @@ void DevToolsHandler::Timer() window_info.SetAsChild(Data->_MainWindowHandle, rect); CefBrowserSettings browser_settings; - Browser->GetHost()->ShowDevTools(window_info, Handler, browser_settings, Point); + _HandlersManager->GetBrowser()->GetHost()->ShowDevTools(window_info, this, browser_settings, Point); + + } + } + + if(DoClose) + { + AfterCloseTimer--; + if(AfterCloseTimer<0) + { + DoClose = false; + DevToolsBrowserId = -1; + //OpenDevTools(); } } } -void DevToolsHandler::OpenNewDevTools(CefPoint Point,CefRefPtr Handler,CefRefPtr Browser, RECT rect) +void DevToolsHandler::OpenDevTools() { + if(DevToolsBrowserId > 0) + return; + CefWindowInfo window_info; + RECT rect = Layout->GetDevToolsRectangle(Data->WidthBrowser,Data->HeightBrowser,Data->WidthAll,Data->HeightAll); + window_info.SetAsChild(Data->_MainWindowHandle, rect); + CefBrowserSettings browser_settings; + _HandlersManager->GetBrowser()->GetHost()->ShowDevTools(window_info, this, browser_settings, CefPoint(0,0)); + DevToolsBrowserId = _HandlersManager->GetBrowser()->GetIdentifier(); + //_HandlersManager->SetDevToolsBorwserId(DevToolsBrowserId); +} + +void DevToolsHandler::OpenDevTools(CefPoint Point) +{ + if(DoClose) + return; + + if(OpenAfterClose) + return; + + RECT rect = Layout->GetDevToolsRectangle(Data->WidthBrowser,Data->HeightBrowser,Data->WidthAll,Data->HeightAll); + HWND HDevTools = Layout->GetDevToolsHandle(); + if(HDevTools) + { + DestroyWindow(HDevTools); + } + OpenAfterClose = true; AfterCloseTimer = 10; this->Point = Point; - this->Handler = Handler; - this->Browser = Browser; this->rect = rect; + + DevToolsBrowserId = _HandlersManager->GetBrowser()->GetIdentifier(); + //_HandlersManager->SetDevToolsBorwserId(DevToolsBrowserId); +} + +void DevToolsHandler::CloseDevTools() +{ + if(DoClose) + return; + + if(OpenAfterClose) + return; + + if(DevToolsBrowserId < 0) + return; + + HWND HDevTools = Layout->GetDevToolsHandle(); + if(HDevTools) + { + DestroyWindow(HDevTools); + DoClose = true; + AfterCloseTimer = 10; + } + + + } diff --git a/ChromeWorker/devtoolshandler.h b/ChromeWorker/devtoolshandler.h index 9affa18..de3e59f 100644 --- a/ChromeWorker/devtoolshandler.h +++ b/ChromeWorker/devtoolshandler.h @@ -2,24 +2,33 @@ #define DEVTOOLSHANDLER_H #include "include/cef_client.h" #include "browserdata.h" +#include "handlersmanager.h" +#include "mainlayout.h" class DevToolsHandler : public CefClient { CefPoint Point; - CefRefPtr Handler; - CefRefPtr Browser; + HandlersManager *_HandlersManager = 0; RECT rect; BrowserData *Data; + MainLayout *Layout; bool OpenAfterClose; + bool DoClose; + int AfterCloseTimer; + int DevToolsBrowserId; + public: DevToolsHandler(); void SetData(BrowserData *Data); - - void OpenNewDevTools(CefPoint Point,CefRefPtr Handler,CefRefPtr Browser, RECT rect); + void SetLayout(MainLayout *Layout); + void SetHandlersManager(HandlersManager *_HandlersManager); + void OpenDevTools(); + void OpenDevTools(CefPoint Point); + void CloseDevTools(); void Timer(); private: IMPLEMENT_REFCOUNTING(DevToolsHandler); diff --git a/ChromeWorker/handlersmanager.cpp b/ChromeWorker/handlersmanager.cpp new file mode 100644 index 0000000..41094d9 --- /dev/null +++ b/ChromeWorker/handlersmanager.cpp @@ -0,0 +1,219 @@ +#include "handlersmanager.h" +#include "include/base/cef_bind.h" +#include "include/wrapper/cef_closure_task.h" +#include "multithreading.h" +#include + +using namespace std::placeholders; + +void HandlersManager::Init1(CefRefPtr Handler,std::function SendTextResponceCallback,std::function UrlLoadedCallback,std::function LoadSuccessCallback,std::function PaintCallback) +{ + this->Handler.swap(Handler); + this->SendTextResponceCallback = SendTextResponceCallback; + this->UrlLoadedCallback = UrlLoadedCallback; + this->LoadSuccessCallback = LoadSuccessCallback; + this->PaintCallback = PaintCallback; + + this->Handler->EventLoadSuccess.push_back(std::bind(&HandlersManager::LoadSuccess,this,_1)); + this->Handler->EventPaint.push_back(std::bind(&HandlersManager::Paint,this,_1,_2,_3,_4)); + this->Handler->EventSendTextResponce.push_back(std::bind(&HandlersManager::SendTextResponce,this,_1,_2)); + this->Handler->EventUrlLoaded.push_back(std::bind(&HandlersManager::UrlLoaded,this,_1,_2,_3)); + + this->Handler->EventPopupClosed.push_back(std::bind(&HandlersManager::PopupRemoved,this,_1)); + this->Handler->EventPopupCreated.push_back(std::bind(&HandlersManager::PopupCreated,this,_1,_2)); +} + +void HandlersManager::Init2(CefRefPtr Browser) +{ + OriginalHandler = std::make_shared(); + OriginalHandler->Handler = this->Handler; + OriginalHandler->Browser = Browser; + OriginalHandler->BrowserId = Browser->GetIdentifier(); + OriginalHandler->IsActive = true; + + UpdateCurrent(); +} + +void HandlersManager::UpdateCurrent() +{ + bool IsPopupActive = false; + for(HandlerUnit h:HandlerUnits) + { + if(h->IsActive && !h->DontUseAsActive && h->IsContextCreated) + { + IsPopupActive = true; + Handler = h->Handler; + Browser = h->Browser; + } + } + + if(!IsPopupActive && OriginalHandler) + { + Handler = OriginalHandler->Handler; + Browser = OriginalHandler->Browser; + } + + CurrentBrowserId = -1; + if(Browser) + CurrentBrowserId = Browser->GetIdentifier(); +} + +MainHandler* HandlersManager::GetHandler() +{ + return Handler.get(); +} + +CefBrowser* HandlersManager::GetBrowser() +{ + return Browser.get(); +} + +void HandlersManager::Timer() +{ + std::vector Ids; + { + LOCK_CONTEXT_LIST + Ids = std::move(NewContextCreatedIds); + NewContextCreatedIds.clear(); + } + + bool Updated = false; + + + for(HandlerUnit h:HandlerUnits) + { + if(std::find(Ids.begin(), Ids.end(), h->BrowserId) != Ids.end()) + { + h->IsContextCreated = true; + Updated = true; + } + } + + auto i = HandlerUnits.begin(); + while (i != HandlerUnits.end()) + { + if(!(*i)->IsActive && (*i)->Handler->ref_count_.ref_count_ == 2 && (*i)->Handler->GetResourceListLength() == 0) + { + (*i)->Browser = 0; + + MainHandler *h = (*i)->Handler.get(); + (*i)->Handler = 0; + delete h; + + i = HandlerUnits.erase(i); + + Updated = true; + }else + { + MainHandler *h = (*i)->Handler.get(); + CefPostTask(TID_IO, base::Bind(&MainHandler::CleanResourceHandlerList, h)); + + ++i; + } + } + if(OriginalHandler) + { + MainHandler *h = OriginalHandler->Handler.get(); + CefPostTask(TID_IO, base::Bind(&MainHandler::CleanResourceHandlerList, h)); + }else + { + MainHandler *h = Handler.get(); + CefPostTask(TID_IO, base::Bind(&MainHandler::CleanResourceHandlerList, h)); + } + if(Updated) + UpdateCurrent(); +} + +void HandlersManager::Reset() +{ + for(HandlerUnit h:HandlerUnits) + { + h->DontUseAsActive = true; + h->Browser->GetHost()->CloseBrowser(true); + } + UpdateCurrent(); + +} + +void HandlersManager::PopupCreated(CefRefPtr new_handler,CefRefPtr new_browser) +{ + HandlerUnit p = std::make_shared(); + p->Handler = new_handler; + p->Browser = new_browser; + p->BrowserId = new_browser->GetIdentifier(); + p->IsActive = true; + p->DontUseAsActive = false; + + + p->Handler->EventLoadSuccess.clear(); + p->Handler->EventPaint.clear(); + p->Handler->EventSendTextResponce.clear(); + p->Handler->EventUrlLoaded.clear(); + p->Handler->EventPopupClosed.clear(); + p->Handler->EventPopupCreated.clear(); + + + p->Handler->EventLoadSuccess.push_back(std::bind(&HandlersManager::LoadSuccess,this,_1)); + p->Handler->EventPaint.push_back(std::bind(&HandlersManager::Paint,this,_1,_2,_3,_4)); + p->Handler->EventSendTextResponce.push_back(std::bind(&HandlersManager::SendTextResponce,this,_1,_2)); + p->Handler->EventUrlLoaded.push_back(std::bind(&HandlersManager::UrlLoaded,this,_1,_2,_3)); + + p->Handler->EventPopupClosed.push_back(std::bind(&HandlersManager::PopupRemoved,this,_1)); + p->Handler->EventPopupCreated.push_back(std::bind(&HandlersManager::PopupCreated,this,_1,_2)); + + HandlerUnits.push_back(p); + + UpdateCurrent(); +} + +void HandlersManager::PopupRemoved(int BrowserId) +{ + for(HandlerUnit h:HandlerUnits) + { + if(h->BrowserId == BrowserId) + { + h->IsActive = false; + + //if(DevToolsBorwserId == h->BrowserId) + { + for(auto f:EventNeedToCloseDevTools) + f(); + } + } + } + UpdateCurrent(); + +} +void HandlersManager::SendTextResponce(const std::string& text, int BrowserId) +{ + if(CurrentBrowserId == BrowserId) + SendTextResponceCallback(text); +} +void HandlersManager::UrlLoaded(const std::string& url, int status, int BrowserId) +{ + if(CurrentBrowserId == BrowserId) + UrlLoadedCallback(url, status); +} +void HandlersManager::LoadSuccess(int BrowserId) +{ + if(CurrentBrowserId == BrowserId) + LoadSuccessCallback(); +} +void HandlersManager::Paint(char * data, int width, int height, int BrowserId) +{ + if(CurrentBrowserId == BrowserId) + PaintCallback(data,width,height); +} + +void HandlersManager::NewContextCreated(int ContextId) +{ + LOCK_CONTEXT_LIST + NewContextCreatedIds.push_back(ContextId); +} + +void HandlersManager::SetDevToolsBorwserId(int DevToolsBorwserId) +{ + this->DevToolsBorwserId = DevToolsBorwserId; +} + + diff --git a/ChromeWorker/handlersmanager.h b/ChromeWorker/handlersmanager.h new file mode 100644 index 0000000..577853f --- /dev/null +++ b/ChromeWorker/handlersmanager.h @@ -0,0 +1,68 @@ +#ifndef HANDLERSMANAGER_H +#define HANDLERSMANAGER_H + +#include +#include +#include "include/cef_app.h" +#include "mainhandler.h" + +class HandlersManager +{ + struct HandlerUnitClass + { + CefRefPtr Handler; + CefRefPtr Browser; + int BrowserId = -1; + bool IsActive = true; + bool DontUseAsActive = false; + bool IsContextCreated = false; + }; + using HandlerUnit = std::shared_ptr; + + std::vector HandlerUnits; + HandlerUnit OriginalHandler; + + /* Callbacks */ + void PopupCreated(CefRefPtr new_handler,CefRefPtr new_browser); + void PopupRemoved(int BrowserId); + void SendTextResponce(const std::string& text, int BrowserId); + void UrlLoaded(const std::string& url, int status, int BrowserId); + void LoadSuccess(int BrowserId); + void Paint(char * data, int width, int height, int BrowserId); + + + int CurrentBrowserId = -1; + int DevToolsBorwserId = -1; + + + CefRefPtr Handler; + CefRefPtr Browser; + + std::function SendTextResponceCallback; + std::function UrlLoadedCallback; + std::function LoadSuccessCallback; + std::function PaintCallback; + + std::vector NewContextCreatedIds; + + void UpdateCurrent(); + +public: + void Reset(); + void Timer(); + MainHandler* GetHandler(); + CefBrowser* GetBrowser(); + void NewContextCreated(int ContextId); + void Init1(CefRefPtr Handler, + std::function SendTextResponceCallback, + std::function UrlLoadedCallback, + std::function LoadSuccessCallback, + std::function PaintCallback + ); + void Init2(CefRefPtr Browser); + + std::vector > EventNeedToCloseDevTools; + void SetDevToolsBorwserId(int DevToolsBorwserId); +}; + +#endif // HANDLERSMANAGER_H diff --git a/ChromeWorker/html/toolbox/expressioneditor.css b/ChromeWorker/html/toolbox/expressioneditor.css index d3227d9..addc550 100644 --- a/ChromeWorker/html/toolbox/expressioneditor.css +++ b/ChromeWorker/html/toolbox/expressioneditor.css @@ -204,6 +204,11 @@ body z-index:99999999; } +#FunctionModal +{ + z-index:99999999; +} + .expressioneditor-overlay-buttons .btn { min-width:100px; diff --git a/ChromeWorker/html/toolbox/expressioneditor.js b/ChromeWorker/html/toolbox/expressioneditor.js index e21ebf1..9a81300 100644 --- a/ChromeWorker/html/toolbox/expressioneditor.js +++ b/ChromeWorker/html/toolbox/expressioneditor.js @@ -444,8 +444,8 @@ /* Random */ {name: "random number", description: "Generate random number", code: "Math.floor(Math.random() * (<<0>> - <<1>> + 1)) + <<1>>", params: [{name: "max"},{name: "min"}]}, - {name: "random string", description: "Generate random string", code: "Array(<<0>>).join().split(',').map(function() { return \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\".charAt(Math.floor(Math.random() * 62)); }).join('');", params: [{name: "length"}]}, - {name: "random alphabet", description: "Generate random string, with given alphabet. Alphabet is just a string, for example, if alphabet is 01 action will generate random binary number", code: "Array(<<1>>).join().split(',').map(function() { return (<<0>>).charAt(Math.floor(Math.random() * <<0>>.length)); }).join('');", params: [{name: "alphabet"},{name: "length"}]}, + {name: "random string", description: "Generate random string", code: "Array(<<0>>).join().split(',').map(function() { return \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\".charAt(Math.floor(Math.random() * 62)); }).join('')", params: [{name: "length"}]}, + {name: "random alphabet", description: "Generate random string, with given alphabet. Alphabet is just a string, for example, if alphabet is 01 action will generate random binary number", code: "Array(<<1>>).join().split(',').map(function() { return (<<0>>).charAt(Math.floor(Math.random() * <<0>>.length)); }).join('')", params: [{name: "alphabet"},{name: "length"}]}, /* Array */ diff --git a/ChromeWorker/html/toolbox/index.html b/ChromeWorker/html/toolbox/index.html index 5eae4c4..87afe72 100644 --- a/ChromeWorker/html/toolbox/index.html +++ b/ChromeWorker/html/toolbox/index.html @@ -355,7 +355,7 @@ @@ -462,6 +462,21 @@ <%= _.template($('#back').html())({action:"executeandadd", visible:true}) %> + + + + + + + +