From 83579570a19a21a1130e214744e58c62969171de Mon Sep 17 00:00:00 2001 From: Flier Lu Date: Thu, 8 Dec 2022 19:30:37 +0800 Subject: [PATCH] add omnibox --- Cargo.toml | 7 +- examples/omnibox/new-tab-search/Cargo.toml | 37 +++ examples/omnibox/new-tab-search/background.js | 13 + examples/omnibox/new-tab-search/manifest.json | 46 +++ .../new-tab-search/newtab_search128.png | Bin 0 -> 2567 bytes .../new-tab-search/newtab_search16.png | Bin 0 -> 481 bytes .../new-tab-search/newtab_search32.png | Bin 0 -> 821 bytes .../new-tab-search/newtab_search48.png | Bin 0 -> 1162 bytes examples/omnibox/new-tab-search/src/lib.rs | 128 ++++++++ examples/omnibox/new-tab-search/src/utils.rs | 10 + src/lib.rs | 1 + src/omnibox.rs | 278 ++++++++++++++++++ src/tabs/mod.rs | 41 +++ src/tabs/query_details.rs | 2 +- tests/contextual_identities.rs | 2 + 15 files changed, 563 insertions(+), 2 deletions(-) create mode 100644 examples/omnibox/new-tab-search/Cargo.toml create mode 100644 examples/omnibox/new-tab-search/background.js create mode 100644 examples/omnibox/new-tab-search/manifest.json create mode 100644 examples/omnibox/new-tab-search/newtab_search128.png create mode 100644 examples/omnibox/new-tab-search/newtab_search16.png create mode 100644 examples/omnibox/new-tab-search/newtab_search32.png create mode 100644 examples/omnibox/new-tab-search/newtab_search48.png create mode 100644 examples/omnibox/new-tab-search/src/lib.rs create mode 100644 examples/omnibox/new-tab-search/src/utils.rs create mode 100644 src/omnibox.rs diff --git a/Cargo.toml b/Cargo.toml index b1af47e..d6d6e6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,9 +16,11 @@ categories = ["api-bindings", "wasm"] crate-type = ["cdylib", "rlib"] [dependencies] +derive_more = "0.99" +gloo-console = "0.2" gloo-utils = "0.1.5" js-sys = "0.3.60" -serde = { version = "1.0.147", features = ["derive"] } +serde = {version = "1.0.147", features = ["derive"]} serde_derive = "1.0.147" serde_json = "1.0.87" thiserror = "1.0.37" @@ -32,3 +34,6 @@ wasm-bindgen-test = "0.3.33" [features] default = [] firefox = [] + +[workspace] +members = ["examples/omnibox/new-tab-search"] diff --git a/examples/omnibox/new-tab-search/Cargo.toml b/examples/omnibox/new-tab-search/Cargo.toml new file mode 100644 index 0000000..e62985c --- /dev/null +++ b/examples/omnibox/new-tab-search/Cargo.toml @@ -0,0 +1,37 @@ +[package] +authors = ["Flier Lu "] +edition = "2018" +name = "new-tab-search" +version = "0.1.0" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +default = ["console_error_panic_hook"] + +[dependencies] +gloo-console = "0.2" +gloo-utils = "0.1" +js-sys = "0.3" +wasm-bindgen = "0.2" +wasm-bindgen-futures = "0.4" + +# The `console_error_panic_hook` crate provides better debugging of panics by +# logging them with `console.error`. This is great for development, but requires +# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for +# code size when deploying. +console_error_panic_hook = {version = "0.1.6", optional = true} + +# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size +# compared to the default allocator's ~10K. It is slower than the default +# allocator, however. +wee_alloc = {version = "0.4", optional = true} + +web-extensions = {version = "0.3", path = "../../.."} + +[profile.release] +# Tell `rustc` to optimize for small code size. +codegen-units = 1 +lto = true +opt-level = "s" diff --git a/examples/omnibox/new-tab-search/background.js b/examples/omnibox/new-tab-search/background.js new file mode 100644 index 0000000..d326af5 --- /dev/null +++ b/examples/omnibox/new-tab-search/background.js @@ -0,0 +1,13 @@ +import init, { start } from './pkg/new_tab_search.js'; + +console.log("WASM module loaded") + +async function run() { + await init(); + + console.log("WASM module initialized") + + start(); +} + +run(); diff --git a/examples/omnibox/new-tab-search/manifest.json b/examples/omnibox/new-tab-search/manifest.json new file mode 100644 index 0000000..6796446 --- /dev/null +++ b/examples/omnibox/new-tab-search/manifest.json @@ -0,0 +1,46 @@ +{ + "name": "Omnibox - New Tab Search", + "description": "Type 'nt' plus a search term into the Omnibox to open search in new tab.", + "version": "1.0", + "manifest_version": 3, + "background": { + "service_worker": "background.js", + "type": "module" + }, + "permissions": [ + "activeTab", + "tabs", + "scripting" + ], + "host_permissions": [ + "https://*/*" + ], + "content_security_policy": { + "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'" + }, + "web_accessible_resources": [ + { + "resources": [ + "pkg/new_tab_search_bg.wasm" + ], + "matches": [ + "https://*/*" + ] + } + ], + "omnibox": { + "keyword": "nt" + }, + "action": { + "default_icon": { + "16": "newtab_search16.png", + "32": "newtab_search32.png" + } + }, + "icons": { + "16": "newtab_search16.png", + "32": "newtab_search32.png", + "48": "newtab_search48.png", + "128": "newtab_search128.png" + } +} diff --git a/examples/omnibox/new-tab-search/newtab_search128.png b/examples/omnibox/new-tab-search/newtab_search128.png new file mode 100644 index 0000000000000000000000000000000000000000..d4b8637ffae16686dabb2eb23c05c3d399ad5ce0 GIT binary patch literal 2567 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 E0U1(vrT_o{ literal 0 HcmV?d00001 diff --git a/examples/omnibox/new-tab-search/newtab_search16.png b/examples/omnibox/new-tab-search/newtab_search16.png new file mode 100644 index 0000000000000000000000000000000000000000..09c8ae25954cddc887605de6b0b8b462780bec0d GIT binary patch literal 481 zcmV<70UrK|P)i_@&lSxEDR5;6>lT9eYaTv!x|LwmmFHG^*8_Phu&yQu{* z^S^M4A7WcF<9Z&z_gIcj^wN~SPM-Wp3NR&0zAh3L2G1O<28M|jcViEaG3Z%m`@Tog zf(VdMl`?lcLr1NEwLt*D+5o$!N|yH~F{#~Xm4S=^8G!3$ggn)kI2j!^(5P3i73}`e zfegSGt*5anb3uKXfHz{4251#m_=1@W{J{kJoO5YFQ|=m`8`wSlE{2_x7ffombQ9HK z7emz>tT=n|+`QZN2NUd^#EHMYN{&)K-LEqPod&s+Hr*k2(P9oR^YkA47VSlL03z`m zrp}h|hK<<)ESgQmE9cW7ch9AN2YiQ=sIEUZQ_SY{ESgQU%0Sv!g_zyc47C39ocG=U XJx+n$4W?0b00000NkvXXu0mjf%IVoE literal 0 HcmV?d00001 diff --git a/examples/omnibox/new-tab-search/newtab_search32.png b/examples/omnibox/new-tab-search/newtab_search32.png new file mode 100644 index 0000000000000000000000000000000000000000..17a435a65a63baf1b1ec677fe0de177d7d7a77cc GIT binary patch literal 821 zcmV-51Iqk~P)w#V+{c@{=Xd}2`8tXaf}=Do0EmvJ834_P?0b~%cHh)QFPe|w<7;IFQrT>v1&-f0 zF#GEad?8N0-&Qt-_OdB-ReVrQK@mbAI-24FL5Q$;{c{lS*UqMWSYNyhvu+4A%#sq{ zlXSPcBS8Tn!s4#y4LpA7i;r-SWc4YVi+AdCUN z0HOd?)-T{WfT5-Hm5@ONfR8hh_c= zf`1C+dkif_dn#4*#mjhH*Plx{o}|0o9hzh-r?nq3+HeUMj(-MIC~ZkljCbD1&A@6L zfLUh+AT_3YU!OsUuviHi)G3f`E?5Z}AcR?%I2Wplym0|Hv3(IXk3}lu^d@{FkFJVw zm~}(Bx8r1H;#_HSJT{ovJQnywJ_^+f0GS}5$=7^UQjl1^CboNF^V~li0G{?H4Zutg zc>}0nmS8mwC3J`-x(WuCNfYI2$+94SM@LcW%M``~6KG-7DL^SBK00000NkvXXu0mjfgkER} literal 0 HcmV?d00001 diff --git a/examples/omnibox/new-tab-search/newtab_search48.png b/examples/omnibox/new-tab-search/newtab_search48.png new file mode 100644 index 0000000000000000000000000000000000000000..5678cece9b4b9375ff86274a8b2838d4c3944b8f GIT binary patch literal 1162 zcmV;51abgCE;2bQRmBFM_oT;)AX02lb3+}tD>OS0;&S? zdB?xNz2_W;#v=D#e_V;yu`YDzn!w1s(#?RKGoZ)OK-}9_1^l|7!uEBs&I5o(2{aif zoZC%7qa-0Z9c|Kv&;>^ud;u!lKC*>E{U$1@PBlv4Dv9ukx7+Zw%4K92iMs}JXaYb8W9T$y z;!L*~0M%E2g|A273iA>dD2>w{UFXp zYxAFq3qbjuSYP@++#VWBZ5D1nAqT*y>=%C#n~E+H?XCe1(|-P)588nH)=1k>*e|%q zi3?C1uwc(Oqr^HmahD&&pbh9A;0UEAhPat`8{Ma9gl0*AfH-`E!=P>9n7{gnBb*0- zMZH9ied2`7#82bh^HDOo*KOH>f!Pi z63?tDc!st4PhnI>b{iE9v{y9a`1s|pxDU<5cdFYsW$!@SiqaORd(5 { + if let [tab, ..] = &tabs[..] { + console::debug!( + "current tab", + tab.id.map_or(-1, Into::::into) + ); + + tab_id = tab.id; + } + } + Err(err) => { + console::error!("query tabs failed", err.to_string()); + } + } + } + + if disposition == CurrentTab { + console::info!( + "open on the current tab", + &url, + tab_id.map_or(-1, Into::::into) + ); + + match ext::tabs::update( + tab_id, + ext::tabs::UpdateProperties { + url: Some(&url), + ..Default::default() + }, + ) + .await + { + Ok(tab) => { + console::info!( + "opened on the current tab", + &url, + tab.id.map_or(-1, Into::::into) + ) + } + Err(err) => console::error!("update tabs failed", err.to_string()), + } + } else { + console::info!("open on a new tab", &url); + + match ext::tabs::create(ext::tabs::CreateProperties { + active: disposition == NewForegroundTab, + url: &url, + }) + .await + { + Ok(tab) => { + console::info!( + "open on a new tab", + tab.id.map_or(-1, Into::::into) + ) + } + Err(err) => { + console::error!("create tab failed", err.to_string()); + } + }; + } + }) + }) + .forget(); +} diff --git a/examples/omnibox/new-tab-search/src/utils.rs b/examples/omnibox/new-tab-search/src/utils.rs new file mode 100644 index 0000000..b1d7929 --- /dev/null +++ b/examples/omnibox/new-tab-search/src/utils.rs @@ -0,0 +1,10 @@ +pub fn set_panic_hook() { + // When the `console_error_panic_hook` feature is enabled, we can call the + // `set_panic_hook` function at least once during initialization, and then + // we will get better error messages if our code ever panics. + // + // For more details see + // https://github.com/rustwasm/console_error_panic_hook#readme + #[cfg(feature = "console_error_panic_hook")] + console_error_panic_hook::set_once(); +} diff --git a/src/lib.rs b/src/lib.rs index c9e794d..0269788 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ pub use crate::error::*; pub mod bookmarks; pub mod downloads; pub mod history; +pub mod omnibox; pub mod tabs; #[cfg(feature = "firefox")] diff --git a/src/omnibox.rs b/src/omnibox.rs new file mode 100644 index 0000000..14ba161 --- /dev/null +++ b/src/omnibox.rs @@ -0,0 +1,278 @@ +//! Wrapper for the [`chrome.omnibox` API](https://developer.chrome.com/docs/extensions/reference/omnibox/). + +use derive_more::Display; +use gloo_console as console; +use js_sys::Function; +use serde::{Deserialize, Serialize}; +use wasm_bindgen::prelude::*; +use web_extensions_sys as sys; + +use crate::{event_listener::EventListener, util::*, Error}; + +/// Sets the description and styling for the default suggestion. +/// +/// The default suggestion is the text that is displayed in the first suggestion row underneath the URL bar. +/// +/// +pub fn set_default_suggestion(suggestion: &DefaultSuggestResult<'_>) -> Result<(), Error> { + let js_suggestion = js_from_serde(suggestion)?; + + sys::chrome() + .omnibox() + .set_default_suggestion(&js_suggestion); + + Ok(()) +} + +/// A suggest result. +/// +/// +#[derive(Debug, Clone, Serialize)] +pub struct DefaultSuggestResult<'a> { + /// The text that is displayed in the URL dropdown. + pub description: &'a str, +} + +/// The style type. +/// +/// +#[derive(Debug, Clone, Copy, PartialEq, Eq, Display, Serialize)] +#[serde(rename_all = "camelCase")] +pub enum DescriptionStyleType { + Url, + Match, + Dim, +} + +impl DescriptionStyleType { + pub const DIM: &str = "dim"; + pub const MATCH: &str = "match"; + pub const URL: &str = "url"; +} + +impl TryFrom for DescriptionStyleType { + type Error = Error; + + fn try_from(v: JsValue) -> Result { + v.as_string() + .and_then(|s| match s.as_str() { + Self::DIM => Some(DescriptionStyleType::Dim), + Self::MATCH => Some(DescriptionStyleType::Match), + Self::URL => Some(DescriptionStyleType::Url), + _ => None, + }) + .ok_or(Error::Js(v)) + } +} +/// The window disposition for the omnibox query. +/// +/// This is the recommended context to display results. +/// For example, if the omnibox command is to navigate to a certain URL, +/// a disposition of 'newForegroundTab' means the navigation should take place in a new selected tab. +/// +/// +#[derive(Debug, Clone, Copy, PartialEq, Eq, Display, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum OnInputEnteredDisposition { + CurrentTab, + NewForegroundTab, + NewBackgroundTab, +} + +impl OnInputEnteredDisposition { + pub const CURRENT_TAB: &str = "currentTab"; + pub const NEW_BACKGROUND_TAB: &str = "newBackgroundTab"; + pub const NEW_FOREGROUND_TAB: &str = "newForegroundTab"; +} + +impl TryFrom for OnInputEnteredDisposition { + type Error = Error; + + fn try_from(v: JsValue) -> Result { + v.as_string() + .and_then(|s| match s.as_str() { + Self::CURRENT_TAB => Some(OnInputEnteredDisposition::CurrentTab), + Self::NEW_BACKGROUND_TAB => Some(OnInputEnteredDisposition::NewBackgroundTab), + Self::NEW_FOREGROUND_TAB => Some(OnInputEnteredDisposition::NewForegroundTab), + _ => None, + }) + .ok_or(Error::Js(v)) + } +} + +/// A suggest result. +#[derive(Default, Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SuggestResult<'a> { + /// The text that is put into the URL bar, and that is sent to the extension when the user chooses this entry. + pub content: &'a str, + /// Whether the suggest result can be deleted by the user. + pub deletable: bool, + /// The text that is displayed in the URL dropdown. + pub description: &'a str, +} + +/// User has deleted a suggested result. +pub fn on_delete_suggestion() -> OnDeleteSuggestion { + OnDeleteSuggestion(sys::chrome().omnibox().on_delete_suggestion()) +} + +pub struct OnDeleteSuggestion(sys::EventTarget); + +impl OnDeleteSuggestion { + pub fn add_listener(&self, mut listener: L) -> OnDeleteSuggestionListener + where + L: FnMut(&str) + 'static, + { + let listener = Closure::new(move |text: String| { + console::debug!("on delete suggestion", sys::chrome().omnibox(), &text); + + listener(text.as_str()) + }); + + OnDeleteSuggestionListener(EventListener::raw_new(&self.0, listener)) + } +} + +pub struct OnDeleteSuggestionListener<'a>(EventListener<'a, dyn FnMut(String)>); + +impl OnDeleteSuggestionListener<'_> { + pub fn forget(self) { + self.0.forget() + } +} + +/// User has ended the keyword input session without accepting the input. +pub fn on_input_cancelled() -> OnInputCancelled { + OnInputCancelled(sys::chrome().omnibox().on_input_cancelled()) +} + +pub struct OnInputCancelled(sys::EventTarget); + +impl OnInputCancelled { + pub fn add_listener(&self, mut listener: L) -> OnInputCancelledListener + where + L: FnMut() + 'static, + { + let listener = Closure::new(move || { + console::debug!("on input cancelled", sys::chrome().omnibox()); + + listener() + }); + + OnInputCancelledListener(EventListener::raw_new(&self.0, listener)) + } +} + +pub struct OnInputCancelledListener<'a>(EventListener<'a, dyn FnMut()>); + +impl OnInputCancelledListener<'_> { + pub fn forget(self) { + self.0.forget() + } +} + +/// User has changed what is typed into the omnibox. +pub fn on_input_changed() -> OnInputChanged { + OnInputChanged(sys::chrome().omnibox().on_input_changed()) +} + +pub struct OnInputChanged(sys::EventTarget); + +impl OnInputChanged { + pub fn add_listener(&self, mut listener: L) -> OnInputChangedListener + where + L: FnMut(&str, &mut (dyn FnMut(Vec) -> Result<(), Error> + 'static)) + + 'static, + { + let listener = Closure::new(move |text: String, suggest: Function| { + console::debug!("on input changed", sys::chrome().omnibox(), &text, &suggest); + + let mut f = move |results: Vec| -> Result<(), Error> { + let this = JsValue::null(); + let js_results = js_from_serde(&results).unwrap(); + + suggest.call1(&this, &js_results)?; + + Ok(()) + }; + + listener(text.as_str(), &mut f) + }); + + OnInputChangedListener(EventListener::raw_new(&self.0, listener)) + } +} + +pub struct OnInputChangedListener<'a>(EventListener<'a, dyn FnMut(String, Function)>); + +impl OnInputChangedListener<'_> { + pub fn forget(self) { + self.0.forget() + } +} + +/// User has accepted what is typed into the omnibox. +pub fn on_input_entered() -> OnInputEntered { + OnInputEntered(sys::chrome().omnibox().on_input_entered()) +} + +pub struct OnInputEntered(sys::EventTarget); + +impl OnInputEntered { + pub fn add_listener(&self, mut listener: L) -> OnInputEnteredListener + where + L: FnMut(&str, OnInputEnteredDisposition) + 'static, + { + let callback = Closure::new(move |text: String, disposition: JsValue| { + console::debug!( + "on input entered", + sys::chrome().omnibox(), + &text, + &disposition + ); + + listener(text.as_str(), disposition.try_into().unwrap()) + }); + + OnInputEnteredListener(EventListener::raw_new(&self.0, callback)) + } +} + +pub struct OnInputEnteredListener<'a>(EventListener<'a, dyn FnMut(String, JsValue)>); + +impl OnInputEnteredListener<'_> { + pub fn forget(self) { + self.0.forget() + } +} + +/// User has ended the keyword input session without accepting the input. +pub fn on_input_started() -> OnInputStarted { + OnInputStarted(sys::chrome().omnibox().on_input_started()) +} + +pub struct OnInputStarted(sys::EventTarget); + +impl OnInputStarted { + pub fn add_listener(&self, mut listener: L) -> OnInputStartedListener + where + L: FnMut() + 'static, + { + let listener = Closure::new(move || { + console::debug!("on input started", sys::chrome().omnibox()); + + listener() + }); + + OnInputStartedListener(EventListener::raw_new(&self.0, listener)) + } +} + +pub struct OnInputStartedListener<'a>(EventListener<'a, dyn FnMut()>); + +impl OnInputStartedListener<'_> { + pub fn forget(self) { + self.0.forget() + } +} diff --git a/src/tabs/mod.rs b/src/tabs/mod.rs index 442b52e..ffd34ec 100644 --- a/src/tabs/mod.rs +++ b/src/tabs/mod.rs @@ -30,6 +30,12 @@ impl From for TabId { } } +impl From for i32 { + fn from(id: TabId) -> Self { + id.0 + } +} + mod on_activated; mod on_attached; mod on_created; @@ -93,3 +99,38 @@ pub struct CreateProperties<'a> { pub active: bool, pub url: &'a str, } + +/// Modifies the properties of a tab. +/// +/// Properties that are not specified in updateProperties are not modified. +/// +/// +pub async fn update(tab_id: Option, props: UpdateProperties<'_>) -> Result { + let js_props = js_from_serde(&props)?; + let result = tabs() + .update(tab_id.map(|id| id.0), object_from_js(&js_props)?) + .await; + serde_from_js_result(result) +} + +/// Information necessary to open a new tab. +#[derive(Debug, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UpdateProperties<'a> { + /// Whether the tab should be active. + pub active: Option, + /// Whether the tab should be discarded automatically by the browser when resources are low. + pub auto_discardable: Option, + /// Adds or removes the tab from the current selection. + pub highlighted: Option, + /// Whether the tab should be muted. + pub muted: Option, + /// The ID of the tab that opened this tab. + pub opener_tab_id: Option, + /// Whether the tab should be pinned. + pub pinned: Option, + /// Whether the tab should be selected. + pub selected: Option, + /// A URL to navigate the tab to. + pub url: Option<&'a str>, +} diff --git a/src/tabs/query_details.rs b/src/tabs/query_details.rs index 219fdbc..a3c3df8 100644 --- a/src/tabs/query_details.rs +++ b/src/tabs/query_details.rs @@ -1,7 +1,7 @@ use super::{prelude::*, Status, WindowType}; /// -#[derive(Debug, Serialize)] +#[derive(Default, Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct QueryDetails<'a> { pub active: Option, diff --git a/tests/contextual_identities.rs b/tests/contextual_identities.rs index 98cc10c..4930736 100644 --- a/tests/contextual_identities.rs +++ b/tests/contextual_identities.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "firefox")] + use web_extensions::contextual_identities::*; mod util;