mirror of
https://github.com/MontFerret/ferret.git
synced 2025-08-15 20:02:56 +02:00
Added element syncrhonization
This commit is contained in:
@@ -149,3 +149,18 @@ func (t *Array) IndexOf(item core.Value) Int {
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func (t *Array) Insert(idx Int, value core.Value) {
|
||||
t.value = append(t.value[:idx], append([]core.Value{value}, t.value[idx:]...)...)
|
||||
}
|
||||
|
||||
func (t *Array) RemoveAt(idx Int) {
|
||||
i := int(idx)
|
||||
max := len(t.value) - 1
|
||||
|
||||
if i > max {
|
||||
return
|
||||
}
|
||||
|
||||
t.value = append(t.value[:i], t.value[i+1:]...)
|
||||
}
|
||||
|
@@ -271,4 +271,89 @@ func TestArray(t *testing.T) {
|
||||
So(len(s2), ShouldEqual, arr.Length()-2)
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".Insert", t, func() {
|
||||
Convey("Should insert an item in the middle of an array", func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(0),
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
)
|
||||
|
||||
lenBefore := arr.Length()
|
||||
|
||||
arr.Insert(3, values.NewInt(100))
|
||||
|
||||
lenAfter := arr.Length()
|
||||
|
||||
So(lenAfter, ShouldBeGreaterThan, lenBefore)
|
||||
So(arr.Get(3), ShouldEqual, 100)
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".RemoveAt", t, func() {
|
||||
Convey("Should remove an item from the middle", func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(0),
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
)
|
||||
|
||||
lenBefore := arr.Length()
|
||||
|
||||
arr.RemoveAt(3)
|
||||
|
||||
lenAfter := arr.Length()
|
||||
|
||||
So(lenAfter, ShouldBeLessThan, lenBefore)
|
||||
So(arr.Get(3), ShouldEqual, 4)
|
||||
})
|
||||
|
||||
Convey("Should remove an item from the end", func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(0),
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
)
|
||||
|
||||
lenBefore := arr.Length()
|
||||
|
||||
arr.RemoveAt(5)
|
||||
|
||||
lenAfter := arr.Length()
|
||||
|
||||
So(lenAfter, ShouldBeLessThan, lenBefore)
|
||||
So(lenAfter, ShouldEqual, 5)
|
||||
So(arr.Get(4), ShouldEqual, 4)
|
||||
})
|
||||
|
||||
Convey("Should remove an item from the beginning", func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(0),
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
)
|
||||
|
||||
lenBefore := arr.Length()
|
||||
|
||||
arr.RemoveAt(0)
|
||||
|
||||
lenAfter := arr.Length()
|
||||
|
||||
So(lenAfter, ShouldBeLessThan, lenBefore)
|
||||
So(arr.Get(0), ShouldEqual, 1)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@@ -150,6 +150,10 @@ func (t *Object) Set(key String, value core.Value) {
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Object) Remove(key String) {
|
||||
delete(t.value, string(key))
|
||||
}
|
||||
|
||||
func (t *Object) SetIn(path []core.Value, value core.Value) error {
|
||||
return SetIn(t, path, value)
|
||||
}
|
||||
|
@@ -27,6 +27,13 @@ func NewLazyValue(factory LazyFactory) *LazyValue {
|
||||
return lz
|
||||
}
|
||||
|
||||
func (lv *LazyValue) Ready() bool {
|
||||
lv.Lock()
|
||||
defer lv.Unlock()
|
||||
|
||||
return lv.ready
|
||||
}
|
||||
|
||||
func (lv *LazyValue) Value() (core.Value, error) {
|
||||
lv.Lock()
|
||||
defer lv.Unlock()
|
32
pkg/stdlib/html/driver/common/sync-value.go
Normal file
32
pkg/stdlib/html/driver/common/sync-value.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type SyncValue struct {
|
||||
sync.Mutex
|
||||
value core.Value
|
||||
}
|
||||
|
||||
func NewSyncValue(init core.Value) *SyncValue {
|
||||
val := new(SyncValue)
|
||||
val.value = init
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
func (sv *SyncValue) Get() core.Value {
|
||||
sv.Lock()
|
||||
defer sv.Unlock()
|
||||
|
||||
return sv.value
|
||||
}
|
||||
|
||||
func (sv *SyncValue) Set(val core.Value) {
|
||||
sv.Lock()
|
||||
defer sv.Unlock()
|
||||
|
||||
sv.value = val
|
||||
}
|
@@ -23,11 +23,11 @@ type HtmlElement struct {
|
||||
sync.Mutex
|
||||
client *cdp.Client
|
||||
broker *events.EventBroker
|
||||
connected bool
|
||||
connected *common.SyncValue
|
||||
id dom.NodeID
|
||||
nodeType values.Int
|
||||
nodeName values.String
|
||||
innerHtml values.String
|
||||
innerHtml *common.SyncValue
|
||||
innerText *common.LazyValue
|
||||
value string
|
||||
attributes *common.LazyValue
|
||||
@@ -53,17 +53,14 @@ func LoadElement(
|
||||
dom.
|
||||
NewDescribeNodeArgs().
|
||||
SetNodeID(id).
|
||||
SetDepth(-1),
|
||||
SetDepth(1),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, core.Error(err, strconv.Itoa(int(id)))
|
||||
}
|
||||
|
||||
innerHtml, err := client.DOM.GetOuterHTML(
|
||||
ctx,
|
||||
dom.NewGetOuterHTMLArgs().SetNodeID(id),
|
||||
)
|
||||
innerHtml, err := loadInnerHtml(client, id)
|
||||
|
||||
if err != nil {
|
||||
return nil, core.Error(err, strconv.Itoa(int(id)))
|
||||
@@ -74,7 +71,7 @@ func LoadElement(
|
||||
broker,
|
||||
id,
|
||||
node.Node,
|
||||
values.NewString(innerHtml.OuterHTML),
|
||||
innerHtml,
|
||||
), nil
|
||||
}
|
||||
|
||||
@@ -88,11 +85,11 @@ func NewHtmlElement(
|
||||
el := new(HtmlElement)
|
||||
el.client = client
|
||||
el.broker = broker
|
||||
el.connected = true
|
||||
el.connected = common.NewSyncValue(values.True)
|
||||
el.id = id
|
||||
el.nodeType = values.NewInt(node.NodeType)
|
||||
el.nodeName = values.NewString(node.NodeName)
|
||||
el.innerHtml = innerHtml
|
||||
el.innerHtml = common.NewSyncValue(innerHtml)
|
||||
el.innerText = common.NewLazyValue(func() (core.Value, error) {
|
||||
h := el.InnerHtml()
|
||||
|
||||
@@ -115,24 +112,235 @@ func NewHtmlElement(
|
||||
})
|
||||
el.value = ""
|
||||
el.loadedChildren = common.NewLazyValue(func() (core.Value, error) {
|
||||
if !el.IsConnected() {
|
||||
return values.NewArray(0), nil
|
||||
}
|
||||
|
||||
return loadNodes(client, broker, el.children)
|
||||
})
|
||||
|
||||
var childCount int
|
||||
|
||||
if node.ChildNodeCount != nil {
|
||||
childCount = *node.ChildNodeCount
|
||||
}
|
||||
|
||||
if node.Value != nil {
|
||||
el.value = *node.Value
|
||||
}
|
||||
|
||||
el.children = make([]dom.NodeID, childCount)
|
||||
el.children = createChildrenArray(node.Children)
|
||||
|
||||
for idx, child := range node.Children {
|
||||
el.children[idx] = child.NodeID
|
||||
}
|
||||
broker.AddEventListener("reload", func(_ interface{}) {
|
||||
el.connected.Set(values.False)
|
||||
})
|
||||
|
||||
broker.AddEventListener("attr:modified", func(message interface{}) {
|
||||
reply, ok := message.(*dom.AttributeModifiedReply)
|
||||
|
||||
// well....
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// it's not for this element
|
||||
if reply.NodeID != el.id {
|
||||
return
|
||||
}
|
||||
|
||||
// they are not event loaded
|
||||
// just ignore the event
|
||||
if !el.attributes.Ready() {
|
||||
return
|
||||
}
|
||||
|
||||
val, err := el.attributes.Value()
|
||||
|
||||
// failed to load
|
||||
// TODO: Log
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
attrs, ok := val.(*values.Object)
|
||||
|
||||
// TODO: Log
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: actually, we need to sync it too...
|
||||
attrs.Set(values.NewString(reply.Name), values.NewString(reply.Value))
|
||||
})
|
||||
|
||||
broker.AddEventListener("attr:removed", func(message interface{}) {
|
||||
reply, ok := message.(*dom.AttributeRemovedReply)
|
||||
|
||||
// well....
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// it's not for this element
|
||||
if reply.NodeID != el.id {
|
||||
return
|
||||
}
|
||||
|
||||
// they are not event loaded
|
||||
// just ignore the event
|
||||
if !el.attributes.Ready() {
|
||||
return
|
||||
}
|
||||
|
||||
val, err := el.attributes.Value()
|
||||
|
||||
// failed to load
|
||||
// TODO: Log
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
attrs, ok := val.(*values.Object)
|
||||
|
||||
// TODO: Log
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: actually, we need to sync it too...
|
||||
attrs.Remove(values.NewString(reply.Name))
|
||||
})
|
||||
|
||||
broker.AddEventListener("children:count", func(message interface{}) {
|
||||
reply, ok := message.(*dom.ChildNodeCountUpdatedReply)
|
||||
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if reply.NodeID != el.id {
|
||||
return
|
||||
}
|
||||
|
||||
node, err := client.DOM.DescribeNode(context.Background(), dom.NewDescribeNodeArgs())
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
el.Lock()
|
||||
defer el.Unlock()
|
||||
|
||||
el.children = createChildrenArray(node.Node.Children)
|
||||
})
|
||||
|
||||
broker.AddEventListener("children:inserted", func(message interface{}) {
|
||||
reply, ok := message.(*dom.ChildNodeInsertedReply)
|
||||
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if reply.ParentNodeID != el.id {
|
||||
return
|
||||
}
|
||||
|
||||
targetIdx := -1
|
||||
prevId := reply.PreviousNodeID
|
||||
nextId := reply.Node.NodeID
|
||||
|
||||
el.Lock()
|
||||
defer el.Unlock()
|
||||
|
||||
for idx, id := range el.children {
|
||||
if id == prevId {
|
||||
targetIdx = idx
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if targetIdx == -1 {
|
||||
return
|
||||
}
|
||||
|
||||
arr := el.children
|
||||
el.children = append(arr[:targetIdx], append([]dom.NodeID{nextId}, arr[targetIdx:]...)...)
|
||||
|
||||
if !el.loadedChildren.Ready() {
|
||||
return
|
||||
}
|
||||
|
||||
loaded, err := el.loadedChildren.Value()
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
loadedArr := loaded.(*values.Array)
|
||||
|
||||
loadedEl, err := LoadElement(el.client, el.broker, nextId)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
loadedArr.Insert(values.NewInt(targetIdx), loadedEl)
|
||||
|
||||
newInnerHtml, err := loadInnerHtml(el.client, el.id)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
el.innerHtml.Set(newInnerHtml)
|
||||
})
|
||||
|
||||
broker.AddEventListener("children:deleted", func(message interface{}) {
|
||||
reply, ok := message.(*dom.ChildNodeRemovedReply)
|
||||
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if reply.ParentNodeID != el.id {
|
||||
return
|
||||
}
|
||||
|
||||
targetIdx := -1
|
||||
targetId := reply.NodeID
|
||||
|
||||
el.Lock()
|
||||
defer el.Unlock()
|
||||
|
||||
for idx, id := range el.children {
|
||||
if id == targetId {
|
||||
targetIdx = idx
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if targetIdx == -1 {
|
||||
return
|
||||
}
|
||||
|
||||
arr := el.children
|
||||
el.children = append(arr[:targetIdx], arr[targetIdx+1:]...)
|
||||
|
||||
if !el.loadedChildren.Ready() {
|
||||
return
|
||||
}
|
||||
|
||||
loaded, err := el.loadedChildren.Value()
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
loadedArr := loaded.(*values.Array)
|
||||
loadedArr.RemoveAt(values.NewInt(targetIdx))
|
||||
|
||||
newInnerHtml, err := loadInnerHtml(el.client, el.id)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
el.innerHtml.Set(newInnerHtml)
|
||||
})
|
||||
|
||||
return el
|
||||
}
|
||||
@@ -325,7 +533,7 @@ func (el *HtmlElement) InnerText() values.String {
|
||||
}
|
||||
|
||||
func (el *HtmlElement) InnerHtml() values.String {
|
||||
return el.innerHtml
|
||||
return el.innerHtml.Get().(values.String)
|
||||
}
|
||||
|
||||
func (el *HtmlElement) Click() (values.Boolean, error) {
|
||||
@@ -335,3 +543,7 @@ func (el *HtmlElement) Click() (values.Boolean, error) {
|
||||
|
||||
return events.DispatchEvent(ctx, el.client, el.id, "click")
|
||||
}
|
||||
|
||||
func (el *HtmlElement) IsConnected() values.Boolean {
|
||||
return el.connected.Get().(values.Boolean)
|
||||
}
|
||||
|
@@ -177,14 +177,20 @@ func (broker *EventBroker) Close() error {
|
||||
|
||||
func (broker *EventBroker) emit(name string, message interface{}) {
|
||||
broker.Lock()
|
||||
defer broker.Unlock()
|
||||
|
||||
listeners, ok := broker.listeners[name]
|
||||
|
||||
if !ok {
|
||||
broker.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
for _, listener := range listeners {
|
||||
// we copy the list of listeners and unlock the broker before the execution.
|
||||
// we do it in order to avoid deadlocks during calls of event listeners
|
||||
snapshot := listeners[:]
|
||||
broker.Unlock()
|
||||
|
||||
for _, listener := range snapshot {
|
||||
listener(message)
|
||||
}
|
||||
}
|
||||
|
@@ -50,6 +50,26 @@ func parseAttrs(attrs []string) *values.Object {
|
||||
return res
|
||||
}
|
||||
|
||||
func loadInnerHtml(client *cdp.Client, id dom.NodeID) (values.String, error) {
|
||||
res, err := client.DOM.GetOuterHTML(context.Background(), dom.NewGetOuterHTMLArgs().SetNodeID(id))
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return values.NewString(res.OuterHTML), err
|
||||
}
|
||||
|
||||
func createChildrenArray(nodes []dom.Node) []dom.NodeID {
|
||||
children := make([]dom.NodeID, len(nodes))
|
||||
|
||||
for idx, child := range nodes {
|
||||
children[idx] = child.NodeID
|
||||
}
|
||||
|
||||
return children
|
||||
}
|
||||
|
||||
func loadNodes(client *cdp.Client, broker *events.EventBroker, nodes []dom.NodeID) (*values.Array, error) {
|
||||
arr := values.NewArray(len(nodes))
|
||||
|
||||
|
Reference in New Issue
Block a user