diff --git a/README.md b/README.md
index 52f3b36..d501463 100644
--- a/README.md
+++ b/README.md
@@ -66,7 +66,6 @@ To automatically collect frame rates and compare it with the normal version
## Roadmap
Here are the things that need to be done next.
-- Add support for form elements like , , etc.
-- Support event utilities that enable things like autofocus, etc.
-- Enable preventDefault() semantics in events.
-- Add test suite
+- ~~Enable preventDefault() semantics in events.~~
+- ~~Add support for form elements like ` `, ``, etc.~~
+- Support event utilities that enable things like autofocus, etc.
diff --git a/package.json b/package.json
index 8a8e7fa..b48ec54 100644
--- a/package.json
+++ b/package.json
@@ -26,6 +26,7 @@
"babel-preset-stage-0": "^6.1.18",
"browser-perf": "^1.4.3",
"react-dom": "^15.3.0",
+ "react": "^15.3.0",
"webpack": "^1.12.8",
"webpack-dev-server": "^1.14.1"
},
diff --git a/src/common/channel.js b/src/common/channel.js
index 80a472d..8c66fa4 100644
--- a/src/common/channel.js
+++ b/src/common/channel.js
@@ -2,6 +2,8 @@ import {WORKER_MESSAGES as _} from './constants';
import TouchList from './api/TouchList';
import Screen from './api/Screen';
+let eventGuid = 0;
+
export default class Channel {
constructor(channel) {
this.channel = channel;
@@ -17,9 +19,11 @@ export default class Channel {
}
static serializeEvent(e) {
let result = {
+ guid: eventGuid++,
bubbles: e.bubbles,
cancelable: e.cancelable,
- defaultPrevented: e.defaultPrevented,
+ defaultPrevented: false,
+ propagationStoped: false,
eventPhase: e.eventPhase,
isTrusted: e.isTrusted,
timeStamp: e.timeStamp,
@@ -39,12 +43,21 @@ export default class Channel {
return JSON.stringify(result);
}
+ static lastSerializedEventGuid() {
+ return eventGuid - 1;
+ }
+
static deserializeEvent(msg) {
let e = JSON.parse(msg);
if (isTouchEvent(e)) {
e = Object.assign(e, getTouchProperties(e));
}
- e.preventDefault = e.stopPropgation = function () { }
+ e.preventDefault = () => {
+ e.defaultPrevented = true;
+ };
+ e.stopPropagation = () => {
+ e.propagationStoped = true;
+ };
return e;
}
}
diff --git a/src/page/Dom.js b/src/page/Dom.js
index 7eb2e4d..761f0a0 100644
--- a/src/page/Dom.js
+++ b/src/page/Dom.js
@@ -4,7 +4,7 @@ import {DOCUMENT_NODE} from './../common/nodeType';
import TouchList from '../common/api/TouchList';
-var body, channel, container, head, nodes = {};
+var body, channel, container, head, nodes = {}, eventHandlers = {};
export default (ctr, messageChannel) => {
body = document.body;
@@ -17,6 +17,18 @@ export default (ctr, messageChannel) => {
}
}
+export function eventHandlerCalled({ guid, defaultPrevented, propagationStoped }) {
+ const [event, resolve] = eventHandlers[guid];
+ if (defaultPrevented) {
+ event.preventDefault();
+ }
+ if (propagationStoped) {
+ event.stopPropagation();
+ }
+ resolve();
+ Reflect.deleteProperty(eventHandlers, guid);
+}
+
const DomOperations = {
[_.attachRoot](none, id, node) {
nodes[id] = container;
@@ -81,12 +93,13 @@ const DomOperations = {
// Events
[_.addEventHandler](id, type, handler, useCapture) {
var node = typeof id === 'string' ? window[id] : nodes[id];
- node.addEventListener(type, (e) => {
+ node.addEventListener(type, asyncify((e) => {
+ let resolver = null;
+ const promise = new Promise(resolve => resolver = resolve);
channel.send(WORKER_MESSAGES.event, { handler, event: Channel.serializeEvent(e) });
- if (type === 'submit'){
- e.preventDefault();
- }
- }, useCapture);
+ eventHandlers[Channel.lastSerializedEventGuid()] = [e, resolver];
+ return promise;
+ }), useCapture);
}
}
@@ -97,13 +110,65 @@ function setAttribute(node, key, value) {
node.style[prop] = value[prop];
}
break;
+ case 'value':
+ node.value = value;
+ break;
case 'checked':
- node.checked = !!value;
+ node.checked = Boolean(value);
+ break;
+ case 'selected':
+ node.selected = Boolean(value);
break;
case 'className':
node.className = value;
+ break;
default:
node.setAttribute(key, value);
}
}
+
+// https://jsbin.com/yiwufaz/edit?js,output
+
+const exceptions = [
+ 'keydown'
+ // ...
+];
+
+function asyncify(cb) {
+ return (event) => {
+ if (!event.__CLONED__) {
+ if (!exceptions.includes(event.type)) {
+ event.preventDefault();
+ }
+ // event.stopPropagation(); // doesn't need with react?
+ const clonedEvent = cloneEvent(event);
+ cb(clonedEvent).then(() => {
+ const { type, target, defaultPrevented } = clonedEvent;
+ target.dispatchEvent(clonedEvent);
+ if (type == 'submit' && !defaultPrevented) {
+ target.submit();
+ }
+ });
+ }
+ };
+}
+
+function cloneEvent(event) {
+ const clonedEvent = new event.constructor(event.type, event);
+ Object.defineProperties(clonedEvent, {
+ __CLONED__: {
+ value: true,
+ enumerable: true
+ },
+ target: {
+ value: event.target,
+ enumerable: true
+ },
+ currentTarget: {
+ value: event.currentTarget,
+ enumerable: true
+ }
+ });
+ return clonedEvent;
+}
diff --git a/src/page/index.js b/src/page/index.js
index c1ea227..a503afe 100644
--- a/src/page/index.js
+++ b/src/page/index.js
@@ -1,6 +1,6 @@
import Channel from './../common/channel';
import {WORKER_MESSAGES as _} from './../common/constants';
-import Dom from './Dom';
+import Dom, { eventHandlerCalled } from './Dom';
class ReactWorker {
constructor(worker, container) {
@@ -13,15 +13,18 @@ class ReactWorker {
handleMessage(type, payload) {
switch (type) {
case _.renderQueue:
- var start = performance.now();
+ // var start = performance.now();
payload.forEach(op => this.domOperation(op));
/*this.channel.send(_.renderTime, {
time: performance.now() - start,
count: payload.length
});*/
break;
+ case _.event:
+ eventHandlerCalled(payload);
+ break;
default:
- console.log('Cannot handle message %s', data.type, data.args);
+ console.log('Cannot handle message %s', type, payload);
}
}
}
diff --git a/src/worker/bridge.js b/src/worker/bridge.js
index df6e88c..987a1fd 100644
--- a/src/worker/bridge.js
+++ b/src/worker/bridge.js
@@ -34,6 +34,14 @@ class WorkerBridge {
this.eventHandler = handler;
}
+ eventHandlerCalled(event) {
+ this.channel.send(_.event, {
+ guid: event.guid,
+ defaultPrevented: event.defaultPrevented,
+ propagationStoped: event.propagationStoped
+ });
+ }
+
send(operation, guid, params) {
if (!Array.isArray(params)) {
params = [params];
diff --git a/src/worker/dom/DomElement.js b/src/worker/dom/DomElement.js
index f89f71b..cde2b9a 100644
--- a/src/worker/dom/DomElement.js
+++ b/src/worker/dom/DomElement.js
@@ -16,10 +16,9 @@ class DomElement extends TreeNode {
this.style = Style((key, val) => this._bridge.send(_.setStyle, this._guid, [key, val]));
this.classList = new ClassList(this);
-
- inputAttributes.forEach(prop => {
- Object
- });
+ if (type == 'select') {
+ this.options = this.children;
+ }
}
setAttribute(key, value) {
@@ -28,8 +27,8 @@ class DomElement extends TreeNode {
}
removeAttribute(key) {
- delete this.attributes[key];
- this._bridge.send(_.removeAttribute, this._guid, [key]);
+ delete this.attributes[key];
+ this._bridge.send(_.removeAttribute, this._guid, [key]);
}
addEventListener(eventType, callback, useCapture) {
@@ -58,7 +57,7 @@ class DomElement extends TreeNode {
}
}
-const inputAttributes = ["accept", "align", "alt", "autocomplete ", "autofocus ", "checked", "disabled", "form", "formaction ", "formenctype", "formmethod ", "formnovalidate ", "formtarget ", "height ", "list ", "max ", "maxlength", "min ", "multiple ", "name", "pattern ", "placeholder", "readonly", "required ", "size", "src", "step", "type", "value", "width"];
+const inputAttributes = ["accept", "align", "alt", "autocomplete ", "autofocus ", "checked", "disabled", "form", "formaction ", "formenctype", "formmethod ", "formnovalidate ", "formtarget ", "height ", "list ", "max ", "maxlength", "min ", "multiple ", "name", "pattern ", "placeholder", "readonly", "required ", "selected", "size", "src", "step", "type", "value", "width"];
export default (tag) => {
let element = new DomElement(tag);
diff --git a/src/worker/eventHandler.js b/src/worker/eventHandler.js
index 2738e84..2a249f3 100644
--- a/src/worker/eventHandler.js
+++ b/src/worker/eventHandler.js
@@ -28,6 +28,7 @@ class EventHandler {
e.currentTarget = nodeList.get(e.currentTarget);
e.target = {... nodeList.get(e.target), ...e.targetProps };
this.eventHandlers[handler](e);
+ Bridge.eventHandlerCalled(e);
} else {
console.log(handler, event);
}
diff --git a/src/worker/index.js b/src/worker/index.js
index 8345703..ab1ca5f 100644
--- a/src/worker/index.js
+++ b/src/worker/index.js
@@ -9,8 +9,6 @@ import Comment from './dom/Comment';
import EventHandler from './eventHandler';
-let nodes = {};
-
const Document = {
nodeType: DOCUMENT_NODE,
_guid: 'document',