164 lines
5.4 KiB
Rust
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::<>k::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);
|
|
}
|