//! 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, pub adblock_enabled: Cell, pub popups: RefCell>>, } impl PopupHub { pub fn new(proxy: EventLoopProxy, 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) { 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::<>k::gio::Cancellable>, |_| {}); alive.push(view.downgrade()); } } *hub.popups.borrow_mut() = alive; } fn handle_create( view: &webkit2gtk::WebView, action: &webkit2gtk::NavigationAction, hub: &Rc, ) -> Option { 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, ) -> Option { 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); }