1
0
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:
Tim Voronov
2018-09-27 00:26:56 -04:00
parent 59d5e3c111
commit 447f9504a9
8 changed files with 404 additions and 23 deletions

View File

@@ -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:]...)
}

View File

@@ -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)
})
})
}

View File

@@ -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)
}

View File

@@ -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()

View 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
}

View File

@@ -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)
}

View File

@@ -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)
}
}

View File

@@ -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))