├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── examples └── basic-web-extension │ ├── .justfile │ ├── Cargo.toml │ ├── README.md │ ├── background-script │ ├── Cargo.toml │ ├── index.js │ └── src │ │ └── lib.rs │ ├── foreground-script │ ├── Cargo.toml │ ├── index.js │ ├── src │ │ └── lib.rs │ └── style.css │ ├── manifest.json │ ├── messages │ ├── Cargo.toml │ └── src │ │ └── lib.rs │ └── options │ ├── Cargo.toml │ ├── index.html │ ├── index.js │ └── src │ └── lib.rs └── src ├── action.rs ├── bookmarks.rs ├── browser_action.rs ├── commands.rs ├── contextual_identities.rs ├── downloads.rs ├── history.rs ├── identity.rs ├── lib.rs ├── omnibox.rs ├── port.rs ├── runtime.rs ├── scripting.rs ├── sessions.rs ├── sidebar_action.rs ├── storage.rs ├── tabs.rs ├── theme.rs └── windows.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Don't add any custom patterns for platform-specific files like .DS_Store 2 | # or generated by custom development tools like .vscode or .idea that don't 3 | # affect other developers. Instead add those custom patterns to your global 4 | # .gitignore file that can be configured by setting core.excludesFile. 5 | # See also: https://git-scm.com/docs/gitignore 6 | 7 | # Build artifacts, both files and directories. 8 | # Directories are specified without a trailing slash to allow using symbolic links. 9 | **/target 10 | **/Cargo.lock 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v0.4.1 (2023-04-07) 4 | 5 | - Add `Identity::get_profile_user_info` 6 | 7 | ## v0.4.0 (2022-11-16) 8 | 9 | - Use public function `chrome` to access the API. 10 | - Remove Firefox support (experimental fallback: use `firefox` feature) 11 | 12 | ## v0.3.3 (2022-10-26) 13 | 14 | - Add `Bookmarks::get` 15 | 16 | ## v0.3.2 (2022-10-18) 17 | 18 | - Add `Runtime::last_error` 19 | - Add `Runtime::on_installed` 20 | - Add `Runtime::set_uninstall_url` 21 | - Add `Identity::launch_webauth_flow_with_callback` 22 | 23 | ## v0.3.1 (2022-09-19) 24 | 25 | - Add `Action::on_clicked` 26 | 27 | ## v0.3.0 (2022-08-18) 28 | 29 | - Remove deprecated `tabs.executeScript` function 30 | - Add `SessionStorageo` 31 | - Add `TabHighlightInfo type` 32 | - Add `ZoomChangeInfo type` 33 | 34 | ## v0.2.0 (2022-08-04) 35 | 36 | - Add basic web extension example (Manifest V3) 37 | - Add `TabChangeInfo` 38 | - Add `scripting` methods 39 | - Add `Runtime::open_options_page` methods 40 | - Add `History::search` method 41 | - Add `Bookmarks::search` method 42 | - Add `Commands::on_command` method 43 | - Add `Identity` and some connected methods 44 | 45 | ## v0.1.0 (2020-03-30) 46 | 47 | Initial release. 48 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "web-extensions-sys" 3 | version = "0.4.1" 4 | authors = [ 5 | "Roman Volosatovs ", 6 | "Pauan ", 7 | "Markus Kohlhase ", 8 | ] 9 | edition = "2021" 10 | description = "This crate provides WebExtension API WASM bindings" 11 | repository = "https://github.com/web-extensions-rs/web-extensions-sys" 12 | readme = "README.md" 13 | license = "MIT" 14 | keywords = ["api", "ffi", "wasm", "web", "webassembly"] 15 | categories = ["external-ffi-bindings", "wasm"] 16 | 17 | [lib] 18 | crate-type = ["cdylib", "rlib"] 19 | 20 | [dependencies] 21 | js-sys = "0.3.61" 22 | wasm-bindgen = "0.2.84" 23 | wasm-bindgen-futures = "0.4.34" 24 | 25 | [features] 26 | default = [] 27 | firefox = [] 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Roman Volosatovs 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Web Extensions (sys) 2 | 3 | A Rust library that provides 4 | [WebExtension API](https://developer.chrome.com/docs/extensions/reference/) 5 | [WASM](https://en.wikipedia.org/wiki/WebAssembly) bindings. 6 | 7 | This crate expresses a low level wrapper. 8 | For a higher level abstraction there is the 9 | [`web-extensions`](https://github.com/web-extensions-rs/web-extensions) 10 | crate. 11 | 12 | ## Compatibility 13 | 14 | This library is currently only compatible with Chrome based browsers 15 | with [Manifest V3](https://developer.chrome.com/docs/extensions/mv3/intro/). 16 | -------------------------------------------------------------------------------- /examples/basic-web-extension/.justfile: -------------------------------------------------------------------------------- 1 | # just manual: https://github.com/casey/just/#readme 2 | 3 | set windows-shell := ["pwsh.exe", "-NoLogo", "-Command"] 4 | 5 | _default: 6 | @just --list 7 | 8 | # Build the web extension 9 | build: 10 | cd background-script/ && wasm-pack build --release -t web 11 | cd foreground-script/ && wasm-pack build --release -t no-modules 12 | cd options/ && wasm-pack build --release -t web 13 | -------------------------------------------------------------------------------- /examples/basic-web-extension/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "background-script", 4 | "foreground-script", 5 | "options", 6 | "messages" 7 | ] 8 | 9 | [patch.crates-io] 10 | messages = { path = "messages" } 11 | web-extensions-sys = { path = "../../" } 12 | 13 | [profile.release] 14 | lto = true 15 | opt-level = 'z' 16 | codegen-units = 1 17 | -------------------------------------------------------------------------------- /examples/basic-web-extension/README.md: -------------------------------------------------------------------------------- 1 | # Basic Web Extension 2 | 3 | A basic Web Extension for Google Chrome (version `>= 102`) written in Rust. 4 | 5 | ## Build 6 | 7 | ``` 8 | cargo install just 9 | just build 10 | ``` 11 | 12 | ## Run 13 | 14 | Navigate to `chrome://extensions/` in the browser and activate the developer mode. 15 | Then you can load the unpacked extension 16 | (select the folder `examples/basic-web-extensions`). 17 | -------------------------------------------------------------------------------- /examples/basic-web-extension/background-script/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "background-script" 3 | version = "0.0.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [lib] 8 | crate-type = ["cdylib"] 9 | 10 | [dependencies] 11 | gloo-console = "0.2.3" 12 | gloo-timers = { version = "0.2.4", features = ["futures"] } 13 | gloo-utils = "0.1.5" 14 | js-sys = "0.3.60" 15 | messages = { version = "0.0.0", path = "../messages" } 16 | serde = { version = "1.0.147", features = ["derive"] } 17 | thiserror = "1.0.37" 18 | wasm-bindgen = "0.2.83" 19 | wasm-bindgen-futures = "0.4.33" 20 | web-extensions-sys = "0.3.3" 21 | -------------------------------------------------------------------------------- /examples/basic-web-extension/background-script/index.js: -------------------------------------------------------------------------------- 1 | console.debug('Load WASM background module..'); 2 | import wasm_bindgen from './pkg/background_script.js'; 3 | wasm_bindgen("./pkg/background_script_bg.wasm") 4 | .then(module => { 5 | console.info('WASM loaded'); 6 | module.start(); 7 | }) 8 | .catch(console.error); 9 | -------------------------------------------------------------------------------- /examples/basic-web-extension/background-script/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, collections::HashMap, rc::Rc}; 2 | 3 | use gloo_console as console; 4 | use gloo_timers::future::TimeoutFuture; 5 | use gloo_utils::format::JsValueSerdeExt; 6 | use js_sys::{Function, Object}; 7 | use messages::{ 8 | next_request_id, AppRequest, AppRequestPayload, AppResponse, AppResponsePayload, PortRequest, 9 | PortRequestPayload, PortResponse, PortResponsePayload, Request, RequestHeader, RequestId, 10 | Response, ResponseHeader, StreamingFinishedStatus, StreamingResponsePayload, 11 | StreamingStartedStatus, INITIAL_REQUEST_ID, 12 | }; 13 | use serde::Serialize; 14 | use thiserror::Error; 15 | use wasm_bindgen::{prelude::*, JsCast}; 16 | 17 | use web_extensions_sys::{chrome, Port, Tab, TabChangeInfo}; 18 | 19 | const VERSION: &str = env!("CARGO_PKG_VERSION"); 20 | 21 | type TabId = i32; 22 | 23 | type PortId = usize; 24 | 25 | const FIRST_PORT_ID: RequestId = 1; 26 | 27 | #[derive(Debug)] 28 | struct PortContext { 29 | port: Port, 30 | last_request_id: RequestId, 31 | } 32 | 33 | impl PortContext { 34 | const fn new(port: Port) -> Self { 35 | Self { 36 | port, 37 | last_request_id: INITIAL_REQUEST_ID, 38 | } 39 | } 40 | 41 | fn next_request_id(&mut self) -> RequestId { 42 | let next_request_id = next_request_id(self.last_request_id); 43 | self.last_request_id = next_request_id; 44 | next_request_id 45 | } 46 | } 47 | 48 | #[derive(Default)] 49 | struct ConnectedPorts { 50 | last_id: PortId, 51 | ctx_by_id: HashMap, 52 | } 53 | 54 | #[derive(Debug, Error)] 55 | enum PortError { 56 | #[error("not connected")] 57 | NotConnected, 58 | } 59 | 60 | impl ConnectedPorts { 61 | fn connect(&mut self, port: Port) -> Option { 62 | let id = self.last_id.checked_add(1)?; 63 | debug_assert!(id >= FIRST_PORT_ID); 64 | let ctx = PortContext::new(port); 65 | self.ctx_by_id.insert(id, ctx); 66 | Some(id) 67 | } 68 | 69 | fn disconnect(&mut self, id: PortId) -> Option { 70 | self.ctx_by_id 71 | .remove(&id) 72 | .map(|PortContext { port, .. }| port) 73 | } 74 | 75 | fn post_message_js(&self, id: PortId, msg: &JsValue) -> Result<(), PortError> { 76 | self.ctx_by_id 77 | .get(&id) 78 | .ok_or(PortError::NotConnected) 79 | .map(|ctx| { 80 | let PortContext { 81 | port, 82 | last_request_id: _, 83 | } = ctx; 84 | console::debug!("Posting message on port", port, msg); 85 | port.post_message(msg); 86 | }) 87 | } 88 | 89 | fn post_message(&self, id: PortId, msg: &T) -> Result<(), PortError> { 90 | self.ctx_by_id 91 | .get(&id) 92 | .ok_or(PortError::NotConnected) 93 | .map(|ctx| { 94 | let PortContext { 95 | port, 96 | last_request_id: _, 97 | } = ctx; 98 | let msg = match JsValue::from_serde(msg) { 99 | Ok(msg) => msg, 100 | Err(err) => { 101 | console::error!("Failed to serialize message", err.to_string()); 102 | return; 103 | } 104 | }; 105 | console::debug!("Posting message on port", port, &msg); 106 | port.post_message(&msg); 107 | }) 108 | } 109 | 110 | fn next_request_id(&mut self, id: PortId) -> Result { 111 | self.ctx_by_id 112 | .get_mut(&id) 113 | .ok_or(PortError::NotConnected) 114 | .map(|ctx| ctx.next_request_id()) 115 | } 116 | } 117 | 118 | #[derive(Default)] 119 | struct App { 120 | last_request_id: RequestId, 121 | connected_ports: ConnectedPorts, 122 | } 123 | 124 | impl App { 125 | fn next_request_id(&mut self) -> RequestId { 126 | let next_request_id = next_request_id(self.last_request_id); 127 | self.last_request_id = next_request_id; 128 | next_request_id 129 | } 130 | 131 | fn connect_port(&mut self, port: Port) -> Option { 132 | self.connected_ports.connect(port) 133 | } 134 | 135 | fn disconnect_port(&mut self, port_id: PortId) -> Option { 136 | self.connected_ports.disconnect(port_id) 137 | } 138 | 139 | fn next_port_request_id(&mut self, port_id: PortId) -> Result { 140 | self.connected_ports.next_request_id(port_id) 141 | } 142 | 143 | fn post_port_message(&self, port_id: PortId, msg: &T) -> Result<(), PortError> { 144 | self.connected_ports.post_message(port_id, msg) 145 | } 146 | 147 | fn post_port_message_js(&self, port_id: PortId, msg: &JsValue) -> Result<(), PortError> { 148 | self.connected_ports.post_message_js(port_id, msg) 149 | } 150 | } 151 | 152 | #[wasm_bindgen] 153 | pub fn start() { 154 | console::info!("Starting background script"); 155 | 156 | let app = Rc::new(RefCell::new(App::default())); 157 | 158 | let on_message = { 159 | let app = Rc::clone(&app); 160 | move |request, sender, send_response| on_message(&app, request, sender, send_response) 161 | }; 162 | let closure: Closure = Closure::new(on_message); 163 | chrome() 164 | .runtime() 165 | .on_message() 166 | .add_listener(closure.as_ref().unchecked_ref()); 167 | closure.forget(); 168 | 169 | let closure: Closure = Closure::new(on_tab_changed); 170 | chrome() 171 | .tabs() 172 | .on_updated() 173 | .add_listener(closure.as_ref().unchecked_ref()); 174 | closure.forget(); 175 | 176 | let on_connect = move |port| { 177 | on_connect_port(&app, port); 178 | }; 179 | let closure: Closure = Closure::new(on_connect); 180 | chrome() 181 | .runtime() 182 | .on_connect() 183 | .add_listener(closure.as_ref().unchecked_ref()); 184 | closure.forget(); 185 | } 186 | 187 | fn on_message(app: &Rc>, request: JsValue, sender: JsValue, send_response: Function) { 188 | console::debug!("Received request message", &request, &sender); 189 | let request_id = app.borrow_mut().next_request_id(); 190 | if let Some(response) = on_request(app, request_id, request) { 191 | let this = JsValue::null(); 192 | if let Err(err) = send_response.call1(&this, &response) { 193 | console::error!( 194 | "Failed to send response message", 195 | send_response, 196 | response, 197 | err 198 | ); 199 | } 200 | } 201 | } 202 | 203 | fn on_tab_changed(tab_id: i32, change_info: TabChangeInfo, tab: Tab) { 204 | console::info!("Tab changed", tab_id, &tab, &change_info); 205 | if change_info.status().as_deref() == Some("complete") { 206 | if let Some(url) = tab.url() { 207 | if url.starts_with("http") { 208 | console::info!("Injecting foreground script on tab", tab_id, &tab); 209 | wasm_bindgen_futures::spawn_local(inject_frontend(tab_id)); 210 | } 211 | } 212 | } 213 | } 214 | 215 | fn on_connect_port(app: &Rc>, port: Port) { 216 | console::info!("Connecting new port", &port); 217 | let port_id = if let Some(port_id) = app.borrow_mut().connect_port(port.clone()) { 218 | port_id 219 | } else { 220 | console::error!("Failed to connect new port", &port); 221 | return; 222 | }; 223 | let on_message = { 224 | let app = Rc::clone(app); 225 | move |request| { 226 | on_port_message(&app, port_id, request); 227 | } 228 | }; 229 | let closure: Closure = Closure::new(on_message); 230 | port.on_message() 231 | .add_listener(closure.as_ref().unchecked_ref()); 232 | closure.forget(); 233 | 234 | let on_disconnect = { 235 | let app = Rc::clone(app); 236 | move || { 237 | console::log!(format!("Port {port_id} has disconnected")); 238 | app.borrow_mut().disconnect_port(port_id); 239 | } 240 | }; 241 | let closure: Closure = Closure::new(on_disconnect); 242 | port.on_disconnect() 243 | .add_listener(closure.as_ref().unchecked_ref()); 244 | closure.forget(); 245 | } 246 | 247 | fn on_port_message(app: &Rc>, port_id: PortId, request: JsValue) { 248 | console::debug!("Received request message on port", port_id, &request); 249 | let request_id = match app.borrow_mut().next_port_request_id(port_id) { 250 | Ok(request_id) => request_id, 251 | Err(err) => { 252 | console::warn!( 253 | "Failed to handle port request", 254 | port_id, 255 | request, 256 | err.to_string() 257 | ); 258 | return; 259 | } 260 | }; 261 | if let Some(response) = on_port_request(app, port_id, request_id, request) { 262 | if let Err(err) = app.borrow().post_port_message_js(port_id, &response) { 263 | console::warn!( 264 | "Failed to post response message to port", 265 | port_id, 266 | response, 267 | err.to_string() 268 | ); 269 | } 270 | } 271 | } 272 | 273 | fn on_request(app: &Rc>, request_id: RequestId, request: JsValue) -> Option { 274 | let request = request 275 | .into_serde() 276 | .map_err(|err| { 277 | console::error!("Failed to deserialize request message", &err.to_string()); 278 | }) 279 | .ok()?; 280 | let response = handle_app_request(app, request_id, request); 281 | JsValue::from_serde(&response) 282 | .map_err(|err| { 283 | console::error!("Failed to serialize response message", &err.to_string()); 284 | }) 285 | .ok() 286 | } 287 | 288 | fn on_port_request( 289 | app: &Rc>, 290 | port_id: PortId, 291 | request_id: RequestId, 292 | request: JsValue, 293 | ) -> Option { 294 | let request = request 295 | .into_serde() 296 | .map_err(|err| { 297 | console::error!( 298 | "Failed to deserialize port request message", 299 | &err.to_string() 300 | ); 301 | }) 302 | .ok()?; 303 | let response = handle_port_request(app, port_id, request_id, request); 304 | JsValue::from_serde(&response) 305 | .map_err(|err| { 306 | console::error!( 307 | "Failed to serialize port response message", 308 | &err.to_string() 309 | ); 310 | }) 311 | .ok() 312 | } 313 | 314 | /// Handle a (global) request. 315 | /// 316 | /// Optionally returns a single response. 317 | /// 318 | /// TODO: Extract into domain crate 319 | fn handle_app_request( 320 | _app: &Rc>, 321 | request_id: RequestId, 322 | request: AppRequest, 323 | ) -> Option { 324 | let Request { header, payload } = request; 325 | let payload: Option<_> = match payload { 326 | AppRequestPayload::GetOptionsInfo => AppResponsePayload::OptionsInfo { 327 | version: VERSION.to_string(), 328 | } 329 | .into(), 330 | }; 331 | payload.map(|payload| Response { 332 | header: header.into_response(request_id), 333 | payload, 334 | }) 335 | } 336 | 337 | #[derive(Debug, Clone, Copy)] 338 | enum StreamingTaskStatus { 339 | Pending, 340 | Finished, 341 | } 342 | 343 | #[derive(Debug, Error)] 344 | enum StreamingTaskError { 345 | #[error(transparent)] 346 | Port(#[from] PortError), 347 | 348 | #[error("not pending")] 349 | NotPending, 350 | 351 | #[error("item count overflow")] 352 | ItemCountOverflow, 353 | } 354 | 355 | struct StreamingTask { 356 | app: Rc>, 357 | port_id: PortId, 358 | request_id: RequestId, 359 | request_header: RequestHeader, 360 | item_count: usize, 361 | status: StreamingTaskStatus, 362 | } 363 | 364 | impl StreamingTask { 365 | fn new( 366 | app: Rc>, 367 | port_id: PortId, 368 | request_id: RequestId, 369 | request_header: RequestHeader, 370 | ) -> Self { 371 | Self { 372 | app, 373 | port_id, 374 | request_id, 375 | request_header, 376 | item_count: 0, 377 | status: StreamingTaskStatus::Pending, 378 | } 379 | } 380 | 381 | fn new_response_header(&self) -> ResponseHeader { 382 | self.request_header.clone().into_response(self.request_id) 383 | } 384 | 385 | fn next_item(&mut self /*empty item data*/) -> Result { 386 | if !matches!(self.status, StreamingTaskStatus::Pending) { 387 | return Err(StreamingTaskError::NotPending); 388 | } 389 | let item_count = self 390 | .item_count 391 | .checked_add(1) 392 | .ok_or(StreamingTaskError::ItemCountOverflow)?; 393 | let payload = PortResponsePayload::Streaming(StreamingResponsePayload::Item { item_count }); 394 | let response = Response { 395 | header: self.new_response_header(), 396 | payload, 397 | }; 398 | console::debug!("Next streaming item response", format!("{response:?}")); 399 | self.app 400 | .borrow() 401 | .post_port_message(self.port_id, &response)?; 402 | self.item_count = item_count; 403 | Ok(item_count) 404 | } 405 | 406 | fn abort(&mut self, reason: Option) -> Result<(), StreamingTaskError> { 407 | self.finish(StreamingFinishedStatus::Aborted { reason }) 408 | } 409 | 410 | fn finish(&mut self, status: StreamingFinishedStatus) -> Result<(), StreamingTaskError> { 411 | if !matches!(self.status, StreamingTaskStatus::Pending) { 412 | return Err(StreamingTaskError::NotPending); 413 | } 414 | let payload = PortResponsePayload::Streaming(StreamingResponsePayload::Finished { 415 | status, 416 | item_count: self.item_count, 417 | }); 418 | let response = Response { 419 | header: self.new_response_header(), 420 | payload, 421 | }; 422 | self.app 423 | .borrow() 424 | .post_port_message(self.port_id, &response)?; 425 | self.status = StreamingTaskStatus::Finished; 426 | Ok(()) 427 | } 428 | } 429 | 430 | /// Handle a port-local request. 431 | /// 432 | /// Optionally returns a single response. 433 | /// 434 | /// TODO: Extract into domain crate 435 | fn handle_port_request( 436 | app: &Rc>, 437 | port_id: PortId, 438 | request_id: RequestId, 439 | request: PortRequest, 440 | ) -> Option { 441 | let Request { header, payload } = request; 442 | let payload: Option<_> = match payload { 443 | PortRequestPayload::Ping => PortResponsePayload::Pong.into(), 444 | PortRequestPayload::StartStreaming { num_items } => { 445 | let status = match num_items { 446 | 0 => StreamingStartedStatus::Rejected { 447 | reason: "no items requested".to_string().into(), 448 | }, 449 | 10.. => StreamingStartedStatus::Rejected { 450 | reason: "too many items requested".to_string().into(), 451 | }, 452 | _ => { 453 | let task = 454 | StreamingTask::new(Rc::clone(app), port_id, request_id, header.clone()); 455 | let task = Rc::new(RefCell::new(task)); 456 | wasm_bindgen_futures::spawn_local({ 457 | let task = Rc::clone(&task); 458 | async move { 459 | console::debug!("Start streaming"); 460 | for item_count in 1..=num_items { 461 | if let Err(err) = task.borrow_mut().next_item() { 462 | if matches!(err, StreamingTaskError::NotPending) { 463 | console::info!("Streaming task has been aborted prematurely before item", item_count); 464 | } else { 465 | console::warn!( 466 | "Streaming task failed for item", 467 | item_count, 468 | err.to_string() 469 | ); 470 | } 471 | return; 472 | } 473 | // Delay the next (or final) response. Without yielding at some point 474 | // the locally spawned task would finish before the started response 475 | // could be posted. 476 | TimeoutFuture::new(5_000).await; 477 | } 478 | console::debug!("Finish streaming"); 479 | task.borrow_mut() 480 | .finish(StreamingFinishedStatus::Completed) 481 | .unwrap(); 482 | } 483 | }); 484 | wasm_bindgen_futures::spawn_local({ 485 | let task = Rc::clone(&task); 486 | async move { 487 | // Try to abort the task after 20 secs elapsed 488 | TimeoutFuture::new(20_000).await; 489 | if let Err(err) = task 490 | .borrow_mut() 491 | .abort("timeout expired".to_string().into()) 492 | { 493 | if matches!(err, StreamingTaskError::NotPending) { 494 | console::info!("Streaming task has already finished and does not need to be aborted"); 495 | } else { 496 | console::warn!( 497 | "Failed to abort streaming task", 498 | err.to_string() 499 | ); 500 | } 501 | } 502 | } 503 | }); 504 | StreamingStartedStatus::Accepted 505 | } 506 | }; 507 | PortResponsePayload::Streaming(StreamingResponsePayload::Started { status }).into() 508 | } 509 | }; 510 | // The started response might be posted after the first stream item response 511 | // or even after the finished response that are all generated asynchronously! 512 | payload.map(|payload| Response { 513 | header: header.into_response(request_id), 514 | payload, 515 | }) 516 | } 517 | 518 | // https://developer.chrome.com/docs/extensions/reference/scripting/#type-CSSInjection 519 | #[derive(Debug, Serialize)] 520 | #[serde(rename_all = "camelCase")] 521 | struct CssInjection<'a> { 522 | target: InjectionTarget<'a>, 523 | #[serde(skip_serializing_if = "Option::is_none")] 524 | css: Option<&'a str>, 525 | #[serde(skip_serializing_if = "Option::is_none")] 526 | files: Option<&'a [&'a str]>, 527 | } 528 | 529 | // https://developer.chrome.com/docs/extensions/reference/scripting/#type-ScriptInjection 530 | #[derive(Debug, Serialize)] 531 | #[serde(rename_all = "camelCase")] 532 | struct ScriptInjection<'a> { 533 | target: InjectionTarget<'a>, 534 | #[serde(skip_serializing_if = "Option::is_none")] 535 | files: Option<&'a [&'a str]>, 536 | } 537 | 538 | #[derive(Debug, Serialize)] 539 | #[serde(rename_all = "camelCase")] 540 | struct InjectionTarget<'a> { 541 | tab_id: TabId, 542 | #[serde(skip_serializing_if = "Option::is_none")] 543 | all_frames: Option, 544 | #[serde(skip_serializing_if = "Option::is_none")] 545 | frame_ids: Option<&'a [i32]>, 546 | } 547 | 548 | async fn inject_frontend(tab_id: TabId) { 549 | let css_injection = JsValue::from_serde(&CssInjection { 550 | files: Some(&["foreground-script/style.css"]), 551 | css: None, 552 | target: InjectionTarget { 553 | tab_id, 554 | all_frames: None, 555 | frame_ids: None, 556 | }, 557 | }) 558 | .unwrap(); 559 | console::info!("Inject CSS", &css_injection); 560 | if let Err(err) = chrome() 561 | .scripting() 562 | .insert_css(&Object::from(css_injection)) 563 | .await 564 | { 565 | console::info!("Unable to inject CSS", err); 566 | } 567 | let script_injection = JsValue::from_serde(&ScriptInjection { 568 | files: Some(&[ 569 | "foreground-script/pkg/foreground_script.js", 570 | "foreground-script/index.js", 571 | ]), 572 | target: InjectionTarget { 573 | tab_id, 574 | all_frames: None, 575 | frame_ids: None, 576 | }, 577 | }) 578 | .unwrap(); 579 | 580 | if let Err(err) = chrome() 581 | .scripting() 582 | .execute_script(&Object::from(script_injection)) 583 | .await 584 | { 585 | console::info!("Unable to inject JS", err); 586 | } 587 | } 588 | -------------------------------------------------------------------------------- /examples/basic-web-extension/foreground-script/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "foreground-script" 3 | version = "0.0.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [lib] 8 | crate-type = ["cdylib"] 9 | 10 | [dependencies] 11 | # Project dependencies 12 | messages = "=0.0.0" 13 | 14 | # External dependencies 15 | gloo-console = "0.2.3" 16 | gloo-utils = "0.1.5" 17 | wasm-bindgen = "0.2.83" 18 | web-extensions-sys = "0.3.3" 19 | web-sys = { version = "0.3.60", features = ["DomTokenList", "Element"] } 20 | -------------------------------------------------------------------------------- /examples/basic-web-extension/foreground-script/index.js: -------------------------------------------------------------------------------- 1 | const wasm_url = chrome.runtime.getURL("foreground-script/pkg/foreground_script_bg.wasm"); 2 | wasm_bindgen(wasm_url).then(module => { 3 | console.info('WASM loaded'); 4 | module.start(); 5 | }) 6 | .catch(console.error); 7 | -------------------------------------------------------------------------------- /examples/basic-web-extension/foreground-script/src/lib.rs: -------------------------------------------------------------------------------- 1 | use gloo_console as console; 2 | use gloo_utils::format::JsValueSerdeExt; 3 | use gloo_utils::{body, document}; 4 | use wasm_bindgen::{prelude::*, JsCast}; 5 | use web_extensions_sys::{chrome, Port}; 6 | 7 | #[wasm_bindgen] 8 | pub fn start() { 9 | console::info!("Start foreground script"); 10 | render_container(); 11 | let port = connect(); 12 | 13 | let on_message = |msg: JsValue| { 14 | console::info!("Received message:", msg); 15 | }; 16 | let closure: Closure = Closure::new(on_message); 17 | let callback = closure.as_ref().unchecked_ref(); 18 | port.on_message().add_listener(callback); 19 | closure.forget(); 20 | let payload = messages::PortRequestPayload::Ping; 21 | let msg = JsValue::from_serde(&messages::Request::new(payload)).unwrap(); 22 | port.post_message(&msg); 23 | 24 | let payload = messages::PortRequestPayload::StartStreaming { num_items: 5 }; 25 | let msg = JsValue::from_serde(&messages::Request::new(payload)).unwrap(); 26 | port.post_message(&msg); 27 | } 28 | 29 | fn render_container() { 30 | let container = document().create_element("div").unwrap(); 31 | container 32 | .class_list() 33 | .add_1("wea-example-container") 34 | .unwrap(); 35 | 36 | let title = document().create_element("h2").unwrap(); 37 | title.set_inner_html("Example Web Extension Foreground"); 38 | 39 | let data = document().create_element("div").unwrap(); 40 | data.set_inner_html("Hello from foreground script"); 41 | 42 | container.append_child(&title).unwrap(); 43 | container.append_child(&data).unwrap(); 44 | body().append_child(&container).unwrap(); 45 | } 46 | 47 | fn connect() -> Port { 48 | let connect_info = JsValue::null(); 49 | chrome() 50 | .runtime() 51 | .connect(None, connect_info.as_ref().unchecked_ref()) 52 | } 53 | -------------------------------------------------------------------------------- /examples/basic-web-extension/foreground-script/style.css: -------------------------------------------------------------------------------- 1 | .wea-example-container { 2 | width: 350px; 3 | height: 600px; 4 | display: flex; 5 | flex-direction: column; 6 | justify-content: space-evenly; 7 | align-items: center; 8 | position: absolute; 9 | z-index: 999999; 10 | top: 0; 11 | right: 0; 12 | padding: 2em; 13 | box-sizing: border-box; 14 | 15 | color: #444; 16 | background-color: #f8f; 17 | } 18 | -------------------------------------------------------------------------------- /examples/basic-web-extension/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Basic Web Extension", 3 | "description": "A basic Web Extension written in Rust", 4 | "version": "0.0.0", 5 | "manifest_version": 3, 6 | "background": { 7 | "service_worker": "./background-script/index.js", 8 | "type": "module" 9 | }, 10 | "options_page": "./options/index.html", 11 | "permissions": [ 12 | "activeTab", 13 | "tabs", 14 | "scripting" 15 | ], 16 | "host_permissions": [ 17 | "https://*/*" 18 | ], 19 | "content_security_policy": { 20 | "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'" 21 | }, 22 | "web_accessible_resources": [{ 23 | "resources": [ "foreground-script/pkg/foreground_script_bg.wasm" ], 24 | "matches": [ "https://*/*" ], 25 | "extension_ids": [] 26 | }] 27 | } 28 | -------------------------------------------------------------------------------- /examples/basic-web-extension/messages/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "messages" 3 | version = "0.0.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | serde = { version = "1.0.147", features = ["derive"] } 9 | -------------------------------------------------------------------------------- /examples/basic-web-extension/messages/src/lib.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | pub type RequestId = usize; 4 | 5 | pub const FIRST_REQUEST_ID: RequestId = 1; 6 | 7 | pub const INITIAL_REQUEST_ID: RequestId = FIRST_REQUEST_ID - 1; 8 | 9 | pub fn next_request_id(last_request_id: RequestId) -> RequestId { 10 | last_request_id.wrapping_add(1).max(FIRST_REQUEST_ID) 11 | } 12 | 13 | #[derive(Debug, Default, Clone, Serialize, Deserialize)] 14 | pub struct RequestHeader { 15 | pub client_token: Option, 16 | } 17 | 18 | impl RequestHeader { 19 | pub const fn new() -> Self { 20 | Self { client_token: None } 21 | } 22 | 23 | pub fn into_response(self, request_id: RequestId) -> ResponseHeader { 24 | let Self { client_token } = self; 25 | ResponseHeader { 26 | client_token, 27 | request_id, 28 | } 29 | } 30 | } 31 | 32 | #[derive(Debug, Serialize, Deserialize)] 33 | pub struct Request { 34 | pub header: RequestHeader, 35 | pub payload: T, 36 | } 37 | 38 | impl Request { 39 | pub const fn new(payload: T) -> Self { 40 | Self { 41 | header: RequestHeader::new(), 42 | payload, 43 | } 44 | } 45 | } 46 | 47 | #[derive(Debug, Serialize, Deserialize)] 48 | pub struct ResponseHeader { 49 | pub client_token: Option, 50 | pub request_id: RequestId, 51 | } 52 | 53 | #[derive(Debug, Serialize, Deserialize)] 54 | pub struct Response { 55 | pub header: ResponseHeader, 56 | pub payload: T, 57 | } 58 | 59 | /// App request message. 60 | #[derive(Debug, Serialize, Deserialize)] 61 | pub enum AppRequestPayload { 62 | GetOptionsInfo, 63 | } 64 | 65 | pub type AppRequest = Request; 66 | 67 | /// App response message. 68 | #[derive(Debug, Serialize, Deserialize)] 69 | pub enum AppResponsePayload { 70 | OptionsInfo { version: String }, 71 | } 72 | 73 | pub type AppResponse = Response; 74 | 75 | /// Port-local request message. 76 | #[derive(Debug, Serialize, Deserialize)] 77 | pub enum PortRequestPayload { 78 | Ping, 79 | StartStreaming { num_items: usize }, 80 | } 81 | 82 | pub type PortRequest = Request; 83 | 84 | /// Port-local response message. 85 | #[derive(Debug, Serialize, Deserialize)] 86 | pub enum PortResponsePayload { 87 | Pong, 88 | Streaming(StreamingResponsePayload), 89 | } 90 | 91 | pub type PortResponse = Response; 92 | 93 | #[derive(Debug, Serialize, Deserialize)] 94 | pub enum StreamingStartedStatus { 95 | /// The request has been accepted and will be processed. 96 | Accepted, 97 | /// The request has been rejected. 98 | Rejected { 99 | #[serde(skip_serializing_if = "Option::is_none")] 100 | reason: Option, 101 | }, 102 | } 103 | 104 | #[derive(Debug, Serialize, Deserialize)] 105 | pub enum StreamingFinishedStatus { 106 | /// Processing has been completed. 107 | Completed, 108 | /// Processing has been aborted prematurely. 109 | Aborted { 110 | #[serde(skip_serializing_if = "Option::is_none")] 111 | reason: Option, 112 | }, 113 | } 114 | 115 | #[derive(Debug, Serialize, Deserialize)] 116 | pub enum StreamingResponsePayload { 117 | Started { 118 | status: StreamingStartedStatus, 119 | }, 120 | /// A streaming item. 121 | /// 122 | /// The `item_count` serves as a 1-based index for (re-)ordering 123 | /// received item messages. Depending on the dispatching strategy 124 | /// messages might arrive out of order and receivers should account 125 | /// for that. 126 | Item { 127 | item_count: usize, 128 | }, 129 | /// Request processing has finished. 130 | Finished { 131 | status: StreamingFinishedStatus, 132 | 133 | /// The total item count. 134 | /// 135 | /// Denotes the total number of item messages that have been sent. 136 | /// After the finished message has been sent no more item messages 137 | /// will be sent. 138 | /// 139 | /// In-flight item messages that have already been sent but have 140 | /// not yet been received might arrive later and out of order depending 141 | /// on the dispatching strategy. 142 | item_count: usize, 143 | }, 144 | } 145 | -------------------------------------------------------------------------------- /examples/basic-web-extension/options/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "options" 3 | version = "0.0.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [lib] 8 | crate-type = ["cdylib"] 9 | 10 | [dependencies] 11 | # Project dependencies 12 | messages = "=0.0.0" 13 | 14 | # External dependencies 15 | gloo-console = "0.2.3" 16 | gloo-utils = "0.1.5" 17 | wasm-bindgen = "0.2.83" 18 | web-extensions-sys = "0.3.3" 19 | web-sys = { version = "0.3.60", features = ["DomTokenList", "Element"] } 20 | wasm-bindgen-futures = "0.4.33" 21 | -------------------------------------------------------------------------------- /examples/basic-web-extension/options/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Web Extension Options Demo 8 | 24 | 25 | 26 |

