var useDataUrls = window.location.search.includes("datauri");
/* check if we are on Android and using Chrome */ var isAndroidChrome = false;
{ var ua = navigator.userAgent.toLowerCase(); if (ua.indexOf("android") > -1 && ua.indexOf("chrom") > -1) {
isAndroidChrome = true;
}
} /* check for the passive option for Event listener */
let passiveSupported = false; try { const options = {
get passive() { // This function will be called when the browser // attempts to access the passive property.
passiveSupported = true; returnfalse;
}
};
var b64_lookup = []; var base64_code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; for (var i = 0, len = base64_code.length; i < len; ++i) {
b64_lookup[i] = base64_code[i];
}
function encodeBase64Chunk (uint8, start, end) { var tmp; var output = []; for (var i = start; i < end; i += 3) {
tmp =
((uint8[i] << 16) & 0xFF0000) +
((uint8[i + 1] << 8) & 0xFF00) +
(uint8[i + 2] & 0xFF);
output.push(tripletToBase64(tmp));
} return output.join('');
}
function bytesToDataUri(uint8) { var tmp; var len = uint8.length; var extraBytes = len % 3; // if we have 1 byte left, pad 2 bytes var parts = []; var maxChunkLength = 16383; // must be multiple of 3
parts.push("data:image/png;base64,");
// go through the array every three bytes, we'll deal with trailing stuff later for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) {
parts.push(encodeBase64Chunk(uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength)));
}
// pad the end with zeros, but make sure to not forget the extra bytes if (extraBytes === 1) {
tmp = uint8[len - 1];
parts.push(b64_lookup[tmp >> 2] + b64_lookup[(tmp << 4) & 0x3F] + '==');
} elseif (extraBytes === 2) {
tmp = (uint8[len - 2] << 8) + uint8[len - 1];
parts.push(b64_lookup[tmp >> 10] + b64_lookup[(tmp >> 4) & 0x3F] + b64_lookup[(tmp << 2) & 0x3F] + '=');
}
return parts.join('');
}
/* Helper functions for debugging */ var logDiv = null; function log(str) { if (!logDiv) {
logDiv = document.createElement('pre');
document.body.appendChild(logDiv);
logDiv.style["position"] = "absolute";
logDiv.style["right"] = "0px";
logDiv.style["font-size"] = "small";
}
logDiv.insertBefore(document.createElement('br'), logDiv.firstChild);
logDiv.insertBefore(document.createTextNode(str), logDiv.firstChild);
}
function getStackTrace()
{ var callstack = []; var isCallstackPopulated = false; try {
i.dont.exist+=0;
} catch(e) { if (e.stack) { // Firefox var lines = e.stack.split("\n"); for (var i=0, len=lines.length; i<len; i++) { if (lines[i].match(/^\s*[A-Za-z0-9\-_\$]+\(/)) {
callstack.push(lines[i]);
}
} // Remove call to getStackTrace()
callstack.shift();
isCallstackPopulated = true;
} elseif (window.opera && e.message) { // Opera var lines = e.message.split("\n"); for (var i=0, len=lines.length; i<len; i++) { if (lines[i].match(/^\s*[A-Za-z0-9\-_\$]+\(/)) { var entry = lines[i]; // Append next line also since it has the file info if (lines[i+1]) {
entry += " at " + lines[i+1];
i++;
}
callstack.push(entry);
}
} // Remove call to getStackTrace()
callstack.shift();
isCallstackPopulated = true;
}
} if (!isCallstackPopulated) { //IE and Safari var currentFunction = arguments.callee.caller; while (currentFunction) { var fn = currentFunction.toString(); var fname = fn.substring(fn.indexOf("function") + 8, fn.indexOf("(")) || "anonymous";
callstack.push(fname);
currentFunction = currentFunction.caller;
}
} return callstack;
}
function logStackTrace(len) { var callstack = getStackTrace(); var end = callstack.length; if (len > 0)
end = Math.min(len + 1, end); for (var i = 1; i < end; i++)
log(callstack[i]);
}
/* Helper functions for touch identifier to make it unique on Android */ var globalTouchIdentifier = Math.round(Date.now() / 1000); function touchIdentifierStart(tId)
{ if (isAndroidChrome) { if (tId == 0) { return ++globalTouchIdentifier;
} return globalTouchIdentifier + tId;
} return tId;
} function touchIdentifier(tId)
{ if (isAndroidChrome) { return globalTouchIdentifier + tId;
} return tId;
}
var grab = new Object();
grab.surface = null;
grab.ownerEvents = false;
grab.implicit = false; var keyDownList = []; var inputList = []; var lastSerial = 0; var lastX = 0; var lastY = 0; var lastState; var lastTimeStamp = 0; var realSurfaceWithMouse = 0; var surfaceWithMouse = 0; var surfaces = {}; var textures = {}; var stackingOrder = []; var outstandingCommands = new Array(); var outstandingDisplayCommands = null; var inputSocket = null; var debugDecoding = false; var fakeInput = null; var showKeyboard = false; var showKeyboardChanged = false; var firstTouchDownId = null;
function getButtonMask (button) { if (button == 1) return GDK_BUTTON1_MASK; if (button == 2) return GDK_BUTTON2_MASK; if (button == 3) return GDK_BUTTON3_MASK; if (button == 4) return GDK_BUTTON4_MASK; if (button == 5) return GDK_BUTTON5_MASK; return0;
}
function Texture(id, data) { var url; if (useDataUrls) {
url = bytesToDataUri(data);
} else { var blob = new Blob([data],{type: "image/png"});
url = window.URL.createObjectURL(blob);
}
this.url = url; this.refcount = 1; this.id = id;
var image = new Image();
image.src = this.url; this.image = image; this.decoded = image.decode();
textures[id] = this;
}
function restackSurfaces() { for (var i = 0; i < stackingOrder.length; i++) { var surface = stackingOrder[i];
surface.div.style.zIndex = i;
}
}
function moveToHelper(surface, position) { var i = stackingOrder.indexOf(surface);
stackingOrder.splice(i, 1); if (position != undefined)
stackingOrder.splice(position, 0, surface); else
stackingOrder.push(surface);
for (var cid in surfaces) { var child = surfaces[cid]; if (child.transientParent == surface.id)
moveToHelper(child, stackingOrder.indexOf(surface) + 1);
}
}
function cmdRoundtrip(id, tag)
{
sendInput(BROADWAY_EVENT_ROUNDTRIP_NOTIFY, [id, tag]);
}
function cmdRaiseSurface(id)
{ var surface = surfaces[id]; if (surface)
moveToHelper(surface);
}
function cmdLowerSurface(id)
{ var surface = surfaces[id]; if (surface)
moveToHelper(surface, 0);
}
TransformNodes.prototype.decode_uint32 = function() { var v = this.node_data.getUint32(this.data_pos, true); this.data_pos += 4; return v;
}
TransformNodes.prototype.decode_int32 = function() { var v = this.node_data.getInt32(this.data_pos, true); this.data_pos += 4; return v;
}
TransformNodes.prototype.decode_float = function() { var v = this.node_data.getFloat32(this.data_pos, true); this.data_pos += 4; return v;
}
TransformNodes.prototype.decode_color = function() { var rgba = this.decode_uint32(); var a = (rgba >> 24) & 0xff; var r = (rgba >> 16) & 0xff; var g = (rgba >> 8) & 0xff; var b = (rgba >> 0) & 0xff; var c; if (a == 255)
c = "rgb(" + r + "," + g + "," + b + ")"; else
c = "rgba(" + r + "," + g + "," + b + "," + (a / 255.0) + ")"; return c;
}
TransformNodes.prototype.decode_size = function() { var s = new Object();
s.width = this.decode_float ();
s.height = this.decode_float (); return s;
}
TransformNodes.prototype.decode_point = function() { var p = new Object();
p.x = this.decode_float ();
p.y = this.decode_float (); return p;
}
TransformNodes.prototype.decode_rect = function() { var r = new Object();
r.x = this.decode_float ();
r.y = this.decode_float ();
r.width = this.decode_float ();
r.height = this.decode_float (); return r;
}
TransformNodes.prototype.decode_irect = function() { var r = new Object();
r.x = this.decode_int32 ();
r.y = this.decode_int32 ();
r.width = this.decode_int32 ();
r.height = this.decode_int32 (); return r;
}
TransformNodes.prototype.decode_rounded_rect = function() { var r = new Object();
r.bounds = this.decode_rect();
r.sizes = []; for (var i = 0; i < 4; i++)
r.sizes[i] = this.decode_size(); return r;
}
TransformNodes.prototype.decode_color_stop = function() { var s = new Object();
s.offset = this.decode_float ();
s.color = this.decode_color (); return s;
}
TransformNodes.prototype.decode_color_stops = function() { var stops = []; var len = this.decode_uint32(); for (var i = 0; i < len; i++)
stops[i] = this.decode_color_stop(); return stops;
}
function utf8_to_string(array) { var out, i, len, c; var char2, char3;
TransformNodes.prototype.decode_string = function() { var len = this.decode_uint32(); var utf8 = new Array(); var b; for (var i = 0; i < len; i++) { if (i % 4 == 0) {
b = this.decode_uint32();
}
utf8[i] = b & 0xff;
b = b >> 8;
}
return utf8_to_string (utf8);
}
TransformNodes.prototype.decode_transform = function() { var transform_type = this.decode_uint32();
if (transform_type == 0) { var point = this.decode_point(); return"translate(" + px(point.x) + "," + px(point.y) + ")";
} elseif (transform_type == 1) { var m = new Array(); for (var i = 0; i < 16; i++) {
m[i] = this.decode_float ();
}
TransformNodes.prototype.createDiv = function(id)
{ var div = document.createElement('div');
div.node_id = id; this.nodes[id] = div; return div;
}
TransformNodes.prototype.createImage = function(id)
{ var image = new Image();
image.node_id = id; this.nodes[id] = image; return image;
}
TransformNodes.prototype.insertNode = function(parent, previousSibling, is_toplevel)
{ var type = this.decode_uint32(); var id = this.decode_uint32(); var newNode = null; var oldNode = null;
switch (type)
{ /* Reuse divs from last frame */ case BROADWAY_NODE_REUSE:
{
oldNode = this.nodes[id];
} break; /* Leaf nodes */
case BROADWAY_NODE_TEXTURE:
{ var rect = this.decode_rect(); var texture_id = this.decode_uint32(); var image = this.createImage(id);
image.width = rect.width;
image.height = rect.height;
image.style["position"] = "absolute";
set_rect_style(image, rect); var texture = textures[texture_id].ref();
image.src = texture.url; // Unref blob url when loaded
image.onload = function() { texture.unref(); };
newNode = image;
} break;
case BROADWAY_NODE_COLOR:
{ var rect = this.decode_rect(); var c = this.decode_color (); var div = this.createDiv(id);
div.style["position"] = "absolute";
set_rect_style(div, rect);
div.style["background-color"] = c;
newNode = div;
} break;
case BROADWAY_NODE_BORDER:
{ var rrect = this.decode_rounded_rect(); var border_widths = []; for (var i = 0; i < 4; i++)
border_widths[i] = this.decode_float(); var border_colors = []; for (var i = 0; i < 4; i++)
border_colors[i] = this.decode_color();
case BROADWAY_NODE_OUTSET_SHADOW:
{ var rrect = this.decode_rounded_rect(); var color = this.decode_color(); var dx = this.decode_float(); var dy = this.decode_float(); var spread = this.decode_float(); var blur = this.decode_float();
case BROADWAY_NODE_INSET_SHADOW:
{ var rrect = this.decode_rounded_rect(); var color = this.decode_color(); var dx = this.decode_float(); var dy = this.decode_float(); var spread = this.decode_float(); var blur = this.decode_float();
case BROADWAY_NODE_LINEAR_GRADIENT:
{ var rect = this.decode_rect(); var start = this.decode_point (); var end = this.decode_point (); var stops = this.decode_color_stops (); var div = this.createDiv(id);
div.style["position"] = "absolute";
set_rect_style(div, rect);
// direction: var dx = end.x - start.x; var dy = end.y - start.y;
// Angle in css coords (clockwise degrees, up = 0), note that y goes downwards so we have to invert var angle = Math.atan2(dx, -dy) * 180.0 / Math.PI;
// Figure out which corner has offset == 0 in css var start_corner_x, start_corner_y; if (dx >= 0) // going right
start_corner_x = rect.x; else
start_corner_x = rect.x + rect.width; if (dy >= 0) // going down
start_corner_y = rect.y; else
start_corner_y = rect.y + rect.height;
/* project start corner on the line */ var l2 = dx*dx + dy*dy; var l = Math.sqrt(l2); var offset = ((start_corner_x - start.x) * dx + (start_corner_y - start.y) * dy) / l2;
var gradient = "linear-gradient(" + angle + "deg"; for (var i = 0; i < stops.length; i++) { var stop = stops[i];
gradient = gradient + ", " + stop.color + " " + px(stop.offset * l - offset);
}
gradient = gradient + ")";
case BROADWAY_NODE_SHADOW:
{ var len = this.decode_uint32(); var filters = ""; for (var i = 0; i < len; i++) { var color = this.decode_color(); var dx = this.decode_float(); var dy = this.decode_float(); var blur = this.decode_float();
filters = filters + "drop-shadow(" + args (px(dx), px(dy), px(blur), color) + ")";
} var div = this.createDiv(id);
div.style["position"] = "absolute";
div.style["left"] = px(0);
div.style["top"] = px(0);
div.style["filter"] = filters;
case BROADWAY_NODE_DEBUG:
{ var str = this.decode_string(); var div = this.createDiv(id);
div.setAttribute('debug', str); this.insertNode(div, null, false);
newNode = div;
} break;
/* Generic nodes */
case BROADWAY_NODE_CONTAINER:
{ var div = this.createDiv(id); var len = this.decode_uint32(); var lastChild = null; for (var i = 0; i < len; i++) {
lastChild = this.insertNode(div, lastChild, false);
}
newNode = div;
} break;
default:
alert("Unexpected node type " + type);
}
if (newNode) { if (is_toplevel) this.display_commands.push([DISPLAY_OP_INSERT_AFTER_CHILD, parent, previousSibling, newNode]); else// It is safe to display directly because we have not added the toplevel to the document yet
parent.appendChild(newNode); return newNode;
} elseif (oldNode) { // This must be delayed until display ops, because it will remove from the old parent this.display_commands.push([DISPLAY_OP_INSERT_AFTER_CHILD, parent, previousSibling, oldNode]); return oldNode;
}
}
TransformNodes.prototype.execute = function(display_commands)
{ var root = this.div;
while (this.data_pos < this.node_data.byteLength) { var op = this.decode_uint32(); var parentId, parent;
switch (op) { case BROADWAY_NODE_OP_INSERT_NODE:
parentId = this.decode_uint32(); if (parentId == 0)
parent = root; else {
parent = this.nodes[parentId]; if (parent == null)
console.log("Wanted to insert into parent " + parentId + " but it is unknown");
}
var previousChildId = this.decode_uint32(); var previousChild = null; if (previousChildId != 0)
previousChild = this.nodes[previousChildId]; this.insertNode(parent, previousChild, true); break; case BROADWAY_NODE_OP_REMOVE_NODE: var removeId = this.decode_uint32(); var remove = this.nodes[removeId]; deletethis.nodes[removeId]; if (remove == null)
console.log("Wanted to delete node " + removeId + " but it is unknown");
this.display_commands.push([DISPLAY_OP_DELETE_NODE, remove]); break; case BROADWAY_NODE_OP_MOVE_AFTER_CHILD:
parentId = this.decode_uint32(); if (parentId == 0)
parent = root; else
parent = this.nodes[parentId]; var previousChildId = this.decode_uint32(); var previousChild = null; if (previousChildId != 0)
previousChild = this.nodes[previousChildId]; var toMoveId = this.decode_uint32(); var toMove = this.nodes[toMoveId]; this.display_commands.push([DISPLAY_OP_INSERT_AFTER_CHILD, parent, previousChild, toMove]); break; case BROADWAY_NODE_OP_PATCH_TEXTURE: var textureNodeId = this.decode_uint32(); var textureNode = this.nodes[textureNodeId]; var textureId = this.decode_uint32(); var texture = textures[textureId].ref(); this.display_commands.push([DISPLAY_OP_CHANGE_TEXTURE, textureNode, texture]); break; case BROADWAY_NODE_OP_PATCH_TRANSFORM: var transformNodeId = this.decode_uint32(); var transformNode = this.nodes[transformNodeId]; var transformString = this.decode_transform(); this.display_commands.push([DISPLAY_OP_CHANGE_TRANSFORM, transformNode, transformString]); break;
}
function cmdUngrabPointer()
{
sendInput (BROADWAY_EVENT_UNGRAB_NOTIFY, []); if (grab.surface)
doUngrab();
}
function handleDisplayCommands(display_commands)
{ var div, parent; var len = display_commands.length; for (var i = 0; i < len; i++) { var cmd = display_commands[i];
switch (cmd[0]) { case DISPLAY_OP_REPLACE_CHILD:
cmd[1].replaceChild(cmd[2], cmd[3]); break; case DISPLAY_OP_APPEND_CHILD:
cmd[1].appendChild(cmd[2]); break; case DISPLAY_OP_INSERT_AFTER_CHILD:
parent = cmd[1]; var afterThis = cmd[2];
div = cmd[3]; if (afterThis == null) // First
parent.insertBefore(div, parent.firstChild); else
parent.insertBefore(div, afterThis.nextSibling); break; case DISPLAY_OP_APPEND_ROOT:
document.body.appendChild(cmd[1]); break; case DISPLAY_OP_SHOW_SURFACE:
div = cmd[1]; var xOffset = cmd[2]; var yOffset = cmd[3];
div.style["left"] = xOffset + "px";
div.style["top"] = yOffset + "px";
div.style["visibility"] = "visible"; break; case DISPLAY_OP_HIDE_SURFACE:
div = cmd[1];
div.style["visibility"] = "hidden"; break; case DISPLAY_OP_DELETE_NODE:
div = cmd[1];
div.parentNode.removeChild(div); break; case DISPLAY_OP_MOVE_NODE:
div = cmd[1];
div.style["left"] = cmd[2] + "px";
div.style["top"] = cmd[3] + "px"; break; case DISPLAY_OP_RESIZE_NODE:
div = cmd[1];
div.style["width"] = cmd[2] + "px";
div.style["height"] = cmd[3] + "px"; break;
case DISPLAY_OP_RESTACK_SURFACES:
restackSurfaces(); break; case DISPLAY_OP_DELETE_SURFACE: var id = cmd[1]; if (id == surfaceWithMouse) {
surfaceWithMouse = 0;
} if (id == realSurfaceWithMouse) {
realSurfaceWithMouse = 0;
firstTouchDownId = null;
} delete surfaces[id]; break; case DISPLAY_OP_CHANGE_TEXTURE: var image = cmd[1]; var texture = cmd[2]; // We need a new closure here to have a separate copy of "texture" for each iteration in the onload callback... var block = function(t) {
image.src = t.url; // Unref blob url when loaded
image.onload = function() { t.unref(); };
};
block(texture); break; case DISPLAY_OP_CHANGE_TRANSFORM: var div = cmd[1]; var transform_string = cmd[2];
div.style["transform"] = transform_string; break; default:
alert("Unknown display op " + command);
}
}
}
function handleCommands(cmd, display_commands, new_textures, modified_trees)
{ var res = true; var need_restack = false;
while (res && cmd.pos < cmd.length) { var id, x, y, w, h, q, surface; var saved_pos = cmd.pos; var command = cmd.get_uint8();
lastSerial = cmd.get_32(); switch (command) { case BROADWAY_OP_DISCONNECTED:
alert ("disconnected");
inputSocket = null; break;
case BROADWAY_OP_NEW_SURFACE:
id = cmd.get_16();
x = cmd.get_16s();
y = cmd.get_16s();
w = cmd.get_16();
h = cmd.get_16(); var div = cmdCreateSurface(id, x, y, w, h);
display_commands.push([DISPLAY_OP_APPEND_ROOT, div]);
need_restack = true; break;
case BROADWAY_OP_SHOW_SURFACE:
id = cmd.get_16();
surface = surfaces[id]; if (!surface.visible) {
surface.visible = true;
display_commands.push([DISPLAY_OP_SHOW_SURFACE, surface.div, surface.x, surface.y]);
need_restack = true;
} break;
case BROADWAY_OP_HIDE_SURFACE:
id = cmd.get_16(); if (grab.surface == id)
doUngrab();
surface = surfaces[id]; if (surface.visible) {
surface.visible = false;
display_commands.push([DISPLAY_OP_HIDE_SURFACE, surface.div]);
} break;
case BROADWAY_OP_SET_TRANSIENT_FOR:
id = cmd.get_16(); var parentId = cmd.get_16();
surface = surfaces[id]; if (surface.transientParent !== parentId) {
surface.transientParent = parentId; if (parentId != 0 && surfaces[parentId]) {
moveToHelper(surface, stackingOrder.indexOf(surfaces[parentId])+1);
}
need_restack = true;
} break;
case BROADWAY_OP_DESTROY_SURFACE:
id = cmd.get_16();
if (grab.surface == id)
doUngrab();
surface = surfaces[id]; var i = stackingOrder.indexOf(surface); if (i >= 0)
stackingOrder.splice(i, 1); var div = surface.div;
display_commands.push([DISPLAY_OP_DELETE_NODE, div]); // We need to delay this until its really deleted because we can still get events to it
display_commands.push([DISPLAY_OP_DELETE_SURFACE, id]); break;
case BROADWAY_OP_ROUNDTRIP:
id = cmd.get_16(); var tag = cmd.get_32();
cmdRoundtrip(id, tag); break;
case BROADWAY_OP_MOVE_RESIZE:
id = cmd.get_16(); var ops = cmd.get_flags(); var has_pos = ops & 1; var has_size = ops & 2;
surface = surfaces[id]; if (has_pos) {
surface.x = cmd.get_16s();
surface.y = cmd.get_16s();
display_commands.push([DISPLAY_OP_MOVE_NODE, surface.div, surface.x, surface.y]);
} if (has_size) {
surface.width = cmd.get_16();
surface.height = cmd.get_16();
display_commands.push([DISPLAY_OP_RESIZE_NODE, surface.div, surface.width, surface.height]);
}
sendConfigureNotify(surface); break;
case BROADWAY_OP_RAISE_SURFACE:
id = cmd.get_16();
cmdRaiseSurface(id);
need_restack = true; break;
case BROADWAY_OP_LOWER_SURFACE:
id = cmd.get_16();
cmdLowerSurface(id);
need_restack = true; break;
case BROADWAY_OP_UPLOAD_TEXTURE:
id = cmd.get_32(); var data = cmd.get_data(); var texture = new Texture (id, data); // Stores a ref in global textures array
new_textures.push(texture); break;
case BROADWAY_OP_RELEASE_TEXTURE:
id = cmd.get_32();
textures[id].unref(); break;
case BROADWAY_OP_SET_NODES:
id = cmd.get_16(); if (id in modified_trees) { // Can't modify the same dom tree in the same loop, bail out and do the first one
cmd.pos = saved_pos;
res = false;
} else {
modified_trees[id] = true;
var node_data = cmd.get_nodes ();
surface = surfaces[id]; var transform_nodes = new TransformNodes (node_data, surface.div, surface.nodes, display_commands);
transform_nodes.execute();
} break;
case BROADWAY_OP_GRAB_POINTER:
id = cmd.get_16(); var ownerEvents = cmd.get_bool ();
cmdGrabPointer(id, ownerEvents); break;
case BROADWAY_OP_UNGRAB_POINTER:
cmdUngrabPointer(); break;
if (need_restack)
display_commands.push([DISPLAY_OP_RESTACK_SURFACES]);
return res;
}
function handleOutstandingDisplayCommands()
{ if (outstandingDisplayCommands) {
window.requestAnimationFrame( function () {
handleDisplayCommands(outstandingDisplayCommands);
outstandingDisplayCommands = null;
if (outstandingCommands.length > 0)
setTimeout(handleOutstanding);
});
} else { if (outstandingCommands.length > 0)
handleOutstanding ();
}
}
/* Mode of operation. *WerunalloutstandingCommands,untileitherwerunoutofthings *toprocess,orweupdatethedomnodesofthesamesurfacetwice. *Thenwewaitforalltexturestoload,andthenwerequestam *animationframeandapplythedisplaychanges.Thenweloopbackand *handleoutstandingcommandsagain. * *Thereasonforstoppingifweupdatethesametreetwiceisthat *thedeltaoperationsgenerallyassumethatthepreviousdomtree *isinpristinecondition.
*/ function handleOutstanding()
{ var display_commands = new Array(); var new_textures = new Array(); var modified_trees = {};
if (outstandingDisplayCommands != null) return;
while (outstandingCommands.length > 0) { var cmd = outstandingCommands.shift(); if (!handleCommands(cmd, display_commands, new_textures, modified_trees)) {
outstandingCommands.unshift(cmd); break;
}
}
if (display_commands.length > 0)
outstandingDisplayCommands = display_commands;
if (new_textures.length > 0) { var decodes = []; for (var i = 0; i < new_textures.length; i++) {
decodes.push(new_textures[i].decoded);
}
Promise.allSettled(decodes).then(
() => {
handleOutstandingDisplayCommands();
});
} else {
handleOutstandingDisplayCommands();
}
}
function BinCommands(message) { this.arraybuffer = message; this.dataview = new DataView(message); this.length = this.arraybuffer.byteLength; this.pos = 0;
}
BinCommands.prototype.get_uint8 = function() { returnthis.dataview.getUint8(this.pos++);
};
BinCommands.prototype.get_bool = function() { returnthis.dataview.getUint8(this.pos++) != 0;
};
BinCommands.prototype.get_flags = function() { returnthis.dataview.getUint8(this.pos++);
}
BinCommands.prototype.get_16 = function() { var v = this.dataview.getUint16(this.pos, true); this.pos = this.pos + 2; return v;
};
BinCommands.prototype.get_16s = function() { var v = this.dataview.getInt16(this.pos, true); this.pos = this.pos + 2; return v;
};
BinCommands.prototype.get_32 = function() { var v = this.dataview.getUint32(this.pos, true); this.pos = this.pos + 4; return v;
};
BinCommands.prototype.get_nodes = function() { var len = this.get_32(); var node_data = new DataView(this.arraybuffer, this.pos, len * 4); this.pos = this.pos + len * 4; return node_data;
};
BinCommands.prototype.get_data = function() { var size = this.get_32(); var data = new Uint8Array (this.arraybuffer, this.pos, size); this.pos = this.pos + size; return data;
};
var active = false; function handleMessage(message)
{ if (!active) {
start();
active = true;
}
var cmd = new BinCommands(message);
outstandingCommands.push(cmd); if (outstandingCommands.length == 1) {
handleOutstanding();
}
}
function getSurfaceId(ev) { var target = ev.target; while (target.surface == undefined) { if (target == document) return0;
target = target.parentNode;
}
return target.surface.id;
}
function sendInput(cmd, args)
{ if (inputSocket == null) return;
var fullArgs = [cmd, lastSerial, lastTimeStamp].concat(args); var buffer = new ArrayBuffer(fullArgs.length * 4); var view = new DataView(buffer);
fullArgs.forEach(function(arg, i) {
view.setInt32(i*4, arg, false);
});
inputSocket.send(buffer);
}
function getPositionsFromAbsCoord(absX, absY, relativeId) { var res = Object();
function getPositionsFromEvent(ev, relativeId) { var absX, absY;
absX = ev.pageX;
absY = ev.pageY; var res = getPositionsFromAbsCoord(absX, absY, relativeId);
lastX = res.rootX;
lastY = res.rootY;
return res;
}
function getEffectiveEventTarget (id) { if (grab.surface != null) { if (!grab.ownerEvents) return grab.surface; if (id == 0) return grab.surface;
} return id;
}
function updateKeyboardStatus() { if (fakeInput != null && showKeyboardChanged) {
showKeyboardChanged = false; if (showKeyboard) { if (isAndroidChrome) {
fakeInput.blur();
fakeInput.value = ' '.repeat(80); // TODO: Should be exchange with broadway server // to bring real value here.
}
fakeInput.focus();
} else
fakeInput.blur();
}
}
function updateForEvent(ev) {
lastState &= ~(GDK_SHIFT_MASK|GDK_CONTROL_MASK|GDK_ALT_MASK); if (ev.shiftKey)
lastState |= GDK_SHIFT_MASK; if (ev.ctrlKey)
lastState |= GDK_CONTROL_MASK; if (ev.altKey)
lastState |= GDK_ALT_MASK;
lastTimeStamp = ev.timeStamp;
}
function onMouseMove (ev) {
updateForEvent(ev); var id = getSurfaceId(ev);
id = getEffectiveEventTarget (id); var pos = getPositionsFromEvent(ev, id);
sendInput (BROADWAY_EVENT_POINTER_MOVE, [realSurfaceWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState]);
}
function onMouseOver (ev) {
updateForEvent(ev);
var id = getSurfaceId(ev);
realSurfaceWithMouse = id;
id = getEffectiveEventTarget (id); var pos = getPositionsFromEvent(ev, id);
surfaceWithMouse = id; if (surfaceWithMouse != 0) {
sendInput (BROADWAY_EVENT_ENTER, [realSurfaceWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_NORMAL]);
}
}
function onMouseOut (ev) {
updateForEvent(ev); var id = getSurfaceId(ev); var origId = id;
id = getEffectiveEventTarget (id); var pos = getPositionsFromEvent(ev, id);
function onMouseDown (ev) {
updateForEvent(ev); var button = ev.button + 1;
lastState = lastState | getButtonMask (button); var id = getSurfaceId(ev);
id = getEffectiveEventTarget (id);
if (grab.surface != null && grab.implicit)
doUngrab();
returnfalse;
}
/* Some of the keyboard handling code is from noVNC and *(c)JoelMartin(github@martintribe.org),usedwithpermission *Originalcodeat: *https://github.com/kanaka/noVNC/blob/master/include/input.js
*/
var ON_KEYDOWN = 1 << 0; /* Report on keydown, otherwise wait until keypress */
var specialKeyTable = { // These generate a keyDown and keyPress in Firefox and Opera 8: [0xFF08, ON_KEYDOWN], // BACKSPACE 13: [0xFF0D, ON_KEYDOWN], // ENTER
// This generates a keyDown and keyPress in Opera 9: [0xFF09, ON_KEYDOWN], // TAB
function getEventKeySym(ev) { if (typeof ev.which !== "undefined" && ev.which > 0) return ev.which; return ev.keyCode;
}
// This is based on the approach from noVNC. We handle // everything in keydown that we have all info for, and that // are not safe to pass on to the browser (as it may do something // with the key. The rest we pass on to keypress so we can get the // translated keysym. function getKeysymSpecial(ev) { if (ev.keyCode in specialKeyTable) { var r = specialKeyTable[ev.keyCode]; var flags = 0; if (typeof r != 'number') {
flags = r[1];
r = r[0];
} if (ev.type === 'keydown' || flags & ON_KEYDOWN) return r;
} // If we don't hold alt or ctrl, then we should be safe to pass // on to keypressed and look at the translated data if (!ev.ctrlKey && !ev.altKey) returnnull;
/* Translate DOM keyPress event to keysym value */ function getKeysym(ev) { var keysym, msg;
keysym = getEventKeySym(ev);
if ((keysym > 255) && (keysym < 0xFF00)) { // Map Unicode outside Latin 1 to gdk keysyms
keysym = unicodeTable[keysym]; if (typeof keysym === 'undefined')
keysym = 0;
}
return keysym;
}
function copyKeyEvent(ev) { var members = ['type', 'keyCode', 'charCode', 'which', 'altKey', 'ctrlKey', 'shiftKey', 'keyLocation', 'keyIdentifier'], i, obj = {}; for (i = 0; i < members.length; i++) { if (typeof ev[members[i]] !== "undefined")
obj[members[i]] = ev[members[i]];
} return obj;
}
function pushKeyEvent(fev) {
keyDownList.push(fev);
}
function copyInputEvent(ev) { var members = ['inputType', 'data'], i, obj = {}; for (i = 0; i < members.length; i++) { if (typeof ev[members[i]] !== "undefined")
obj[members[i]] = ev[members[i]];
} return obj;
}
function pushInputEvent(fev) {
inputList.push(fev);
}
function getKeyEvent(keyCode, pop) { var i, fev = null; for (i = keyDownList.length-1; i >= 0; i--) { if (keyDownList[i].keyCode === keyCode) { if ((typeof pop !== "undefined") && pop)
fev = keyDownList.splice(i, 1)[0]; else
fev = keyDownList[i]; break;
}
} return fev;
}
function ignoreKeyEvent(ev) { // Blarg. Some keys have a different keyCode on keyDown vs keyUp if (ev.keyCode === 229) { // French AZERTY keyboard dead key. // Lame thing is that the respective keyUp is 219 so we can't // properly ignore the keyUp event returntrue;
} returnfalse;
}
function handleKeyDown(e) { var fev = null, ev = (e ? e : window.event), keysym = null, suppress = false;
fev = copyKeyEvent(ev);
keysym = getKeysymSpecial(ev); // Save keysym decoding for use in keyUp
fev.keysym = keysym; if (keysym) { // If it is a key or key combination that might trigger // browser behaviors or it has no corresponding keyPress // event, then send it immediately if (!ignoreKeyEvent(ev))
sendInput(BROADWAY_EVENT_KEY_PRESS, [keysym, lastState]);
suppress = true;
}
if (! ignoreKeyEvent(ev)) { // Add it to the list of depressed keys
pushKeyEvent(fev);
}
if (suppress) { // Suppress bubbling/default actions return cancelEvent(ev);
}
// Allow the event to bubble and become a keyPress event which // will have the character code translated returntrue;
}
function handleKeyPress(e) { var ev = (e ? e : window.event), kdlen = keyDownList.length, keysym = null;
if (((ev.which !== "undefined") && (ev.which === 0)) ||
getKeysymSpecial(ev)) { // Firefox and Opera generate a keyPress event even if keyDown // is suppressed. But the keys we want to suppress will have // either: // - the which attribute set to 0 // - getKeysymSpecial() will identify it return cancelEvent(ev);
}
keysym = getKeysym(ev);
// Modify the which attribute in the depressed keys list so // that the keyUp event will be able to have the character code // translation available. if (kdlen > 0) {
keyDownList[kdlen-1].keysym = keysym;
} else { //log("keyDownList empty when keyPress triggered");
}
// Send the translated keysym if (keysym > 0)
sendInput (BROADWAY_EVENT_KEY_PRESS, [keysym, lastState]);
// Stop keypress events just in case return cancelEvent(ev);
}
function handleKeyUp(e) { var fev = null, ev = (e ? e : window.event), i, keysym;
fev = getKeyEvent(ev.keyCode, true);
if (fev)
keysym = fev.keysym; else { //log("Key event (keyCode = " + ev.keyCode + ") not found on keyDownList"); if (isAndroidChrome && (ev.keyCode == 229)) { var i, fev = null, len = inputList.length, str; for (i = 0; i < len; i++) {
fev = inputList[i]; switch(fev.inputType) { case"deleteContentBackward":
sendInput(BROADWAY_EVENT_KEY_PRESS, [65288, lastState]);
sendInput(BROADWAY_EVENT_KEY_RELEASE, [65288, lastState]); break; case"insertText": if (fev.data !== undefined) { for (let sym of fev.data) {
sendInput(BROADWAY_EVENT_KEY_PRESS, [sym.codePointAt(0), lastState]);
sendInput(BROADWAY_EVENT_KEY_RELEASE, [sym.codePointAt(0), lastState]);
}
} break; default: break;
}
}
inputList.splice(0, len);
}
keysym = 0;
}
function handleInput (e) { var fev = null, ev = (e ? e : window.event), keysym = null, suppress = false;
fev = copyInputEvent(ev);
pushInputEvent(fev);
// Stop keypress events just in case return cancelEvent(ev);
}
function onKeyDown (ev) {
updateForEvent(ev); return handleKeyDown(ev);
}
function onKeyPress(ev) {
updateForEvent(ev); return handleKeyPress(ev);
}
function onKeyUp (ev) {
updateForEvent(ev); return handleKeyUp(ev);
}
function onInput (ev) {
updateForEvent(ev); return handleInput(ev);
}
function cancelEvent(ev)
{
ev = ev ? ev : window.event; if (ev.stopPropagation)
ev.stopPropagation(); if (ev.preventDefault)
ev.preventDefault();
ev.cancelBubble = true;
ev.cancel = true;
ev.returnValue = false; returnfalse;
}
function onMouseWheel(ev)
{
updateForEvent(ev);
ev = ev ? ev : window.event;
var id = getSurfaceId(ev); var pos = getPositionsFromEvent(ev, id);
var offset = ev.detail ? ev.detail : -ev.wheelDelta; var dir = 0; if (offset > 0)
dir = 1;
sendInput (BROADWAY_EVENT_SCROLL, [realSurfaceWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, dir]);
return cancelEvent(ev);
}
function onTouchStart(ev) {
ev.preventDefault();
updateKeyboardStatus();
updateForEvent(ev);
for (var i = 0; i < ev.changedTouches.length; i++) { var touch = ev.changedTouches.item(i); var touchId = touchIdentifierStart(touch.identifier);
var origId = getSurfaceId(touch); var id = getEffectiveEventTarget (origId); var pos = getPositionsFromEvent(touch, id); var isEmulated = 0;
function sendScreenSizeChanged() { var w, h, s;
w = window.innerWidth;
h = window.innerHeight;
s = Math.round(window.devicePixelRatio);
sendInput (BROADWAY_EVENT_SCREEN_SIZE_CHANGED, [w, h, s]);
}
function connect()
{ var url = window.location.toString(); var query_string = url.split("?"); if (query_string.length > 1) { var params = query_string[1].split("&");
for (var i=0; i<params.length; i++) { var pair = params[i].split("="); if (pair[0] == "debug" && pair[1] == "decoding")
debugDecoding = true;
}
}
var loc = window.location.toString().replace("http:", "ws:").replace("https:", "wss:");
loc = loc.substr(0, loc.lastIndexOf('/')) + "/socket";
ws = new WebSocket(loc, "broadway");
ws.binaryType = "arraybuffer";
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.