constellation: restructure navigation, remove sync comm

This commit is contained in:
Gregory Terzian 2018-11-06 18:43:21 +08:00
parent f3ca48206e
commit 483bf245df
8 changed files with 100 additions and 39 deletions

View file

@ -9,7 +9,7 @@ use euclid::TypedScale;
#[cfg(feature = "gl")] #[cfg(feature = "gl")]
use gleam::gl; use gleam::gl;
use keyboard_types::KeyboardEvent; use keyboard_types::KeyboardEvent;
use msg::constellation_msg::{TopLevelBrowsingContextId, TraversalDirection}; use msg::constellation_msg::{PipelineId, TopLevelBrowsingContextId, TraversalDirection};
use script_traits::{MouseButton, TouchEventType, TouchId}; use script_traits::{MouseButton, TouchEventType, TouchId};
use servo_geometry::DeviceIndependentPixel; use servo_geometry::DeviceIndependentPixel;
use servo_url::ServoUrl; use servo_url::ServoUrl;
@ -49,6 +49,8 @@ pub enum WindowEvent {
Refresh, Refresh,
/// Sent when the window is resized. /// Sent when the window is resized.
Resize, Resize,
/// Sent when a navigation request from script is allowed/refused.
AllowNavigationResponse(PipelineId, bool),
/// Sent when a new URL is to be loaded. /// Sent when a new URL is to be loaded.
LoadUrl(TopLevelBrowsingContextId, ServoUrl), LoadUrl(TopLevelBrowsingContextId, ServoUrl),
/// Sent when a mouse hit test is to be performed. /// Sent when a mouse hit test is to be performed.
@ -96,6 +98,7 @@ impl Debug for WindowEvent {
WindowEvent::Refresh => write!(f, "Refresh"), WindowEvent::Refresh => write!(f, "Refresh"),
WindowEvent::Resize => write!(f, "Resize"), WindowEvent::Resize => write!(f, "Resize"),
WindowEvent::Keyboard(..) => write!(f, "Keyboard"), WindowEvent::Keyboard(..) => write!(f, "Keyboard"),
WindowEvent::AllowNavigationResponse(..) => write!(f, "AllowNavigationResponse"),
WindowEvent::LoadUrl(..) => write!(f, "LoadUrl"), WindowEvent::LoadUrl(..) => write!(f, "LoadUrl"),
WindowEvent::MouseWindowEventClass(..) => write!(f, "Mouse"), WindowEvent::MouseWindowEventClass(..) => write!(f, "Mouse"),
WindowEvent::MouseWindowMoveEventClass(..) => write!(f, "MouseMove"), WindowEvent::MouseWindowMoveEventClass(..) => write!(f, "MouseMove"),

View file

@ -156,6 +156,7 @@ use servo_rand::{random, Rng, SeedableRng, ServoRng};
use servo_remutex::ReentrantMutex; use servo_remutex::ReentrantMutex;
use servo_url::{Host, ImmutableOrigin, ServoUrl}; use servo_url::{Host, ImmutableOrigin, ServoUrl};
use std::borrow::ToOwned; use std::borrow::ToOwned;
use std::collections::hash_map::Entry;
use std::collections::{HashMap, VecDeque}; use std::collections::{HashMap, VecDeque};
use std::marker::PhantomData; use std::marker::PhantomData;
use std::mem::replace; use std::mem::replace;
@ -168,6 +169,8 @@ use style_traits::viewport::ViewportConstraints;
use style_traits::CSSPixel; use style_traits::CSSPixel;
use webvr_traits::{WebVREvent, WebVRMsg}; use webvr_traits::{WebVREvent, WebVRMsg};
type PendingApprovalNavigations = HashMap<PipelineId, (LoadData, bool)>;
/// Servo supports tabs (referred to as browsers), so `Constellation` needs to /// Servo supports tabs (referred to as browsers), so `Constellation` needs to
/// store browser specific data for bookkeeping. /// store browser specific data for bookkeeping.
struct Browser { struct Browser {
@ -366,6 +369,9 @@ pub struct Constellation<Message, LTF, STF> {
/// A channel through which messages can be sent to the canvas paint thread. /// A channel through which messages can be sent to the canvas paint thread.
canvas_chan: IpcSender<CanvasMsg>, canvas_chan: IpcSender<CanvasMsg>,
/// Navigation requests from script awaiting approval from the embedder.
pending_approval_navigations: PendingApprovalNavigations,
} }
/// State needed to construct a constellation. /// State needed to construct a constellation.
@ -698,6 +704,7 @@ where
webgl_threads: state.webgl_threads, webgl_threads: state.webgl_threads,
webvr_chan: state.webvr_chan, webvr_chan: state.webvr_chan,
canvas_chan: CanvasPaintThread::start(), canvas_chan: CanvasPaintThread::start(),
pending_approval_navigations: HashMap::new(),
}; };
constellation.run(); constellation.run();
@ -1044,6 +1051,36 @@ where
FromCompositorMsg::Keyboard(key_event) => { FromCompositorMsg::Keyboard(key_event) => {
self.handle_key_msg(key_event); self.handle_key_msg(key_event);
}, },
// Perform a navigation previously requested by script, if approved by the embedder.
// If there is already a pending page (self.pending_changes), it will not be overridden;
// However, if the id is not encompassed by another change, it will be.
FromCompositorMsg::AllowNavigationResponse(pipeline_id, allowed) => {
let pending = self.pending_approval_navigations.remove(&pipeline_id);
let top_level_browsing_context_id = match self.pipelines.get(&pipeline_id) {
Some(pipeline) => pipeline.top_level_browsing_context_id,
None => return warn!("Attempted to navigate {} after closure.", pipeline_id),
};
match pending {
Some((load_data, replace)) => {
if allowed {
self.load_url(
top_level_browsing_context_id,
pipeline_id,
load_data,
replace,
);
}
},
None => {
return warn!(
"AllowNavigationReqsponse for unknow request: {:?}",
pipeline_id
)
},
};
},
// Load a new page from a typed url // Load a new page from a typed url
// If there is already a pending page (self.pending_changes), it will not be overridden; // If there is already a pending page (self.pending_changes), it will not be overridden;
// However, if the id is not encompassed by another change, it will be. // However, if the id is not encompassed by another change, it will be.
@ -1059,12 +1096,9 @@ where
) )
}, },
}; };
self.handle_load_url_msg( // Since this is a top-level load, initiated by the embedder, go straight to load_url,
top_level_browsing_context_id, // bypassing schedule_navigation.
pipeline_id, self.load_url(top_level_browsing_context_id, pipeline_id, load_data, false);
load_data,
false,
);
}, },
FromCompositorMsg::IsReadyToSaveImage(pipeline_states) => { FromCompositorMsg::IsReadyToSaveImage(pipeline_states) => {
let is_ready = self.handle_is_ready_to_save_image(pipeline_states); let is_ready = self.handle_is_ready_to_save_image(pipeline_states);
@ -1176,11 +1210,9 @@ where
FromScriptMsg::ChangeRunningAnimationsState(animation_state) => { FromScriptMsg::ChangeRunningAnimationsState(animation_state) => {
self.handle_change_running_animations_state(source_pipeline_id, animation_state) self.handle_change_running_animations_state(source_pipeline_id, animation_state)
}, },
// Load a new page from a mouse click // Ask the embedder for permission to load a new page.
// If there is already a pending page (self.pending_changes), it will not be overridden;
// However, if the id is not encompassed by another change, it will be.
FromScriptMsg::LoadUrl(load_data, replace) => { FromScriptMsg::LoadUrl(load_data, replace) => {
self.handle_load_url_msg(source_top_ctx_id, source_pipeline_id, load_data, replace); self.schedule_navigation(source_top_ctx_id, source_pipeline_id, load_data, replace);
}, },
FromScriptMsg::AbortLoadUrl => { FromScriptMsg::AbortLoadUrl => {
self.handle_abort_load_url_msg(source_pipeline_id); self.handle_abort_load_url_msg(source_pipeline_id);
@ -2115,14 +2147,33 @@ where
} }
} }
fn handle_load_url_msg( /// Schedule a navigation(via load_url).
/// 1: Ask the embedder for permission.
/// 2: Store the details of the navigation, pending approval from the embedder.
fn schedule_navigation(
&mut self, &mut self,
top_level_browsing_context_id: TopLevelBrowsingContextId, top_level_browsing_context_id: TopLevelBrowsingContextId,
source_id: PipelineId, source_id: PipelineId,
load_data: LoadData, load_data: LoadData,
replace: bool, replace: bool,
) { ) {
self.load_url(top_level_browsing_context_id, source_id, load_data, replace); match self.pending_approval_navigations.entry(source_id) {
Entry::Occupied(_) => {
return warn!(
"Pipeline {:?} tried to schedule a navigation while one is already pending.",
source_id
)
},
Entry::Vacant(entry) => {
let _ = entry.insert((load_data.clone(), replace));
},
};
// Allow the embedder to handle the url itself
let msg = (
Some(top_level_browsing_context_id),
EmbedderMsg::AllowNavigationRequest(source_id, load_data.url.clone()),
);
self.embedder_proxy.send(msg);
} }
fn load_url( fn load_url(
@ -2132,17 +2183,6 @@ where
load_data: LoadData, load_data: LoadData,
replace: bool, replace: bool,
) -> Option<PipelineId> { ) -> Option<PipelineId> {
// Allow the embedder to handle the url itself
let (chan, port) = ipc::channel().expect("Failed to create IPC channel!");
let msg = (
Some(top_level_browsing_context_id),
EmbedderMsg::AllowNavigation(load_data.url.clone(), chan),
);
self.embedder_proxy.send(msg);
if let Ok(false) = port.recv() {
return None;
}
debug!("Loading {} in pipeline {}.", load_data.url, source_id); debug!("Loading {} in pipeline {}.", load_data.url, source_id);
// If this load targets an iframe, its framing element may exist // If this load targets an iframe, its framing element may exist
// in a separate script thread than the framed document that initiated // in a separate script thread than the framed document that initiated

View file

@ -14,7 +14,7 @@ pub mod resources;
use crossbeam_channel::{Receiver, Sender}; use crossbeam_channel::{Receiver, Sender};
use ipc_channel::ipc::IpcSender; use ipc_channel::ipc::IpcSender;
use keyboard_types::KeyboardEvent; use keyboard_types::KeyboardEvent;
use msg::constellation_msg::{InputMethodType, TopLevelBrowsingContextId}; use msg::constellation_msg::{InputMethodType, PipelineId, TopLevelBrowsingContextId};
use servo_url::ServoUrl; use servo_url::ServoUrl;
use std::fmt::{Debug, Error, Formatter}; use std::fmt::{Debug, Error, Formatter};
use style_traits::cursor::CursorKind; use style_traits::cursor::CursorKind;
@ -79,8 +79,8 @@ pub enum EmbedderMsg {
ResizeTo(DeviceIntSize), ResizeTo(DeviceIntSize),
// Show an alert message. // Show an alert message.
Alert(String, IpcSender<()>), Alert(String, IpcSender<()>),
/// Wether or not to follow a link /// Wether or not to allow a pipeline to load a url.
AllowNavigation(ServoUrl, IpcSender<bool>), AllowNavigationRequest(PipelineId, ServoUrl),
/// Whether or not to allow script to open a new tab/browser /// Whether or not to allow script to open a new tab/browser
AllowOpeningBrowser(IpcSender<bool>), AllowOpeningBrowser(IpcSender<bool>),
/// A new browser was created by script /// A new browser was created by script
@ -128,7 +128,7 @@ impl Debug for EmbedderMsg {
EmbedderMsg::ResizeTo(..) => write!(f, "ResizeTo"), EmbedderMsg::ResizeTo(..) => write!(f, "ResizeTo"),
EmbedderMsg::Alert(..) => write!(f, "Alert"), EmbedderMsg::Alert(..) => write!(f, "Alert"),
EmbedderMsg::AllowUnload(..) => write!(f, "AllowUnload"), EmbedderMsg::AllowUnload(..) => write!(f, "AllowUnload"),
EmbedderMsg::AllowNavigation(..) => write!(f, "AllowNavigation"), EmbedderMsg::AllowNavigationRequest(..) => write!(f, "AllowNavigationRequest"),
EmbedderMsg::Keyboard(..) => write!(f, "Keyboard"), EmbedderMsg::Keyboard(..) => write!(f, "Keyboard"),
EmbedderMsg::SetCursor(..) => write!(f, "SetCursor"), EmbedderMsg::SetCursor(..) => write!(f, "SetCursor"),
EmbedderMsg::NewFavicon(..) => write!(f, "NewFavicon"), EmbedderMsg::NewFavicon(..) => write!(f, "NewFavicon"),

View file

@ -726,6 +726,8 @@ pub enum ConstellationMsg {
IsReadyToSaveImage(HashMap<PipelineId, Epoch>), IsReadyToSaveImage(HashMap<PipelineId, Epoch>),
/// Inform the constellation of a key event. /// Inform the constellation of a key event.
Keyboard(KeyboardEvent), Keyboard(KeyboardEvent),
/// Whether to allow script to navigate.
AllowNavigationResponse(PipelineId, bool),
/// Request to load a page. /// Request to load a page.
LoadUrl(TopLevelBrowsingContextId, ServoUrl), LoadUrl(TopLevelBrowsingContextId, ServoUrl),
/// Request to traverse the joint session history of the provided browsing context. /// Request to traverse the joint session history of the provided browsing context.
@ -770,6 +772,7 @@ impl fmt::Debug for ConstellationMsg {
GetFocusTopLevelBrowsingContext(..) => "GetFocusTopLevelBrowsingContext", GetFocusTopLevelBrowsingContext(..) => "GetFocusTopLevelBrowsingContext",
IsReadyToSaveImage(..) => "IsReadyToSaveImage", IsReadyToSaveImage(..) => "IsReadyToSaveImage",
Keyboard(..) => "Keyboard", Keyboard(..) => "Keyboard",
AllowNavigationResponse(..) => "AllowNavigationResponse",
LoadUrl(..) => "LoadUrl", LoadUrl(..) => "LoadUrl",
TraverseHistory(..) => "TraverseHistory", TraverseHistory(..) => "TraverseHistory",
WindowSize(..) => "WindowSize", WindowSize(..) => "WindowSize",

View file

@ -267,6 +267,16 @@ where
self.compositor.on_resize_window_event(); self.compositor.on_resize_window_event();
}, },
WindowEvent::AllowNavigationResponse(pipeline_id, allowed) => {
let msg = ConstellationMsg::AllowNavigationResponse(pipeline_id, allowed);
if let Err(e) = self.constellation_chan.send(msg) {
warn!(
"Sending allow navigation to constellation failed ({:?}).",
e
);
}
},
WindowEvent::LoadUrl(top_level_browsing_context_id, url) => { WindowEvent::LoadUrl(top_level_browsing_context_id, url) => {
let msg = ConstellationMsg::LoadUrl(top_level_browsing_context_id, url); let msg = ConstellationMsg::LoadUrl(top_level_browsing_context_id, url);
if let Err(e) = self.constellation_chan.send(msg) { if let Err(e) = self.constellation_chan.send(msg) {

View file

@ -128,12 +128,15 @@ pub unsafe extern "C" fn heartbeat_servo(servo: *mut ServoInstance) {
// Servo heartbeat goes here! // Servo heartbeat goes here!
if let Some(servo) = servo.as_mut() { if let Some(servo) = servo.as_mut() {
servo.servo.handle_events(vec![]); servo.servo.handle_events(vec![]);
for ((_browser_id, event)) in servo.servo.get_events() { for ((browser_id, event)) in servo.servo.get_events() {
match event { match event {
// Respond to any messages with a response channel // Respond to any messages with a response channel
// to avoid deadlocking the constellation // to avoid deadlocking the constellation
EmbedderMsg::AllowNavigation(_url, sender) => { EmbedderMsg::AllowNavigationRequest(pipeline_id, _url) => {
let _ = sender.send(true); if let Some(_browser_id) = browser_id {
let window_event = WindowEvent::AllowNavigationResponse(pipeline_id, true);
servo.servo.handle_events(vec![window_event]);
}
}, },
EmbedderMsg::GetSelectedBluetoothDevice(_, sender) => { EmbedderMsg::GetSelectedBluetoothDevice(_, sender) => {
let _ = sender.send(None); let _ = sender.send(None);

View file

@ -388,7 +388,7 @@ impl ServoGlue {
} }
fn handle_servo_events(&mut self) -> Result<(), &'static str> { fn handle_servo_events(&mut self) -> Result<(), &'static str> {
for (_browser_id, event) in self.servo.get_events() { for (browser_id, event) in self.servo.get_events() {
match event { match event {
EmbedderMsg::ChangePageTitle(title) => { EmbedderMsg::ChangePageTitle(title) => {
let fallback_title: String = if let Some(ref current_url) = self.current_url { let fallback_title: String = if let Some(ref current_url) = self.current_url {
@ -403,10 +403,11 @@ impl ServoGlue {
let title = format!("{} - Servo", title); let title = format!("{} - Servo", title);
self.callbacks.host_callbacks.on_title_changed(title); self.callbacks.host_callbacks.on_title_changed(title);
}, },
EmbedderMsg::AllowNavigation(_url, response_chan) => { EmbedderMsg::AllowNavigationRequest(pipeline_id, _url) => {
if let Err(e) = response_chan.send(true) { if let Some(_browser_id) = browser_id {
warn!("Failed to send allow_navigation() response: {}", e); let window_event = WindowEvent::AllowNavigationResponse(pipeline_id, true);
}; let _ = self.process_event(window_event);
}
}, },
EmbedderMsg::HistoryChanged(entries, current) => { EmbedderMsg::HistoryChanged(entries, current) => {
let can_go_back = current > 0; let can_go_back = current > 0;

View file

@ -290,9 +290,10 @@ impl Browser {
.push(WindowEvent::SendError(browser_id, reason)); .push(WindowEvent::SendError(browser_id, reason));
} }
}, },
EmbedderMsg::AllowNavigation(_url, sender) => { EmbedderMsg::AllowNavigationRequest(pipeline_id, _url) => {
if let Err(e) = sender.send(true) { if let Some(_browser_id) = browser_id {
warn!("Failed to send AllowNavigation response: {}", e); self.event_queue
.push(WindowEvent::AllowNavigationResponse(pipeline_id, true));
} }
}, },
EmbedderMsg::AllowOpeningBrowser(response_chan) => { EmbedderMsg::AllowOpeningBrowser(response_chan) => {