Demo Extension Options

27 |
28 | Hello from the options page. 29 |

30 |
31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /examples/basic-web-extension/options/index.js: -------------------------------------------------------------------------------- 1 | console.debug('Load WASM options module..'); 2 | import wasm_bindgen from './pkg/options.js'; 3 | wasm_bindgen("./pkg/options_bg.wasm") 4 | .then(module => { 5 | console.info('WASM loaded'); 6 | module.start(); 7 | }) 8 | .catch(console.error); 9 | -------------------------------------------------------------------------------- /examples/basic-web-extension/options/src/lib.rs: -------------------------------------------------------------------------------- 1 | use gloo_console as console; 2 | use gloo_utils::document; 3 | use gloo_utils::format::JsValueSerdeExt; 4 | use messages::{AppRequestPayload, AppResponsePayload, Request, Response}; 5 | use wasm_bindgen::prelude::*; 6 | use web_extensions_sys::chrome; 7 | 8 | #[wasm_bindgen] 9 | pub fn start() { 10 | console::info!("Start options script"); 11 | 12 | let payload = AppRequestPayload::GetOptionsInfo; 13 | let msg = JsValue::from_serde(&Request::new(payload)).unwrap(); 14 | 15 | wasm_bindgen_futures::spawn_local(async move { 16 | match chrome().runtime().send_message(None, &msg, None).await { 17 | Ok(js_value) => { 18 | if js_value.is_object() { 19 | handle_response(js_value); 20 | } else { 21 | console::debug!("The sender has unexpectedly not sent a reply"); 22 | } 23 | } 24 | Err(err) => { 25 | console::error!("Unable to send request", err); 26 | } 27 | }; 28 | }); 29 | } 30 | 31 | fn handle_response(response: JsValue) { 32 | if let Ok(Response { 33 | header: _, 34 | payload: AppResponsePayload::OptionsInfo { version }, 35 | }) = response.into_serde() 36 | { 37 | let container = document().query_selector("#version").unwrap().unwrap(); 38 | container.set_inner_html(&format!("Version: {version}")); 39 | } else { 40 | console::warn!("Received unexpected message"); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/action.rs: -------------------------------------------------------------------------------- 1 | use crate::EventTarget; 2 | use wasm_bindgen::prelude::*; 3 | 4 | #[wasm_bindgen] 5 | extern "C" { 6 | // https://developer.chrome.com/docs/extensions/reference/action/ 7 | pub type Action; 8 | 9 | // https://developer.chrome.com/docs/extensions/reference/action/#event-onClicked 10 | #[wasm_bindgen(method, getter, js_name = onClicked)] 11 | pub fn on_clicked(this: &Action) -> EventTarget; 12 | 13 | // https://developer.chrome.com/docs/extensions/reference/action/#method-openPopup 14 | #[wasm_bindgen(catch, method, js_name = openPopup)] 15 | pub async fn open_popup(this: &Action, options: &JsValue) -> Result; 16 | } 17 | -------------------------------------------------------------------------------- /src/bookmarks.rs: -------------------------------------------------------------------------------- 1 | //! Bindings to the `bookmarks` API. 2 | 3 | use js_sys::{Array, Number}; 4 | use wasm_bindgen::prelude::*; 5 | 6 | #[wasm_bindgen] 7 | extern "C" { 8 | pub type Bookmarks; 9 | 10 | #[wasm_bindgen(method)] 11 | pub async fn get(this: &Bookmarks, id_or_list: &JsValue) -> JsValue; 12 | 13 | #[wasm_bindgen(method)] 14 | pub async fn search(this: &Bookmarks, query: &JsValue) -> JsValue; 15 | } 16 | 17 | #[wasm_bindgen] 18 | extern "C" { 19 | 20 | // A node (either a bookmark or a folder) in the bookmark tree. 21 | pub type BookmarkTreeNode; 22 | 23 | // An ordered list of children of this node. 24 | #[wasm_bindgen(method)] 25 | pub fn children(this: &BookmarkTreeNode) -> Option; 26 | 27 | // When this node was created, in milliseconds since the epoch (new Date(dateAdded)). 28 | #[wasm_bindgen(method, js_name = dateAdded)] 29 | pub fn date_added(this: &BookmarkTreeNode) -> Option; 30 | 31 | // When the contents of this folder last changed, in milliseconds since the epoch. 32 | #[wasm_bindgen(method, js_name = dateGroupModified)] 33 | pub fn date_group_modified(this: &BookmarkTreeNode) -> Option; 34 | 35 | // The unique identifier for the node. IDs are unique within the current profile, and they remain valid even after the browser is restarted. 36 | #[wasm_bindgen(method)] 37 | pub fn id(this: &BookmarkTreeNode) -> String; 38 | 39 | // The 0-based position of this node within its parent folder. 40 | #[wasm_bindgen(method)] 41 | pub fn index(this: &BookmarkTreeNode) -> Option; 42 | 43 | // The id of the parent folder. Omitted for the root node. 44 | #[wasm_bindgen(method, js_name = parentId)] 45 | pub fn parent_id(this: &BookmarkTreeNode) -> Option; 46 | 47 | // The text displayed for the node. 48 | #[wasm_bindgen(method)] 49 | pub fn title(this: &BookmarkTreeNode) -> String; 50 | 51 | // Indicates the reason why this node is unmodifiable. 52 | #[wasm_bindgen(method)] 53 | pub fn unmodifiable(this: &BookmarkTreeNode) -> Option; 54 | 55 | // The URL navigated to when a user clicks the bookmark. Omitted for folders. 56 | #[wasm_bindgen(method)] 57 | pub fn url(this: &BookmarkTreeNode) -> Option; 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/browser_action.rs: -------------------------------------------------------------------------------- 1 | use crate::EventTarget; 2 | use wasm_bindgen::prelude::*; 3 | 4 | // TODO 5 | #[wasm_bindgen] 6 | extern "C" { 7 | pub type BrowserAction; 8 | 9 | #[wasm_bindgen(method, getter, js_name = onClicked)] 10 | pub fn on_clicked(this: &BrowserAction) -> EventTarget; 11 | } 12 | -------------------------------------------------------------------------------- /src/commands.rs: -------------------------------------------------------------------------------- 1 | //! Bindings to the `commands` API. 2 | 3 | use crate::EventTarget; 4 | use wasm_bindgen::prelude::*; 5 | 6 | #[wasm_bindgen] 7 | extern "C" { 8 | 9 | pub type Commands; 10 | 11 | #[wasm_bindgen(method, getter, js_name = onCommand)] 12 | pub fn on_command(this: &Commands) -> EventTarget; 13 | } 14 | -------------------------------------------------------------------------------- /src/contextual_identities.rs: -------------------------------------------------------------------------------- 1 | use crate::EventTarget; 2 | use js_sys::Object; 3 | use wasm_bindgen::prelude::*; 4 | 5 | #[wasm_bindgen] 6 | extern "C" { 7 | pub type ContextualIdentities; 8 | 9 | #[wasm_bindgen(catch, method)] 10 | pub async fn create(this: &ContextualIdentities, details: &Object) -> Result; 11 | 12 | #[wasm_bindgen(catch, method)] 13 | pub async fn get( 14 | this: &ContextualIdentities, 15 | cookie_store_id: &str, 16 | ) -> Result; 17 | 18 | #[wasm_bindgen(catch, method)] 19 | pub async fn query(this: &ContextualIdentities, details: &Object) -> Result; 20 | 21 | #[wasm_bindgen(catch, method)] 22 | pub async fn update( 23 | this: &ContextualIdentities, 24 | cookie_store_id: &str, 25 | details: &Object, 26 | ) -> Result; 27 | 28 | #[wasm_bindgen(catch, method)] 29 | pub async fn remove( 30 | this: &ContextualIdentities, 31 | cookie_store_id: &str, 32 | ) -> Result; 33 | 34 | #[wasm_bindgen(method, getter, js_name = onCreated)] 35 | pub fn on_created(this: &ContextualIdentities) -> EventTarget; 36 | 37 | #[wasm_bindgen(method, getter, js_name = onRemoved)] 38 | pub fn on_removed(this: &ContextualIdentities) -> EventTarget; 39 | 40 | #[wasm_bindgen(method, getter, js_name = onUpdated)] 41 | pub fn on_updated(this: &ContextualIdentities) -> EventTarget; 42 | } 43 | -------------------------------------------------------------------------------- /src/downloads.rs: -------------------------------------------------------------------------------- 1 | use js_sys::Object; 2 | use wasm_bindgen::prelude::*; 3 | 4 | // TODO other methods 5 | #[wasm_bindgen] 6 | extern "C" { 7 | pub type Downloads; 8 | 9 | #[wasm_bindgen(catch, method)] 10 | pub async fn download(this: &Downloads, info: &Object) -> Result; 11 | 12 | #[wasm_bindgen(catch, method)] 13 | pub async fn search(this: &Downloads, query: &JsValue) -> Result; 14 | } 15 | -------------------------------------------------------------------------------- /src/history.rs: -------------------------------------------------------------------------------- 1 | //! Bindings to the `history` API. 2 | 3 | use js_sys::{Number, Object}; 4 | use wasm_bindgen::prelude::*; 5 | 6 | #[wasm_bindgen] 7 | extern "C" { 8 | pub type History; 9 | 10 | #[wasm_bindgen(method)] 11 | pub async fn search(this: &History, query: &Object) -> JsValue; 12 | } 13 | 14 | #[wasm_bindgen] 15 | extern "C" { 16 | 17 | // An object encapsulating one result of a history query. 18 | pub type HistoryItem; 19 | 20 | // The unique identifier for the item. 21 | #[wasm_bindgen(method, getter)] 22 | pub fn id(this: &HistoryItem) -> String; 23 | 24 | // When this page was last loaded, represented in milliseconds since the epoch. 25 | #[wasm_bindgen(method, getter, js_name = lastVisitTime)] 26 | pub fn last_visit_time(this: &HistoryItem) -> Option; 27 | 28 | // The title of the page when it was last loaded. 29 | #[wasm_bindgen(method, getter)] 30 | pub fn title(this: &HistoryItem) -> Option; 31 | 32 | // The number of times the user has navigated to this page by typing in the address. 33 | #[wasm_bindgen(method, getter, js_name = typedCount)] 34 | pub fn typed_count(this: &HistoryItem) -> Option; 35 | 36 | // The URL navigated to by a user. 37 | #[wasm_bindgen(method, getter)] 38 | pub fn url(this: &HistoryItem) -> Option; 39 | 40 | // The number of times the user has navigated to this page. 41 | #[wasm_bindgen(method, getter, js_name = visitCount)] 42 | pub fn visit_count(this: &HistoryItem) -> Option; 43 | } 44 | -------------------------------------------------------------------------------- /src/identity.rs: -------------------------------------------------------------------------------- 1 | //! Authorization via OAuth2. 2 | 3 | use js_sys::Function; 4 | use wasm_bindgen::prelude::*; 5 | 6 | #[wasm_bindgen] 7 | extern "C" { 8 | 9 | pub type Identity; 10 | 11 | #[wasm_bindgen(method, js_name = getRedirectURL)] 12 | pub fn get_redirect_url(this: &Identity) -> String; 13 | 14 | #[wasm_bindgen(method, js_name = getRedirectURL)] 15 | pub fn get_redirect_url_with_path(this: &Identity, path: &str) -> String; 16 | 17 | #[wasm_bindgen(method, catch, js_name = launchWebAuthFlow)] 18 | pub async fn launch_webauth_flow( 19 | this: &Identity, 20 | details: &JsValue, 21 | ) -> Result; 22 | 23 | #[wasm_bindgen(method, catch, js_name = launchWebAuthFlow)] 24 | pub fn launch_webauth_flow_with_callback( 25 | this: &Identity, 26 | details: &JsValue, 27 | callback: &Function, 28 | ) -> Result<(), JsValue>; 29 | 30 | #[wasm_bindgen(method, catch, js_name = getAuthToken)] 31 | pub fn get_auth_token(this: &Identity) -> Result<(), JsValue>; 32 | 33 | #[wasm_bindgen(method, catch, js_name = getAuthToken)] 34 | pub fn get_auth_token_with_details(this: &Identity, details: &JsValue) -> Result<(), JsValue>; 35 | 36 | #[wasm_bindgen(method, catch, js_name = getAuthToken)] 37 | pub fn get_auth_token_with_details_and_callback( 38 | this: &Identity, 39 | details: &JsValue, 40 | callback: &Function, 41 | ) -> Result<(), JsValue>; 42 | 43 | // https://developer.chrome.com/docs/extensions/reference/identity/#method-getProfileUserInfo 44 | #[wasm_bindgen(method, catch, js_name = getProfileUserInfo)] 45 | pub async fn get_profile_user_info( 46 | this: &Identity, 47 | details: &JsValue, 48 | ) -> Result; 49 | } 50 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | 3 | use js_sys::Function; 4 | use wasm_bindgen::{prelude::*, JsStatic}; 5 | 6 | mod action; 7 | mod bookmarks; 8 | #[cfg(feature = "firefox")] 9 | mod browser_action; 10 | mod commands; 11 | #[cfg(feature = "firefox")] 12 | mod contextual_identities; 13 | mod downloads; 14 | mod history; 15 | mod identity; 16 | mod omnibox; 17 | mod port; 18 | mod runtime; 19 | mod scripting; 20 | mod sessions; 21 | #[cfg(feature = "firefox")] 22 | mod sidebar_action; 23 | mod storage; 24 | mod tabs; 25 | #[cfg(feature = "firefox")] 26 | mod theme; 27 | mod windows; 28 | 29 | pub use action::*; 30 | pub use bookmarks::*; 31 | #[cfg(feature = "firefox")] 32 | pub use browser_action::*; 33 | pub use commands::*; 34 | #[cfg(feature = "firefox")] 35 | pub use contextual_identities::*; 36 | pub use downloads::*; 37 | pub use history::*; 38 | pub use identity::*; 39 | pub use omnibox::*; 40 | pub use port::*; 41 | pub use runtime::*; 42 | pub use scripting::*; 43 | pub use sessions::*; 44 | #[cfg(feature = "firefox")] 45 | pub use sidebar_action::*; 46 | pub use storage::*; 47 | pub use tabs::*; 48 | #[cfg(feature = "firefox")] 49 | pub use theme::*; 50 | pub use windows::*; 51 | 52 | pub mod traits { 53 | pub use crate::storage::{StorageArea, StorageAreaRead}; 54 | } 55 | 56 | #[cfg(feature = "firefox")] 57 | pub fn browser() -> &'static JsStatic { 58 | &BROWSER 59 | } 60 | 61 | #[cfg(not(feature = "firefox"))] 62 | pub fn chrome() -> &'static JsStatic { 63 | &CHROME 64 | } 65 | 66 | #[wasm_bindgen] 67 | extern "C" { 68 | pub type Browser; 69 | 70 | // This is used for Mozilla Firefox Addons 71 | #[cfg(feature = "firefox")] 72 | #[wasm_bindgen(js_name = browser)] 73 | static BROWSER: Browser; 74 | 75 | // This is used for Google Chrome Extensions 76 | #[cfg(not(feature = "firefox"))] 77 | #[wasm_bindgen(js_name = chrome)] 78 | static CHROME: Browser; 79 | 80 | #[wasm_bindgen(method, getter)] 81 | pub fn action(this: &Browser) -> Action; 82 | 83 | #[cfg(feature = "firefox")] 84 | #[wasm_bindgen(method, getter, js_name = browserAction)] 85 | pub fn browser_action(this: &Browser) -> BrowserAction; 86 | 87 | #[cfg(feature = "firefox")] 88 | #[wasm_bindgen(method, getter, js_name = contextualIdentities)] 89 | pub fn contextual_identities(this: &Browser) -> ContextualIdentities; 90 | 91 | #[wasm_bindgen(method, getter)] 92 | pub fn downloads(this: &Browser) -> Downloads; 93 | 94 | #[wasm_bindgen(method, getter)] 95 | pub fn runtime(this: &Browser) -> Runtime; 96 | 97 | #[wasm_bindgen(method, getter)] 98 | pub fn sessions(this: &Browser) -> Sessions; 99 | 100 | #[cfg(feature = "firefox")] 101 | #[wasm_bindgen(method, getter, js_name = sidebarAction)] 102 | pub fn sidebar_action(this: &Browser) -> SidebarAction; 103 | 104 | #[wasm_bindgen(method, getter)] 105 | pub fn storage(this: &Browser) -> Storage; 106 | 107 | #[wasm_bindgen(method, getter)] 108 | pub fn tabs(this: &Browser) -> Tabs; 109 | 110 | #[cfg(feature = "firefox")] 111 | #[wasm_bindgen(method, getter)] 112 | pub fn theme(this: &Browser) -> BrowserTheme; 113 | 114 | #[wasm_bindgen(method, getter)] 115 | pub fn windows(this: &Browser) -> Windows; 116 | 117 | #[wasm_bindgen(method, getter)] 118 | pub fn scripting(this: &Browser) -> Scripting; 119 | 120 | #[wasm_bindgen(method, getter)] 121 | pub fn history(this: &Browser) -> History; 122 | 123 | #[wasm_bindgen(method, getter)] 124 | pub fn bookmarks(this: &Browser) -> Bookmarks; 125 | 126 | #[wasm_bindgen(method, getter)] 127 | pub fn commands(this: &Browser) -> Commands; 128 | 129 | #[wasm_bindgen(method, getter)] 130 | pub fn identity(this: &Browser) -> Identity; 131 | 132 | #[wasm_bindgen(method, getter)] 133 | pub fn omnibox(this: &Browser) -> Omnibox; 134 | } 135 | 136 | #[wasm_bindgen] 137 | extern "C" { 138 | pub type EventTarget; 139 | 140 | #[wasm_bindgen(method, js_name = addListener)] 141 | pub fn add_listener(this: &EventTarget, listener: &Function); 142 | 143 | #[wasm_bindgen(method, js_name = removeListener)] 144 | pub fn remove_listener(this: &EventTarget, listener: &Function); 145 | 146 | #[wasm_bindgen(method, js_name = hasListener)] 147 | pub fn has_listener(this: &EventTarget, listener: &Function) -> bool; 148 | } 149 | -------------------------------------------------------------------------------- /src/omnibox.rs: -------------------------------------------------------------------------------- 1 | //! Bindings to the `omnibox` API. 2 | 3 | use wasm_bindgen::prelude::*; 4 | 5 | use crate::EventTarget; 6 | 7 | #[wasm_bindgen] 8 | extern "C" { 9 | pub type Omnibox; 10 | 11 | #[wasm_bindgen(method, js_name = setDefaultSuggestion)] 12 | pub fn set_default_suggestion(this: &Omnibox, suggestion: &JsValue); 13 | 14 | #[wasm_bindgen(method, getter, js_name = onDeleteSuggestion)] 15 | pub fn on_delete_suggestion(this: &Omnibox) -> EventTarget; 16 | 17 | #[wasm_bindgen(method, getter, js_name = onInputCancelled)] 18 | pub fn on_input_cancelled(this: &Omnibox) -> EventTarget; 19 | 20 | #[wasm_bindgen(method, getter, js_name = onInputChanged)] 21 | pub fn on_input_changed(this: &Omnibox) -> EventTarget; 22 | 23 | #[wasm_bindgen(method, getter, js_name = onInputEntered)] 24 | pub fn on_input_entered(this: &Omnibox) -> EventTarget; 25 | 26 | #[wasm_bindgen(method, getter, js_name = onInputStarted)] 27 | pub fn on_input_started(this: &Omnibox) -> EventTarget; 28 | } 29 | -------------------------------------------------------------------------------- /src/port.rs: -------------------------------------------------------------------------------- 1 | use crate::{EventTarget, Tab}; 2 | use wasm_bindgen::prelude::*; 3 | 4 | #[wasm_bindgen] 5 | extern "C" { 6 | #[derive(Debug, Clone)] 7 | pub type MessageSender; 8 | 9 | #[wasm_bindgen(method, getter)] 10 | pub fn tab(this: &MessageSender) -> Option; 11 | 12 | // TODO is this correct ? 13 | #[wasm_bindgen(method, getter, js_name = frameId)] 14 | pub fn frame_id(this: &MessageSender) -> Option; 15 | 16 | #[wasm_bindgen(method, getter)] 17 | pub fn id(this: &MessageSender) -> Option; 18 | 19 | #[wasm_bindgen(method, getter)] 20 | pub fn url(this: &MessageSender) -> Option; 21 | 22 | #[wasm_bindgen(method, getter, js_name = tlsChannelId)] 23 | pub fn tls_channel_id(this: &MessageSender) -> Option; 24 | } 25 | 26 | #[wasm_bindgen] 27 | extern "C" { 28 | #[derive(Debug, Clone, PartialEq)] 29 | pub type Port; 30 | 31 | #[wasm_bindgen(method, getter)] 32 | pub fn name(this: &Port) -> String; 33 | 34 | // TODO is this correct ? 35 | #[wasm_bindgen(method, getter)] 36 | pub fn error(this: &Port) -> js_sys::Error; 37 | 38 | #[wasm_bindgen(method)] 39 | pub fn disconnect(this: &Port); 40 | 41 | #[wasm_bindgen(method, getter, js_name = onDisconnect)] 42 | pub fn on_disconnect(this: &Port) -> EventTarget; 43 | 44 | #[wasm_bindgen(method, getter, js_name = onMessage)] 45 | pub fn on_message(this: &Port) -> EventTarget; 46 | 47 | #[wasm_bindgen(method, js_name = postMessage)] 48 | pub fn post_message(this: &Port, value: &JsValue); 49 | 50 | #[wasm_bindgen(method, getter)] 51 | pub fn sender(this: &Port) -> Option; 52 | } 53 | -------------------------------------------------------------------------------- /src/runtime.rs: -------------------------------------------------------------------------------- 1 | use crate::{EventTarget, Port}; 2 | use js_sys::Object; 3 | use wasm_bindgen::prelude::*; 4 | 5 | #[wasm_bindgen] 6 | extern "C" { 7 | pub type Runtime; 8 | 9 | #[wasm_bindgen(catch, method, js_name = sendMessage)] 10 | pub async fn send_message( 11 | this: &Runtime, 12 | extension_id: Option<&str>, 13 | message: &JsValue, 14 | options: Option<&Object>, 15 | ) -> Result; 16 | 17 | #[wasm_bindgen(catch, method, js_name = sendNativeMessage)] 18 | pub async fn send_native_message( 19 | this: &Runtime, 20 | application: &str, 21 | message: &Object, 22 | ) -> Result; 23 | 24 | #[wasm_bindgen(method)] 25 | pub fn connect(this: &Runtime, extension_id: Option<&str>, connect_info: &Object) -> Port; 26 | 27 | #[wasm_bindgen(method, getter, js_name = onMessage)] 28 | pub fn on_message(this: &Runtime) -> EventTarget; 29 | 30 | #[wasm_bindgen(method, getter, js_name = onConnect)] 31 | pub fn on_connect(this: &Runtime) -> EventTarget; 32 | 33 | #[wasm_bindgen(method, getter, js_name = onInstalled)] 34 | pub fn on_installed(this: &Runtime) -> EventTarget; 35 | 36 | #[wasm_bindgen(method, js_name = setUninstallURL)] 37 | pub fn set_uninstall_url(this: &Runtime, url: &str); 38 | 39 | #[wasm_bindgen(method, js_name = openOptionsPage)] 40 | pub fn open_options_page(this: &Runtime); 41 | 42 | #[wasm_bindgen(method, getter, js_name = lastError)] 43 | pub fn last_error(this: &Runtime) -> Option; 44 | } 45 | -------------------------------------------------------------------------------- /src/scripting.rs: -------------------------------------------------------------------------------- 1 | //! 2 | 3 | use js_sys::Object; 4 | use wasm_bindgen::prelude::*; 5 | 6 | #[wasm_bindgen] 7 | extern "C" { 8 | pub type Scripting; 9 | 10 | // https://developer.chrome.com/docs/extensions/reference/scripting/#method-insertCSS 11 | #[wasm_bindgen(method, catch, js_name = insertCSS)] 12 | pub async fn insert_css(this: &Scripting, options: &Object) -> Result<(), JsValue>; 13 | 14 | // https://developer.chrome.com/docs/extensions/reference/scripting/#method-executeScript 15 | #[wasm_bindgen(method, catch, js_name = executeScript)] 16 | pub async fn execute_script(this: &Scripting, options: &Object) -> Result; 17 | } 18 | -------------------------------------------------------------------------------- /src/sessions.rs: -------------------------------------------------------------------------------- 1 | use crate::tabs::Tab; 2 | use crate::windows::Window; 3 | use crate::EventTarget; 4 | use js_sys::Object; 5 | use wasm_bindgen::prelude::*; 6 | 7 | #[wasm_bindgen] 8 | extern "C" { 9 | #[derive(Debug)] 10 | pub type Session; 11 | 12 | #[wasm_bindgen(method, getter, js_name = lastModified)] 13 | pub fn last_modified(this: &Session) -> f64; 14 | 15 | #[wasm_bindgen(method, getter)] 16 | pub fn tab(this: &Session) -> Option; 17 | 18 | #[wasm_bindgen(method, getter)] 19 | pub fn window(this: &Session) -> Option; 20 | } 21 | 22 | #[wasm_bindgen] 23 | extern "C" { 24 | pub type Sessions; 25 | 26 | #[wasm_bindgen(method, getter, js_name = MAX_SESSION_RESULTS)] 27 | // TODO is u32 correct ? 28 | pub fn max_session_results(this: &Sessions) -> u32; 29 | 30 | #[wasm_bindgen(catch, method, js_name = forgetClosedTab)] 31 | pub async fn forget_closed_tab( 32 | this: &Sessions, 33 | window_id: i32, 34 | session_id: &str, 35 | ) -> Result; 36 | 37 | #[wasm_bindgen(catch, method, js_name = forgetClosedWindow)] 38 | pub async fn forget_closed_window( 39 | this: &Sessions, 40 | session_id: &str, 41 | ) -> Result; 42 | 43 | #[wasm_bindgen(catch, method, js_name = getRecentlyClosed)] 44 | pub async fn get_recently_closed( 45 | this: &Sessions, 46 | filter: Option<&Object>, 47 | ) -> Result; 48 | 49 | #[wasm_bindgen(catch, method)] 50 | pub async fn restore(this: &Sessions, session_id: &str) -> Result; 51 | 52 | #[wasm_bindgen(catch, method, js_name = getTabValue)] 53 | pub async fn get_tab_value(this: &Sessions, tab_id: i32, key: &str) 54 | -> Result; 55 | 56 | #[wasm_bindgen(catch, method, js_name = setTabValue)] 57 | pub async fn set_tab_value( 58 | this: &Sessions, 59 | tab_id: i32, 60 | key: &str, 61 | value: &JsValue, 62 | ) -> Result; 63 | 64 | #[wasm_bindgen(catch, method, js_name = removeTabValue)] 65 | pub async fn remove_tab_value( 66 | this: &Sessions, 67 | tab_id: i32, 68 | key: &str, 69 | ) -> Result; 70 | 71 | #[wasm_bindgen(catch, method, js_name = getWindowValue)] 72 | pub async fn get_window_value( 73 | this: &Sessions, 74 | window_id: i32, 75 | key: &str, 76 | ) -> Result; 77 | 78 | #[wasm_bindgen(catch, method, js_name = setWindowValue)] 79 | pub async fn set_window_value( 80 | this: &Sessions, 81 | window_id: i32, 82 | key: &str, 83 | value: &JsValue, 84 | ) -> Result; 85 | 86 | #[wasm_bindgen(catch, method, js_name = removeWindowValue)] 87 | pub async fn remove_window_value( 88 | this: &Sessions, 89 | window_id: i32, 90 | key: &str, 91 | ) -> Result; 92 | 93 | #[wasm_bindgen(method, getter, js_name = onChanged)] 94 | pub fn on_changed(this: &Sessions) -> EventTarget; 95 | } 96 | -------------------------------------------------------------------------------- /src/sidebar_action.rs: -------------------------------------------------------------------------------- 1 | use js_sys::Object; 2 | use wasm_bindgen::prelude::*; 3 | 4 | #[wasm_bindgen] 5 | extern "C" { 6 | pub type SidebarAction; 7 | 8 | #[wasm_bindgen(catch, method)] 9 | pub async fn open(this: &SidebarAction) -> Result; 10 | 11 | #[wasm_bindgen(catch, method, js_name = setPanel)] 12 | pub async fn set_panel(this: &SidebarAction, details: &Object) -> Result; 13 | } 14 | -------------------------------------------------------------------------------- /src/storage.rs: -------------------------------------------------------------------------------- 1 | use crate::EventTarget; 2 | use js_sys::Object; 3 | use wasm_bindgen::prelude::*; 4 | 5 | #[wasm_bindgen] 6 | extern "C" { 7 | pub type StorageAreaRead; 8 | 9 | #[wasm_bindgen(catch, method, js_name = "getBytesInUse")] 10 | pub async fn get_bytes_in_use( 11 | this: &StorageAreaRead, 12 | keys: &JsValue, 13 | ) -> Result; 14 | 15 | #[wasm_bindgen(catch, method)] 16 | pub async fn get(this: &StorageAreaRead, keys: &JsValue) -> Result; 17 | } 18 | 19 | #[wasm_bindgen] 20 | extern "C" { 21 | #[wasm_bindgen(extends = StorageAreaRead)] 22 | pub type StorageArea; 23 | 24 | #[wasm_bindgen(catch, method)] 25 | pub async fn set(this: &StorageArea, keys: &Object) -> Result; 26 | 27 | #[wasm_bindgen(catch, method)] 28 | pub async fn remove(this: &StorageArea, keys: &JsValue) -> Result; 29 | 30 | #[wasm_bindgen(catch, method)] 31 | pub async fn clear(this: &StorageArea) -> Result; 32 | } 33 | 34 | #[wasm_bindgen] 35 | extern "C" { 36 | pub type Storage; 37 | 38 | #[wasm_bindgen(method, getter)] 39 | pub fn sync(this: &Storage) -> StorageArea; 40 | 41 | #[wasm_bindgen(method, getter)] 42 | pub fn local(this: &Storage) -> StorageArea; 43 | 44 | #[wasm_bindgen(method, getter)] 45 | pub fn session(this: &Storage) -> StorageArea; 46 | 47 | #[wasm_bindgen(method, getter)] 48 | pub fn managed(this: &Storage) -> StorageAreaRead; 49 | 50 | #[wasm_bindgen(method, getter, js_name = onChanged)] 51 | pub fn on_changed(this: &Storage) -> EventTarget; 52 | } 53 | 54 | #[wasm_bindgen] 55 | extern "C" { 56 | pub type StorageChange; 57 | 58 | #[wasm_bindgen(method, getter, js_name = oldValue)] 59 | pub fn old_value(this: &StorageChange) -> JsValue; 60 | 61 | #[wasm_bindgen(method, getter, js_name = newValue)] 62 | pub fn new_value(this: &StorageChange) -> JsValue; 63 | } 64 | -------------------------------------------------------------------------------- /src/tabs.rs: -------------------------------------------------------------------------------- 1 | // https://developer.chrome.com/docs/extensions/reference/tabs/ 2 | 3 | use crate::EventTarget; 4 | use js_sys::Object; 5 | use wasm_bindgen::prelude::*; 6 | 7 | /// The tab's ID. 8 | /// 9 | /// Tab IDs are unique within a browser session. 10 | type TabId = i32; // `TAB_ID_NONE` has value `-1` so we have to use i32 11 | 12 | /// The ID of the window that hosts a tab. 13 | type WindowId = i32; 14 | 15 | /// The ID of the group that the tab belongs to. 16 | type GroupId = i32; 17 | 18 | /// Zero-based index of the tab within its window. 19 | type TabIndex = u32; 20 | 21 | #[wasm_bindgen] 22 | extern "C" { 23 | // https://developer.chrome.com/docs/extensions/reference/tabs/#type-onActivated-callback-activeInfo 24 | #[derive(Debug)] 25 | pub type TabActiveInfo; 26 | 27 | #[wasm_bindgen(method, getter, js_name = previousTabId)] 28 | pub fn previous_tab_id(this: &TabActiveInfo) -> Option; 29 | 30 | #[wasm_bindgen(method, getter, js_name = tabId)] 31 | pub fn tab_id(this: &TabActiveInfo) -> TabId; 32 | 33 | #[wasm_bindgen(method, getter, js_name = windowId)] 34 | pub fn window_id(this: &TabActiveInfo) -> WindowId; 35 | } 36 | 37 | #[wasm_bindgen] 38 | extern "C" { 39 | // https://developer.chrome.com/docs/extensions/reference/tabs/#type-onDetached-callback-detachInfo 40 | #[derive(Debug)] 41 | pub type TabDetachInfo; 42 | 43 | #[wasm_bindgen(method, getter, js_name = oldWindowId)] 44 | pub fn old_window_id(this: &TabDetachInfo) -> WindowId; 45 | 46 | #[wasm_bindgen(method, getter, js_name = oldPosition)] 47 | pub fn old_position(this: &TabDetachInfo) -> TabIndex; 48 | } 49 | 50 | #[wasm_bindgen] 51 | extern "C" { 52 | // https://developer.chrome.com/docs/extensions/reference/tabs/#type-onAttached-callback-attachInfo 53 | #[derive(Debug)] 54 | pub type TabAttachInfo; 55 | 56 | #[wasm_bindgen(method, getter, js_name = newWindowId)] 57 | pub fn new_window_id(this: &TabAttachInfo) -> WindowId; 58 | 59 | #[wasm_bindgen(method, getter, js_name = newPosition)] 60 | pub fn new_position(this: &TabAttachInfo) -> TabIndex; 61 | } 62 | 63 | #[wasm_bindgen] 64 | extern "C" { 65 | // https://developer.chrome.com/docs/extensions/reference/tabs/#type-onMoved-callback-moveInfo 66 | #[derive(Debug)] 67 | pub type TabMoveInfo; 68 | 69 | #[wasm_bindgen(method, getter, js_name = windowId)] 70 | pub fn window_id(this: &TabMoveInfo) -> WindowId; 71 | 72 | #[wasm_bindgen(method, getter, js_name = fromIndex)] 73 | pub fn from_index(this: &TabMoveInfo) -> TabIndex; 74 | 75 | #[wasm_bindgen(method, getter, js_name = toIndex)] 76 | pub fn to_index(this: &TabMoveInfo) -> TabIndex; 77 | } 78 | 79 | #[wasm_bindgen] 80 | extern "C" { 81 | // https://developer.chrome.com/docs/extensions/reference/tabs/#type-onRemoved-callback-removeInfo 82 | #[derive(Debug)] 83 | pub type TabRemoveInfo; 84 | 85 | #[wasm_bindgen(method, getter, js_name = windowId)] 86 | pub fn window_id(this: &TabRemoveInfo) -> WindowId; 87 | 88 | #[wasm_bindgen(method, getter, js_name = isWindowClosing)] 89 | pub fn is_window_closing(this: &TabRemoveInfo) -> bool; 90 | } 91 | 92 | #[wasm_bindgen] 93 | extern "C" { 94 | // https://developer.chrome.com/docs/extensions/reference/tabs/#type-MutedInfo 95 | #[derive(Debug)] 96 | pub type TabMutedInfo; 97 | 98 | #[wasm_bindgen(method, getter)] 99 | pub fn muted(this: &TabMutedInfo) -> bool; 100 | 101 | #[wasm_bindgen(method, getter, js_name = extensionId)] 102 | pub fn extension_id(this: &TabMutedInfo) -> Option; 103 | 104 | #[wasm_bindgen(method, getter)] 105 | pub fn reason(this: &TabMutedInfo) -> Option; 106 | } 107 | 108 | #[wasm_bindgen] 109 | extern "C" { 110 | // https://developer.chrome.com/docs/extensions/reference/tabs/#type-Tab 111 | #[derive(Debug, Clone)] 112 | pub type Tab; 113 | 114 | #[wasm_bindgen(method, getter)] 115 | pub fn active(this: &Tab) -> bool; 116 | 117 | #[wasm_bindgen(method, getter)] 118 | pub fn audible(this: &Tab) -> Option; 119 | 120 | #[cfg(not(feature = "firefox"))] 121 | #[wasm_bindgen(method, getter, js_name = autoDiscardable)] 122 | pub fn auto_discardable(this: &Tab) -> bool; 123 | 124 | #[cfg(feature = "firefox")] 125 | #[wasm_bindgen(method, getter, js_name = autoDiscardable)] 126 | pub fn auto_discardable(this: &Tab) -> Option; 127 | 128 | #[cfg(not(feature = "firefox"))] 129 | #[wasm_bindgen(method, getter)] 130 | pub fn discarded(this: &Tab) -> bool; 131 | 132 | #[cfg(feature = "firefox")] 133 | #[wasm_bindgen(method, getter)] 134 | pub fn discarded(this: &Tab) -> Option; 135 | 136 | #[wasm_bindgen(method, getter, js_name = favIconUrl)] 137 | pub fn fav_icon_url(this: &Tab) -> Option; 138 | 139 | #[cfg(not(feature = "firefox"))] 140 | #[wasm_bindgen(method, getter, js_name = groupId)] 141 | pub fn group_id(this: &Tab) -> GroupId; 142 | 143 | #[cfg(feature = "firefox")] 144 | #[wasm_bindgen(method, getter, js_name = groupId)] 145 | pub fn group_id(this: &Tab) -> Option; 146 | 147 | #[wasm_bindgen(method, getter)] 148 | pub fn height(this: &Tab) -> Option; 149 | 150 | #[wasm_bindgen(method, getter)] 151 | pub fn highlighted(this: &Tab) -> bool; 152 | 153 | #[wasm_bindgen(method, getter)] 154 | pub fn id(this: &Tab) -> Option; 155 | 156 | #[wasm_bindgen(method, getter)] 157 | pub fn incognito(this: &Tab) -> bool; 158 | 159 | #[wasm_bindgen(method, getter)] 160 | pub fn index(this: &Tab) -> TabIndex; 161 | 162 | #[cfg(not(feature = "firefox"))] 163 | #[wasm_bindgen(method, getter, js_name = mutedInfo)] 164 | pub fn muted_info(this: &Tab) -> Option; 165 | 166 | #[cfg(feature = "firefox")] 167 | #[wasm_bindgen(method, getter, js_name = mutedInfo)] 168 | pub fn muted_info(this: &Tab) -> TabMutedInfo; 169 | 170 | #[wasm_bindgen(method, getter, js_name = openerTabId)] 171 | pub fn opener_tab_id(this: &Tab) -> Option; 172 | 173 | #[wasm_bindgen(method, getter, js_name = pendingUrl)] 174 | pub fn pending_url(this: &Tab) -> Option; 175 | 176 | #[wasm_bindgen(method, getter)] 177 | pub fn pinned(this: &Tab) -> bool; 178 | 179 | #[wasm_bindgen(method, getter, js_name = sessionId)] 180 | pub fn session_id(this: &Tab) -> Option; 181 | 182 | #[wasm_bindgen(method, getter)] 183 | pub fn status(this: &Tab) -> Option; 184 | 185 | #[wasm_bindgen(method, getter)] 186 | pub fn title(this: &Tab) -> Option; 187 | 188 | #[wasm_bindgen(method, getter)] 189 | pub fn url(this: &Tab) -> Option; 190 | 191 | #[wasm_bindgen(method, getter)] 192 | pub fn width(this: &Tab) -> Option; 193 | 194 | #[wasm_bindgen(method, getter, js_name = windowId)] 195 | pub fn window_id(this: &Tab) -> WindowId; 196 | 197 | // --- Firefox only --- // 198 | 199 | #[cfg(feature = "firefox")] 200 | #[wasm_bindgen(method, getter)] 201 | pub fn attention(this: &Tab) -> Option; 202 | 203 | #[cfg(feature = "firefox")] 204 | #[wasm_bindgen(method, getter, js_name = cookieStoreId)] 205 | pub fn cookie_store_id(this: &Tab) -> Option; 206 | 207 | #[cfg(feature = "firefox")] 208 | #[wasm_bindgen(method, getter)] 209 | pub fn hidden(this: &Tab) -> bool; 210 | 211 | #[cfg(feature = "firefox")] 212 | #[wasm_bindgen(method, getter, js_name = isArticle)] 213 | pub fn is_article(this: &Tab) -> bool; 214 | 215 | #[cfg(feature = "firefox")] 216 | #[wasm_bindgen(method, getter, js_name = isInReaderMode)] 217 | pub fn is_in_reader_mode(this: &Tab) -> bool; 218 | 219 | #[cfg(feature = "firefox")] 220 | #[wasm_bindgen(method, getter, js_name = lastAccessed)] 221 | pub fn last_accessed(this: &Tab) -> f64; 222 | 223 | #[cfg(feature = "firefox")] 224 | #[wasm_bindgen(method, getter, js_name = successorId)] 225 | pub fn successor_id(this: &Tab) -> Option; 226 | 227 | } 228 | 229 | #[wasm_bindgen] 230 | extern "C" { 231 | // https://developer.chrome.com/docs/extensions/reference/tabs/#method 232 | pub type Tabs; 233 | 234 | #[wasm_bindgen(method, getter, js_name = TAB_ID_NONE)] 235 | pub fn tab_id_none(this: &Tabs) -> TabId; 236 | 237 | #[wasm_bindgen(catch, method, js_name = captureTab)] 238 | pub async fn capture_tab( 239 | this: &Tabs, 240 | tab_id: Option, 241 | info: Option<&Object>, 242 | ) -> Result; 243 | 244 | #[wasm_bindgen(catch, method, js_name = captureVisibleTab)] 245 | pub async fn capture_visible_tab( 246 | this: &Tabs, 247 | window_id: Option, 248 | info: Option<&Object>, 249 | ) -> Result; 250 | 251 | #[wasm_bindgen(catch, method)] 252 | pub async fn connect( 253 | this: &Tabs, 254 | tab_id: TabId, 255 | info: Option<&Object>, 256 | ) -> Result; 257 | 258 | #[wasm_bindgen(catch, method)] 259 | pub async fn create(this: &Tabs, info: &Object) -> Result; 260 | 261 | #[wasm_bindgen(catch, method)] 262 | pub async fn discard(this: &Tabs, tab_ids: &JsValue) -> Result; 263 | 264 | #[wasm_bindgen(catch, method)] 265 | pub async fn duplicate(this: &Tabs, tab_id: TabId) -> Result; 266 | 267 | #[wasm_bindgen(catch, method)] 268 | pub async fn get(this: &Tabs, tab_id: TabId) -> Result; 269 | 270 | #[wasm_bindgen(catch, method, js_name = getCurrent)] 271 | pub async fn get_current(this: &Tabs) -> Result; 272 | 273 | #[wasm_bindgen(catch, method, js_name = getZoom)] 274 | pub async fn get_zoom(this: &Tabs, tab_id: Option) -> Result; 275 | 276 | #[wasm_bindgen(catch, method, js_name = getZoomSettings)] 277 | pub async fn get_zoom_settings(this: &Tabs, tab_id: Option) -> Result; 278 | 279 | #[wasm_bindgen(catch, method)] 280 | pub async fn hide(this: &Tabs, tab_ids: &JsValue) -> Result; 281 | 282 | #[wasm_bindgen(catch, method)] 283 | pub async fn highlight(this: &Tabs, info: &Object) -> Result; 284 | 285 | #[wasm_bindgen(catch, method, js_name = insertCSS)] 286 | pub async fn insert_css( 287 | this: &Tabs, 288 | tab_id: Option, 289 | info: &Object, 290 | ) -> Result; 291 | 292 | #[wasm_bindgen(catch, method, js_name = move)] 293 | pub async fn move_(this: &Tabs, tab_ids: &JsValue, info: &Object) -> Result; 294 | 295 | #[wasm_bindgen(catch, method, js_name = moveInSuccession)] 296 | pub async fn move_in_succession( 297 | this: &Tabs, 298 | tab_ids: &JsValue, 299 | tab_id: Option, 300 | info: Option<&Object>, 301 | ) -> Result; 302 | 303 | #[wasm_bindgen(method)] 304 | pub fn print(this: &Tabs); 305 | 306 | #[wasm_bindgen(catch, method, js_name = printPreview)] 307 | pub async fn print_preview(this: &Tabs) -> Result; 308 | 309 | #[wasm_bindgen(catch, method)] 310 | pub async fn query(this: &Tabs, info: &Object) -> Result; 311 | 312 | #[wasm_bindgen(catch, method)] 313 | pub async fn reload( 314 | this: &Tabs, 315 | tab_id: Option, 316 | info: Option<&Object>, 317 | ) -> Result; 318 | 319 | #[wasm_bindgen(catch, method)] 320 | pub async fn remove(this: &Tabs, tab_ids: &JsValue) -> Result; 321 | 322 | #[wasm_bindgen(catch, method, js_name = removeCSS)] 323 | pub async fn remove_css( 324 | this: &Tabs, 325 | tab_id: Option, 326 | info: &Object, 327 | ) -> Result; 328 | 329 | #[wasm_bindgen(catch, method, js_name = saveAsPDF)] 330 | pub async fn save_as_pdf(this: &Tabs, info: &Object) -> Result; 331 | 332 | #[wasm_bindgen(catch, method, js_name = sendMessage)] 333 | pub async fn send_message( 334 | this: &Tabs, 335 | tab_id: TabId, 336 | message: &JsValue, 337 | info: Option<&Object>, 338 | ) -> Result; 339 | 340 | #[wasm_bindgen(catch, method, js_name = setZoom)] 341 | pub async fn set_zoom( 342 | this: &Tabs, 343 | tab_id: Option, 344 | zoom_factor: f64, 345 | ) -> Result; 346 | 347 | #[wasm_bindgen(catch, method, js_name = setZoomSettings)] 348 | pub async fn set_zoom_settings( 349 | this: &Tabs, 350 | tab_id: Option, 351 | info: &Object, 352 | ) -> Result; 353 | 354 | #[wasm_bindgen(catch, method)] 355 | pub async fn show(this: &Tabs, tab_ids: &JsValue) -> Result; 356 | 357 | #[wasm_bindgen(catch, method, js_name = toggleReaderMode)] 358 | pub async fn toggle_reader_mode(this: &Tabs, tab_id: Option) 359 | -> Result; 360 | 361 | #[wasm_bindgen(catch, method)] 362 | pub async fn update( 363 | this: &Tabs, 364 | tab_id: Option, 365 | info: &Object, 366 | ) -> Result; 367 | 368 | #[wasm_bindgen(catch, method, js_name = detectLanguage)] 369 | pub async fn detect_language(this: &Tabs, tab_id: Option) -> Result; 370 | 371 | #[wasm_bindgen(method, getter, js_name = onActivated)] 372 | pub fn on_activated(this: &Tabs) -> EventTarget; 373 | 374 | #[wasm_bindgen(method, getter, js_name = onAttached)] 375 | pub fn on_attached(this: &Tabs) -> EventTarget; 376 | 377 | #[wasm_bindgen(method, getter, js_name = onCreated)] 378 | pub fn on_created(this: &Tabs) -> EventTarget; 379 | 380 | #[wasm_bindgen(method, getter, js_name = onDetached)] 381 | pub fn on_detached(this: &Tabs) -> EventTarget; 382 | 383 | #[wasm_bindgen(method, getter, js_name = onHighlighted)] 384 | pub fn on_highlighted(this: &Tabs) -> EventTarget; 385 | 386 | #[wasm_bindgen(method, getter, js_name = onMoved)] 387 | pub fn on_moved(this: &Tabs) -> EventTarget; 388 | 389 | #[wasm_bindgen(method, getter, js_name = onRemoved)] 390 | pub fn on_removed(this: &Tabs) -> EventTarget; 391 | 392 | #[wasm_bindgen(method, getter, js_name = onReplaced)] 393 | pub fn on_replaced(this: &Tabs) -> EventTarget; 394 | 395 | #[wasm_bindgen(method, getter, js_name = onUpdated)] 396 | pub fn on_updated(this: &Tabs) -> EventTarget; 397 | 398 | #[wasm_bindgen(method, getter, js_name = onZoomChange)] 399 | pub fn on_zoom_change(this: &Tabs) -> EventTarget; 400 | } 401 | 402 | #[wasm_bindgen] 403 | extern "C" { 404 | // https://developer.chrome.com/docs/extensions/reference/tabs/#type-onUpdated-callback-changeInfo 405 | #[derive(Debug)] 406 | pub type TabChangeInfo; 407 | 408 | // The tab's new audible state. 409 | #[wasm_bindgen(method, getter)] 410 | pub fn audible(this: &TabChangeInfo) -> Option; 411 | 412 | // The tab's new auto-discardable state. 413 | #[wasm_bindgen(method, getter, js_name = autoDiscardable)] 414 | pub fn auto_discardable(this: &TabChangeInfo) -> Option; 415 | 416 | // The tab's new discarded state. 417 | #[wasm_bindgen(method, getter)] 418 | pub fn discarded(this: &TabChangeInfo) -> Option; 419 | 420 | // The tab's new favicon URL. 421 | #[wasm_bindgen(method, getter, js_name = favIconUrl)] 422 | pub fn fav_icon_url(this: &TabChangeInfo) -> Option; 423 | 424 | // The tab's new group. 425 | #[wasm_bindgen(method, getter, js_name = groupId)] 426 | pub fn group_id(this: &TabChangeInfo) -> Option; 427 | 428 | // The tab's new muted state and the reason for the change. 429 | #[wasm_bindgen(method, getter, js_name = mutedInfo)] 430 | pub fn muted_info(this: &TabChangeInfo) -> Option; 431 | 432 | // The tab's new pinned state. 433 | #[wasm_bindgen(method, getter)] 434 | pub fn pinned(this: &TabChangeInfo) -> Option; 435 | 436 | // The tab's loading status. 437 | #[wasm_bindgen(method, getter)] 438 | pub fn status(this: &TabChangeInfo) -> Option; 439 | 440 | // The tab's new title. 441 | #[wasm_bindgen(method, getter)] 442 | pub fn title(this: &TabChangeInfo) -> Option; 443 | 444 | // The tab's URL if it has changed. 445 | #[wasm_bindgen(method, getter)] 446 | pub fn url(this: &TabChangeInfo) -> Option; 447 | } 448 | 449 | #[wasm_bindgen] 450 | extern "C" { 451 | // https://developer.chrome.com/docs/extensions/reference/tabs/#type-onHighlighted-callback-highlightInfo 452 | #[derive(Debug)] 453 | pub type TabHighlightInfo; 454 | 455 | // All highlighted tabs in the window. 456 | #[wasm_bindgen(method, getter, js_name = tabIds)] 457 | pub fn tab_ids(this: &TabHighlightInfo) -> JsValue; 458 | 459 | // The window whose tabs changed. 460 | #[wasm_bindgen(method, getter, js_name = windowId)] 461 | pub fn window_id(this: &TabHighlightInfo) -> WindowId; 462 | } 463 | 464 | #[wasm_bindgen] 465 | extern "C" { 466 | // https://developer.chrome.com/docs/extensions/reference/tabs/#type-onZoomChange-callback-ZoomChangeInfo 467 | #[derive(Debug)] 468 | pub type TabZoomChangeInfo; 469 | 470 | #[wasm_bindgen(method, getter, js_name = newZoomFactor)] 471 | pub fn new_zoom_factor(this: &TabZoomChangeInfo) -> f64; 472 | 473 | #[wasm_bindgen(method, getter, js_name = oldZoomFactor)] 474 | pub fn old_zoom_factor(this: &TabZoomChangeInfo) -> f64; 475 | 476 | #[wasm_bindgen(method, getter, js_name = tabId)] 477 | pub fn tab_id(this: &TabZoomChangeInfo) -> TabId; 478 | 479 | #[wasm_bindgen(method, getter, js_name = zoomSettings)] 480 | pub fn zoom_settings(this: &TabZoomChangeInfo) -> JsValue; 481 | } 482 | -------------------------------------------------------------------------------- /src/theme.rs: -------------------------------------------------------------------------------- 1 | use crate::EventTarget; 2 | use js_sys::{Array, Promise}; 3 | use wasm_bindgen::{prelude::*, JsCast}; 4 | 5 | #[wasm_bindgen] 6 | extern "C" { 7 | #[derive(Debug, Clone)] 8 | pub type ThemeImages; 9 | 10 | #[wasm_bindgen(method, getter)] 11 | pub fn theme_frame(this: &ThemeImages) -> Option; 12 | } 13 | 14 | #[wasm_bindgen] 15 | extern "C" { 16 | #[derive(Debug)] 17 | pub type Color; 18 | } 19 | 20 | impl Color { 21 | #[allow(clippy::should_implement_trait)] 22 | pub fn from_str(color: &str) -> Self { 23 | JsValue::from(color).unchecked_into() 24 | } 25 | 26 | pub fn from_rgb(array: [u8; 3]) -> Self { 27 | Array::of3( 28 | &JsValue::from(array[0]), 29 | &JsValue::from(array[1]), 30 | &JsValue::from(array[2]), 31 | ) 32 | .unchecked_into() 33 | } 34 | 35 | pub fn as_string(&self) -> Option { 36 | if let Some(string) = self.unchecked_ref::().as_string() { 37 | Some(string) 38 | } else if let Some(array) = self.dyn_ref::() { 39 | // Convert RGB Array into String 40 | let r = array.get(0).as_f64()?; 41 | let g = array.get(1).as_f64()?; 42 | let b = array.get(2).as_f64()?; 43 | Some(format!("rgb({}, {}, {})", r / 255.0, g / 255.0, b / 255.0)) 44 | } else { 45 | None 46 | } 47 | } 48 | 49 | fn as_u8(value: JsValue) -> Option { 50 | let float = value.as_f64()?; 51 | if float.fract() == 0.0 && (0.0..=255.0).contains(&float) { 52 | Some(float as u8) 53 | } else { 54 | None 55 | } 56 | } 57 | 58 | pub fn as_rgb(&self) -> Option<[u8; 3]> { 59 | if let Some(array) = self.dyn_ref::() { 60 | let r = Self::as_u8(array.get(0))?; 61 | let g = Self::as_u8(array.get(1))?; 62 | let b = Self::as_u8(array.get(2))?; 63 | Some([r, g, b]) 64 | } else { 65 | None 66 | } 67 | } 68 | } 69 | 70 | #[wasm_bindgen] 71 | extern "C" { 72 | #[derive(Debug, Clone)] 73 | pub type ThemeColors; 74 | 75 | #[wasm_bindgen(method, getter)] 76 | pub fn button_background_active(this: &ThemeColors) -> Color; 77 | 78 | #[wasm_bindgen(method, getter)] 79 | pub fn button_background_hover(this: &ThemeColors) -> Color; 80 | 81 | #[wasm_bindgen(method, getter)] 82 | pub fn icons(this: &ThemeColors) -> Color; 83 | 84 | #[wasm_bindgen(method, getter)] 85 | pub fn icons_attention(this: &ThemeColors) -> Color; 86 | 87 | #[wasm_bindgen(method, getter)] 88 | pub fn frame(this: &ThemeColors) -> Color; 89 | 90 | #[wasm_bindgen(method, getter)] 91 | pub fn frame_inactive(this: &ThemeColors) -> Color; 92 | 93 | #[wasm_bindgen(method, getter)] 94 | pub fn ntp_background(this: &ThemeColors) -> Color; 95 | 96 | #[wasm_bindgen(method, getter)] 97 | pub fn ntp_text(this: &ThemeColors) -> Color; 98 | 99 | #[wasm_bindgen(method, getter)] 100 | pub fn popup(this: &ThemeColors) -> Color; 101 | 102 | #[wasm_bindgen(method, getter)] 103 | pub fn popup_border(this: &ThemeColors) -> Color; 104 | 105 | #[wasm_bindgen(method, getter)] 106 | pub fn popup_highlight(this: &ThemeColors) -> Color; 107 | 108 | #[wasm_bindgen(method, getter)] 109 | pub fn popup_highlight_text(this: &ThemeColors) -> Color; 110 | 111 | #[wasm_bindgen(method, getter)] 112 | pub fn popup_text(this: &ThemeColors) -> Color; 113 | 114 | #[wasm_bindgen(method, getter)] 115 | pub fn sidebar(this: &ThemeColors) -> Color; 116 | 117 | #[wasm_bindgen(method, getter)] 118 | pub fn sidebar_border(this: &ThemeColors) -> Color; 119 | 120 | #[wasm_bindgen(method, getter)] 121 | pub fn sidebar_highlight(this: &ThemeColors) -> Color; 122 | 123 | #[wasm_bindgen(method, getter)] 124 | pub fn sidebar_highlight_text(this: &ThemeColors) -> Color; 125 | 126 | #[wasm_bindgen(method, getter)] 127 | pub fn sidebar_text(this: &ThemeColors) -> Color; 128 | 129 | #[wasm_bindgen(method, getter)] 130 | pub fn tab_background_separator(this: &ThemeColors) -> Color; 131 | 132 | #[wasm_bindgen(method, getter)] 133 | pub fn tab_background_text(this: &ThemeColors) -> Color; 134 | 135 | #[wasm_bindgen(method, getter)] 136 | pub fn tab_line(this: &ThemeColors) -> Color; 137 | 138 | #[wasm_bindgen(method, getter)] 139 | pub fn tab_loading(this: &ThemeColors) -> Color; 140 | 141 | #[wasm_bindgen(method, getter)] 142 | pub fn tab_selected(this: &ThemeColors) -> Color; 143 | 144 | #[wasm_bindgen(method, getter)] 145 | pub fn tab_text(this: &ThemeColors) -> Color; 146 | 147 | #[wasm_bindgen(method, getter)] 148 | pub fn toolbar(this: &ThemeColors) -> Color; 149 | 150 | #[wasm_bindgen(method, getter)] 151 | pub fn toolbar_bottom_separator(this: &ThemeColors) -> Color; 152 | 153 | #[wasm_bindgen(method, getter)] 154 | pub fn toolbar_field(this: &ThemeColors) -> Color; 155 | 156 | #[wasm_bindgen(method, getter)] 157 | pub fn toolbar_field_border(this: &ThemeColors) -> Color; 158 | 159 | #[wasm_bindgen(method, getter)] 160 | pub fn toolbar_field_border_focus(this: &ThemeColors) -> Color; 161 | 162 | #[wasm_bindgen(method, getter)] 163 | pub fn toolbar_field_focus(this: &ThemeColors) -> Color; 164 | 165 | #[wasm_bindgen(method, getter)] 166 | pub fn toolbar_field_highlight(this: &ThemeColors) -> Color; 167 | 168 | #[wasm_bindgen(method, getter)] 169 | pub fn toolbar_field_highlight_text(this: &ThemeColors) -> Color; 170 | 171 | #[wasm_bindgen(method, getter)] 172 | pub fn toolbar_field_separator(this: &ThemeColors) -> Color; 173 | 174 | #[wasm_bindgen(method, getter)] 175 | pub fn toolbar_field_text(this: &ThemeColors) -> Color; 176 | 177 | #[wasm_bindgen(method, getter)] 178 | pub fn toolbar_field_text_focus(this: &ThemeColors) -> Color; 179 | 180 | #[wasm_bindgen(method, getter)] 181 | pub fn toolbar_text(this: &ThemeColors) -> Color; 182 | 183 | #[wasm_bindgen(method, getter)] 184 | pub fn toolbar_top_separator(this: &ThemeColors) -> Color; 185 | 186 | #[wasm_bindgen(method, getter)] 187 | pub fn toolbar_vertical_separator(this: &ThemeColors) -> Color; 188 | } 189 | 190 | #[wasm_bindgen] 191 | extern "C" { 192 | #[derive(Debug, Clone)] 193 | pub type ThemeProperties; 194 | 195 | #[wasm_bindgen(method, getter)] 196 | pub fn additional_backgrounds_alignment(this: &ThemeProperties) -> Option; 197 | 198 | #[wasm_bindgen(method, getter)] 199 | pub fn additional_backgrounds_tiling(this: &ThemeProperties) -> Option; 200 | } 201 | 202 | #[wasm_bindgen] 203 | extern "C" { 204 | #[derive(Debug, Clone)] 205 | pub type Theme; 206 | 207 | #[wasm_bindgen(method, getter)] 208 | pub fn images(this: &Theme) -> Option; 209 | 210 | #[wasm_bindgen(method, getter)] 211 | pub fn colors(this: &Theme) -> Option; 212 | 213 | #[wasm_bindgen(method, getter)] 214 | pub fn properties(this: &Theme) -> Option; 215 | } 216 | 217 | #[wasm_bindgen] 218 | extern "C" { 219 | #[derive(Debug)] 220 | pub type ThemeUpdateInfo; 221 | 222 | #[wasm_bindgen(method, getter)] 223 | pub fn theme(this: &ThemeUpdateInfo) -> Theme; 224 | 225 | #[wasm_bindgen(method, getter, js_name = windowId)] 226 | pub fn window_id(this: &ThemeUpdateInfo) -> Option; 227 | } 228 | 229 | #[wasm_bindgen] 230 | extern "C" { 231 | pub type BrowserTheme; 232 | 233 | #[wasm_bindgen(method, js_name = getCurrent)] 234 | pub fn get_current(this: &BrowserTheme, window_id: Option) -> Promise; 235 | 236 | #[wasm_bindgen(method)] 237 | pub fn update(this: &BrowserTheme, window_id: Option, theme: &Theme); 238 | 239 | #[wasm_bindgen(method)] 240 | pub fn reset(this: &BrowserTheme, window_id: Option); 241 | 242 | #[wasm_bindgen(method, getter, js_name = onUpdated)] 243 | pub fn on_updated(this: &BrowserTheme) -> EventTarget; 244 | } 245 | -------------------------------------------------------------------------------- /src/windows.rs: -------------------------------------------------------------------------------- 1 | use crate::EventTarget; 2 | use js_sys::{Array, Object}; 3 | use wasm_bindgen::prelude::*; 4 | 5 | #[wasm_bindgen] 6 | extern "C" { 7 | #[derive(Debug)] 8 | pub type Window; 9 | 10 | #[wasm_bindgen(method, getter, js_name = alwaysOnTop)] 11 | pub fn always_on_top(this: &Window) -> bool; 12 | 13 | #[wasm_bindgen(method, getter)] 14 | pub fn focused(this: &Window) -> bool; 15 | 16 | #[wasm_bindgen(method, getter)] 17 | pub fn incognito(this: &Window) -> bool; 18 | 19 | #[wasm_bindgen(method, getter)] 20 | // TODO is i32 correct ? 21 | pub fn left(this: &Window) -> Option; 22 | 23 | #[wasm_bindgen(method, getter)] 24 | // TODO is i32 correct ? 25 | pub fn top(this: &Window) -> Option; 26 | 27 | #[wasm_bindgen(method, getter)] 28 | // TODO is u32 correct ? 29 | pub fn width(this: &Window) -> Option; 30 | 31 | #[wasm_bindgen(method, getter)] 32 | // TODO is u32 correct ? 33 | pub fn height(this: &Window) -> Option; 34 | 35 | #[wasm_bindgen(method, getter)] 36 | // TODO is i32 correct ? 37 | pub fn id(this: &Window) -> Option; 38 | 39 | #[wasm_bindgen(method, getter, js_name = sessionId)] 40 | pub fn session_id(this: &Window) -> Option; 41 | 42 | #[wasm_bindgen(method, getter)] 43 | pub fn title(this: &Window) -> Option; 44 | 45 | #[wasm_bindgen(method, getter)] 46 | pub fn state(this: &Window) -> Option; 47 | 48 | #[wasm_bindgen(method, getter)] 49 | pub fn tabs(this: &Window) -> Option; 50 | 51 | #[wasm_bindgen(method, getter, js_name = type)] 52 | pub fn type_(this: &Window) -> Option; 53 | } 54 | 55 | #[wasm_bindgen] 56 | extern "C" { 57 | pub type Windows; 58 | 59 | #[wasm_bindgen(method, getter, js_name = WINDOW_ID_NONE)] 60 | pub fn window_id_none(this: &Windows) -> i32; 61 | 62 | #[wasm_bindgen(method, getter, js_name = WINDOW_ID_CURRENT)] 63 | pub fn window_id_current(this: &Windows) -> i32; 64 | 65 | #[wasm_bindgen(catch, method)] 66 | pub async fn get(this: &Windows, window_id: i32, info: &Object) -> Result; 67 | 68 | #[wasm_bindgen(catch, method, js_name = getCurrent)] 69 | pub async fn get_current(this: &Windows, info: &Object) -> Result; 70 | 71 | #[wasm_bindgen(catch, method, js_name = getLastFocused)] 72 | pub async fn get_last_focused(this: &Windows, info: &Object) -> Result; 73 | 74 | #[wasm_bindgen(catch, method, js_name = getAll)] 75 | pub async fn get_all(this: &Windows, info: &Object) -> Result; 76 | 77 | #[wasm_bindgen(catch, method)] 78 | pub async fn create(this: &Windows, info: &Object) -> Result; 79 | 80 | #[wasm_bindgen(catch, method)] 81 | pub async fn update(this: &Windows, window_id: i32, info: &Object) -> Result; 82 | 83 | #[wasm_bindgen(catch, method)] 84 | pub async fn remove(this: &Windows, window_id: i32) -> Result; 85 | 86 | #[wasm_bindgen(method, getter, js_name = onCreated)] 87 | pub fn on_created(this: &Windows) -> EventTarget; 88 | 89 | #[wasm_bindgen(method, getter, js_name = onRemoved)] 90 | pub fn on_removed(this: &Windows) -> EventTarget; 91 | 92 | #[wasm_bindgen(method, getter, js_name = onFocusChanged)] 93 | pub fn on_focus_changed(this: &Windows) -> EventTarget; 94 | } 95 | --------------------------------------------------------------------------------