mirror of
				https://github.com/MontFerret/ferret.git
				synced 2025-10-30 23:37:40 +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