├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── dom.rs ├── ffi ├── mod.rs └── object.rs ├── lib.rs ├── local_storage.rs ├── location.rs ├── log.rs ├── navigator.rs ├── prelude.rs └── string.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jsapi" 3 | version = "0.1.0" 4 | authors = ["ticki "] 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jsapi 2 | 3 | A full-featured Rust to JavaScript (asm.js/wasm) API wrapper. 4 | 5 | **!! WIP !!** 6 | -------------------------------------------------------------------------------- /src/dom.rs: -------------------------------------------------------------------------------- 1 | use prelude::*; 2 | 3 | /// A DOM element. 4 | pub struct Element { 5 | /// The inner object. 6 | obj: Object, 7 | } 8 | 9 | impl Element { 10 | /// Get a DOM element from its ID. 11 | /// 12 | /// The behavior is equivalent to JavaScript's `document.getElementFromId()`. 13 | /// 14 | /// In case no element with matching ID exists, `None` is returned. 15 | pub fn from_id(id: &JsString) -> Option { 16 | let obj = Object::new(); 17 | 18 | unsafe { 19 | asm!("__ObjectPool[$0]=document.getElementFromId(__ObjectPool[$1])" 20 | :: "r"(obj.get_id()), 21 | "r"(id.get_inner_object().get_id())) 22 | } 23 | 24 | if obj.is_null() { None } else { 25 | Some(Element { 26 | obj: obj, 27 | }) 28 | } 29 | } 30 | 31 | /// Set or unset the "hidden" property. 32 | /// 33 | /// `true` will make the element invisible and `false` vice versa. 34 | pub fn set_hidden(&self, opt: bool) { 35 | unsafe { 36 | if opt { 37 | asm!("__ObjectPool[$0].hidden=true" 38 | :: "r"(self.obj.get_id())); 39 | } else { 40 | asm!("__ObjectPool[$0].hidden=false" 41 | :: "r"(self.obj.get_id())); 42 | } 43 | } 44 | } 45 | 46 | /// Set a CSS style propety. 47 | pub fn set_style_property(&self, key: &JsString, val: &JsString) { 48 | unsafe { 49 | asm!("__ObjectPool[$0].style[__ObjectPool[$1]]=__ObjectPool[$2]" 50 | :: "r"(self.obj.get_id()), 51 | "r"(key.get_inner_object().get_id()), 52 | "r"(val.get_inner_object().get_id())); 53 | } 54 | } 55 | 56 | /// Set the contained text of the element. 57 | pub fn set_text(&self, txt: &JsString) { 58 | unsafe { 59 | asm!("__ObjectPool[$0].textContent=__ObjectPool[$1]" 60 | :: "r"(self.obj.get_id()), 61 | "r"(txt.get_inner_object().get_id())); 62 | } 63 | } 64 | 65 | /// Set the inner HTML of the element. 66 | pub fn set_html(&self, html: &JsString) { 67 | unsafe { 68 | asm!("__ObjectPool[$0].innerHTML=__ObjectPool[$1]" 69 | :: "r"(self.obj.get_id()), 70 | "r"(html.get_inner_object().get_id())); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/ffi/mod.rs: -------------------------------------------------------------------------------- 1 | //! JavaScript FFI. 2 | 3 | use prelude::*; 4 | 5 | /// The integer type of JavaScript. 6 | pub type Int = u32; 7 | 8 | pub mod object; 9 | 10 | /// Evaluate arbitrary JavaScript code. 11 | /// 12 | /// # Safety 13 | /// 14 | /// You can break invariants, and thus it's marked `unsafe`. 15 | pub unsafe fn eval(js: &JsString) { 16 | asm!("eval(__ObjectPool[$0])" 17 | :: "r"(js.get_inner_object().get_id())); 18 | } 19 | -------------------------------------------------------------------------------- /src/ffi/object.rs: -------------------------------------------------------------------------------- 1 | //! A simple allocator for typed JavaScript objects. 2 | 3 | use prelude::*; 4 | 5 | /// Initialize the typed arena if not already initialized. 6 | fn arena_init() { 7 | unsafe { 8 | asm!("\ 9 | if('undefined'===typeof __ObjectPool){\ 10 | __ObjectPool=[];\ 11 | __ObjectPoolFree=[]\ 12 | }"); 13 | } 14 | } 15 | 16 | /// Allocate an object into the `__ObjectPool` global array. 17 | fn alloc() -> Int { 18 | let ret; 19 | 20 | arena_init(); 21 | unsafe { 22 | asm!("\ 23 | var $0=__ObjectPoolFree.pop()\ 24 | if(!$0){\ 25 | $0=__ObjectPool.len();\ 26 | __ObjectPool.push(null);\ 27 | }" : "=r"(ret)); 28 | } 29 | 30 | ret 31 | } 32 | 33 | /// An opaque pointer into some JavaScript-typed object. 34 | pub struct Object { 35 | /// The index of the `ObjectPool` array this pointer refers to. 36 | id: Int, 37 | } 38 | 39 | impl Object { 40 | /// Allocate a new object. 41 | /// 42 | /// The initial value is `null`. 43 | pub fn new() -> Object { 44 | Object { 45 | id: alloc(), 46 | } 47 | } 48 | 49 | /// Get the identifier of this object. 50 | /// 51 | /// To access the object in JavaScript code, you do `ObjectPool[id]`. 52 | pub fn get_id(&self) -> Int { 53 | self.id 54 | } 55 | 56 | /// Is this object null? 57 | pub fn is_null(&self) -> bool { 58 | let ret; 59 | 60 | unsafe { 61 | asm!("$0=__ObjectPool[$1]===null" 62 | : "=r"(ret) 63 | : "r"(self.id)); 64 | } 65 | 66 | ret 67 | } 68 | } 69 | 70 | impl Drop for Object { 71 | fn drop(&mut self) { 72 | unsafe { 73 | asm!("delete __ObjectPool[$0];\ 74 | __ObjectPoolFree.push($0)" :: "r"(self.id)); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # jsapi: A full-featured Rust to JavaScript (asm.js/wasm) API wrapper. 2 | //! 3 | //! ## Interaction between the type systems 4 | //! 5 | //! Interacting with the poorly typed JavaScript API is hard. We get around it by using a construct 6 | //! we call _the object pool_. Essentially, "pointers" into JavaScript objects (strings, maps, 7 | //! arrays, etc.) are represented as indexes into a homogenous array. 8 | //! 9 | //! Obviously, this is an incredibly hacky solution, but it's deeply rooted in the flawed design of 10 | //! JavaScript. 11 | 12 | #![feature(asm, conservative_impl_trait, try_from)] 13 | 14 | pub mod dom; 15 | pub mod ffi; 16 | pub mod local_storage; 17 | pub mod location; 18 | pub mod log; 19 | pub mod navigator; 20 | pub mod prelude; 21 | pub mod string; 22 | -------------------------------------------------------------------------------- /src/local_storage.rs: -------------------------------------------------------------------------------- 1 | //! Bindings to JavaScript's `localStorage` API. 2 | 3 | use prelude::*; 4 | 5 | /// Get the value of some key in the local storage (`localStorage`). 6 | /// 7 | /// It consumes the key and uses the allocation of the key to store the return value. 8 | pub fn get(key: &JsString) -> JsString { 9 | let ret = JsString::new(""); 10 | 11 | unsafe { 12 | asm!("__ObjectPool[$0]=localStorage[__ObjectPool[$1]]" 13 | :: "r"(ret.get_inner_object().get_id()), 14 | "r"(key.get_inner_object().get_id())) 15 | } 16 | 17 | ret 18 | } 19 | 20 | /// Set the value of some key in the local storage (`localStorage`). 21 | /// 22 | /// Return the old value. 23 | pub fn set(key: &JsString, val: &JsString) -> JsString { 24 | let ret = JsString::new(""); 25 | 26 | unsafe { 27 | asm!("__ObjectPool[$0]=localStorage[__ObjectPool[$1]]=__ObjectPool[$2]" 28 | :: "r"(ret.get_inner_object().get_id()), 29 | "r"(key.get_inner_object().get_id()), 30 | "r"(val.get_inner_object().get_id())) 31 | } 32 | 33 | ret 34 | } 35 | -------------------------------------------------------------------------------- /src/location.rs: -------------------------------------------------------------------------------- 1 | //! Bindings to the location API. 2 | 3 | use prelude::*; 4 | 5 | /// Goto some specified destination. 6 | pub fn goto(href: &JsString) { 7 | unsafe { 8 | asm!("location.href=__ObjectPool[$0]" 9 | :: "r"(href.get_inner_object().get_id())); 10 | } 11 | } 12 | 13 | /// Reload the page. 14 | pub fn reload() { 15 | unsafe { 16 | asm!("location.reload()"); 17 | } 18 | } 19 | 20 | /// Get the full hyperreference (URL) to the current page. 21 | pub fn get_href() -> JsString { 22 | let ret = JsString::new(""); 23 | 24 | unsafe { 25 | asm!("__ObjectPool[$0]=location.href" 26 | :: "r"(ret.get_inner_object().get_id())); 27 | } 28 | 29 | ret 30 | } 31 | -------------------------------------------------------------------------------- /src/log.rs: -------------------------------------------------------------------------------- 1 | //! Logging to the JavaScript consoles and other integrated targets. 2 | 3 | use prelude::*; 4 | 5 | /// Issue an error log message. 6 | pub fn error(msg: &JsString) { 7 | unsafe { 8 | asm!("console.error(__ObjectPool[$0])" 9 | :: "r"(msg.get_inner_object().get_id())); 10 | } 11 | } 12 | 13 | /// Issue an log message of unspecified kind. 14 | pub fn log(msg: &JsString) { 15 | unsafe { 16 | asm!("console.log(__ObjectPool[$0])" 17 | :: "r"(msg.get_inner_object().get_id())); 18 | } 19 | } 20 | 21 | /// Issue an warning. 22 | pub fn warn(msg: &JsString) { 23 | unsafe { 24 | asm!("console.warn(__ObjectPool[$0])" 25 | :: "r"(msg.get_inner_object().get_id())); 26 | } 27 | } 28 | 29 | /// Issue an info log message. 30 | pub fn info(msg: &JsString) { 31 | unsafe { 32 | asm!("console.info(__ObjectPool[$0])" 33 | :: "r"(msg.get_inner_object().get_id())); 34 | } 35 | } 36 | 37 | /// Make a popup box with some message. 38 | /// 39 | /// This is not recommended for anything other than debugging. 40 | pub fn alert(msg: &JsString) { 41 | unsafe { 42 | asm!("alert(__ObjectPool[$0])" 43 | :: "r"(msg.get_inner_object().get_id())); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/navigator.rs: -------------------------------------------------------------------------------- 1 | //! Bindings to the `Navigator` object. 2 | 3 | use prelude::*; 4 | 5 | /// An user agent identifier. 6 | pub struct UserAgent { 7 | /// The textual representation of the user agent. 8 | string: String, 9 | } 10 | 11 | impl UserAgent { 12 | /// Get the user agent of the browser. 13 | pub fn get() -> UserAgent { 14 | let string = JsString::new(""); 15 | 16 | unsafe { 17 | asm!("__ObjectPool[$0]=navigator.userAgent" 18 | :: "r"(string.get_inner_object().get_id())); 19 | } 20 | 21 | UserAgent { 22 | string: string.into(), 23 | } 24 | } 25 | 26 | /// Get the browser of this user agent. 27 | pub fn browser(&self) -> Browser { 28 | if self.string.contains("Chrome") { 29 | // Where's my customizability? 30 | Browser::Chrome 31 | } else if self.string.contains("Safari") { 32 | // Overpriced shit. 33 | Browser::Safari 34 | } else if self.string.contains("Opera") { 35 | // WTF? Do you actually use Opera? 36 | Browser::Opera 37 | } else if self.string.contains("Firefox") { 38 | // Where did the memory go? 39 | Browser::Firefox 40 | } else if self.string.contains("Microsoft Internet Explorer") { 41 | // You're screwed. 42 | Browser::Explorer 43 | } else { 44 | Browser::Other 45 | } 46 | } 47 | 48 | /// Get the inner string (raw representation of the user agent). 49 | pub fn into_string(self) -> String { 50 | self.string 51 | } 52 | } 53 | 54 | /// Browser identifier. 55 | pub enum Browser { 56 | /// Mozilla Firefox. 57 | Firefox, 58 | /// Google Chrome. 59 | Chrome, 60 | /// Microsoft Internet Explorer. 61 | Explorer, 62 | /// Opera Web Browser. 63 | Opera, 64 | /// Apple Safari. 65 | Safari, 66 | /// Unknown browser. 67 | Other, 68 | } 69 | 70 | /// The user's specified language. 71 | /// 72 | /// # Example 73 | /// 74 | /// `en-US` 75 | pub fn language() -> JsString { 76 | let ret = JsString::new(""); 77 | 78 | unsafe { 79 | asm!("__ObjectPool[$0]=navigator.language" 80 | :: "r"(ret.get_inner_object().get_id())); 81 | } 82 | 83 | ret 84 | } 85 | 86 | /// Is cookies enabled? 87 | pub fn cookies_enabled() -> bool { 88 | let ret; 89 | 90 | unsafe { 91 | asm!("$0=navigator.cookiesEnabled" 92 | : "=r"(ret)); 93 | } 94 | 95 | ret 96 | } 97 | -------------------------------------------------------------------------------- /src/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use ffi::Int; 2 | pub use ffi::object::Object; 3 | pub use string::JsString; 4 | -------------------------------------------------------------------------------- /src/string.rs: -------------------------------------------------------------------------------- 1 | //! Native JavaScript strings. 2 | 3 | use std::cmp; 4 | use std::convert::TryFrom; 5 | 6 | use prelude::*; 7 | 8 | /// A native JavaScript string. 9 | pub struct JsString { 10 | /// The internal object. 11 | obj: Object, 12 | } 13 | 14 | impl JsString { 15 | /// Allocate a new JavaScript string with some content. 16 | pub fn new(s: &str) -> JsString { 17 | // Allocate the object. 18 | let obj = Object::new(); 19 | 20 | // Load the string into the object. 21 | unsafe { 22 | asm!("__ObjectPool[$0]=Pointer_stringify($1,$2)" 23 | :: "r"(obj.get_id()), 24 | "r"(s.as_ptr()), 25 | "r"(s.len())); 26 | } 27 | 28 | JsString { 29 | obj: obj, 30 | } 31 | } 32 | 33 | /// Push some character to the string. 34 | pub fn push(&self, c: char) { 35 | unsafe { 36 | asm!("__ObjectPool[$0]+=String.fromCharCode($1)" 37 | :: "r"(self.obj.get_id()), "r"(c as Int)); 38 | } 39 | } 40 | 41 | /// Append another JavaScript string. 42 | pub fn append(&self, other: JsString) { 43 | unsafe { 44 | asm!("__ObjectPool[$0]+=__ObjectPool[$1]" 45 | :: "r"(self.obj.get_id()), "r"(other.obj.get_id())); 46 | } 47 | } 48 | 49 | /// Get the length (codepoints) of this string. 50 | pub fn len(&self) -> Int { 51 | let ret; 52 | unsafe { 53 | asm!("$0=__ObjectPool[$1].length" 54 | : "=r"(ret) 55 | : "r"(self.obj.get_id())); 56 | } 57 | ret 58 | } 59 | 60 | /// Get the n'th character of the string. 61 | /// 62 | /// This cannot be implemented through the `Index` trait due to the individual characters not 63 | /// being addressable. 64 | pub fn nth(&self, n: Int) -> Option { 65 | let code: Int; 66 | 67 | unsafe { 68 | asm!("$0=__ObjectPool[$1].charCodeAt($2)" 69 | : "=r"(code) 70 | : "r"(self.obj.get_id()), "r"(n)); 71 | } 72 | 73 | char::try_from(code).ok() 74 | } 75 | 76 | /// Get an iterator of the characters of this string. 77 | pub fn chars(&self) -> Iter { 78 | Iter { 79 | string: self, 80 | n: 0, 81 | } 82 | } 83 | 84 | /// Get the inner object object of this string. 85 | pub fn get_inner_object(&self) -> &Object { 86 | &self.obj 87 | } 88 | } 89 | 90 | impl<'a> Into for &'a JsString { 91 | fn into(self) -> String { 92 | // TODO: Optimize. 93 | 94 | let mut string = String::new(); 95 | string.extend(self.chars()); 96 | 97 | string 98 | } 99 | } 100 | 101 | impl Into for JsString { 102 | fn into(self) -> String { 103 | (&self).into() 104 | } 105 | } 106 | 107 | impl Clone for JsString { 108 | fn clone(&self) -> JsString { 109 | // Allocate the object. 110 | let obj = Object::new(); 111 | 112 | // Just assign the string from the old one. 113 | unsafe { 114 | asm!("__ObjectPool[$0]=__ObjectPool[$1]" 115 | :: "r"(obj.get_id()), 116 | "r"(self.obj.get_id())); 117 | } 118 | 119 | JsString { 120 | obj: obj, 121 | } 122 | } 123 | } 124 | 125 | impl cmp::PartialEq for JsString { 126 | fn eq(&self, other: &JsString) -> bool { 127 | let ret; 128 | 129 | unsafe { 130 | asm!("$0=$1===$2" 131 | : "=r"(ret) 132 | : "r"(self.obj.get_id()), "r"(other.obj.get_id())); 133 | } 134 | 135 | ret 136 | } 137 | } 138 | 139 | impl cmp::Eq for JsString {} 140 | 141 | pub struct Iter<'a> { 142 | string: &'a JsString, 143 | n: Int, 144 | } 145 | 146 | impl<'a> Iterator for Iter<'a> { 147 | type Item = char; 148 | 149 | fn next(&mut self) -> Option { 150 | let ret = self.string.nth(self.n); 151 | self.n += 1; 152 | ret 153 | } 154 | } 155 | --------------------------------------------------------------------------------