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

Implement Window.open and related infrastructure #20678

Merged
merged 7 commits into from Aug 11, 2018
Next

implement window.open, create auxiliary browsing context

  • Loading branch information
gterzian committed Aug 10, 2018
commit f408b798c4666eddeb8b52d8965d7d4a6942e066
@@ -123,7 +123,7 @@ use network_listener::NetworkListener;
use pipeline::{InitialPipelineState, Pipeline};
use profile_traits::mem;
use profile_traits::time;
use script_traits::{AnimationState, AnimationTickType, CompositorEvent};
use script_traits::{AnimationState, AuxiliaryBrowsingContextLoadInfo, AnimationTickType, CompositorEvent};
use script_traits::{ConstellationControlMsg, ConstellationMsg as FromCompositorMsg, DiscardBrowsingContext};
use script_traits::{DocumentActivity, DocumentState, LayoutControlMsg, LoadData};
use script_traits::{IFrameLoadInfo, IFrameLoadInfoWithData, IFrameSandboxState, TimerSchedulerMsg};
@@ -1121,6 +1121,9 @@ where
FromScriptMsg::ScriptNewIFrame(load_info, layout_sender) => {
self.handle_script_new_iframe(load_info, layout_sender);
},
FromScriptMsg::ScriptNewAuxiliary(load_info, layout_sender) => {
self.handle_script_new_auxiliary(load_info, layout_sender);
},
FromScriptMsg::ChangeRunningAnimationsState(animation_state) => {
self.handle_change_running_animations_state(source_pipeline_id, animation_state)
},
@@ -1868,6 +1871,62 @@ where
});
}

fn handle_script_new_auxiliary(&mut self,
load_info: AuxiliaryBrowsingContextLoadInfo,
layout_sender: IpcSender<LayoutControlMsg>) {
let AuxiliaryBrowsingContextLoadInfo {
opener_pipeline_id,
new_top_level_browsing_context_id,
new_browsing_context_id,
new_pipeline_id,
} = load_info;

let url = ServoUrl::parse("about:blank").expect("infallible");

// TODO: Referrer?
let load_data = LoadData::new(url.clone(), None, None, None);

let pipeline = {
let opener_pipeline = match self.pipelines.get(&opener_pipeline_id) {
Some(parent_pipeline) => parent_pipeline,
None => return warn!("Auxiliary loaded url in closed pipeline {}.", opener_pipeline_id),
};
let opener_host = match reg_host(&opener_pipeline.url) {
Some(host) => host,
None => return warn!("Auxiliary loaded pipeline with no url {}.", opener_pipeline_id),
};
let script_sender = opener_pipeline.event_loop.clone();
// https://html.spec.whatwg.org/multipage/#unit-of-related-similar-origin-browsing-contexts
// If the auxiliary shares the host/scheme with the creator, they share an event-loop.
// So the first entry for the auxiliary, itself currently "about:blank",
// is the event-loop for the current host of the creator.
self.event_loops.entry(new_top_level_browsing_context_id)
.or_insert_with(HashMap::new)
.insert(opener_host, Rc::downgrade(&script_sender));
Pipeline::new(new_pipeline_id,
new_browsing_context_id,
new_top_level_browsing_context_id,
None,
script_sender,
layout_sender,
self.compositor_proxy.clone(),
opener_pipeline.is_private,
url,
opener_pipeline.visible,
load_data)
};

assert!(!self.pipelines.contains_key(&new_pipeline_id));
self.pipelines.insert(new_pipeline_id, pipeline);
self.joint_session_histories.insert(new_top_level_browsing_context_id, JointSessionHistory::new());
self.add_pending_change(SessionHistoryChange {
top_level_browsing_context_id: new_top_level_browsing_context_id,
browsing_context_id: new_browsing_context_id,
new_pipeline_id: new_pipeline_id,
replace: None,
});
}

