stdweb 0.2.0

A standard library for the client-side Web
Documentation
use std::panic;
use webcore::ffi;

/// Initializes the library.
///
/// Calling this is required for anything to work.
pub fn initialize() {
    static mut INITIALIZED: bool = false;
    unsafe {
        if INITIALIZED {
            return;
        }

        INITIALIZED = true;
    }

    js! { @(no_return)
        Module.STDWEB = {};
    };

    #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
    js! { @(no_return)
        Module.STDWEB.alloc = _malloc;
        Module.STDWEB.dyncall = Runtime.dynCall;
        Module.STDWEB.utf8_len = lengthBytesUTF8;
    };

    #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
    js! { @(no_return)
        Module.STDWEB.alloc = Module.web_malloc;
        Module.STDWEB.dyncall = function( signature, ptr, args ) {
            return Module.web_table.get( ptr ).apply( null, args );
        };
        // This is based on code from Emscripten's preamble.js.
        Module.STDWEB.utf8_len = function utf8_len( str ) {
            let len = 0;
            for( let i = 0; i < str.length; ++i ) {
                // Gotcha: charCodeAt returns a 16-bit word that is a UTF-16 encoded code unit, not a Unicode code point of the character! So decode UTF16->UTF32->UTF8.
                // See http://unicode.org/faq/utf_bom.html#utf16-3
                let u = str.charCodeAt( i ); // possibly a lead surrogate
                if( u >= 0xD800 && u <= 0xDFFF ) {
                    u = 0x10000 + ((u & 0x3FF) << 10) | (str.charCodeAt( ++i ) & 0x3FF);
                }

                if( u <= 0x7F ) {
                    ++len;
                } else if( u <= 0x7FF ) {
                    len += 2;
                } else if( u <= 0xFFFF ) {
                    len += 3;
                } else if( u <= 0x1FFFFF ) {
                    len += 4;
                } else if( u <= 0x3FFFFFF ) {
                    len += 5;
                } else {
                    len += 6;
                }
            }
            return len;
        };
    };

    js! { @(no_return)
        // This is based on code from Emscripten's preamble.js.
        Module.STDWEB.to_utf8 = function to_utf8( str, addr ) {
            for( var i = 0; i < str.length; ++i ) {
                // Gotcha: charCodeAt returns a 16-bit word that is a UTF-16 encoded code unit, not a Unicode code point of the character! So decode UTF16->UTF32->UTF8.
                // See http://unicode.org/faq/utf_bom.html#utf16-3
                // For UTF8 byte structure, see http://en.wikipedia.org/wiki/UTF-8#Description and https://www.ietf.org/rfc/rfc2279.txt and https://tools.ietf.org/html/rfc3629
                var u = str.charCodeAt( i ); // possibly a lead surrogate
                if( u >= 0xD800 && u <= 0xDFFF ) {
                    u = 0x10000 + ((u & 0x3FF) << 10) | (str.charCodeAt( ++i ) & 0x3FF);
                }

                if( u <= 0x7F ) {
                    HEAPU8[ addr++ ] = u;
                } else if( u <= 0x7FF ) {
                    HEAPU8[ addr++ ] = 0xC0 | (u >> 6);
                    HEAPU8[ addr++ ] = 0x80 | (u & 63);
                } else if( u <= 0xFFFF ) {
                    HEAPU8[ addr++ ] = 0xE0 | (u >> 12);
                    HEAPU8[ addr++ ] = 0x80 | ((u >> 6) & 63);
                    HEAPU8[ addr++ ] = 0x80 | (u & 63);
                } else if( u <= 0x1FFFFF ) {
                    HEAPU8[ addr++ ] = 0xF0 | (u >> 18);
                    HEAPU8[ addr++ ] = 0x80 | ((u >> 12) & 63);
                    HEAPU8[ addr++ ] = 0x80 | ((u >> 6) & 63);
                    HEAPU8[ addr++ ] = 0x80 | (u & 63);
                } else if( u <= 0x3FFFFFF ) {
                    HEAPU8[ addr++ ] = 0xF8 | (u >> 24);
                    HEAPU8[ addr++ ] = 0x80 | ((u >> 18) & 63);
                    HEAPU8[ addr++ ] = 0x80 | ((u >> 12) & 63);
                    HEAPU8[ addr++ ] = 0x80 | ((u >> 6) & 63);
                    HEAPU8[ addr++ ] = 0x80 | (u & 63);
                } else {
                    HEAPU8[ addr++ ] = 0xFC | (u >> 30);
                    HEAPU8[ addr++ ] = 0x80 | ((u >> 24) & 63);
                    HEAPU8[ addr++ ] = 0x80 | ((u >> 18) & 63);
                    HEAPU8[ addr++ ] = 0x80 | ((u >> 12) & 63);
                    HEAPU8[ addr++ ] = 0x80 | ((u >> 6) & 63);
                    HEAPU8[ addr++ ] = 0x80 | (u & 63);
                }
            }
        };
    };

    js! { @(no_return)
        Module.STDWEB.to_js = function to_js( address ) {
            var kind = HEAPU8[ address + 12 ];
            if( kind === 0 ) {
                return undefined;
            } else if( kind === 1 ) {
                return null;
            } else if( kind === 2 ) {
                return HEAP32[ address / 4 ];
            } else if( kind === 3 ) {
                return HEAPF64[ address / 8 ];
            } else if( kind === 4 ) {
                var pointer = HEAPU32[ address / 4 ];
                var length = HEAPU32[ (address + 4) / 4 ];
                return Module.STDWEB.to_js_string( pointer, length );
            } else if( kind === 5 ) {
                return false;
            } else if( kind === 6 ) {
                return true;
            } else if( kind === 7 ) {
                var pointer = HEAPU32[ address / 4 ];
                var length = HEAPU32[ (address + 4) / 4 ];
                var output = [];
                for( var i = 0; i < length; ++i ) {
                    output.push( Module.STDWEB.to_js( pointer + i * 16 ) );
                }
                return output;
            } else if( kind === 8 ) {
                var value_array_pointer = HEAPU32[ address / 4 ];
                var length = HEAPU32[ (address + 4) / 4 ];
                var key_array_pointer = HEAPU32[ (address + 8) / 4 ];
                var output = {};
                for( var i = 0; i < length; ++i ) {
                    var key_pointer = HEAPU32[ (key_array_pointer + i * 8) / 4 ];
                    var key_length = HEAPU32[ (key_array_pointer + 4 + i * 8) / 4 ];
                    var key = Module.STDWEB.to_js_string( key_pointer, key_length );
                    var value = Module.STDWEB.to_js( value_array_pointer + i * 16 );
                    output[ key ] = value;
                }
                return output;
            } else if( kind === 9 || kind == 11 || kind == 12 ) {
                return Module.STDWEB.acquire_js_reference( HEAP32[ address / 4 ] );
            } else if( kind === 10 ) {
                var adapter_pointer = HEAPU32[ address / 4 ];
                var pointer = HEAPU32[ (address + 4) / 4 ];
                var deallocator_pointer = HEAPU32[ (address + 8) / 4 ];
                var output = function() {
                    var args = Module.STDWEB.alloc( 16 );
                    Module.STDWEB.serialize_array( args, arguments );
                    Module.STDWEB.dyncall( "vii", adapter_pointer, [pointer, args] );
                    var result = Module.STDWEB.tmp;
                    Module.STDWEB.tmp = null;

                    return result;
                };

                output.drop = function() {
                    output.drop = null;
                    Module.STDWEB.dyncall( "vi", deallocator_pointer, [pointer] );
                };

                return output;
            }
        };
    };

    js! { @(no_return)
        Module.STDWEB.serialize_object = function serialize_object( address, value ) {
            var keys = Object.keys( value );
            var length = keys.length;
            var key_array_pointer = Module.STDWEB.alloc( length * 8 );
            var value_array_pointer = Module.STDWEB.alloc( length * 16 );
            HEAPU8[ address + 12 ] = 8;
            HEAPU32[ address / 4 ] = value_array_pointer;
            HEAPU32[ (address + 4) / 4 ] = length;
            HEAPU32[ (address + 8) / 4 ] = key_array_pointer;
            for( var i = 0; i < length; ++i ) {
                var key = keys[ i ];
                var key_length = Module.STDWEB.utf8_len( key );
                var key_pointer = Module.STDWEB.alloc( key_length );
                Module.STDWEB.to_utf8( key, key_pointer );

                var key_address = key_array_pointer + i * 8;
                HEAPU32[ key_address / 4 ] = key_pointer;
                HEAPU32[ (key_address + 4) / 4 ] = key_length;

                Module.STDWEB.from_js( value_array_pointer + i * 16, value[ key ] );
            }
        };

        Module.STDWEB.serialize_array = function serialize_array( address, value ) {
            var length = value.length;
            var pointer = Module.STDWEB.alloc( length * 16 );
            HEAPU8[ address + 12 ] = 7;
            HEAPU32[ address / 4 ] = pointer;
            HEAPU32[ (address + 4) / 4 ] = length;
            for( var i = 0; i < length; ++i ) {
                Module.STDWEB.from_js( pointer + i * 16, value[ i ] );
            }
        };

        Module.STDWEB.from_js = function from_js( address, value ) {
            var kind = Object.prototype.toString.call( value );
            if( kind === "[object String]" ) {
                var length = Module.STDWEB.utf8_len( value );
                var pointer = 0;
                if( length > 0 ) {
                    pointer = Module.STDWEB.alloc( length );
                    Module.STDWEB.to_utf8( value, pointer );
                }
                HEAPU8[ address + 12 ] = 4;
                HEAPU32[ address / 4 ] = pointer;
                HEAPU32[ (address + 4) / 4 ] = length;
            } else if( kind === "[object Number]" ) {
                if( value === (value|0) ) {
                    HEAPU8[ address + 12 ] = 2;
                    HEAP32[ address / 4 ] = value;
                } else {
                    HEAPU8[ address + 12 ] = 3;
                    HEAPF64[ address / 8 ] = value;
                }
            } else if( value === null ) {
                HEAPU8[ address + 12 ] = 1;
            } else if( value === undefined ) {
                HEAPU8[ address + 12 ] = 0;
            } else if( value === false ) {
                HEAPU8[ address + 12 ] = 5;
            } else if( value === true ) {
                HEAPU8[ address + 12 ] = 6;
            } else {
                var refid = Module.STDWEB.acquire_rust_reference( value );
                var id = 9;
                if( kind === "[object Object]" ) {
                    id = 11;
                } else if( kind === "[object Array]" || kind === "[object Arguments]" ) {
                    id = 12;
                }

                HEAPU8[ address + 12 ] = id;
                HEAP32[ address / 4 ] = refid;
            }
        };
    };

    js! { @(no_return)
        // This is ported from Rust's stdlib; it's faster than
        // the string conversion from Emscripten.
        Module.STDWEB.to_js_string = function to_js_string( index, length ) {
            index = index|0;
            length = length|0;
            var end = (index|0) + (length|0);
            var output = "";
            while( index < end ) {
                var x = HEAPU8[ index++ ];
                if( x < 128 ) {
                    output += String.fromCharCode( x );
                    continue;
                }
                var init = (x & (0x7F >> 2));
                var y = 0;
                if( index < end ) {
                    y = HEAPU8[ index++ ];
                }
                var ch = (init << 6) | (y & 63);
                if( x >= 0xE0 ) {
                    var z = 0;
                    if( index < end ) {
                        z = HEAPU8[ index++ ];
                    }
                    var y_z = ((y & 63) << 6) | (z & 63);
                    ch = init << 12 | y_z;
                    if( x >= 0xF0 ) {
                        var w = 0;
                        if( index < end ) {
                            w = HEAPU8[ index++ ];
                        }
                        ch = (init & 7) << 18 | ((y_z << 6) | (w & 63));
                    }
                }
                output += String.fromCharCode( ch );
                continue;
            }
            return output;
        };
    };

    js! { @(no_return)
        var id_to_ref_map = {};
        var id_to_refcount_map = {};
        var ref_to_id_map = new WeakMap();
        var ref_to_id_symbol_map = {};
        var last_refid = 1;

        Module.STDWEB.acquire_rust_reference = function( reference ) {
            if( reference === undefined || reference === null ) {
                return 0;
            }

            var refid = ref_to_id_map.get( reference );
            if( refid === undefined ) {
                refid = ref_to_id_symbol_map[ reference ];
            }

            if( refid === undefined ) {
                refid = last_refid++;
                if( typeof reference === "symbol" ) {
                    ref_to_id_symbol_map[ reference ] = refid;
                } else {
                    ref_to_id_map.set( reference, refid );
                }
                id_to_ref_map[ refid ] = reference;
                id_to_refcount_map[ refid ] = 1;
            } else {
                id_to_refcount_map[ refid ]++;
            }

            return refid;
        };

        Module.STDWEB.acquire_js_reference = function( refid ) {
            return id_to_ref_map[ refid ];
        };

        Module.STDWEB.increment_refcount = function( refid ) {
            id_to_refcount_map[ refid ]++;
        };

        Module.STDWEB.decrement_refcount = function( refid ) {
            id_to_refcount_map[ refid ]--;
            if( id_to_refcount_map[ refid ] === 0 ) {
                var reference = id_to_ref_map[ refid ];
                delete id_to_ref_map[ refid ];
                delete id_to_refcount_map[ refid ];
                if( typeof reference === "symbol" ) {
                    delete ref_to_id_symbol_map[ reference ];
                } else {
                    ref_to_id_map.delete( reference );
                }
            }
        };
    }

    if cfg!( test ) == false {
        panic::set_hook( Box::new( |info| {
            __js_raw_asm!( "console.error( 'Encountered a panic!' );" );
            if let Some( value ) = info.payload().downcast_ref::< String >() {
                __js_raw_asm!( "\
                    console.error( 'Panic error message:', Module.STDWEB.to_js_string( $0, $1 ) );\
                ", value.as_ptr(), value.len() );
            }
            if let Some( location ) = info.location() {
                let file = location.file();
                __js_raw_asm!( "\
                    console.error( 'Panic location:', Module.STDWEB.to_js_string( $0, $1 ) + ':' + $2 );\
                ", file.as_ptr(), file.len(), location.line() );
            }
        }));
    }
}

/// Runs the event loop.
///
/// You should call this before returning from `main()`,
/// otherwise bad things will happen.
///
/// On Emscripten-based targets (`asmjs-unknown-emscripten`,
/// `wasm32-unknown-emscripten`) calling this is **mandatory**
/// and will **not** return. (It is, effectively, an infinite loop.)
///
/// On Rust's native wasm target (`wasm32-unknown-unknown`)
/// calling this is not necessary and doesn't do anything.
pub fn event_loop() {
    ffi::event_loop();
}