android: compat webviewexecutor to support arraybuffer

This commit is contained in:
pengfei.zhou 2021-12-31 16:55:38 +08:00 committed by osborn
parent c67803b717
commit 8c57e5e500
19 changed files with 381 additions and 177 deletions

View File

@ -17,6 +17,7 @@ package pub.doric.engine;
import com.github.pengfeizhou.jscore.ArchiveException; import com.github.pengfeizhou.jscore.ArchiveException;
import com.github.pengfeizhou.jscore.JSArray; import com.github.pengfeizhou.jscore.JSArray;
import com.github.pengfeizhou.jscore.JSArrayBuffer;
import com.github.pengfeizhou.jscore.JSBoolean; import com.github.pengfeizhou.jscore.JSBoolean;
import com.github.pengfeizhou.jscore.JSDecoder; import com.github.pengfeizhou.jscore.JSDecoder;
import com.github.pengfeizhou.jscore.JSNull; import com.github.pengfeizhou.jscore.JSNull;
@ -127,6 +128,9 @@ public class DoricJSDecoder extends JSDecoder {
} }
return jsArray; return jsArray;
} }
if (this.value instanceof byte[]) {
return new JSArrayBuffer((byte[]) this.value);
}
return JS_NULL; return JS_NULL;
} }
} }

View File

@ -109,14 +109,14 @@ public class DoricJSEngine implements Handler.Callback, DoricTimerExtension.Time
e.printStackTrace(new PrintWriter(stringWriter)); e.printStackTrace(new PrintWriter(stringWriter));
mDoricRegistry.onLog(Log.ERROR, stringWriter.toString()); mDoricRegistry.onLog(Log.ERROR, stringWriter.toString());
//In case some unexpected errors happened //In case some unexpected errors happened
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mDoricRegistry.onLog(Log.WARN, "Use DoricWebViewJSExecutor"); // mDoricRegistry.onLog(Log.WARN, "Use DoricWebViewJSExecutor");
mDoricJSE = new DoricWebViewJSExecutor(Doric.application()); // mDoricJSE = new DoricWebViewJSExecutor(Doric.application());
loadBuiltinJS("doric-web.js"); // loadBuiltinJS("doric-web.js");
} else { // } else {
mDoricRegistry.onLog(Log.WARN, "Use DoricWebShellJSExecutor"); mDoricRegistry.onLog(Log.WARN, "Use DoricWebShellJSExecutor");
mDoricJSE = new DoricWebShellJSExecutor(Doric.application()); mDoricJSE = new DoricWebShellJSExecutor(Doric.application());
} // }
} }
} }

View File

