fune/servo/components/script/document_loader.rs
Patrick Walton 3b92de7a22 servo: Merge #6586 - script: Make the resource task communication use IPC channels (from pcwalton:resource-task-ipc); r=jdm
This change makes Servo use serialized messages over IPC channels for resource loading. The goal is to make it easier to make Servo multiprocess in the future. This patch does not make Servo multiprocess now; there are many other channels that need to be changed to IPC before that can happen. It does introduce a dependency on https://github.com/serde-rs/serde and https://github.com/pcwalton/ipc-channel for the first time.

At the moment, `ipc-channel` uses JSON for serialization. This is because serde does not yet have official support for bincode. When serde gains support for bincode, I'll switch to that. For now, however, the JSON encoding and decoding will constitute a significant performance regression in resource loading.

To avoid having to send boxed `AsyncResponseTarget` trait objects across process boundaries, this series of commits changes `AsyncResponseTarget` to wrap a sender only. It is then the client's responsibility to spawn a thread to proxy calls from that sender to the consumer of the resource data. This only had to be done in a few places. In the future, we may want to collapse those threads into one per process to reduce overhead. (It is impossible to continue to use `AsyncResponseTarget` as a boxed trait object across processes, regardless of how much work is done on `ipc-channel`. Vtables are fundamentally incompatible with IPC across mutually untrusting processes.)

In general, I was pretty pleased with how this turned out. The main changes are adding serialization functionality to various objects that `serde` does not know how to serialize natively—the most complicated being Hyper objects—and reworking `AsyncResponseTarget`. The overall structure of the code is unchanged, and other than `AsyncResponseTarget` no functionality was lost in moving to serialization and IPC.

r? @jdm

Source-Repo: https://github.com/servo/servo
Source-Revision: 2eb122f394651232abd683fc576a5c4288bf277f
2015-07-31 16:06:36 -06:00

114 lines
3.9 KiB
Rust

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! Tracking of pending loads in a document.
//! https://html.spec.whatwg.org/multipage/#the-end
use script_task::{ScriptMsg, ScriptChan};
use msg::constellation_msg::{PipelineId};
use net_traits::{Metadata, load_whole_resource, ResourceTask, PendingAsyncLoad};
use net_traits::AsyncResponseTarget;
use std::sync::Arc;
use url::Url;
#[derive(JSTraceable, PartialEq, Clone, Debug)]
pub enum LoadType {
Image(Url),
Script(Url),
Subframe(Url),
Stylesheet(Url),
PageSource(Url),
}
impl LoadType {
fn url(&self) -> &Url {
match *self {
LoadType::Image(ref url) |
LoadType::Script(ref url) |
LoadType::Subframe(ref url) |
LoadType::Stylesheet(ref url) |
LoadType::PageSource(ref url) => url,
}
}
}
#[derive(JSTraceable)]
pub struct DocumentLoader {
/// We use an `Arc<ResourceTask>` here in order to avoid file descriptor exhaustion when there
/// are lots of iframes.
pub resource_task: Arc<ResourceTask>,
notifier_data: Option<NotifierData>,
blocking_loads: Vec<LoadType>,
}
#[derive(JSTraceable)]
pub struct NotifierData {
pub script_chan: Box<ScriptChan + Send>,
pub pipeline: PipelineId,
}
impl DocumentLoader {
pub fn new(existing: &DocumentLoader) -> DocumentLoader {
DocumentLoader::new_with_task(existing.resource_task.clone(), None, None)
}
/// We use an `Arc<ResourceTask>` here in order to avoid file descriptor exhaustion when there
/// are lots of iframes.
pub fn new_with_task(resource_task: Arc<ResourceTask>,
data: Option<NotifierData>,
initial_load: Option<Url>,)
-> DocumentLoader {
let initial_loads = initial_load.into_iter().map(LoadType::PageSource).collect();
DocumentLoader {
resource_task: resource_task,
notifier_data: data,
blocking_loads: initial_loads,
}
}
/// Create a new pending network request, which can be initiated at some point in
/// the future.
pub fn prepare_async_load(&mut self, load: LoadType) -> PendingAsyncLoad {
let url = load.url().clone();
self.blocking_loads.push(load);
let pipeline = self.notifier_data.as_ref().map(|data| data.pipeline);
PendingAsyncLoad::new((*self.resource_task).clone(), url, pipeline)
}
/// Create and initiate a new network request.
pub fn load_async(&mut self, load: LoadType, listener: AsyncResponseTarget) {
let pending = self.prepare_async_load(load);
pending.load_async(listener)
}
/// Create, initiate, and await the response for a new network request.
pub fn load_sync(&mut self, load: LoadType) -> Result<(Metadata, Vec<u8>), String> {
self.blocking_loads.push(load.clone());
let result = load_whole_resource(&self.resource_task, load.url().clone());
self.finish_load(load);
result
}
/// Mark an in-progress network request complete.
pub fn finish_load(&mut self, load: LoadType) {
let idx = self.blocking_loads.iter().position(|unfinished| *unfinished == load);
self.blocking_loads.remove(idx.expect(&format!("unknown completed load {:?}", load)));
if let Some(NotifierData { ref script_chan, pipeline }) = self.notifier_data {
if !self.is_blocked() {
script_chan.send(ScriptMsg::DocumentLoadsComplete(pipeline)).unwrap();
}
}
}
pub fn is_blocked(&self) -> bool {
//TODO: Ensure that we report blocked if parsing is still ongoing.
!self.blocking_loads.is_empty()
}
pub fn inhibit_events(&mut self) {
self.notifier_data = None;
}
}