fn handle_pending_paint_metric(&self, pipeline_id: PipelineId, epoch: Epoch) {
self.compositor_proxy
.send(ToCompositorMsg::PendingPaintMetric(pipeline_id, epoch))
@@ -84,6 +84,10 @@ pub enum EmbedderMsg {
Alert(String, IpcSender<()>),
/// Wether or not to follow a link
AllowNavigation(ServoUrl, IpcSender<bool>),
/// Whether or not to allow script to open a new tab/browser
AllowOpeningBrowser(IpcSender<bool>),
/// A new browser was created by script
BrowserCreated(TopLevelBrowsingContextId),

This comment has been minimized.

Copy link
@paulrouget

paulrouget Jun 7, 2018

Contributor

Question about this new event: when do get BrowserCreated? On window.open if AllowOpeningBrowser returned true? If so, could we make it so that the message NewBrowser uses this new message to send back the context id instead of using an IpcSender for that?

This comment has been minimized.

Copy link
@gterzian

gterzian Jun 7, 2018

Author Member

Seems to work... 75f44a4

/// Wether or not to unload a document
AllowUnload(IpcSender<bool>),
/// Sends an unconsumed key event back to the embedder.
@@ -143,6 +147,8 @@ impl Debug for EmbedderMsg {
EmbedderMsg::ShowIME(..) => write!(f, "ShowIME"),
EmbedderMsg::HideIME => write!(f, "HideIME"),
EmbedderMsg::Shutdown => write!(f, "Shutdown"),
EmbedderMsg::AllowOpeningBrowser(..) => write!(f, "AllowOpeningBrowser"),
EmbedderMsg::BrowserCreated(..) => write!(f, "BrowserCreated")
}
}
}
@@ -574,45 +574,61 @@ impl Activatable for HTMLAnchorElement {
}
}

/// <https://html.spec.whatwg.org/multipage/#the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name>
fn is_current_browsing_context(target: DOMString) -> bool {
target.is_empty() || target == "_self"
}

