mirror of
https://github.com/servo/servo.git
synced 2025-07-23 15:23:42 +01:00
script: Allow opening links in a new WebView
(#35017)
This changes starts tracking the keyboard modifier state in the `Constellation` and forwards it with every input event. The state is used to modify the target of link click so when the platform-dependent alternate action key is enabled, the target is overriden to "_blank". In addition, specification step numbers and text is updated. Signed-off-by: webbeef <me@webbeef.org> Signed-off-by: Martin Robinson <mrobinson@igalia.com> Co-authored-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
parent
8b8b447ef0
commit
90161c1c91
5 changed files with 114 additions and 25 deletions
|
@ -131,6 +131,7 @@ use ipc_channel::Error as IpcError;
|
||||||
use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
|
use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
|
||||||
use ipc_channel::router::ROUTER;
|
use ipc_channel::router::ROUTER;
|
||||||
use keyboard_types::webdriver::Event as WebDriverInputEvent;
|
use keyboard_types::webdriver::Event as WebDriverInputEvent;
|
||||||
|
use keyboard_types::{Key, KeyState, KeyboardEvent, Modifiers};
|
||||||
use log::{debug, error, info, trace, warn};
|
use log::{debug, error, info, trace, warn};
|
||||||
use media::WindowGLContext;
|
use media::WindowGLContext;
|
||||||
use net_traits::pub_domains::reg_host;
|
use net_traits::pub_domains::reg_host;
|
||||||
|
@ -454,6 +455,9 @@ pub struct Constellation<STF, SWF> {
|
||||||
/// currently being pressed.
|
/// currently being pressed.
|
||||||
pressed_mouse_buttons: u16,
|
pressed_mouse_buttons: u16,
|
||||||
|
|
||||||
|
/// The currently activated keyboard modifiers.
|
||||||
|
active_keyboard_modifiers: Modifiers,
|
||||||
|
|
||||||
/// If True, exits on thread failure instead of displaying about:failure
|
/// If True, exits on thread failure instead of displaying about:failure
|
||||||
hard_fail: bool,
|
hard_fail: bool,
|
||||||
|
|
||||||
|
@ -730,6 +734,7 @@ where
|
||||||
canvas_ipc_sender,
|
canvas_ipc_sender,
|
||||||
pending_approval_navigations: HashMap::new(),
|
pending_approval_navigations: HashMap::new(),
|
||||||
pressed_mouse_buttons: 0,
|
pressed_mouse_buttons: 0,
|
||||||
|
active_keyboard_modifiers: Modifiers::empty(),
|
||||||
hard_fail,
|
hard_fail,
|
||||||
active_media_session: None,
|
active_media_session: None,
|
||||||
user_agent: state.user_agent,
|
user_agent: state.user_agent,
|
||||||
|
@ -2830,6 +2835,38 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_active_keybord_modifiers(&mut self, event: &KeyboardEvent) {
|
||||||
|
self.active_keyboard_modifiers = event.modifiers;
|
||||||
|
|
||||||
|
// `KeyboardEvent::modifiers` contains the pre-existing modifiers before this key was
|
||||||
|
// either pressed or released, but `active_keyboard_modifiers` should track the subsequent
|
||||||
|
// state. If this event will update that state, we need to ensure that we are tracking what
|
||||||
|
// the event changes.
|
||||||
|
let modified_modifier = match event.key {
|
||||||
|
Key::Alt => Modifiers::ALT,
|
||||||
|
Key::AltGraph => Modifiers::ALT_GRAPH,
|
||||||
|
Key::CapsLock => Modifiers::CAPS_LOCK,
|
||||||
|
Key::Control => Modifiers::CONTROL,
|
||||||
|
Key::Fn => Modifiers::FN,
|
||||||
|
Key::FnLock => Modifiers::FN_LOCK,
|
||||||
|
Key::Meta => Modifiers::META,
|
||||||
|
Key::NumLock => Modifiers::NUM_LOCK,
|
||||||
|
Key::ScrollLock => Modifiers::SCROLL_LOCK,
|
||||||
|
Key::Shift => Modifiers::SHIFT,
|
||||||
|
Key::Symbol => Modifiers::SYMBOL,
|
||||||
|
Key::SymbolLock => Modifiers::SYMBOL_LOCK,
|
||||||
|
Key::Hyper => Modifiers::HYPER,
|
||||||
|
// The web doesn't make a distinction between these keys (there is only
|
||||||
|
// "meta") so map "super" to "meta".
|
||||||
|
Key::Super => Modifiers::META,
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
match event.state {
|
||||||
|
KeyState::Down => self.active_keyboard_modifiers.insert(modified_modifier),
|
||||||
|
KeyState::Up => self.active_keyboard_modifiers.remove(modified_modifier),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn forward_input_event(
|
fn forward_input_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
webview_id: WebViewId,
|
webview_id: WebViewId,
|
||||||
|
@ -2840,9 +2877,14 @@ where
|
||||||
self.update_pressed_mouse_buttons(event);
|
self.update_pressed_mouse_buttons(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The constellation tracks the state of pressed mouse buttons and updates the event
|
if let InputEvent::Keyboard(event) = &event {
|
||||||
// here to reflect the current state.
|
self.update_active_keybord_modifiers(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The constellation tracks the state of pressed mouse buttons and keyboard
|
||||||
|
// modifiers and updates the event here to reflect the current state.
|
||||||
let pressed_mouse_buttons = self.pressed_mouse_buttons;
|
let pressed_mouse_buttons = self.pressed_mouse_buttons;
|
||||||
|
let active_keyboard_modifiers = self.active_keyboard_modifiers;
|
||||||
|
|
||||||
// TODO: Click should be handled internally in the `Document`.
|
// TODO: Click should be handled internally in the `Document`.
|
||||||
if let InputEvent::MouseButton(event) = &event {
|
if let InputEvent::MouseButton(event) = &event {
|
||||||
|
@ -2885,6 +2927,7 @@ where
|
||||||
let event = ConstellationInputEvent {
|
let event = ConstellationInputEvent {
|
||||||
hit_test_result,
|
hit_test_result,
|
||||||
pressed_mouse_buttons,
|
pressed_mouse_buttons,
|
||||||
|
active_keyboard_modifiers,
|
||||||
event,
|
event,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4392,11 +4435,13 @@ where
|
||||||
let event = match event {
|
let event = match event {
|
||||||
WebDriverInputEvent::Keyboard(event) => ConstellationInputEvent {
|
WebDriverInputEvent::Keyboard(event) => ConstellationInputEvent {
|
||||||
pressed_mouse_buttons: self.pressed_mouse_buttons,
|
pressed_mouse_buttons: self.pressed_mouse_buttons,
|
||||||
|
active_keyboard_modifiers: event.modifiers,
|
||||||
hit_test_result: None,
|
hit_test_result: None,
|
||||||
event: InputEvent::Keyboard(event),
|
event: InputEvent::Keyboard(event),
|
||||||
},
|
},
|
||||||
WebDriverInputEvent::Composition(event) => ConstellationInputEvent {
|
WebDriverInputEvent::Composition(event) => ConstellationInputEvent {
|
||||||
pressed_mouse_buttons: self.pressed_mouse_buttons,
|
pressed_mouse_buttons: self.pressed_mouse_buttons,
|
||||||
|
active_keyboard_modifiers: self.active_keyboard_modifiers,
|
||||||
hit_test_result: None,
|
hit_test_result: None,
|
||||||
event: InputEvent::Ime(ImeEvent::Composition(event)),
|
event: InputEvent::Ime(ImeEvent::Composition(event)),
|
||||||
},
|
},
|
||||||
|
@ -4422,6 +4467,7 @@ where
|
||||||
pipeline_id,
|
pipeline_id,
|
||||||
ConstellationInputEvent {
|
ConstellationInputEvent {
|
||||||
pressed_mouse_buttons: self.pressed_mouse_buttons,
|
pressed_mouse_buttons: self.pressed_mouse_buttons,
|
||||||
|
active_keyboard_modifiers: event.modifiers,
|
||||||
hit_test_result: None,
|
hit_test_result: None,
|
||||||
event: InputEvent::Keyboard(event),
|
event: InputEvent::Keyboard(event),
|
||||||
},
|
},
|
||||||
|
|
|
@ -36,7 +36,7 @@ use html5ever::{LocalName, Namespace, QualName, local_name, namespace_url, ns};
|
||||||
use hyper_serde::Serde;
|
use hyper_serde::Serde;
|
||||||
use ipc_channel::ipc;
|
use ipc_channel::ipc;
|
||||||
use js::rust::{HandleObject, HandleValue};
|
use js::rust::{HandleObject, HandleValue};
|
||||||
use keyboard_types::{Code, Key, KeyState};
|
use keyboard_types::{Code, Key, KeyState, Modifiers};
|
||||||
use metrics::{InteractiveFlag, InteractiveWindow, ProgressiveWebMetrics};
|
use metrics::{InteractiveFlag, InteractiveWindow, ProgressiveWebMetrics};
|
||||||
use mime::{self, Mime};
|
use mime::{self, Mime};
|
||||||
use net_traits::CookieSource::NonHTTP;
|
use net_traits::CookieSource::NonHTTP;
|
||||||
|
@ -531,6 +531,9 @@ pub(crate) struct Document {
|
||||||
/// The lifetime of an intersection observer is specified at
|
/// The lifetime of an intersection observer is specified at
|
||||||
/// <https://github.com/w3c/IntersectionObserver/issues/525>.
|
/// <https://github.com/w3c/IntersectionObserver/issues/525>.
|
||||||
intersection_observers: DomRefCell<Vec<Dom<IntersectionObserver>>>,
|
intersection_observers: DomRefCell<Vec<Dom<IntersectionObserver>>>,
|
||||||
|
/// The active keyboard modifiers for the WebView. This is updated when receiving any input event.
|
||||||
|
#[no_trace]
|
||||||
|
active_keyboard_modifiers: Cell<Modifiers>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
|
@ -3868,6 +3871,7 @@ impl Document {
|
||||||
inherited_insecure_requests_policy: Cell::new(inherited_insecure_requests_policy),
|
inherited_insecure_requests_policy: Cell::new(inherited_insecure_requests_policy),
|
||||||
intersection_observer_task_queued: Cell::new(false),
|
intersection_observer_task_queued: Cell::new(false),
|
||||||
intersection_observers: Default::default(),
|
intersection_observers: Default::default(),
|
||||||
|
active_keyboard_modifiers: Cell::new(Modifiers::empty()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3888,6 +3892,26 @@ impl Document {
|
||||||
.unwrap_or(InsecureRequestsPolicy::DoNotUpgrade)
|
.unwrap_or(InsecureRequestsPolicy::DoNotUpgrade)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Update the active keyboard modifiers for this [`Document`] while handling events.
|
||||||
|
pub(crate) fn update_active_keyboard_modifiers(&self, modifiers: Modifiers) {
|
||||||
|
self.active_keyboard_modifiers.set(modifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn alternate_action_keyboard_modifier_active(&self) -> bool {
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
self.active_keyboard_modifiers
|
||||||
|
.get()
|
||||||
|
.contains(Modifiers::META)
|
||||||
|
}
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
{
|
||||||
|
self.active_keyboard_modifiers
|
||||||
|
.get()
|
||||||
|
.contains(Modifiers::CONTROL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Note a pending compositor event, to be processed at the next `update_the_rendering` task.
|
/// Note a pending compositor event, to be processed at the next `update_the_rendering` task.
|
||||||
pub(crate) fn note_pending_input_event(&self, event: ConstellationInputEvent) {
|
pub(crate) fn note_pending_input_event(&self, event: ConstellationInputEvent) {
|
||||||
let mut pending_compositor_events = self.pending_input_events.borrow_mut();
|
let mut pending_compositor_events = self.pending_input_events.borrow_mut();
|
||||||
|
|
|
@ -342,30 +342,45 @@ pub(crate) fn follow_hyperlink(
|
||||||
relations: LinkRelations,
|
relations: LinkRelations,
|
||||||
hyperlink_suffix: Option<String>,
|
hyperlink_suffix: Option<String>,
|
||||||
) {
|
) {
|
||||||
// Step 1. If subject cannot navigate, then return.
|
// Step 1: If subject cannot navigate, then return.
|
||||||
if subject.cannot_navigate() {
|
if subject.cannot_navigate() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Step 2, done in Step 7.
|
|
||||||
|
|
||||||
|
// Step 2: Let targetAttributeValue be the empty string.
|
||||||
|
// This is done below.
|
||||||
|
|
||||||
|
// Step 3: If subject is an a or area element, then set targetAttributeValue to the
|
||||||
|
// result of getting an element's target given subject.
|
||||||
|
//
|
||||||
|
// Also allow the user to open links in a new WebView by pressing either the meta or
|
||||||
|
// control key (depending on the platform).
|
||||||
let document = subject.owner_document();
|
let document = subject.owner_document();
|
||||||
let window = document.window();
|
|
||||||
|
|
||||||
// Step 3: source browsing context.
|
|
||||||
let source = document.browsing_context().unwrap();
|
|
||||||
|
|
||||||
// Step 4-5: target attribute.
|
|
||||||
let target_attribute_value =
|
let target_attribute_value =
|
||||||
if subject.is::<HTMLAreaElement>() || subject.is::<HTMLAnchorElement>() {
|
if subject.is::<HTMLAreaElement>() || subject.is::<HTMLAnchorElement>() {
|
||||||
|
if document.alternate_action_keyboard_modifier_active() {
|
||||||
|
Some("_blank".into())
|
||||||
|
} else {
|
||||||
get_element_target(subject)
|
get_element_target(subject)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
// Step 6.
|
// Step 4: Let urlRecord be the result of encoding-parsing a URL given subject's href
|
||||||
|
// attribute value, relative to subject's node document.
|
||||||
|
// Step 5: If urlRecord is failure, then return.
|
||||||
|
// TODO: Implement this.
|
||||||
|
|
||||||
|
// Step 6: Let noopener be the result of getting an element's noopener with subject,
|
||||||
|
// urlRecord, and targetAttributeValue.
|
||||||
let noopener = relations.get_element_noopener(target_attribute_value.as_ref());
|
let noopener = relations.get_element_noopener(target_attribute_value.as_ref());
|
||||||
|
|
||||||
// Step 7.
|
// Step 7: Let targetNavigable be the first return value of applying the rules for
|
||||||
|
// choosing a navigable given targetAttributeValue, subject's node navigable, and
|
||||||
|
// noopener.
|
||||||
|
let window = document.window();
|
||||||
|
let source = document.browsing_context().unwrap();
|
||||||
let (maybe_chosen, history_handling) = match target_attribute_value {
|
let (maybe_chosen, history_handling) = match target_attribute_value {
|
||||||
Some(name) => {
|
Some(name) => {
|
||||||
let (maybe_chosen, new) = source.choose_browsing_context(name, noopener);
|
let (maybe_chosen, new) = source.choose_browsing_context(name, noopener);
|
||||||
|
@ -379,7 +394,7 @@ pub(crate) fn follow_hyperlink(
|
||||||
None => (Some(window.window_proxy()), NavigationHistoryBehavior::Push),
|
None => (Some(window.window_proxy()), NavigationHistoryBehavior::Push),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Step 8.
|
// Step 8: If targetNavigable is null, then return.
|
||||||
let chosen = match maybe_chosen {
|
let chosen = match maybe_chosen {
|
||||||
Some(proxy) => proxy,
|
Some(proxy) => proxy,
|
||||||
None => return,
|
None => return,
|
||||||
|
@ -387,17 +402,13 @@ pub(crate) fn follow_hyperlink(
|
||||||
|
|
||||||
if let Some(target_document) = chosen.document() {
|
if let Some(target_document) = chosen.document() {
|
||||||
let target_window = target_document.window();
|
let target_window = target_document.window();
|
||||||
// Step 9, dis-owning target's opener, if necessary
|
// Step 9: Let urlString be the result of applying the URL serializer to urlRecord.
|
||||||
// will have been done as part of Step 7 above
|
// TODO: Implement this.
|
||||||
// in choose_browsing_context/create_auxiliary_browsing_context.
|
|
||||||
|
|
||||||
// Step 10, 11. TODO: if parsing the URL failed, navigate to error page.
|
|
||||||
|
|
||||||
let attribute = subject.get_attribute(&ns!(), &local_name!("href")).unwrap();
|
let attribute = subject.get_attribute(&ns!(), &local_name!("href")).unwrap();
|
||||||
let mut href = attribute.Value();
|
let mut href = attribute.Value();
|
||||||
|
|
||||||
// Step 11: append a hyperlink suffix.
|
// Step 10: If hyperlinkSuffix is non-null, then append it to urlString.
|
||||||
// https://www.w3.org/Bugs/Public/show_bug.cgi?id=28925
|
|
||||||
if let Some(suffix) = hyperlink_suffix {
|
if let Some(suffix) = hyperlink_suffix {
|
||||||
href.push_str(&suffix);
|
href.push_str(&suffix);
|
||||||
}
|
}
|
||||||
|
@ -405,17 +416,20 @@ pub(crate) fn follow_hyperlink(
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Step 12.
|
// Step 11: Let referrerPolicy be the current state of subject's referrerpolicy content attribute.
|
||||||
let referrer_policy = referrer_policy_for_element(subject);
|
let referrer_policy = referrer_policy_for_element(subject);
|
||||||
|
|
||||||
// Step 13
|
// Step 12: If subject's link types includes the noreferrer keyword, then set
|
||||||
|
// referrerPolicy to "no-referrer".
|
||||||
let referrer = if relations.contains(LinkRelations::NO_REFERRER) {
|
let referrer = if relations.contains(LinkRelations::NO_REFERRER) {
|
||||||
Referrer::NoReferrer
|
Referrer::NoReferrer
|
||||||
} else {
|
} else {
|
||||||
target_window.as_global_scope().get_referrer()
|
target_window.as_global_scope().get_referrer()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Step 14
|
// Step 13: Navigate targetNavigable to urlString using subject's node document,
|
||||||
|
// with referrerPolicy set to referrerPolicy, userInvolvement set to
|
||||||
|
// userInvolvement, and sourceElement set to subject.
|
||||||
let pipeline_id = target_window.as_global_scope().pipeline_id();
|
let pipeline_id = target_window.as_global_scope().pipeline_id();
|
||||||
let secure = target_window.as_global_scope().is_secure_context();
|
let secure = target_window.as_global_scope().is_secure_context();
|
||||||
let load_data = LoadData::new(
|
let load_data = LoadData::new(
|
||||||
|
|
|
@ -1069,6 +1069,8 @@ impl ScriptThread {
|
||||||
let window = document.window();
|
let window = document.window();
|
||||||
let _realm = enter_realm(document.window());
|
let _realm = enter_realm(document.window());
|
||||||
for event in document.take_pending_input_events().into_iter() {
|
for event in document.take_pending_input_events().into_iter() {
|
||||||
|
document.update_active_keyboard_modifiers(event.active_keyboard_modifiers);
|
||||||
|
|
||||||
match event.event {
|
match event.event {
|
||||||
InputEvent::MouseButton(mouse_button_event) => {
|
InputEvent::MouseButton(mouse_button_event) => {
|
||||||
document.handle_mouse_button_event(
|
document.handle_mouse_button_event(
|
||||||
|
|
|
@ -38,6 +38,7 @@ use euclid::{Rect, Scale, Size2D, UnknownUnit};
|
||||||
use http::{HeaderMap, Method};
|
use http::{HeaderMap, Method};
|
||||||
use ipc_channel::Error as IpcError;
|
use ipc_channel::Error as IpcError;
|
||||||
use ipc_channel::ipc::{IpcReceiver, IpcSender};
|
use ipc_channel::ipc::{IpcReceiver, IpcSender};
|
||||||
|
use keyboard_types::Modifiers;
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use malloc_size_of_derive::MallocSizeOf;
|
use malloc_size_of_derive::MallocSizeOf;
|
||||||
use media::WindowGLContext;
|
use media::WindowGLContext;
|
||||||
|
@ -401,6 +402,8 @@ pub struct ConstellationInputEvent {
|
||||||
/// The pressed mouse button state of the constellation when this input
|
/// The pressed mouse button state of the constellation when this input
|
||||||
/// event was triggered.
|
/// event was triggered.
|
||||||
pub pressed_mouse_buttons: u16,
|
pub pressed_mouse_buttons: u16,
|
||||||
|
/// The currently active keyboard modifiers.
|
||||||
|
pub active_keyboard_modifiers: Modifiers,
|
||||||
/// The [`InputEvent`] itself.
|
/// The [`InputEvent`] itself.
|
||||||
pub event: InputEvent,
|
pub event: InputEvent,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue