From b26525e8928f5eec5ee8295257986f207331f4eb Mon Sep 17 00:00:00 2001 From: anoshenko Date: Tue, 1 Nov 2022 20:13:09 +0300 Subject: [PATCH] Updated wasm support --- appServer.go | 2 +- appWasm.go | 280 +++++++++++++++++++++++------------------------- app_scripts.js | 53 +++++++++ app_socket.js | 39 +------ app_wasm.js | 43 +------- downloadFile.go | 3 +- session.go | 13 +-- sessionTheme.go | 9 +- wasmBrige.go | 98 ++++++++++++----- webBrige.go | 1 - 10 files changed, 271 insertions(+), 270 deletions(-) diff --git a/appServer.go b/appServer.go index 7051ce6..e5dec7f 100644 --- a/appServer.go +++ b/appServer.go @@ -200,7 +200,7 @@ func (app *application) startSession(params DataObject, events chan DataObject, session := newSession(app, app.nextSessionID(), "", params) session.setBrige(events, brige) - if !session.setContent(app.createContentFunc(session), session) { + if !session.setContent(app.createContentFunc(session)) { return nil, "" } diff --git a/appWasm.go b/appWasm.go index ea7e50b..d38ba81 100644 --- a/appWasm.go +++ b/appWasm.go @@ -4,6 +4,7 @@ package rui import ( _ "embed" + "strings" "syscall/js" ) @@ -15,35 +16,88 @@ type wasmApp struct { createContentFunc func(Session) SessionContent session Session brige webBrige + close chan DataObject } func (app *wasmApp) Finish() { app.session.close() } -func (app *wasmApp) startSession(this js.Value, args []js.Value) interface{} { - if app.createContentFunc == nil || len(args) == 1 { - return nil - } +func wasmLog(text string) { + js.Global().Call("log", text) +} - params := ParseDataText(args[0].String()) - session := newSession(app, 0, "", params) - session.setBrige(make(chan DataObject), app.brige) - if !session.setContent(app.createContentFunc(session), session) { - return nil - } +func (app *wasmApp) handleMessage(this js.Value, args []js.Value) any { + if len(args) > 0 { + if obj := ParseDataText(args[0].String()); obj != nil { + switch command := obj.Tag(); command { + /* + case "startSession": + answer := "" + if session, answer = app.startSession(obj, events, brige); session != nil { + if !brige.writeMessage(answer) { + return + } + session.onStart() + go sessionEventHandler(session, events, brige) + } - app.session = session + case "reconnect": + if sessionText, ok := obj.PropertyValue("session"); ok { + if sessionID, err := strconv.Atoi(sessionText); err == nil { + if session = app.sessions[sessionID]; session != nil { + session.setBrige(events, brige) + answer := allocStringBuilder() + defer freeStringBuilder(answer) - answer := allocStringBuilder() - defer freeStringBuilder(answer) + session.writeInitScript(answer) + if !brige.writeMessage(answer.String()) { + return + } + session.onReconnect() + go sessionEventHandler(session, events, brige) + return + } + DebugLogF("Session #%d not exists", sessionID) + } else { + ErrorLog(`strconv.Atoi(sessionText) error: ` + err.Error()) + } + } else { + ErrorLog(`"session" key not found`) + } - session.writeInitScript(answer) - answerText := answer.String() + answer := "" + if session, answer = app.startSession(obj, events, brige); session != nil { + if !brige.writeMessage(answer) { + return + } + session.onStart() + go sessionEventHandler(session, events, brige) + } - if ProtocolInDebugLog { - DebugLog("Start session:") - DebugLog(answerText) + case "disconnect": + session.onDisconnect() + return + + case "session-close": + session.onFinish() + session.App().removeSession(session.ID()) + brige.close() + + */ + case "answer": + app.session.handleAnswer(obj) + + case "imageLoaded": + app.session.imageManager().imageLoaded(obj, app.session) + + case "imageError": + app.session.imageManager().imageLoadError(obj, app.session) + + default: + app.session.handleEvent(command, obj) + } + } } return nil } @@ -51,117 +105,75 @@ func (app *wasmApp) startSession(this js.Value, args []js.Value) interface{} { func (app *wasmApp) removeSession(id int) { } +func (app *wasmApp) createSession() Session { + session := newSession(app, 0, "", ParseDataText(js.Global().Call("sessionInfo", "").String())) + session.setBrige(app.close, app.brige) + session.setContent(app.createContentFunc(session)) + return session +} + +func (app *wasmApp) init() { + + document := js.Global().Get("document") + body := document.Call("querySelector", "body") + + script := document.Call("createElement", "script") + script.Set("type", "text/javascript") + script.Set("textContent", defaultScripts+wasmScripts) + body.Call("appendChild", script) + + js.Global().Set("sendMessage", js.FuncOf(app.handleMessage)) + + app.close = make(chan DataObject) + app.session = app.createSession() + + style := document.Call("createElement", "style") + css := appStyles + app.session.getCurrentTheme().cssText(app.session) + css = strings.ReplaceAll(css, `\n`, "\n") + css = strings.ReplaceAll(css, `\t`, "\t") + style.Set("textContent", css) + document.Call("querySelector", "head").Call("appendChild", style) + + buffer := allocStringBuilder() + defer freeStringBuilder(buffer) + + div := document.Call("createElement", "div") + div.Set("className", "ruiRoot") + div.Set("id", "ruiRootView") + viewHTML(app.session.RootView(), buffer) + div.Set("innerHTML", buffer.String()) + body.Call("appendChild", div) + + div = document.Call("createElement", "div") + div.Set("className", "ruiPopupLayer") + div.Set("id", "ruiPopupLayer") + div.Set("onclick", "clickOutsidePopup(event)") + div.Set("style", "visibility: hidden;") + body.Call("appendChild", div) + + div = document.Call("createElement", "a") + div.Set("id", "ruiDownloader") + div.Set("download", "") + div.Set("style", "display: none;") + body.Call("appendChild", div) +} + // StartApp - create the new wasmApp and start it func StartApp(addr string, createContentFunc func(Session) SessionContent, params AppParams) { - app := new(wasmApp) - app.params = params - app.createContentFunc = createContentFunc + SetDebugLog(wasmLog) + SetErrorLog(wasmLog) if createContentFunc == nil { return } + app := new(wasmApp) + app.params = params + app.createContentFunc = createContentFunc app.brige = createWasmBrige() - js.Global().Set("startSession", js.FuncOf(app.startSession)) - /* - script := defaultScripts + wasmScripts - script = strings.ReplaceAll(script, "\\", `\\`) - script = strings.ReplaceAll(script, "\n", `\n`) - script = strings.ReplaceAll(script, "\t", `\t`) - script = strings.ReplaceAll(script, "\"", `\"`) - script = strings.ReplaceAll(script, "'", `\'`) - - js.Global().Call("execScript", `document.getElementById('ruiscript').text += "`+script+`"`) - */ - - document := js.Global().Get("document") - body := document.Call("querySelector", "body") - body.Set("innerHTML", `
- -`) - - //js.Global().Call("execScript", "initSession()") - js.Global().Call("initSession", "") - //window.Call("execScript", "initSession()") - - for true { - if message, ok := app.brige.readMessage(); ok && app.session != nil { - if ProtocolInDebugLog { - DebugLog(message) - } - - if obj := ParseDataText(message); obj != nil { - switch command := obj.Tag(); command { - /* - case "startSession": - answer := "" - if session, answer = app.startSession(obj, events, brige); session != nil { - if !brige.writeMessage(answer) { - return - } - session.onStart() - go sessionEventHandler(session, events, brige) - } - - case "reconnect": - if sessionText, ok := obj.PropertyValue("session"); ok { - if sessionID, err := strconv.Atoi(sessionText); err == nil { - if session = app.sessions[sessionID]; session != nil { - session.setBrige(events, brige) - answer := allocStringBuilder() - defer freeStringBuilder(answer) - - session.writeInitScript(answer) - if !brige.writeMessage(answer.String()) { - return - } - session.onReconnect() - go sessionEventHandler(session, events, brige) - return - } - DebugLogF("Session #%d not exists", sessionID) - } else { - ErrorLog(`strconv.Atoi(sessionText) error: ` + err.Error()) - } - } else { - ErrorLog(`"session" key not found`) - } - - answer := "" - if session, answer = app.startSession(obj, events, brige); session != nil { - if !brige.writeMessage(answer) { - return - } - session.onStart() - go sessionEventHandler(session, events, brige) - } - - case "disconnect": - session.onDisconnect() - return - - case "session-close": - session.onFinish() - session.App().removeSession(session.ID()) - brige.close() - - */ - case "answer": - app.session.handleAnswer(obj) - - case "imageLoaded": - app.session.imageManager().imageLoaded(obj, app.session) - - case "imageError": - app.session.imageManager().imageLoadError(obj, app.session) - - default: - app.session.handleEvent(command, obj) - } - } - } - } + app.init() + <-app.close } func FinishApp() { @@ -171,31 +183,3 @@ func FinishApp() { func OpenBrowser(url string) bool { return false } - -/* -func OpenBrowser(url string) bool { - var err error - - switch runtime.GOOS { - case "linux": - for _, provider := range []string{"xdg-open", "x-www-browser", "www-browser"} { - if _, err = exec.LookPath(provider); err == nil { - if exec.Command(provider, url).Start(); err == nil { - return true - } - } - } - - case "windows": - err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start() - - case "darwin": - err = exec.Command("open", url).Start() - - default: - err = fmt.Errorf("unsupported platform") - } - - return err != nil -} -*/ diff --git a/app_scripts.js b/app_scripts.js index f6fdbe9..10f2a20 100644 --- a/app_scripts.js +++ b/app_scripts.js @@ -14,6 +14,47 @@ window.onblur = function(event) { sendMessage( "session-pause{session=" + sessionID +"}" ); } +function sessionInfo() { + + const touch_screen = (('ontouchstart' in document.documentElement) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)) ? "1" : "0"; + var message = "startSession{touch=" + touch_screen + + const style = window.getComputedStyle(document.body); + if (style) { + var direction = style.getPropertyValue('direction'); + if (direction) { + message += ",direction=" + direction + } + } + + const lang = window.navigator.language; + if (lang) { + message += ",language=\"" + lang + "\""; + } + + const langs = window.navigator.languages; + if (langs) { + message += ",languages=\"" + langs + "\""; + } + + const userAgent = window.navigator.userAgent + if (userAgent) { + message += ",user-agent=\"" + userAgent + "\""; + } + + const darkThemeMq = window.matchMedia("(prefers-color-scheme: dark)"); + if (darkThemeMq.matches) { + message += ",dark=1"; + } + + const pixelRatio = window.devicePixelRatio; + if (pixelRatio) { + message += ",pixel-ratio=" + pixelRatio; + } + + return message + "}"; +} + function getIntAttribute(element, tag) { let value = element.getAttribute(tag); if (value) { @@ -1276,6 +1317,10 @@ function startDowndload(url, filename) { } } +function setTitle(title) { + document.title = title; +} + function setTitleColor(color) { var metas = document.getElementsByTagName('meta'); if (metas) { @@ -1292,6 +1337,10 @@ function setTitleColor(color) { document.getElementsByTagName('head')[0].appendChild(meta); } +function openURL(url) { + window.open(url, "_blank"); +} + function detailsEvent(element) { sendMessage("details-open{session=" + sessionID + ",id=" + element.id + ",open=" + (element.open ? "1}" : "0}")); } @@ -1742,4 +1791,8 @@ function getPropertyValue(answerID, elementId, name) { } sendMessage('answer{answerID=' + answerID + ', value=""}') +} + +function appendStyles(styles) { + document.querySelector('style').textContent += styles } \ No newline at end of file diff --git a/app_socket.js b/app_socket.js index 6b280a5..8694a43 100644 --- a/app_socket.js +++ b/app_socket.js @@ -26,44 +26,7 @@ window.onload = function() { }; function socketOpen() { - - const touch_screen = (('ontouchstart' in document.documentElement) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)) ? "1" : "0"; - var message = "startSession{touch=" + touch_screen - - const style = window.getComputedStyle(document.body); - if (style) { - var direction = style.getPropertyValue('direction'); - if (direction) { - message += ",direction=" + direction - } - } - - const lang = window.navigator.language; - if (lang) { - message += ",language=\"" + lang + "\""; - } - - const langs = window.navigator.languages; - if (langs) { - message += ",languages=\"" + langs + "\""; - } - - const userAgent = window.navigator.userAgent - if (userAgent) { - message += ",user-agent=\"" + userAgent + "\""; - } - - const darkThemeMq = window.matchMedia("(prefers-color-scheme: dark)"); - if (darkThemeMq.matches) { - message += ",dark=1"; - } - - const pixelRatio = window.devicePixelRatio; - if (pixelRatio) { - message += ",pixel-ratio=" + pixelRatio; - } - - sendMessage( message + "}" ); + sendMessage( sessionInfo() ); } function socketReopen() { diff --git a/app_wasm.js b/app_wasm.js index 8916025..a33e94f 100644 --- a/app_wasm.js +++ b/app_wasm.js @@ -1,47 +1,8 @@ - -function initSession() { - - const touch_screen = (('ontouchstart' in document.documentElement) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)) ? "1" : "0"; - var message = "sessionInfo{touch=" + touch_screen - - const style = window.getComputedStyle(document.body); - if (style) { - var direction = style.getPropertyValue('direction'); - if (direction) { - message += ",direction=" + direction - } - } - - const lang = window.navigator.language; - if (lang) { - message += ",language=\"" + lang + "\""; - } - - const langs = window.navigator.languages; - if (langs) { - message += ",languages=\"" + langs + "\""; - } - - const userAgent = window.navigator.userAgent - if (userAgent) { - message += ",user-agent=\"" + userAgent + "\""; - } - - const darkThemeMq = window.matchMedia("(prefers-color-scheme: dark)"); - if (darkThemeMq.matches) { - message += ",dark=1"; - } - - const pixelRatio = window.devicePixelRatio; - if (pixelRatio) { - message += ",pixel-ratio=" + pixelRatio; - } - - startSession( message + "}" ); +function log(s) { + console.log(s); } - window.onfocus = function(event) { windowFocus = true sendMessage( "session-resume{session=" + sessionID +"}" ); diff --git a/downloadFile.go b/downloadFile.go index 44c146a..80e81b2 100644 --- a/downloadFile.go +++ b/downloadFile.go @@ -2,7 +2,6 @@ package rui import ( "bytes" - "fmt" "math/rand" "net/http" "os" @@ -24,7 +23,7 @@ func (session *sessionData) startDownload(file downloadFile) { currentDownloadId++ id := strconv.Itoa(currentDownloadId) downloadFiles[id] = file - session.runScript(fmt.Sprintf(`startDowndload("%s", "%s")`, id, file.filename)) + session.runFunc("startDowndload", id, file.filename) } func serveDownloadFile(id string, w http.ResponseWriter, r *http.Request) bool { diff --git a/session.go b/session.go index 554607a..22521e9 100644 --- a/session.go +++ b/session.go @@ -67,7 +67,7 @@ type Session interface { // Content returns the SessionContent of session Content() SessionContent - setContent(content SessionContent, self Session) bool + setContent(content SessionContent) bool // SetTitle sets the text of the browser title/tab SetTitle(title string) @@ -91,6 +91,7 @@ type Session interface { // OpenURL opens the url in the new browser tab OpenURL(url string) + getCurrentTheme() Theme registerAnimation(props []AnimatedProperty) string resolveConstants(value string) (string, bool) @@ -245,10 +246,10 @@ func (session *sessionData) Content() SessionContent { return session.content } -func (session *sessionData) setContent(content SessionContent, self Session) bool { +func (session *sessionData) setContent(content SessionContent) bool { if content != nil { session.content = content - session.rootView = content.CreateRootView(self) + session.rootView = content.CreateRootView(session) if session.rootView != nil { session.rootView.setParentID("ruiRootView") return true @@ -554,11 +555,11 @@ func (session *sessionData) handleEvent(command string, data DataObject) { func (session *sessionData) SetTitle(title string) { title, _ = session.GetString(title) - session.runScript(`document.title = "` + title + `";`) + session.runFunc("setTitle", title) } func (session *sessionData) SetTitleColor(color Color) { - session.runScript(`setTitleColor("` + color.cssString() + `");`) + session.runFunc("setTitleColor", color.cssString()) } func (session *sessionData) RemoteAddr() string { @@ -570,5 +571,5 @@ func (session *sessionData) OpenURL(urlStr string) { ErrorLog(err.Error()) return } - session.runScript(`window.open("` + urlStr + `", "_blank");`) + session.runFunc("openURL", urlStr) } diff --git a/sessionTheme.go b/sessionTheme.go index 27ee820..8149fab 100644 --- a/sessionTheme.go +++ b/sessionTheme.go @@ -325,15 +325,16 @@ func (session *sessionData) SetLanguage(lang string) { if lang != session.language { session.language = lang - if session.rootView != nil { + if session.rootView != nil && session.brige != nil { buffer := allocStringBuilder() defer freeStringBuilder(buffer) - buffer.WriteString(`document.getElementById('ruiRootView').innerHTML = '`) + //buffer.WriteString(`document.getElementById('ruiRootView').innerHTML = '`) viewHTML(session.rootView, buffer) - buffer.WriteString("';\nscanElementsSize();") + //buffer.WriteString("';\nscanElementsSize();") - session.runScript(buffer.String()) + //session.runScript(buffer.String()) + session.brige.updateInnerHTML("ruiRootView", buffer.String()) } } } diff --git a/wasmBrige.go b/wasmBrige.go index b2bc788..d2309d0 100644 --- a/wasmBrige.go +++ b/wasmBrige.go @@ -3,11 +3,10 @@ package rui import ( + "fmt" "strconv" "sync" "syscall/js" - - "github.com/gorilla/websocket" ) type wasmBrige struct { @@ -18,11 +17,6 @@ type wasmBrige struct { closed bool } -var upgrader = websocket.Upgrader{ - ReadBufferSize: 1024, - WriteBufferSize: 8096, -} - func createWasmBrige() webBrige { brige := new(wasmBrige) brige.queue = make(chan string, 1000) @@ -30,23 +24,58 @@ func createWasmBrige() webBrige { brige.answer = make(map[int]chan DataObject) brige.closed = false - js.Global().Set("sendMessage", js.FuncOf(brige.sendMessage)) - return brige } -func (brige *wasmBrige) sendMessage(this js.Value, args []js.Value) interface{} { - if len(args) > 0 { - brige.queue <- args[0].String() +func (brige *wasmBrige) startUpdateScript(htmlID string) bool { + return false +} + +func (brige *wasmBrige) finishUpdateScript(htmlID string) { +} + +func (brige *wasmBrige) runFunc(funcName string, args ...any) bool { + if ProtocolInDebugLog { + text := funcName + "(" + for i, arg := range args { + if i > 0 { + text += fmt.Sprintf(", `%v`", arg) + } else { + text += fmt.Sprintf("`%v`", arg) + } + } + DebugLog(text + ")") } - return nil + + js.Global().Call(funcName, args...) + return true +} + +func (brige *wasmBrige) updateInnerHTML(htmlID, html string) { + brige.runFunc("updateInnerHTML", htmlID, html) +} + +func (brige *wasmBrige) appendToInnerHTML(htmlID, html string) { + brige.runFunc("appendToInnerHTML", htmlID, html) +} + +func (brige *wasmBrige) updateCSSProperty(htmlID, property, value string) { + brige.runFunc("updateCSSProperty", htmlID, property, value) +} + +func (brige *wasmBrige) updateProperty(htmlID, property string, value any) { + brige.runFunc("updateProperty", htmlID, property, value) +} + +func (brige *wasmBrige) removeProperty(htmlID, property string) { + brige.runFunc("removeProperty", htmlID, property) } func (brige *wasmBrige) close() { } func (brige *wasmBrige) readMessage() (string, bool) { - return <-brige.queue, true + return "", false } func (brige *wasmBrige) writeMessage(script string) bool { @@ -61,25 +90,36 @@ func (brige *wasmBrige) writeMessage(script string) bool { return true } -func (brige *wasmBrige) runGetterScript(script string) DataObject { - brige.answerMutex.Lock() - answerID := brige.answerID - brige.answerID++ - brige.answerMutex.Unlock() +/* + func (brige *wasmBrige) runGetterScript(script string) DataObject { + brige.answerMutex.Lock() + answerID := brige.answerID + brige.answerID++ + brige.answerMutex.Unlock() - answer := make(chan DataObject) - brige.answer[answerID] = answer - errorText := "" + answer := make(chan DataObject) + brige.answer[answerID] = answer + errorText := "" - js.Global().Set("answerID", strconv.Itoa(answerID)) + js.Global().Set("answerID", strconv.Itoa(answerID)) - window := js.Global().Get("window") - window.Call("execScript", script) + window := js.Global().Get("window") + window.Call("execScript", script) - result := NewDataObject("error") - result.SetPropertyValue("text", errorText) - delete(brige.answer, answerID) - return result + result := NewDataObject("error") + result.SetPropertyValue("text", errorText) + delete(brige.answer, answerID) + return result + } +*/ + +func (brige *wasmBrige) canvasTextMetrics(htmlID, font, text string) TextMetrics { + // TODO + return TextMetrics{} +} + +func (brige *wasmBrige) htmlPropertyValue(htmlID, name string) string { + return "" } func (brige *wasmBrige) answerReceived(answer DataObject) { diff --git a/webBrige.go b/webBrige.go index ff44bce..a14fabb 100644 --- a/webBrige.go +++ b/webBrige.go @@ -175,7 +175,6 @@ func (brige *wsBrige) updateCSSProperty(htmlID, property, value string) { } else { brige.runFunc("updateCSSProperty", htmlID, property, value) } - } func (brige *wsBrige) updateProperty(htmlID, property string, value any) {