/// <https://html.spec.whatwg.org/multipage/#following-hyperlinks-2>
pub fn follow_hyperlink(subject: &Element, hyperlink_suffix: Option<String>, referrer_policy: Option<ReferrerPolicy>) {
// Step 1: replace.
// Step 2: source browsing context.
// Step 3: target browsing context.
let target = subject.get_attribute(&ns!(), &local_name!("target"));
// Step 1: TODO: If subject cannot navigate, then return.
// Step 2, done in Step 7.

// Step 4: disown target's opener if needed.
let attribute = subject.get_attribute(&ns!(), &local_name!("href")).unwrap();
let mut href = attribute.Value();
let document = document_from_node(subject);
let window = document.window();

// Step 7: append a hyperlink suffix.
// https://www.w3.org/Bugs/Public/show_bug.cgi?id=28925
if let Some(suffix) = hyperlink_suffix {
href.push_str(&suffix);
}
// Step 3: source browsing context.
let source = document.browsing_context().unwrap();

// Step 5: parse the URL.
// Step 6: navigate to an error document if parsing failed.
let document = document_from_node(subject);
let url = match document.url().join(&href) {
Ok(url) => url,
Err(_) => return,
// Step 4-5: target attribute.
let target_attribute_value = subject.get_attribute(&ns!(), &local_name!("target"));

// Step 6.
let noopener = if let Some(link_types) = subject.get_attribute(&ns!(), &local_name!("rel")) {
let values = link_types.Value();
let contains_noopener = values.contains("noopener");
let contains_noreferrer = values.contains("noreferrer");
contains_noreferrer || contains_noopener
} else {
false
};

// Step 8: navigate to the URL.
if let Some(target) = target {
if !is_current_browsing_context(target.Value()) {
// https://github.com/servo/servo/issues/13241
// Step 7.
let (maybe_chosen, replace) = match target_attribute_value {

This comment has been minimized.

Copy link
@gterzian

gterzian Jun 8, 2018

Author Member

Just changed this, so 'replace' is actually set to true if a new bc was created...

Some(name) => source.choose_browsing_context(name.Value(), noopener),
None => (Some(window.window_proxy()), false)
};
let chosen = match maybe_chosen {
Some(proxy) => proxy,
None => return,
};
if let Some(target_document) = chosen.document() {
let target_window = target_document.window();

// Step 9, dis-owning target's opener, if necessary
// will have been done as part of Step 7 above
// in choose_browsing_context/create_auxiliary_browsing_context.

// Step 10, 11, 12, 13. TODO: if parsing the URL failed, navigate to error page.
let attribute = subject.get_attribute(&ns!(), &local_name!("href")).unwrap();
let mut href = attribute.Value();
// Step 12: append a hyperlink suffix.
// https://www.w3.org/Bugs/Public/show_bug.cgi?id=28925
if let Some(suffix) = hyperlink_suffix {
href.push_str(&suffix);
}
}

debug!("following hyperlink to {}", url);
let url = match document.url().join(&href) {
Ok(url) => url,
Err(_) => return,
};

let window = document.window();
window.load_url(url, false, false, referrer_policy);
// Step 13, 14.
debug!("following hyperlink to {}", url);
target_window.load_url(url, replace, false, referrer_policy);
};
}
@@ -41,6 +41,7 @@ use dom::node::{Node, NodeFlags, UnbindContext, VecPreOrderInsertionHelper};
use dom::node::{document_from_node, window_from_node};
use dom::validitystate::ValidationFlags;
use dom::virtualmethods::VirtualMethods;
use dom::window::Window;
use dom_struct::dom_struct;
use encoding_rs::{Encoding, UTF_8};
use html5ever::{LocalName, Prefix};
@@ -337,10 +338,24 @@ impl HTMLFormElement {
let scheme = action_components.scheme().to_owned();
let enctype = submitter.enctype();
let method = submitter.method();
let _target = submitter.target();
// TODO: Handle browsing contexts, partially loaded documents (step 16-17)

let mut load_data = LoadData::new(action_components, None, doc.get_referrer_policy(), Some(doc.url()));
// Step 16, 17
let target_attribute_value = submitter.target();
let source = doc.browsing_context().unwrap();
let (maybe_chosen, _new) = source.choose_browsing_context(target_attribute_value, false);
let chosen = match maybe_chosen {
Some(proxy) => proxy,
None => return
};
let target_document = match chosen.document() {
Some(doc) => doc,
None => return
};
let target_window = target_document.window();
let mut load_data = LoadData::new(action_components,
None,
target_document.get_referrer_policy(),
Some(target_document.url()));

// Step 18
match (&*scheme, method) {
@@ -351,17 +366,17 @@ impl HTMLFormElement {
// https://html.spec.whatwg.org/multipage/#submit-mutate-action
("http", FormMethod::FormGet) | ("https", FormMethod::FormGet) | ("data", FormMethod::FormGet) => {
load_data.headers.set(ContentType::form_url_encoded());
self.mutate_action_url(&mut form_data, load_data, encoding);
self.mutate_action_url(&mut form_data, load_data, encoding, &target_window);
}
// https://html.spec.whatwg.org/multipage/#submit-body
("http", FormMethod::FormPost) | ("https", FormMethod::FormPost) => {
load_data.method = Method::Post;
self.submit_entity_body(&mut form_data, load_data, enctype, encoding);
self.submit_entity_body(&mut form_data, load_data, enctype, encoding, &target_window);
}
// https://html.spec.whatwg.org/multipage/#submit-get-action
("file", _) | ("about", _) | ("data", FormMethod::FormPost) |
("ftp", _) | ("javascript", _) => {
self.plan_to_navigate(load_data);
self.plan_to_navigate(load_data, &target_window);
}
("mailto", FormMethod::FormPost) => {
// TODO: Mail as body
@@ -376,20 +391,28 @@ impl HTMLFormElement {
}

// https://html.spec.whatwg.org/multipage/#submit-mutate-action
fn mutate_action_url(&self, form_data: &mut Vec<FormDatum>, mut load_data: LoadData, encoding: &'static Encoding) {
fn mutate_action_url(&self,
form_data: &mut Vec<FormDatum>,
mut load_data: LoadData,
encoding: &'static Encoding,
target: &Window) {
let charset = encoding.name();

self.set_encoding_override(load_data.url.as_mut_url().query_pairs_mut())
.clear()
.extend_pairs(form_data.into_iter()
.map(|field| (field.name.clone(), field.replace_value(charset))));

self.plan_to_navigate(load_data);
self.plan_to_navigate(load_data, target);
}

// https://html.spec.whatwg.org/multipage/#submit-body
fn submit_entity_body(&self, form_data: &mut Vec<FormDatum>, mut load_data: LoadData,
enctype: FormEncType, encoding: &'static Encoding) {
fn submit_entity_body(&self,
form_data: &mut Vec<FormDatum>,
mut load_data: LoadData,
enctype: FormEncType,
encoding: &'static Encoding,
target: &Window) {
let boundary = generate_boundary();
let bytes = match enctype {
FormEncType::UrlEncoded => {
@@ -415,7 +438,7 @@ impl HTMLFormElement {
};

load_data.data = Some(bytes);
self.plan_to_navigate(load_data);
self.plan_to_navigate(load_data, target);
}

fn set_encoding_override<'a>(&self, mut serializer: Serializer<UrlQuery<'a>>)
@@ -426,9 +449,7 @@ impl HTMLFormElement {
}

/// [Planned navigation](https://html.spec.whatwg.org/multipage/#planned-navigation)
fn plan_to_navigate(&self, load_data: LoadData) {
let window = window_from_node(self);

fn plan_to_navigate(&self, load_data: LoadData, target: &Window) {
// Step 1
// Each planned navigation task is tagged with a generation ID, and
// before the task is handled, it first checks whether the HTMLFormElement's
@@ -437,8 +458,8 @@ impl HTMLFormElement {
self.generation_id.set(generation_id);

// Step 2.
let pipeline_id = window.upcast::<GlobalScope>().pipeline_id();
let script_chan = window.main_thread_script_chan().clone();
let pipeline_id = target.upcast::<GlobalScope>().pipeline_id();
let script_chan = target.main_thread_script_chan().clone();
let this = Trusted::new(self);
let task = task!(navigate_to_form_planned_navigation: move || {
if generation_id != this.root().generation_id.get() {
@@ -452,7 +473,7 @@ impl HTMLFormElement {
});

// Step 3.
window.dom_manipulation_task_source().queue(task, window.upcast()).unwrap();
target.dom_manipulation_task_source().queue(task, target.upcast()).unwrap();
}

/// Interactively validate the constraints of form elements
@@ -40,8 +40,8 @@
// https://github.com/whatwg/html/issues/2115
[Replaceable] readonly attribute WindowProxy? parent;
readonly attribute Element? frameElement;
//WindowProxy open(optional DOMString url = "about:blank", optional DOMString target = "_blank",
// optional DOMString features = "", optional boolean replace = false);
WindowProxy? open(optional DOMString url = "about:blank", optional DOMString target = "_blank",
optional DOMString features = "");
//getter WindowProxy (unsigned long index);

// https://github.com/servo/servo/issues/14453
@@ -565,6 +565,15 @@ impl WindowMethods for Window {
doc.abort();
}

// https://html.spec.whatwg.org/multipage/#dom-open
fn Open(&self,
url: DOMString,
target: DOMString,
features: DOMString)
-> Option<DomRoot<WindowProxy>> {
self.window_proxy().open(url, target, features)
}

// https://html.spec.whatwg.org/multipage/#dom-window-closed
fn Closed(&self) -> bool {
self.window_proxy.get()
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.