@ -21,6 +21,7 @@ import android.os.Build;
import android.os.Handler; import android.os.Handler;
import android.os.HandlerThread; import android.os.HandlerThread;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Base64;
import android.webkit.ConsoleMessage; import android.webkit.ConsoleMessage;
import android.webkit.JavascriptInterface; import android.webkit.JavascriptInterface;
import android.webkit.JsResult; import android.webkit.JsResult;
@ -43,6 +44,7 @@ import org.json.JSONObject;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.InputStream; import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
@ -66,6 +68,8 @@ public class DoricWebShellJSExecutor implements IDoricJSE {
private WebView webView; private WebView webView;
private final Map<String, JavaFunction> globalFunctions = new HashMap<>(); private final Map<String, JavaFunction> globalFunctions = new HashMap<>();
private final Handler handler; private final Handler handler;
private static final Map<String, SoftReference<byte[]>> arrayBuffers = new HashMap<>();
private static final AtomicInteger arrayBufferId = new AtomicInteger();
private static Object unwrapJSObject(JSONObject jsonObject) { private static Object unwrapJSObject(JSONObject jsonObject) {
String type = jsonObject.optString("type"); String type = jsonObject.optString("type");
@ -90,6 +94,9 @@ public class DoricWebShellJSExecutor implements IDoricJSE {
e.printStackTrace(); e.printStackTrace();
return JSONObject.NULL; return JSONObject.NULL;
} }
case "arrayBuffer":
String base64 = jsonObject.optString("value");
return Base64.decode(base64, Base64.NO_WRAP);
default: default:
return JSONObject.NULL; return JSONObject.NULL;
} }
@ -127,6 +134,12 @@ public class DoricWebShellJSExecutor implements IDoricJSE {
e.printStackTrace(); e.printStackTrace();
} }
} }
if (javaValue.getType() == 6) {
byte[] data = javaValue.getByteData();
String id = String.valueOf(arrayBufferId.incrementAndGet());
arrayBuffers.put(id, new SoftReference<>(data));
return new JSONBuilder().put("type", "arrayBuffer").put("value", id).toJSONObject();
}
return WRAPPED_NULL; return WRAPPED_NULL;
} }
@ -142,6 +155,16 @@ public class DoricWebShellJSExecutor implements IDoricJSE {
readyFuture.set(true); readyFuture.set(true);
} }
@JavascriptInterface
public String fetchArrayBuffer(String arrayBufferId) {
SoftReference<byte[]> ref = arrayBuffers.remove(arrayBufferId);
if (ref != null && ref.get() != null) {
byte[] data = ref.get();
return Base64.encodeToString(data, Base64.NO_WRAP);
}
return "";
}
@JavascriptInterface @JavascriptInterface
public void log(String message) { public void log(String message) {
DoricLog.d(message); DoricLog.d(message);

View File

@ -22,6 +22,7 @@ import android.os.Build;
import android.os.Handler; import android.os.Handler;
import android.os.HandlerThread; import android.os.HandlerThread;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Base64;
import android.webkit.ConsoleMessage; import android.webkit.ConsoleMessage;
import android.webkit.JavascriptInterface; import android.webkit.JavascriptInterface;
import android.webkit.JsResult; import android.webkit.JsResult;
@ -39,8 +40,10 @@ import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import java.lang.ref.SoftReference;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import pub.doric.BuildConfig; import pub.doric.BuildConfig;
import pub.doric.async.SettableFuture; import pub.doric.async.SettableFuture;
@ -57,6 +60,8 @@ public class DoricWebViewJSExecutor implements IDoricJSE {
private WebView webView; private WebView webView;
private final Map<String, JavaFunction> globalFunctions = new HashMap<>(); private final Map<String, JavaFunction> globalFunctions = new HashMap<>();
private final Handler handler; private final Handler handler;
private static final Map<String, SoftReference<byte[]>> arrayBuffers = new HashMap<>();
private static final AtomicInteger arrayBufferId = new AtomicInteger();
private static Object unwrapJSObject(JSONObject jsonObject) { private static Object unwrapJSObject(JSONObject jsonObject) {
String type = jsonObject.optString("type"); String type = jsonObject.optString("type");
@ -81,6 +86,9 @@ public class DoricWebViewJSExecutor implements IDoricJSE {
e.printStackTrace(); e.printStackTrace();
return JSONObject.NULL; return JSONObject.NULL;
} }
case "arrayBuffer":
String base64 = jsonObject.optString("value");
return Base64.decode(base64, Base64.NO_WRAP);
default: default:
return JSONObject.NULL; return JSONObject.NULL;
} }
@ -118,6 +126,12 @@ public class DoricWebViewJSExecutor implements IDoricJSE {
e.printStackTrace(); e.printStackTrace();
} }
} }
if (javaValue.getType() == 6) {
byte[] data = javaValue.getByteData();
String id = String.valueOf(arrayBufferId.incrementAndGet());
arrayBuffers.put(id, new SoftReference<>(data));
return new JSONBuilder().put("type", "arrayBuffer").put("value", id).toJSONObject();
}
return WRAPPED_NULL; return WRAPPED_NULL;
} }
@ -132,9 +146,19 @@ public class DoricWebViewJSExecutor implements IDoricJSE {
DoricLog.d(message); DoricLog.d(message);
} }
@JavascriptInterface
public String fetchArrayBuffer(String arrayBufferId) {
SoftReference<byte[]> ref = arrayBuffers.remove(arrayBufferId);
if (ref != null && ref.get() != null) {
byte[] data = ref.get();
return Base64.encodeToString(data, Base64.NO_WRAP);
}
return "";
}
@JavascriptInterface @JavascriptInterface
public void returnNative(String result) { public void returnNative(String result) {
DoricLog.d("return Native" + result); DoricLog.d("return Native " + result);
if (returnFuture != null) { if (returnFuture != null) {
returnFuture.set(result); returnFuture.set(result);
} }

View File

@ -52,6 +52,8 @@ import com.bumptech.glide.request.target.DrawableImageViewTarget;
import com.bumptech.glide.request.target.SizeReadyCallback; import com.bumptech.glide.request.target.SizeReadyCallback;
import com.bumptech.glide.request.target.Target; import com.bumptech.glide.request.target.Target;
import com.facebook.yoga.YogaNode; import com.facebook.yoga.YogaNode;
import com.github.pengfeizhou.jscore.ArchiveException;
import com.github.pengfeizhou.jscore.JSDecoder;
import com.github.pengfeizhou.jscore.JSONBuilder; import com.github.pengfeizhou.jscore.JSONBuilder;
import com.github.pengfeizhou.jscore.JSObject; import com.github.pengfeizhou.jscore.JSObject;
import com.github.pengfeizhou.jscore.JSValue; import com.github.pengfeizhou.jscore.JSValue;
@ -394,7 +396,7 @@ public class ImageNode extends ViewNode<ImageView> {
} }
@Override @Override
protected void blend(ImageView view, String name, JSValue prop) { protected void blend(final ImageView view, String name, JSValue prop) {
switch (name) { switch (name) {
case "image": case "image":
if (!prop.isObject()) { if (!prop.isObject()) {
@ -522,13 +524,46 @@ public class ImageNode extends ViewNode<ImageView> {
if (!prop.isObject()) { if (!prop.isObject()) {
return; return;
} }
int width = prop.asObject().getProperty("width").asNumber().toInt(); final int width = prop.asObject().getProperty("width").asNumber().toInt();
int height = prop.asObject().getProperty("height").asNumber().toInt(); final int height = prop.asObject().getProperty("height").asNumber().toInt();
JSValue pixelsValue = prop.asObject().getProperty("pixels");
if (pixelsValue.isArrayBuffer()) {
byte[] pixels = prop.asObject().getProperty("pixels").asArrayBuffer().value(); byte[] pixels = prop.asObject().getProperty("pixels").asArrayBuffer().value();
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
ByteBuffer byteBuffer = ByteBuffer.wrap(pixels); ByteBuffer byteBuffer = ByteBuffer.wrap(pixels);
bitmap.copyPixelsFromBuffer(byteBuffer); bitmap.copyPixelsFromBuffer(byteBuffer);
view.setImageBitmap(bitmap); view.setImageBitmap(bitmap);
} else if (pixelsValue.isString()) {
String pixelsCallbackId = pixelsValue.asString().value();
callJSResponse(pixelsCallbackId).setCallback(new AsyncResult.Callback<JSDecoder>() {
@Override
public void onResult(JSDecoder result) {
try {
JSValue value = result.decode();
if (value.isArrayBuffer()) {
byte[] pixels = value.asArrayBuffer().value();
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
ByteBuffer byteBuffer = ByteBuffer.wrap(pixels);
bitmap.copyPixelsFromBuffer(byteBuffer);
view.setImageBitmap(bitmap);
}
} catch (ArchiveException e) {
e.printStackTrace();
}
}
@Override
public void onError(Throwable t) {
}
@Override
public void onFinish() {
}
});
}
break; break;
default: default:
super.blend(view, name, prop); super.blend(view, name, prop);

View File

@ -0,0 +1,23 @@
package com.github.pengfeizhou.jscore;
public class JSArrayBuffer extends JSValue {
private final byte[] mVal;
public JSArrayBuffer(byte[] val) {
this.mVal = val;
}
public int getByteLength() {
return mVal.length;
}
@Override
public JSType getJSType() {
return JSType.ArrayBuffer;
}
@Override
public byte[] value() {
return mVal;
}
}

View File

@ -25,6 +25,7 @@ public abstract class JSValue {
String, String,
Object, Object,
Array, Array,
ArrayBuffer,
} }
public abstract JSType getJSType(); public abstract JSType getJSType();
@ -55,6 +56,10 @@ public abstract class JSValue {
return getJSType() == JSType.Array; return getJSType() == JSType.Array;
} }
public boolean isArrayBuffer() {
return getJSType() == JSType.ArrayBuffer;
}
public JSNull asNull() { public JSNull asNull() {
return (JSNull) this; return (JSNull) this;
} }
@ -79,6 +84,10 @@ public abstract class JSValue {
return (JSArray) this; return (JSArray) this;
} }
public JSArrayBuffer asArrayBuffer() {
return (JSArrayBuffer) this;
}
@Override @Override
public String toString() { public String toString() {
return String.valueOf(value()); return String.valueOf(value());

View File

@ -25,10 +25,14 @@ public class JavaValue {
protected static final int TYPE_STRING = 3; protected static final int TYPE_STRING = 3;
protected static final int TYPE_OBJECT = 4; protected static final int TYPE_OBJECT = 4;
protected static final int TYPE_ARRAY = 5; protected static final int TYPE_ARRAY = 5;
protected static final int TYPE_ARRAYBUFFER = 6;
protected JavaFunction[] functions = null; protected JavaFunction[] functions = null;
protected String[] functionNames = null; protected String[] functionNames = null;
protected int type; protected int type;
protected String value = ""; protected String value = "";
protected byte[] data = null;
protected MemoryReleaser memoryReleaser = null;
public JavaValue() { public JavaValue() {
this.type = TYPE_NULL; this.type = TYPE_NULL;
@ -75,6 +79,17 @@ public class JavaValue {
this.value = jsonArray.toString(); this.value = jsonArray.toString();
} }
public JavaValue(byte[] data) {
this.type = TYPE_ARRAYBUFFER;
this.data = data;
}
public JavaValue(byte[] data, MemoryReleaser memoryReleaser) {
this.type = TYPE_ARRAYBUFFER;
this.data = data;
this.memoryReleaser = memoryReleaser;
}
public int getType() { public int getType() {
return type; return type;
} }
@ -82,4 +97,24 @@ public class JavaValue {
public String getValue() { public String getValue() {
return value; return value;
} }
public byte[] getByteData() {
return data;
}
public interface MemoryReleaser {
/**
* Called when JS deallocated the arraybuffer
*/
void deallocate(byte[] data);
}
/**
* Called by JNI
*/
private void onDeallocated() {
if (this.memoryReleaser != null) {
this.memoryReleaser.deallocate(this.data);
}
}
} }

View File

@ -2314,13 +2314,11 @@ var Image = /** @class */ (function (_super) {
}; };
Image.prototype.toModel = function () { Image.prototype.toModel = function () {
var ret = _super.prototype.toModel.call(this); var ret = _super.prototype.toModel.call(this);
if (Environment.platform === 'iOS') {
if (Reflect.has(ret.props, "imagePixels")) { if (Reflect.has(ret.props, "imagePixels")) {
var imagePixels = Reflect.get(ret.props, "imagePixels"); var imagePixels = Reflect.get(ret.props, "imagePixels");
var pixels_1 = imagePixels.pixels; var pixels_1 = imagePixels.pixels;
imagePixels.pixels = this.callback2Id(function () { return pixels_1; }); imagePixels.pixels = this.callback2Id(function () { return pixels_1; });
} }
}
return ret; return ret;
}; };
__decorate$b([ __decorate$b([

View File

@ -1728,13 +1728,11 @@ class Image extends View {
} }
toModel() { toModel() {
const ret = super.toModel(); const ret = super.toModel();
if (Environment.platform === 'iOS') {
if (Reflect.has(ret.props, "imagePixels")) { if (Reflect.has(ret.props, "imagePixels")) {
const imagePixels = Reflect.get(ret.props, "imagePixels"); const imagePixels = Reflect.get(ret.props, "imagePixels");
const pixels = imagePixels.pixels; const pixels = imagePixels.pixels;
imagePixels.pixels = this.callback2Id(() => pixels); imagePixels.pixels = this.callback2Id(() => pixels);
} }
}
return ret; return ret;
} }
} }

View File

@ -3256,13 +3256,11 @@ class Image extends View {
} }
toModel() { toModel() {
const ret = super.toModel(); const ret = super.toModel();
if (Environment.platform === 'iOS') {
if (Reflect.has(ret.props, "imagePixels")) { if (Reflect.has(ret.props, "imagePixels")) {
const imagePixels = Reflect.get(ret.props, "imagePixels"); const imagePixels = Reflect.get(ret.props, "imagePixels");
const pixels = imagePixels.pixels; const pixels = imagePixels.pixels;
imagePixels.pixels = this.callback2Id(() => pixels); imagePixels.pixels = this.callback2Id(() => pixels);
} }
}
return ret; return ret;
} }
} }

View File

@ -1,42 +1,53 @@
'use strict'; 'use strict';
"use strict"; "use strict";
function _binaryValue(v) { function _arrayBufferToBase64(arrayBuffer) {
switch (typeof v) { let base64 = '';
case "number": const encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
return { const bytes = new Uint8Array(arrayBuffer);
type: "number", const byteLength = bytes.byteLength;
value: v const byteRemainder = byteLength % 3;
}; const mainLength = byteLength - byteRemainder;
case "string": let a, b, c, d;
return { let chunk;
type: "string", // Main loop deals with bytes in chunks of 3
value: v for (var i = 0; i < mainLength; i = i + 3) {
}; // Combine the three bytes into a single integer
case "boolean": chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
return { // Use bitmasks to extract 6-bit segments from the triplet
type: "boolean", a = (chunk & 16515072) >> 18; // 16515072 = (2^6 - 1) << 18
value: v b = (chunk & 258048) >> 12; // 258048 = (2^6 - 1) << 12
}; c = (chunk & 4032) >> 6; // 4032 = (2^6 - 1) << 6
case "object": d = chunk & 63; // 63 = 2^6 - 1
if (v instanceof Array) { // Convert the raw binary segments to the appropriate ASCII encoding
return { base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d];
type: "array",
value: JSON.stringify(v)
};
} }
else { // Deal with the remaining bytes and padding
return { if (byteRemainder == 1) {
type: "object", chunk = bytes[mainLength];
value: JSON.stringify(v) a = (chunk & 252) >> 2; // 252 = (2^6 - 1) << 2
}; // Set the 4 least significant bits to zero
b = (chunk & 3) << 4; // 3 = 2^2 - 1
base64 += encodings[a] + encodings[b] + '==';
} }
default: else if (byteRemainder == 2) {
return { chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1];
type: "null", a = (chunk & 64512) >> 10; // 64512 = (2^6 - 1) << 10
value: undefined b = (chunk & 1008) >> 4; // 1008 = (2^6 - 1) << 4
}; // Set the 2 least significant bits to zero
c = (chunk & 15) << 2; // 15 = 2^4 - 1
base64 += encodings[a] + encodings[b] + encodings[c] + '=';
} }
return base64;
}
function _base64ToArrayBuffer(v) {
const binary_string = window.atob(v);
const len = binary_string.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
} }
function _wrappedValue(v) { function _wrappedValue(v) {
switch (typeof v) { switch (typeof v) {
@ -56,7 +67,13 @@ function _wrappedValue(v) {
value: v value: v
}; };
case "object": case "object":
if (v instanceof Array) { if (v instanceof ArrayBuffer) {
return {
type: "arrayBuffer",
value: _arrayBufferToBase64(v)
};
}
else if (v instanceof Array) {
return { return {
type: "array", type: "array",
value: JSON.stringify(v) value: JSON.stringify(v)
@ -89,6 +106,10 @@ function _rawValue(v) {
return JSON.parse(v.value); return JSON.parse(v.value);
} }
return v.value; return v.value;
case "arrayBuffer":
const data = NativeClient.fetchArrayBuffer(v.value);
return _base64ToArrayBuffer(data);
;
default: default:
return undefined; return undefined;
} }

View File

@ -2,51 +2,74 @@ declare module NativeClient {
function log(message: string): void function log(message: string): void
function returnNative(ret: string): void function returnNative(ret: string): void
function callNative(name: string, args: string): string function callNative(name: string, args: string): string
function fetchArrayBuffer(id: string): string
} }
type RawValue = number | string | boolean | object | undefined type RawValue = number | string | boolean | object | undefined | ArrayBuffer
type WrappedValue = { type WrappedValue = {
type: "number" | "string" | "boolean" | "object" | "array" | "null", type: "number" | "string" | "boolean" | "object" | "array" | "null" | "arrayBuffer",
value: RawValue, value: RawValue,
} }
function _binaryValue(v: RawValue) {
switch (typeof v) { function _arrayBufferToBase64(arrayBuffer: ArrayBuffer) {
case "number": let base64 = ''
return { const encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
type: "number",
value: v const bytes = new Uint8Array(arrayBuffer)
}; const byteLength = bytes.byteLength
case "string": const byteRemainder = byteLength % 3
return { const mainLength = byteLength - byteRemainder
type: "string",
value: v let a, b, c, d
}; let chunk
case "boolean":
return { // Main loop deals with bytes in chunks of 3
type: "boolean", for (var i = 0; i < mainLength; i = i + 3) {
value: v // Combine the three bytes into a single integer
}; chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2]
case "object": // Use bitmasks to extract 6-bit segments from the triplet
if (v instanceof Array) { a = (chunk & 16515072) >> 18 // 16515072 = (2^6 - 1) << 18
return { b = (chunk & 258048) >> 12 // 258048 = (2^6 - 1) << 12
type: "array", c = (chunk & 4032) >> 6 // 4032 = (2^6 - 1) << 6
value: JSON.stringify(v) d = chunk & 63 // 63 = 2^6 - 1
}; // Convert the raw binary segments to the appropriate ASCII encoding
} else { base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d]
return {
type: "object",
value: JSON.stringify(v)
};
}
default:
return {
type: "null",
value: undefined
};
} }
// Deal with the remaining bytes and padding
if (byteRemainder == 1) {
chunk = bytes[mainLength]
a = (chunk & 252) >> 2 // 252 = (2^6 - 1) << 2
// Set the 4 least significant bits to zero
b = (chunk & 3) << 4 // 3 = 2^2 - 1
base64 += encodings[a] + encodings[b] + '=='
} else if (byteRemainder == 2) {
chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1]
a = (chunk & 64512) >> 10 // 64512 = (2^6 - 1) << 10
b = (chunk & 1008) >> 4 // 1008 = (2^6 - 1) << 4
// Set the 2 least significant bits to zero
c = (chunk & 15) << 2 // 15 = 2^4 - 1
base64 += encodings[a] + encodings[b] + encodings[c] + '='
}
return base64
} }
function _base64ToArrayBuffer(v: string) {
const binary_string = window.atob(v);
const len = binary_string.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
}
function _wrappedValue(v: RawValue): WrappedValue { function _wrappedValue(v: RawValue): WrappedValue {
switch (typeof v) { switch (typeof v) {
case "number": case "number":
@ -65,7 +88,12 @@ function _wrappedValue(v: RawValue): WrappedValue {
value: v value: v
}; };
case "object": case "object":
if (v instanceof Array) { if (v instanceof ArrayBuffer) {
return {
type: "arrayBuffer",
value: _arrayBufferToBase64(v)
};
} else if (v instanceof Array) {
return { return {
type: "array", type: "array",
value: JSON.stringify(v) value: JSON.stringify(v)
@ -98,6 +126,9 @@ function _rawValue(v: WrappedValue): RawValue {
return JSON.parse(v.value) return JSON.parse(v.value)
} }
return v.value; return v.value;
case "arrayBuffer":
const data = NativeClient.fetchArrayBuffer(v.value as string);
return _base64ToArrayBuffer(data);;
default: default:
return undefined; return undefined;
} }

View File

@ -2,25 +2,15 @@ declare module NativeClient {
function log(message: string): void; function log(message: string): void;
function returnNative(ret: string): void; function returnNative(ret: string): void;
function callNative(name: string, args: string): string; function callNative(name: string, args: string): string;
function fetchArrayBuffer(id: string): string;
} }
declare type RawValue = number | string | boolean | object | undefined; declare type RawValue = number | string | boolean | object | undefined | ArrayBuffer;
declare type WrappedValue = { declare type WrappedValue = {
type: "number" | "string" | "boolean" | "object" | "array" | "null"; type: "number" | "string" | "boolean" | "object" | "array" | "null" | "arrayBuffer";
value: RawValue; value: RawValue;
}; };
declare function _binaryValue(v: RawValue): { declare function _arrayBufferToBase64(arrayBuffer: ArrayBuffer): string;
type: string; declare function _base64ToArrayBuffer(v: string): ArrayBufferLike;
value: number;
} | {
type: string;
value: string;
} | {
type: string;
value: boolean;
} | {
type: string;
value: undefined;
};
declare function _wrappedValue(v: RawValue): WrappedValue; declare function _wrappedValue(v: RawValue): WrappedValue;
declare function _rawValue(v: WrappedValue): RawValue; declare function _rawValue(v: WrappedValue): RawValue;
declare function __injectGlobalObject(name: string, args: string): void; declare function __injectGlobalObject(name: string, args: string): void;

View File

@ -1,40 +1,51 @@
"use strict"; "use strict";
function _binaryValue(v) { function _arrayBufferToBase64(arrayBuffer) {
switch (typeof v) { let base64 = '';
case "number": const encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
return { const bytes = new Uint8Array(arrayBuffer);
type: "number", const byteLength = bytes.byteLength;
value: v const byteRemainder = byteLength % 3;
}; const mainLength = byteLength - byteRemainder;
case "string": let a, b, c, d;
return { let chunk;
type: "string", // Main loop deals with bytes in chunks of 3
value: v for (var i = 0; i < mainLength; i = i + 3) {
}; // Combine the three bytes into a single integer
case "boolean": chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
return { // Use bitmasks to extract 6-bit segments from the triplet
type: "boolean", a = (chunk & 16515072) >> 18; // 16515072 = (2^6 - 1) << 18
value: v b = (chunk & 258048) >> 12; // 258048 = (2^6 - 1) << 12
}; c = (chunk & 4032) >> 6; // 4032 = (2^6 - 1) << 6
case "object": d = chunk & 63; // 63 = 2^6 - 1
if (v instanceof Array) { // Convert the raw binary segments to the appropriate ASCII encoding
return { base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d];
type: "array",
value: JSON.stringify(v)
};
} }
else { // Deal with the remaining bytes and padding
return { if (byteRemainder == 1) {
type: "object", chunk = bytes[mainLength];
value: JSON.stringify(v) a = (chunk & 252) >> 2; // 252 = (2^6 - 1) << 2
}; // Set the 4 least significant bits to zero
b = (chunk & 3) << 4; // 3 = 2^2 - 1
base64 += encodings[a] + encodings[b] + '==';
} }
default: else if (byteRemainder == 2) {
return { chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1];
type: "null", a = (chunk & 64512) >> 10; // 64512 = (2^6 - 1) << 10
value: undefined b = (chunk & 1008) >> 4; // 1008 = (2^6 - 1) << 4
}; // Set the 2 least significant bits to zero
c = (chunk & 15) << 2; // 15 = 2^4 - 1
base64 += encodings[a] + encodings[b] + encodings[c] + '=';
} }
return base64;
}
function _base64ToArrayBuffer(v) {
const binary_string = window.atob(v);
const len = binary_string.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
} }
function _wrappedValue(v) { function _wrappedValue(v) {
switch (typeof v) { switch (typeof v) {
@ -54,7 +65,13 @@ function _wrappedValue(v) {
value: v value: v
}; };
case "object": case "object":
if (v instanceof Array) { if (v instanceof ArrayBuffer) {
return {
type: "arrayBuffer",
value: _arrayBufferToBase64(v)
};
}
else if (v instanceof Array) {
return { return {
type: "array", type: "array",
value: JSON.stringify(v) value: JSON.stringify(v)
@ -87,6 +104,10 @@ function _rawValue(v) {
return JSON.parse(v.value); return JSON.parse(v.value);
} }
return v.value; return v.value;
case "arrayBuffer":
const data = NativeClient.fetchArrayBuffer(v.value);
return _base64ToArrayBuffer(data);
;
default: default:
return undefined; return undefined;
} }

View File

@ -50,13 +50,11 @@ export class Image extends View {
} }
toModel() { toModel() {
const ret = super.toModel(); const ret = super.toModel();
if (Environment.platform === 'iOS') {
if (Reflect.has(ret.props, "imagePixels")) { if (Reflect.has(ret.props, "imagePixels")) {
const imagePixels = Reflect.get(ret.props, "imagePixels"); const imagePixels = Reflect.get(ret.props, "imagePixels");
const pixels = imagePixels.pixels; const pixels = imagePixels.pixels;
imagePixels.pixels = this.callback2Id(() => pixels); imagePixels.pixels = this.callback2Id(() => pixels);
} }
}
return ret; return ret;
} }
} }

View File

@ -150,15 +150,13 @@ export class Image extends View {
return this.nativeChannel(context, "getImagePixels")() return this.nativeChannel(context, "getImagePixels")()
} }
toModel() { toModel(): NativeViewModel {
const ret = super.toModel() const ret = super.toModel()
if (Environment.platform === 'iOS') {
if (Reflect.has(ret.props, "imagePixels")) { if (Reflect.has(ret.props, "imagePixels")) {
const imagePixels = Reflect.get(ret.props, "imagePixels") const imagePixels = Reflect.get(ret.props, "imagePixels")
const pixels = imagePixels.pixels const pixels = imagePixels.pixels
imagePixels.pixels = this.callback2Id(() => pixels) imagePixels.pixels = this.callback2Id(() => pixels)
} }
}
return ret return ret
} }
} }

View File

@ -3330,13 +3330,11 @@ class Image extends View {
} }
toModel() { toModel() {
const ret = super.toModel(); const ret = super.toModel();
if (Environment.platform === 'iOS') {
if (Reflect.has(ret.props, "imagePixels")) { if (Reflect.has(ret.props, "imagePixels")) {
const imagePixels = Reflect.get(ret.props, "imagePixels"); const imagePixels = Reflect.get(ret.props, "imagePixels");
const pixels = imagePixels.pixels; const pixels = imagePixels.pixels;
imagePixels.pixels = this.callback2Id(() => pixels); imagePixels.pixels = this.callback2Id(() => pixels);
} }
}
return ret; return ret;
} }
} }

File diff suppressed because one or more lines are too long