diff --git a/Cargo.lock b/Cargo.lock index dd65b245..9cb6eb3d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -47,7 +47,9 @@ name = "adblock-rs" version = "0.12.1" dependencies = [ "adblock", - "neon", + "napi", + "napi-build", + "napi-derive", "serde", "serde_json", ] @@ -254,6 +256,15 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +[[package]] +name = "convert_case" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "affbf0190ed2caf063e3def54ff444b449371d55c58e513a95ab98eca50adb49" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "cpufeatures" version = "0.2.5" @@ -388,6 +399,22 @@ dependencies = [ "memchr", ] +[[package]] +name = "ctor" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "424e0138278faeb2b401f174ad17e715c829512d74f3d1e81eb43365c2e0590e" +dependencies = [ + "ctor-proc-macro", + "dtor", +] + +[[package]] +name = "ctor-proc-macro" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" + [[package]] name = "derive_more" version = "0.99.17" @@ -434,6 +461,21 @@ dependencies = [ "dtoa", ] +[[package]] +name = "dtor" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "404d02eeb088a82cfd873006cb713fe411306c7d182c344905e101fb1167d301" +dependencies = [ + "dtor-proc-macro", +] + +[[package]] +name = "dtor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" + [[package]] name = "either" version = "1.8.0" @@ -955,12 +997,12 @@ dependencies = [ [[package]] name = "libloading" -version = "0.6.7" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "351a32417a12d5f7e82c368a66781e307834dae04c6ce0cd4456d52989229883" +checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60" dependencies = [ "cfg-if", - "winapi", + "windows-link", ] [[package]] @@ -1032,44 +1074,62 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdcebb6db83796481097dedc7747809243cc81d9ed83e6a938b76d4ea0b249cf" [[package]] -name = "neon" -version = "0.10.1" +name = "napi" +version = "3.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28e15415261d880aed48122e917a45e87bb82cf0260bb6db48bbab44b7464373" +checksum = "e6944d0bf100571cd6e1a98a316cdca262deb6fccf8d93f5ae1502ca3fc88bd3" dependencies = [ - "neon-build", - "neon-macros", - "neon-runtime", - "semver 0.9.0", - "smallvec", + "bitflags", + "ctor", + "futures", + "napi-build", + "napi-sys", + "nohash-hasher", + "rustc-hash 2.1.1", + "serde", + "serde_json", ] [[package]] -name = "neon-build" -version = "0.10.1" +name = "napi-build" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bac98a702e71804af3dacfde41edde4a16076a7bbe889ae61e56e18c5b1c811" +checksum = "d376940fd5b723c6893cd1ee3f33abbfd86acb1cd1ec079f3ab04a2a3bc4d3b1" [[package]] -name = "neon-macros" -version = "0.10.1" +name = "napi-derive" +version = "3.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7288eac8b54af7913c60e0eb0e2a7683020dffa342ab3fd15e28f035ba897cf" +checksum = "2c914b5e420182bfb73504e0607592cdb8e2e21437d450883077669fb72a114d" dependencies = [ + "convert_case", + "ctor", + "napi-derive-backend", + "proc-macro2", "quote", - "syn 1.0.99", - "syn-mid", + "syn 2.0.101", ] [[package]] -name = "neon-runtime" -version = "0.10.1" +name = "napi-derive-backend" +version = "5.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4676720fa8bb32c64c3d9f49c47a47289239ec46b4bdb66d0913cc512cb0daca" +checksum = "f0864cf6a82e2cfb69067374b64c9253d7e910e5b34db833ed7495dda56ccb18" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "semver", + "syn 2.0.101", +] + +[[package]] +name = "napi-sys" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eb602b84d7c1edae45e50bbf1374696548f36ae179dfa667f577e384bb90c2b" dependencies = [ - "cfg-if", "libloading", - "smallvec", ] [[package]] @@ -1078,6 +1138,12 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + [[package]] name = "num-traits" version = "0.2.15" @@ -1504,7 +1570,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.14", + "semver", ] [[package]] @@ -1594,27 +1660,12 @@ dependencies = [ "smallvec", ] -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - [[package]] name = "semver" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - [[package]] name = "serde" version = "1.0.228" @@ -1763,17 +1814,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "syn-mid" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea305d57546cc8cd04feb14b62ec84bf17f50e3f7b12560d7bfa9265f39d9ed" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.99", -] - [[package]] name = "sync_wrapper" version = "1.0.2" @@ -1977,6 +2017,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + [[package]] name = "untrusted" version = "0.9.0" @@ -2178,6 +2224,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-sys" version = "0.52.0" diff --git a/js/Cargo.toml b/js/Cargo.toml index 2ec6d63b..df749e68 100644 --- a/js/Cargo.toml +++ b/js/Cargo.toml @@ -10,10 +10,11 @@ exclude = ["artifacts.json", "index.node"] crate-type = ["cdylib"] [dependencies] +napi = { version = "3", features = ["serde-json"] } +napi-derive = "3" serde = { workspace = true } serde_json = { workspace = true } adblock = { path = "../", features = ["css-validation", "content-blocking", "resource-assembler"] } -neon = { version = "^0.10.1", default-features = false, features = ["napi-1"] } -[features] -default-panic-hook = [] +[build-dependencies] +napi-build = "2" diff --git a/js/build.js b/js/build.js new file mode 100644 index 00000000..8df89c0c --- /dev/null +++ b/js/build.js @@ -0,0 +1,39 @@ +#!/usr/bin/env node +'use strict'; + +// Builds the native addon by invoking cargo and copying the cdylib to js/index.node. +// Replaces @napi-rs/cli to avoid pulling in unnecessary npm dependencies. + +const { execSync } = require('child_process'); +const { copyFileSync } = require('fs'); +const { join } = require('path'); + +const release = process.argv.includes('--release'); +const cargoArgs = ['build', '--manifest-path', 'js/Cargo.toml', '--message-format=json']; +if (release) cargoArgs.push('--release'); + +const output = execSync(`cargo ${cargoArgs.join(' ')}`, { + stdio: ['inherit', 'pipe', 'inherit'], + encoding: 'utf8', +}); + +// Find the cdylib artifact path from cargo's JSON output +let cdylibPath; +for (const line of output.split('\n')) { + if (!line) continue; + try { + const msg = JSON.parse(line); + if (msg.reason === 'compiler-artifact' && msg.target?.crate_types?.includes('cdylib')) { + cdylibPath = msg.filenames[0]; + } + } catch {} +} + +if (!cdylibPath) { + console.error('Error: could not find cdylib artifact in cargo output'); + process.exit(1); +} + +const dest = join(__dirname, 'index.node'); +copyFileSync(cdylibPath, dest); +console.log(`Copied ${cdylibPath} -> ${dest}`); diff --git a/js/build.rs b/js/build.rs new file mode 100644 index 00000000..9fc23678 --- /dev/null +++ b/js/build.rs @@ -0,0 +1,5 @@ +extern crate napi_build; + +fn main() { + napi_build::setup(); +} diff --git a/js/index.js b/js/index.js index e7f389d1..928bcc40 100644 --- a/js/index.js +++ b/js/index.js @@ -1,31 +1,9 @@ -const blocker = require('./index.node'); +const native = require('./index.node'); -// Exposes the specified native method names on `className` -function forwardClassMethods(className, methods) { - for (const method of methods) { - className.prototype[method] = function(...args) { - const blocker_method = blocker[className.name + '_' + method]; - return blocker_method(this.boxed, ...args); - } - } -} +exports.FilterSet = native.FilterSet; +exports.Engine = native.Engine; +exports.validateRequest = native.validateRequest; +exports.uBlockResources = native.uBlockResources; -class FilterSet { - constructor(...args) { - this.boxed = blocker.FilterSet_constructor(...args); - } -} -forwardClassMethods(FilterSet, ['addFilters', 'addFilter', 'intoContentBlocking']); - -class Engine { - constructor(filter_set, ...args) { - this.boxed = blocker.Engine_constructor(filter_set.boxed, ...args); - } -} -forwardClassMethods(Engine, ['check', 'urlCosmeticResources', 'hiddenClassIdSelectors', 'serialize', 'deserialize', 'enableTag', 'useResources', 'tagExists', 'clearTags']); - -exports.FilterFormat = blocker.FilterFormat; -exports.FilterSet = FilterSet; -exports.RuleTypes = blocker.RuleTypes; -exports.Engine = Engine; -exports.uBlockResources = blocker.uBlockResources; +exports.FilterFormat = Object.freeze(native.FilterFormat()); +exports.RuleTypes = Object.freeze(native.RuleTypes()); diff --git a/js/src/lib.rs b/js/src/lib.rs index 3809ac8f..6b3cdf66 100644 --- a/js/src/lib.rs +++ b/js/src/lib.rs @@ -4,131 +4,80 @@ use adblock::lists::{ use adblock::resources::resource_assembler::assemble_web_accessible_resources; use adblock::resources::Resource; use adblock::Engine as EngineInternal; -use neon::prelude::*; -use neon::types::buffer::TypedArray as _; -use serde::{Deserialize, Serialize}; +use napi::bindgen_prelude::*; +use napi_derive::napi; +use serde::Serialize; use std::cell::RefCell; use std::path::Path; use std::sync::Mutex; -/// Use the JS context's JSON.stringify and JSON.parse as an FFI, at least until -/// https://github.com/neon-bindings/neon/pull/953 is available -mod json_ffi { - use super::*; - use serde::de::DeserializeOwned; +// --------------------------------------------------------------------------- +// FilterSet +// --------------------------------------------------------------------------- - /// Call `JSON.stringify` to convert the input to a `JsString`, then call serde_json to parse - /// it to an instance of a native Rust type - pub fn from_js<'a, C: Context<'a>, T: DeserializeOwned>( - cx: &mut C, - input: Handle, - ) -> NeonResult { - let json: Handle = cx.global().get(cx, "JSON")?; - let json_stringify: Handle = json.get(cx, "stringify")?; - - let undefined = JsUndefined::new(cx); - let js_string = json_stringify - .call(cx, undefined, [input])? - .downcast::(cx) - .or_throw(cx)?; +#[napi(js_name = "FilterSet")] +pub struct JsFilterSet { + inner: RefCell, +} - match serde_json::from_str(&js_string.value(cx)) { - Ok(v) => Ok(v), - Err(e) => cx.throw_error(e.to_string())?, +// Safety: the `single-thread` feature on adblock makes FilterSetInternal !Send, +// but NAPI-RS classes require Send. The JS runtime is single-threaded so this is +// safe in practice. +unsafe impl Send for JsFilterSet {} + +#[napi] +impl JsFilterSet { + #[napi(constructor)] + pub fn new(debug: Option) -> Self { + Self { + inner: RefCell::new(FilterSetInternal::new(debug.unwrap_or(false))), } } - /// Use `serde_json` to stringify the input, then call `JSON.parse` to convert it to a - /// `JsValue` - pub fn to_js<'a, C: Context<'a>, T: serde::Serialize>( - cx: &mut C, - input: &T, - ) -> JsResult<'a, JsValue> { - let input_handle = JsString::new(cx, serde_json::to_string(&input).unwrap()); - - let json: Handle = cx.global().get(cx, "JSON")?; - let json_parse: Handle = json.get(cx, "parse")?; - - json_parse.call_with(cx).arg(input_handle).apply(cx) - } -} - -#[derive(Serialize, Deserialize)] -struct EngineOptions { - pub optimize: Option, -} - -#[derive(Default)] -struct FilterSet(RefCell); -impl FilterSet { - fn new(debug: bool) -> Self { - Self(RefCell::new(FilterSetInternal::new(debug))) - } - fn add_filters(&self, rules: &[String], opts: ParseOptions) -> FilterListMetadata { - self.0.borrow_mut().add_filters(rules, opts) - } - fn add_filter( + #[napi] + pub fn add_filters( &self, - filter: &str, - opts: ParseOptions, - ) -> Result<(), adblock::lists::FilterParseError> { - self.0.borrow_mut().add_filter(filter, opts) + rules: Vec, + opts: Option, + ) -> Result { + let parse_opts: ParseOptions = match opts { + Some(v) => serde_json::from_value(v).map_err(|e| Error::from_reason(e.to_string()))?, + None => ParseOptions::default(), + }; + let metadata: FilterListMetadata = self.inner.borrow_mut().add_filters(&rules, parse_opts); + serde_json::to_value(&metadata).map_err(|e| Error::from_reason(e.to_string())) } - fn into_content_blocking( - &self, - ) -> Result<(Vec, Vec), ()> { - self.0.borrow().clone().into_content_blocking() - } -} -impl Finalize for FilterSet {} + #[napi] + pub fn add_filter(&self, filter: String, opts: Option) -> Result { + let parse_opts: ParseOptions = match opts { + Some(v) => serde_json::from_value(v).map_err(|e| Error::from_reason(e.to_string()))?, + None => ParseOptions::default(), + }; + Ok(self + .inner + .borrow_mut() + .add_filter(&filter, parse_opts) + .is_ok()) + } -fn create_filter_set(mut cx: FunctionContext) -> JsResult> { - match cx.argument_opt(0) { - Some(arg) => { - let debug: bool = arg - .downcast::(&mut cx) - .or_throw(&mut cx)? - .value(&mut cx); - Ok(cx.boxed(FilterSet::new(debug))) + #[napi] + pub fn into_content_blocking(&self) -> Result> { + match self.inner.borrow().clone().into_content_blocking() { + Ok((cb_rules, filters_used)) => { + let result = ContentBlockingConversionResult { + content_blocking_rules: cb_rules, + filters_used, + }; + let val = + serde_json::to_value(&result).map_err(|e| Error::from_reason(e.to_string()))?; + Ok(Either::A(val)) + } + Err(_) => Ok(Either::B(())), } - None => Ok(cx.boxed(FilterSet::default())), } } -fn filter_set_add_filters(mut cx: FunctionContext) -> JsResult { - let this = cx.argument::>(0)?; - - // Take the first argument, which must be an array - let rules_handle: Handle = cx.argument(1)?; - // Second argument is optional parse options. All fields are optional. ParseOptions::default() - // if unspecified. - let parse_opts = match cx.argument_opt(2) { - Some(parse_opts_arg) => json_ffi::from_js(&mut cx, parse_opts_arg)?, - None => ParseOptions::default(), - }; - - let rules: Vec = json_ffi::from_js(&mut cx, rules_handle)?; - - let metadata = this.add_filters(&rules, parse_opts); - - json_ffi::to_js(&mut cx, &metadata) -} - -fn filter_set_add_filter(mut cx: FunctionContext) -> JsResult { - let this = cx.argument::>(0)?; - - let filter: String = cx.argument::(1)?.value(&mut cx); - let parse_opts = match cx.argument_opt(2) { - Some(parse_opts_arg) => json_ffi::from_js(&mut cx, parse_opts_arg)?, - None => ParseOptions::default(), - }; - - let ok = this.add_filter(&filter, parse_opts).is_ok(); - // Return true/false depending on whether or not the filter could be added - Ok(JsBoolean::new(&mut cx, ok)) -} - #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct ContentBlockingConversionResult { @@ -136,289 +85,192 @@ struct ContentBlockingConversionResult { filters_used: Vec, } -fn filter_set_into_content_blocking(mut cx: FunctionContext) -> JsResult { - let this = cx.argument::>(0)?; +// --------------------------------------------------------------------------- +// Engine +// --------------------------------------------------------------------------- - match this.into_content_blocking() { - Ok((cb_rules, filters_used)) => { - let r = ContentBlockingConversionResult { - content_blocking_rules: cb_rules, - filters_used, - }; - json_ffi::to_js(&mut cx, &r) - } - Err(_) => return Ok(JsUndefined::new(&mut cx).upcast()), - } +#[napi(js_name = "Engine")] +pub struct JsEngine { + inner: Mutex, } -struct Engine(Mutex); - -impl Finalize for Engine {} - -unsafe impl Send for Engine {} - -fn engine_constructor(mut cx: FunctionContext) -> JsResult> { - // Take the first argument, which must be a JsFilterSet - let rules = cx.argument::>(0)?; - let rules = rules.0.borrow().clone(); - - let engine_internal = match cx.argument_opt(1) { - Some(arg) => { - let optimize = match arg.downcast::(&mut cx) { - Ok(b) => b.value(&mut cx), - Err(_) => { - let config = json_ffi::from_js::<_, EngineOptions>(&mut cx, arg)?; - config.optimize.unwrap_or(true) - } - }; - EngineInternal::from_filter_set(rules, optimize) +// Safety: same rationale as JsFilterSet — single-threaded JS runtime. +unsafe impl Send for JsEngine {} + +#[napi] +impl JsEngine { + #[napi(constructor)] + pub fn new(filter_set: &JsFilterSet, options: Option) -> Self { + let rules = filter_set.inner.borrow().clone(); + + let optimize = match options { + Some(serde_json::Value::Bool(b)) => b, + Some(ref obj) => obj + .get("optimize") + .and_then(|v| v.as_bool()) + .unwrap_or(true), + None => true, + }; + + Self { + inner: Mutex::new(EngineInternal::from_filter_set(rules, optimize)), } - None => EngineInternal::from_filter_set(rules, true), - }; - Ok(cx.boxed(Engine(Mutex::new(engine_internal)))) -} - -fn engine_check(mut cx: FunctionContext) -> JsResult { - let this = cx.argument::>(0)?; - - let url: String = cx.argument::(1)?.value(&mut cx); - let source_url: String = cx.argument::(2)?.value(&mut cx); - let request_type: String = cx.argument::(3)?.value(&mut cx); + } - let debug = match cx.argument_opt(4) { - Some(arg) => { - // Throw if the argument exists and it cannot be downcasted to a boolean - arg.downcast::(&mut cx) - .or_throw(&mut cx)? - .value(&mut cx) + #[napi] + pub fn check( + &self, + url: String, + source_url: String, + request_type: String, + debug: Option, + ) -> Result { + let debug = debug.unwrap_or(false); + let request = adblock::request::Request::new(&url, &source_url, &request_type) + .map_err(|e| Error::from_reason(e.to_string()))?; + + let result = self + .inner + .lock() + .map_err(|e| Error::from_reason(e.to_string()))? + .check_network_request(&request); + + if debug { + serde_json::to_value(&result).map_err(|e| Error::from_reason(e.to_string())) + } else { + Ok(serde_json::Value::Bool(result.matched)) } - None => false, - }; - - let request = match adblock::request::Request::new(&url, &source_url, &request_type) { - Ok(r) => r, - Err(e) => cx.throw_error(e.to_string())?, - }; - - let result = if let Ok(engine) = this.0.lock() { - engine.check_network_request(&request) - } else { - cx.throw_error("Failed to acquire lock on engine")? - }; - if debug { - json_ffi::to_js(&mut cx, &result) - } else { - Ok(cx.boolean(result.matched).upcast()) } -} - -fn engine_hidden_class_id_selectors(mut cx: FunctionContext) -> JsResult { - let this = cx.argument::>(0)?; - - let classes_arg = cx.argument::(1)?; - let classes: Vec = json_ffi::from_js(&mut cx, classes_arg)?; - - let ids_arg = cx.argument::(2)?; - let ids: Vec = json_ffi::from_js(&mut cx, ids_arg)?; - - let exceptions_arg = cx.argument::(3)?; - let exceptions: std::collections::HashSet = json_ffi::from_js(&mut cx, exceptions_arg)?; - let result = if let Ok(engine) = this.0.lock() { - engine.hidden_class_id_selectors(&classes, &ids, &exceptions) - } else { - cx.throw_error("Failed to acquire lock on engine")? - }; - json_ffi::to_js(&mut cx, &result) -} - -fn engine_url_cosmetic_resources(mut cx: FunctionContext) -> JsResult { - let this = cx.argument::>(0)?; - - let url: String = cx.argument::(1)?.value(&mut cx); - - let result = if let Ok(engine) = this.0.lock() { - engine.url_cosmetic_resources(&url) - } else { - cx.throw_error("Failed to acquire lock on engine")? - }; - json_ffi::to_js(&mut cx, &result) -} - -fn engine_serialize(mut cx: FunctionContext) -> JsResult { - let this = cx.argument::>(0)?; - let serialized = if let Ok(engine) = this.0.lock() { - engine.serialize().to_vec() - } else { - cx.throw_error("Failed to acquire lock on engine")? - }; - - // initialise new Array Buffer in the JS context - let mut buffer = JsArrayBuffer::new(&mut cx, serialized.len())?; - // copy data from Rust buffer to JS Array Buffer - buffer.as_mut_slice(&mut cx).copy_from_slice(&serialized); + #[napi] + pub fn url_cosmetic_resources(&self, url: String) -> Result { + let result = self + .inner + .lock() + .map_err(|e| Error::from_reason(e.to_string()))? + .url_cosmetic_resources(&url); + serde_json::to_value(&result).map_err(|e| Error::from_reason(e.to_string())) + } - Ok(buffer) -} + #[napi] + pub fn hidden_class_id_selectors( + &self, + classes: Vec, + ids: Vec, + exceptions: Vec, + ) -> Result> { + let exceptions_set: std::collections::HashSet = exceptions.into_iter().collect(); + let result = self + .inner + .lock() + .map_err(|e| Error::from_reason(e.to_string()))? + .hidden_class_id_selectors(&classes, &ids, &exceptions_set); + Ok(result) + } -fn engine_deserialize(mut cx: FunctionContext) -> JsResult { - let this = cx.argument::>(0)?; - let serialized_handle = cx.argument::(1)?; + #[napi] + pub fn serialize(&self) -> Result { + let serialized = self + .inner + .lock() + .map_err(|e| Error::from_reason(e.to_string()))? + .serialize() + .to_vec(); + Ok(serialized.into()) + } - if let Ok(mut engine) = this.0.lock() { - let _result = engine.deserialize(&serialized_handle.as_slice(&mut cx)); + #[napi] + pub fn deserialize(&self, buffer: Buffer) -> Result<()> { + let mut engine = self + .inner + .lock() + .map_err(|e| Error::from_reason(e.to_string()))?; + let _ = engine.deserialize(&buffer); + Ok(()) } - Ok(JsNull::new(&mut cx)) -} + #[napi] + pub fn enable_tag(&self, tag: String) -> Result<()> { + self.inner + .lock() + .map_err(|e| Error::from_reason(e.to_string()))? + .enable_tags(&[&tag]); + Ok(()) + } -fn engine_enable_tag(mut cx: FunctionContext) -> JsResult { - let this = cx.argument::>(0)?; + #[napi] + pub fn use_resources(&self, resources: serde_json::Value) -> Result<()> { + let resources: Vec = + serde_json::from_value(resources).map_err(|e| Error::from_reason(e.to_string()))?; + self.inner + .lock() + .map_err(|e| Error::from_reason(e.to_string()))? + .use_resources(resources); + Ok(()) + } - let tag: String = cx.argument::(1)?.value(&mut cx); + #[napi] + pub fn tag_exists(&self, tag: String) -> Result { + Ok(self + .inner + .lock() + .map_err(|e| Error::from_reason(e.to_string()))? + .tag_exists(&tag)) + } - if let Ok(mut engine) = this.0.lock() { - engine.enable_tags(&[&tag]) - } else { - cx.throw_error("Failed to acquire lock on engine")? - }; - Ok(JsNull::new(&mut cx)) + #[napi] + pub fn clear_tags(&self) -> Result<()> { + self.inner + .lock() + .map_err(|e| Error::from_reason(e.to_string()))? + .use_tags(&[]); + Ok(()) + } } -fn engine_use_resources(mut cx: FunctionContext) -> JsResult { - let this = cx.argument::>(0)?; +// --------------------------------------------------------------------------- +// Standalone functions +// --------------------------------------------------------------------------- - let resources_arg = cx.argument::(1)?; - let resources: Vec = json_ffi::from_js(&mut cx, resources_arg)?; - - if let Ok(mut engine) = this.0.lock() { - engine.use_resources(resources) - } else { - cx.throw_error("Failed to acquire lock on engine")? - }; - Ok(JsNull::new(&mut cx)) +#[napi] +pub fn validate_request(url: String, source_url: String, request_type: String) -> bool { + adblock::request::Request::new(&url, &source_url, &request_type).is_ok() } -fn engine_tag_exists(mut cx: FunctionContext) -> JsResult { - let this = cx.argument::>(0)?; - - let tag: String = cx.argument::(1)?.value(&mut cx); - - let result = if let Ok(engine) = this.0.lock() { - engine.tag_exists(&tag) - } else { - cx.throw_error("Failed to acquire lock on engine")? - }; - Ok(cx.boolean(result)) +#[napi(js_name = "FilterFormat")] +pub fn filter_format_enum() -> serde_json::Value { + serde_json::json!({ + "STANDARD": serde_json::to_value(FilterFormat::Standard).unwrap(), + "HOSTS": serde_json::to_value(FilterFormat::Hosts).unwrap(), + }) } -fn engine_clear_tags(mut cx: FunctionContext) -> JsResult { - let this = cx.argument::>(0)?; - - if let Ok(mut engine) = this.0.lock() { - engine.use_tags(&[]); - } else { - cx.throw_error("Failed to acquire lock on engine")? - }; - Ok(JsNull::new(&mut cx)) +#[napi(js_name = "RuleTypes")] +pub fn rule_types_enum() -> serde_json::Value { + serde_json::json!({ + "ALL": serde_json::to_value(RuleTypes::All).unwrap(), + "NETWORK_ONLY": serde_json::to_value(RuleTypes::NetworkOnly).unwrap(), + "COSMETIC_ONLY": serde_json::to_value(RuleTypes::CosmeticOnly).unwrap(), + }) } -fn validate_request(mut cx: FunctionContext) -> JsResult { - let url: String = cx.argument::(0)?.value(&mut cx); - let source_url: String = cx.argument::(1)?.value(&mut cx); - let request_type: String = cx.argument::(2)?.value(&mut cx); - let request_ok = adblock::request::Request::new(&url, &source_url, &request_type).is_ok(); - - Ok(cx.boolean(request_ok)) -} - -fn ublock_resources(mut cx: FunctionContext) -> JsResult { - let web_accessible_resource_dir: String = cx.argument::(0)?.value(&mut cx); - let redirect_resources_path: String = cx.argument::(1)?.value(&mut cx); - // `scriptlets_path` is optional, since adblock-rust parsing that file is now deprecated. - let scriptlets_path = match cx.argument_opt(2) { - Some(arg) => Some( - arg.downcast::(&mut cx) - .or_throw(&mut cx)? - .value(&mut cx), - ), - None => None, - }; - +#[napi(js_name = "uBlockResources")] +pub fn u_block_resources( + web_accessible_resource_dir: String, + redirect_resources_path: String, + scriptlets_path: Option, +) -> Result { let mut resources = assemble_web_accessible_resources( - &Path::new(&web_accessible_resource_dir), - &Path::new(&redirect_resources_path), + Path::new(&web_accessible_resource_dir), + Path::new(&redirect_resources_path), ); if let Some(scriptlets_path) = scriptlets_path { #[allow(deprecated)] resources.extend( - adblock::resources::resource_assembler::assemble_scriptlet_resources(&Path::new( + adblock::resources::resource_assembler::assemble_scriptlet_resources(Path::new( &scriptlets_path, )), ); } - - json_ffi::to_js(&mut cx, &resources) + serde_json::to_value(&resources).map_err(|e| Error::from_reason(e.to_string())) } - -fn build_filter_format_enum<'a, C: Context<'a>>(cx: &mut C) -> JsResult<'a, JsObject> { - let filter_format_enum = JsObject::new(cx); - - let standard = json_ffi::to_js(cx, &FilterFormat::Standard)?; - filter_format_enum.set(cx, "STANDARD", standard)?; - - let hosts = json_ffi::to_js(cx, &FilterFormat::Hosts)?; - filter_format_enum.set(cx, "HOSTS", hosts)?; - - Ok(filter_format_enum) -} - -fn build_rule_types_enum<'a, C: Context<'a>>(cx: &mut C) -> JsResult<'a, JsObject> { - let rule_types_enum = JsObject::new(cx); - - let all = json_ffi::to_js(cx, &RuleTypes::All)?; - rule_types_enum.set(cx, "ALL", all)?; - - let network_only = json_ffi::to_js(cx, &RuleTypes::NetworkOnly)?; - rule_types_enum.set(cx, "NETWORK_ONLY", network_only)?; - - let cosmetic_only = json_ffi::to_js(cx, &RuleTypes::CosmeticOnly)?; - rule_types_enum.set(cx, "COSMETIC_ONLY", cosmetic_only)?; - - Ok(rule_types_enum) -} - -register_module!(mut m, { - m.export_function("FilterSet_constructor", create_filter_set)?; - m.export_function("FilterSet_addFilters", filter_set_add_filters)?; - m.export_function("FilterSet_addFilter", filter_set_add_filter)?; - m.export_function( - "FilterSet_intoContentBlocking", - filter_set_into_content_blocking, - )?; - - m.export_function("Engine_constructor", engine_constructor)?; - m.export_function("Engine_check", engine_check)?; - m.export_function("Engine_urlCosmeticResources", engine_url_cosmetic_resources)?; - m.export_function( - "Engine_hiddenClassIdSelectors", - engine_hidden_class_id_selectors, - )?; - m.export_function("Engine_serialize", engine_serialize)?; - m.export_function("Engine_deserialize", engine_deserialize)?; - m.export_function("Engine_enableTag", engine_enable_tag)?; - m.export_function("Engine_useResources", engine_use_resources)?; - m.export_function("Engine_tagExists", engine_tag_exists)?; - m.export_function("Engine_clearTags", engine_clear_tags)?; - - m.export_function("validateRequest", validate_request)?; - m.export_function("uBlockResources", ublock_resources)?; - - let filter_format_enum = build_filter_format_enum(&mut m)?; - m.export_value("FilterFormat", filter_format_enum)?; - - let rule_types_enum = build_rule_types_enum(&mut m)?; - m.export_value("RuleTypes", rule_types_enum)?; - - Ok(()) -}); diff --git a/package-lock.json b/package-lock.json index 85752da8..9f87da40 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,32 +1,14 @@ { "name": "adblock-rs", "version": "0.12.1", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "adblock-rs", "version": "0.12.1", "hasInstallScript": true, - "license": "MPL-2.0", - "dependencies": { - "cargo-cp-artifact": "^0.1" - } - }, - "node_modules/cargo-cp-artifact": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/cargo-cp-artifact/-/cargo-cp-artifact-0.1.9.tgz", - "integrity": "sha512-6F+UYzTaGB+awsTXg0uSJA1/b/B3DDJzpKVRu0UmyI7DmNeaAl2RFHuTGIN6fEgpadRxoXGb7gbC1xo4C3IdyA==", - "bin": { - "cargo-cp-artifact": "bin/cargo-cp-artifact.js" - } - } - }, - "dependencies": { - "cargo-cp-artifact": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/cargo-cp-artifact/-/cargo-cp-artifact-0.1.9.tgz", - "integrity": "sha512-6F+UYzTaGB+awsTXg0uSJA1/b/B3DDJzpKVRu0UmyI7DmNeaAl2RFHuTGIN6fEgpadRxoXGb7gbC1xo4C3IdyA==" + "license": "MPL-2.0" } } } diff --git a/package.json b/package.json index 6315111b..077af2fe 100644 --- a/package.json +++ b/package.json @@ -27,16 +27,12 @@ "type": "git", "url": "git+https://github.com/brave/adblock-rust.git" }, - "dependencies": { - "cargo-cp-artifact": "^0.1" - }, "scripts": { - "build": "cd js && cargo-cp-artifact -nc index.node -- cargo build --message-format=json-render-diagnostics", - "build-debug": "npm run build --", - "build-release": "npm run build -- --release", + "build": "node js/build.js --release", + "build-debug": "node js/build.js", "update-lists": "node data/update-lists.js", - "postinstall": "npm run build-release", + "postinstall": "npm run build", "test": "cargo test", "test:js": "node --test js/test/bindings.test.mjs" } -} +} \ No newline at end of file