Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dialog): add HTTP basic authentication #263

Merged
merged 3 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions resources/components/prompt/http_basic_auth.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<html>
<head>
<link
rel="stylesheet"
type="text/css"
href="verso://resources/components/prompt/prompt.css"
/>
<style>
.field {
padding-bottom: 4px;
}
</style>
</head>
<body>
<div class="dialog">
<div class="msg">
<p id="msg"></p>
<div class="field">
<input
type="text"
id="username"
placeholder="Username"
aria-label="username"
/>
</div>
<div class="field">
<input
type="password"
id="password"
placeholder="Password"
aria-label="password"
/>
</div>
</div>
<div class="btn-group">
<button onclick="sendToVersoAndClose('cancel')">Cancel</button>
<button onclick="sendToVersoAndClose('signin')">Sign In</button>
</div>
</div>
</body>
<script>
const msgEl = document.getElementById('msg');
const usernameEl = document.getElementById('username');
const passwordEl = document.getElementById('password');

const params = URL.parse(window.location.href).searchParams;

// Set dialog message
msgEl.textContent = 'Sign in';
// TODO: add target host and check if it's secure
// const host = params.get('host');
// if (host) {
// msgEl.textContent = `Sign in to ${host}`;
// } else {
// msgEl.textContent = 'Sign in';
// }

function sendToVersoAndClose(action) {
const auth = {
username: null,
password: null,
};

if (action === 'signin') {
auth.username = usernameEl.value;
auth.password = passwordEl.value;
}

// Use as an IPC between Verso and WebView
window.alert(
JSON.stringify({
action,
auth,
})
);
window.close();
}
</script>
</html>
51 changes: 50 additions & 1 deletion src/webview/prompt.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use base::id::WebViewId;
use compositing_traits::ConstellationMsg;
use crossbeam_channel::Sender;
use embedder_traits::{PermissionRequest, PromptResult};
use embedder_traits::{PermissionRequest, PromptCredentialsInput, PromptResult};
use ipc_channel::ipc::IpcSender;
use serde::{Deserialize, Serialize};
use servo_url::ServoUrl;
Expand All @@ -28,6 +28,10 @@ enum PromptType {
///
/// <https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt>
Input(String, Option<String>),
/// HTTP basic authentication dialog (username / password)
///
/// <https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#basic>
HttpBasicAuth,
}

/// Prompt Sender, used to send prompt result back to the caller
Expand All @@ -41,6 +45,8 @@ pub enum PromptSender {
InputSender(IpcSender<Option<String>>),
/// Yes/No Permission sender
PermissionSender(IpcSender<PermissionRequest>),
/// HTTP basic authentication sender
HttpBasicAuthSender(IpcSender<PromptCredentialsInput>),
}

/// Prompt input result send from prompt dialog to backend
Expand All @@ -60,6 +66,21 @@ pub struct PromptInputResult {
pub value: String,
}

/// Prompt input result send from prompt dialog to backend
/// - action: "signin" / "cancel"
/// - auth: { username: string, password: string }
///
/// Behavior:
/// - **signin**: return { username: string, password: string }
/// - **cancel**: return { username: null, password: null }
#[derive(Debug, Serialize, Deserialize)]
pub struct HttpBasicAuthInputResult {
/// User action: "signin" / "cancel"
pub action: String,
/// User input value
pub auth: PromptCredentialsInput,
}

