RustyWolf/src/webkit/popup.rs

164 lines
5.4 KiB
Rust

//! Real popup windows for Linux/WebKitGTK content views.
//!
//! wry does not connect WebKitGTK's `create` signal, so new-window requests
//! would never produce a window with a live `window.opener`. That breaks
//! popup-based logins ("Sign in with Google" and friends). Here we attach the
//! signal to the raw `WebKitWebView` and build proper related-view popups.
use super::{NavKind, NewWindowDecision, UserEvent, decide_new_window};
use gtk::prelude::*;
use std::cell::{Cell, RefCell};
use std::rc::Rc;
use tao::event_loop::EventLoopProxy;
use webkit2gtk::{URIRequestExt, UserContentManagerExt, WebViewExt};
pub struct PopupHub {
pub proxy: EventLoopProxy<UserEvent>,
pub adblock_enabled: Cell<bool>,
pub popups: RefCell<Vec<gtk::glib::WeakRef<webkit2gtk::WebView>>>,
}
impl PopupHub {
pub fn new(proxy: EventLoopProxy<UserEvent>, adblock_enabled: bool) -> Self {
Self {
proxy,
adblock_enabled: Cell::new(adblock_enabled),
popups: RefCell::new(Vec::new()),
}
}
}
/// Hook the `create` signal of a content webview.
pub fn attach(view: &webkit2gtk::WebView, hub: &Rc<PopupHub>) {
let hub = hub.clone();
view.connect_create(move |view, action| handle_create(view, action, &hub));
}
/// Push the adblock toggle into popup pages that are already open.
pub fn set_popup_adblock(hub: &PopupHub, enabled: bool) {
hub.adblock_enabled.set(enabled);
let script = format!(
"window.__rustywolfSetAdblockEnabled && window.__rustywolfSetAdblockEnabled({enabled});"
);
let mut alive = Vec::new();
for weak in hub.popups.borrow_mut().drain(..) {
if let Some(view) = weak.upgrade() {
view.evaluate_javascript(&script, None, None, None::<&gtk::gio::Cancellable>, |_| {});
alive.push(view.downgrade());
}
}
*hub.popups.borrow_mut() = alive;
}
fn handle_create(
view: &webkit2gtk::WebView,
action: &webkit2gtk::NavigationAction,
hub: &Rc<PopupHub>,
) -> Option<gtk::Widget> {
let url = action
.request()
.and_then(|request| request.uri())
.map(|uri| uri.to_string())
.unwrap_or_default();
let user_gesture = action.is_user_gesture();
let nav = match action.navigation_type() {
webkit2gtk::NavigationType::LinkClicked => NavKind::Link,
webkit2gtk::NavigationType::FormSubmitted | webkit2gtk::NavigationType::FormResubmitted => {
NavKind::Form
}
_ => NavKind::Other,
};
match decide_new_window(&url, nav, user_gesture) {
NewWindowDecision::Deny => None,
NewWindowDecision::Tab => {
let _ = hub.proxy.send_event(UserEvent::NewWindow { url });
None
}
NewWindowDecision::Ask => {
let _ = hub.proxy.send_event(UserEvent::NewWindowAsk { url });
None
}
NewWindowDecision::Popup => create_popup(view, &url, hub),
}
}
fn create_popup(
opener: &webkit2gtk::WebView,
url: &str,
hub: &Rc<PopupHub>,
) -> Option<gtk::Widget> {
let view = webkit2gtk::WebView::with_related_view(opener);
if let Some(settings) = WebViewExt::settings(opener) {
view.set_settings(&settings);
}
install_scripts(&view, hub.adblock_enabled.get());
let title = url::Url::parse(url)
.ok()
.and_then(|parsed| parsed.host_str().map(ToString::to_string))
.unwrap_or_else(|| "RustyWolf".to_string());
let window = gtk::Window::new(gtk::WindowType::Toplevel);
window.set_title(&title);
window.set_default_size(960, 700);
window.set_position(gtk::WindowPosition::Center);
window.add(&view);
// Popups can spawn popups (SSO hand-offs often do).
let nested_hub = hub.clone();
view.connect_create(move |view, action| handle_create(view, action, &nested_hub));
// Keep the window title in sync with the page.
let window_weak = window.downgrade();
view.connect_title_notify(move |view| {
if let Some(window) = window_weak.upgrade()
&& let Some(title) = view.title()
{
window.set_title(&title);
}
});
// Minimal popup shortcuts: Ctrl+W closes, F5 / Ctrl+R reload.
let view_weak = view.downgrade();
window.connect_key_press_event(move |window, event| {
use gtk::gdk::keys::constants as key;
let ctrl = event.state().contains(gtk::gdk::ModifierType::CONTROL_MASK);
let keyval = event.keyval();
if ctrl && keyval == key::w {
window.close();
return gtk::glib::Propagation::Stop;
}
if keyval == key::F5 || (ctrl && keyval == key::r) {
if let Some(view) = view_weak.upgrade() {
view.reload();
}
return gtk::glib::Propagation::Stop;
}
gtk::glib::Propagation::Proceed
});
window.show_all();
hub.popups.borrow_mut().push(view.downgrade());
Some(window.upcast())
}
fn install_scripts(view: &webkit2gtk::WebView, adblock_enabled: bool) {
let Some(manager) = view.user_content_manager() else {
return;
};
let source = format!(
"{}\nwindow.__rustywolfAdblockInitEnabled = {adblock_enabled};\n{}",
super::js::PRIVACY_JS,
super::js::ADBLOCK_JS
);
let script = webkit2gtk::UserScript::new(
&source,
webkit2gtk::UserContentInjectedFrames::AllFrames,
webkit2gtk::UserScriptInjectionTime::Start,
&[],
&[],
);
manager.add_script(&script);
}