/// Prompt Dialog
#[derive(Clone)]
pub struct PromptDialog {
Expand Down Expand Up @@ -196,6 +217,31 @@ impl PromptDialog {
self.show(sender, rect, PromptType::Input(message, default_value));
}

/// Show input prompt
///
/// After you call `input(..)`, you must call `sender()` to get prompt sender,
/// then send user interaction result back to caller.
///
/// ## Example
///
/// ```rust
/// if let Some(PromptSender::HttpBasicAuthSender(sender)) = prompt.sender() {
/// let _ = sender.send(PromptCredentialsInput {
/// username: "user".to_string(),
/// password: "password".to_string(),
/// });
/// }
/// ```
pub fn http_basic_auth(
&mut self,
sender: &Sender<ConstellationMsg>,
rect: DeviceIntRect,
prompt_sender: IpcSender<PromptCredentialsInput>,
) {
self.prompt_sender = Some(PromptSender::HttpBasicAuthSender(prompt_sender));
self.show(sender, rect, PromptType::HttpBasicAuth);
}

fn show(
&mut self,
sender: &Sender<ConstellationMsg>,
Expand Down Expand Up @@ -227,6 +273,9 @@ impl PromptDialog {
}
url
}
PromptType::HttpBasicAuth => {
format!("verso://resources/components/prompt/http_basic_auth.html")
}
};
ServoUrl::parse(&url).unwrap()
}
Expand Down
39 changes: 33 additions & 6 deletions src/webview/webview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ use base::id::{BrowsingContextId, WebViewId};
use compositing_traits::ConstellationMsg;
use crossbeam_channel::Sender;
use embedder_traits::{
CompositorEventVariant, EmbedderMsg, PermissionPrompt, PermissionRequest, PromptDefinition,
PromptResult,
CompositorEventVariant, EmbedderMsg, PermissionPrompt, PermissionRequest,
PromptCredentialsInput, PromptDefinition, PromptResult,
};
use ipc_channel::ipc;
use script_traits::{
Expand All @@ -19,7 +19,7 @@ use crate::{
compositor::IOCompositor,
tab::{TabActivateRequest, TabCloseRequest, TabCreateResponse},
verso::send_to_constellation,
webview::prompt::{PromptDialog, PromptInputResult, PromptSender},
webview::prompt::{HttpBasicAuthInputResult, PromptDialog, PromptInputResult, PromptSender},
window::Window,
};

Expand Down Expand Up @@ -73,7 +73,7 @@ impl Window {
message: EmbedderMsg,
sender: &Sender<ConstellationMsg>,
clipboard: Option<&mut Clipboard>,
_compositor: &mut IOCompositor,
compositor: &mut IOCompositor,
) {
log::trace!("Verso WebView {webview_id:?} is handling Embedder message: {message:?}",);
match message {
Expand Down Expand Up @@ -156,6 +156,8 @@ impl Window {
}
EmbedderMsg::HistoryChanged(list, index) => {
self.close_prompt_dialog(webview_id);
compositor.send_root_pipeline_display_list(self);

self.tab_manager
.set_history(webview_id, list.clone(), index);
let url = list.get(index).unwrap();
Expand Down Expand Up @@ -204,8 +206,8 @@ impl Window {
PromptDefinition::Input(message, default_value, prompt_sender) => {
prompt.input(sender, rect, message, Some(default_value), prompt_sender);
}
PromptDefinition::Credentials(_) => {
todo!();
PromptDefinition::Credentials(prompt_sender) => {
prompt.http_basic_auth(sender, rect, prompt_sender);
}
}

Expand Down Expand Up @@ -558,6 +560,31 @@ impl Window {
};
let _ = sender.send(result);
}
PromptSender::HttpBasicAuthSender(sender) => {
let canceled_auth = PromptCredentialsInput {
username: None,
password: None,
};

if let Ok(HttpBasicAuthInputResult { action, auth }) =
serde_json::from_str::<HttpBasicAuthInputResult>(&msg)
{
match action.as_str() {
"signin" => {
let _ = sender.send(auth);
}
"cancel" => {
let _ = sender.send(canceled_auth);
}
_ => {
let _ = sender.send(canceled_auth);
}
};
} else {
log::error!("prompt result message invalid: {msg}");
let _ = sender.send(canceled_auth);
}
}
}

let _ = ignored_prompt_sender.send(());
Expand Down
10 changes: 9 additions & 1 deletion src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ use std::cell::Cell;
use base::id::WebViewId;
use compositing_traits::ConstellationMsg;
use crossbeam_channel::Sender;
use embedder_traits::{Cursor, EmbedderMsg, PermissionRequest, PromptResult};
use embedder_traits::{
Cursor, EmbedderMsg, PermissionRequest, PromptCredentialsInput, PromptResult,
};
use euclid::{Point2D, Size2D};
use glutin::{
config::{ConfigTemplateBuilder, GlConfig},
Expand Down Expand Up @@ -904,6 +906,12 @@ impl Window {
PromptSender::PermissionSender(sender) => {
let _ = sender.send(PermissionRequest::Denied);
}
PromptSender::HttpBasicAuthSender(sender) => {
let _ = sender.send(PromptCredentialsInput {
username: None,
password: None,
});
}
}
}
}
Expand Down
Loading