mirror of
https://github.com/servo/servo.git
synced 2025-08-05 21:50:18 +01:00
Make script thread initiate requests for images needed by layout.
In support of this goal, the layout thread collects information about CSS images that are missing image data and hands it off to the script thread after layout completes. The script thread stores a list of nodes that will need to be reflowed after the associated network request is complete. The script thread ensures that the nodes are not GCed while a request is ongoing, which the layout thread is incapable of guaranteeing. The image cache's API has also been redesigned in support of this work. No network requests are made by the new image cache, since it does not possess the document-specific information necessary to initiate them. Instead, there is now a single, synchronous query operation that optionally reserves a slot when a cache entry for a URL cannot be found. This reserved slot is then the responsibility of the queryer to populate with the contents of the network response for the URL once it is complete. Any subsequent queries for the same URL will be informed that the response is pending until that occurs. The changes to layout also remove the synchronous image loading code path, which means that reftests now test the same code that non-test binaries execute. The decision to take a screenshot now considers whether there are any outstanding image requests for layout in order to avoid intermittent failures in reftests that use CSS images.
This commit is contained in:
parent
78e8c31a4d
commit
c890c9143c
18 changed files with 528 additions and 388 deletions
|
@ -6,6 +6,7 @@ use app_units::Au;
|
|||
use bluetooth_traits::BluetoothRequest;
|
||||
use cssparser::Parser;
|
||||
use devtools_traits::{ScriptToDevtoolsControlMsg, TimelineMarker, TimelineMarkerType};
|
||||
use document_loader::LoadType;
|
||||
use dom::bindings::cell::DOMRefCell;
|
||||
use dom::bindings::codegen::Bindings::DocumentBinding::{DocumentMethods, DocumentReadyState};
|
||||
use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull;
|
||||
|
@ -42,7 +43,7 @@ use dom::location::Location;
|
|||
use dom::mediaquerylist::{MediaQueryList, WeakMediaQueryListVec};
|
||||
use dom::messageevent::MessageEvent;
|
||||
use dom::navigator::Navigator;
|
||||
use dom::node::{Node, from_untrusted_node_address, window_from_node};
|
||||
use dom::node::{Node, from_untrusted_node_address, window_from_node, document_from_node, NodeDamage};
|
||||
use dom::performance::Performance;
|
||||
use dom::promise::Promise;
|
||||
use dom::screen::Screen;
|
||||
|
@ -52,20 +53,25 @@ use euclid::{Point2D, Rect, Size2D};
|
|||
use fetch;
|
||||
use gfx_traits::ScrollRootId;
|
||||
use ipc_channel::ipc::{self, IpcSender};
|
||||
use ipc_channel::router::ROUTER;
|
||||
use js::jsapi::{HandleObject, HandleValue, JSAutoCompartment, JSContext};
|
||||
use js::jsapi::{JS_GC, JS_GetRuntime};
|
||||
use js::jsval::UndefinedValue;
|
||||
use js::rust::Runtime;
|
||||
use msg::constellation_msg::{FrameType, PipelineId};
|
||||
use net_traits::{ResourceThreads, ReferrerPolicy};
|
||||
use net_traits::image_cache_thread::{ImageCacheChan, ImageCacheThread};
|
||||
use net_traits::{ResourceThreads, ReferrerPolicy, FetchResponseListener, FetchMetadata};
|
||||
use net_traits::NetworkError;
|
||||
use net_traits::image_cache_thread::{ImageResponder, ImageResponse};
|
||||
use net_traits::image_cache_thread::{PendingImageResponse, ImageCacheThread, PendingImageId};
|
||||
use net_traits::request::{Type as RequestType, RequestInit as FetchRequestInit};
|
||||
use net_traits::storage_thread::StorageType;
|
||||
use network_listener::{NetworkListener, PreInvoke};
|
||||
use num_traits::ToPrimitive;
|
||||
use open;
|
||||
use profile_traits::mem::ProfilerChan as MemProfilerChan;
|
||||
use profile_traits::time::ProfilerChan as TimeProfilerChan;
|
||||
use rustc_serialize::base64::{FromBase64, STANDARD, ToBase64};
|
||||
use script_layout_interface::TrustedNodeAddress;
|
||||
use script_layout_interface::{TrustedNodeAddress, PendingImageState};
|
||||
use script_layout_interface::message::{Msg, Reflow, ReflowQueryType, ScriptReflow};
|
||||
use script_layout_interface::reporter::CSSErrorReporter;
|
||||
use script_layout_interface::rpc::{ContentBoxResponse, ContentBoxesResponse, LayoutRPC};
|
||||
|
@ -73,7 +79,7 @@ use script_layout_interface::rpc::{MarginStyleResponse, NodeScrollRootIdResponse
|
|||
use script_layout_interface::rpc::{ResolvedStyleResponse, TextIndexResponse};
|
||||
use script_runtime::{CommonScriptMsg, ScriptChan, ScriptPort, ScriptThreadEventCategory};
|
||||
use script_thread::{MainThreadScriptChan, MainThreadScriptMsg, Runnable, RunnableWrapper};
|
||||
use script_thread::SendableMainThreadScriptChan;
|
||||
use script_thread::{SendableMainThreadScriptChan, ImageCacheMsg};
|
||||
use script_traits::{ConstellationControlMsg, LoadData, MozBrowserEvent, UntrustedNodeAddress};
|
||||
use script_traits::{DocumentState, TimerEvent, TimerEventId};
|
||||
use script_traits::{ScriptMsg as ConstellationMsg, TimerEventRequest, WindowSizeData, WindowSizeType};
|
||||
|
@ -87,6 +93,7 @@ use std::ascii::AsciiExt;
|
|||
use std::borrow::ToOwned;
|
||||
use std::cell::Cell;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::default::Default;
|
||||
use std::io::{Write, stderr, stdout};
|
||||
use std::mem;
|
||||
|
@ -165,7 +172,7 @@ pub struct Window {
|
|||
#[ignore_heap_size_of = "channels are hard"]
|
||||
image_cache_thread: ImageCacheThread,
|
||||
#[ignore_heap_size_of = "channels are hard"]
|
||||
image_cache_chan: ImageCacheChan,
|
||||
image_cache_chan: Sender<ImageCacheMsg>,
|
||||
browsing_context: MutNullableJS<BrowsingContext>,
|
||||
document: MutNullableJS<Document>,
|
||||
history: MutNullableJS<History>,
|
||||
|
@ -253,7 +260,13 @@ pub struct Window {
|
|||
webvr_thread: Option<IpcSender<WebVRMsg>>,
|
||||
|
||||
/// A map for storing the previous permission state read results.
|
||||
permission_state_invocation_results: DOMRefCell<HashMap<String, PermissionState>>
|
||||
permission_state_invocation_results: DOMRefCell<HashMap<String, PermissionState>>,
|
||||
|
||||
/// All of the elements that have an outstanding image request that was
|
||||
/// initiated by layout during a reflow. They are stored in the script thread
|
||||
/// to ensure that the element can be marked dirty when the image data becomes
|
||||
/// available at some point in the future.
|
||||
pending_layout_images: DOMRefCell<HashMap<PendingImageId, Vec<JS<Node>>>>,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
|
@ -295,10 +308,6 @@ impl Window {
|
|||
&self.script_chan.0
|
||||
}
|
||||
|
||||
pub fn image_cache_chan(&self) -> ImageCacheChan {
|
||||
self.image_cache_chan.clone()
|
||||
}
|
||||
|
||||
pub fn parent_info(&self) -> Option<(PipelineId, FrameType)> {
|
||||
self.parent_info
|
||||
}
|
||||
|
@ -346,6 +355,28 @@ impl Window {
|
|||
pub fn permission_state_invocation_results(&self) -> &DOMRefCell<HashMap<String, PermissionState>> {
|
||||
&self.permission_state_invocation_results
|
||||
}
|
||||
|
||||
pub fn pending_image_notification(&self, response: PendingImageResponse) {
|
||||
//XXXjdm could be more efficient to send the responses to the layout thread,
|
||||
// rather than making the layout thread talk to the image cache to
|
||||
// obtain the same data.
|
||||
let mut images = self.pending_layout_images.borrow_mut();
|
||||
let nodes = images.entry(response.id);
|
||||
let nodes = match nodes {
|
||||
Entry::Occupied(nodes) => nodes,
|
||||
Entry::Vacant(_) => return,
|
||||
};
|
||||
for node in nodes.get() {
|
||||
node.dirty(NodeDamage::OtherNodeDamage);
|
||||
}
|
||||
match response.response {
|
||||
ImageResponse::MetadataLoaded(_) => {}
|
||||
ImageResponse::Loaded(_) |
|
||||
ImageResponse::PlaceholderLoaded(_) |
|
||||
ImageResponse::None => { nodes.remove(); }
|
||||
}
|
||||
self.add_pending_reflow();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))]
|
||||
|
@ -1168,6 +1199,31 @@ impl Window {
|
|||
self.emit_timeline_marker(marker.end());
|
||||
}
|
||||
|
||||
let pending_images = self.layout_rpc.pending_images();
|
||||
for image in pending_images {
|
||||
let id = image.id;
|
||||
let js_runtime = self.js_runtime.borrow();
|
||||
let js_runtime = js_runtime.as_ref().unwrap();
|
||||
let node = from_untrusted_node_address(js_runtime.rt(), image.node);
|
||||
|
||||
if let PendingImageState::Unrequested(ref url) = image.state {
|
||||
fetch_image_for_layout(url.clone(), &*node, id);
|
||||
}
|
||||
|
||||
let mut images = self.pending_layout_images.borrow_mut();
|
||||
let nodes = images.entry(id).or_insert(vec![]);
|
||||
if nodes.iter().find(|n| &***n as *const _ == &*node as *const _).is_none() {
|
||||
let (responder, responder_listener) = ipc::channel().unwrap();
|
||||
let pipeline = self.upcast::<GlobalScope>().pipeline_id();
|
||||
let image_cache_chan = self.image_cache_chan.clone();
|
||||
ROUTER.add_route(responder_listener.to_opaque(), box move |message| {
|
||||
let _ = image_cache_chan.send((pipeline, message.to().unwrap()));
|
||||
});
|
||||
self.image_cache_thread.add_listener(id, ImageResponder::new(responder, id));
|
||||
nodes.push(JS::from_ref(&*node));
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
|
@ -1227,7 +1283,8 @@ impl Window {
|
|||
|
||||
let ready_state = document.ReadyState();
|
||||
|
||||
if ready_state == DocumentReadyState::Complete && !reftest_wait {
|
||||
let pending_images = self.pending_layout_images.borrow().is_empty();
|
||||
if ready_state == DocumentReadyState::Complete && !reftest_wait && pending_images {
|
||||
let global_scope = self.upcast::<GlobalScope>();
|
||||
let event = ConstellationMsg::SetDocumentState(global_scope.pipeline_id(), DocumentState::Idle);
|
||||
global_scope.constellation_chan().send(event).unwrap();
|
||||
|
@ -1635,7 +1692,7 @@ impl Window {
|
|||
network_task_source: NetworkingTaskSource,
|
||||
history_task_source: HistoryTraversalTaskSource,
|
||||
file_task_source: FileReadingTaskSource,
|
||||
image_cache_chan: ImageCacheChan,
|
||||
image_cache_chan: Sender<ImageCacheMsg>,
|
||||
image_cache_thread: ImageCacheThread,
|
||||
resource_threads: ResourceThreads,
|
||||
bluetooth_thread: IpcSender<BluetoothRequest>,
|
||||
|
@ -1717,6 +1774,7 @@ impl Window {
|
|||
test_runner: Default::default(),
|
||||
webvr_thread: webvr_thread,
|
||||
permission_state_invocation_results: DOMRefCell::new(HashMap::new()),
|
||||
pending_layout_images: DOMRefCell::new(HashMap::new()),
|
||||
};
|
||||
|
||||
unsafe {
|
||||
|
@ -1836,3 +1894,64 @@ impl Runnable for PostMessageHandler {
|
|||
message.handle());
|
||||
}
|
||||
}
|
||||
|
||||
struct LayoutImageContext {
|
||||
node: Trusted<Node>,
|
||||
data: Vec<u8>,
|
||||
id: PendingImageId,
|
||||
url: ServoUrl,
|
||||
}
|
||||
|
||||
impl FetchResponseListener for LayoutImageContext {
|
||||
fn process_request_body(&mut self) {}
|
||||
fn process_request_eof(&mut self) {}
|
||||
fn process_response(&mut self, _metadata: Result<FetchMetadata, NetworkError>) {/*XXXjdm*/}
|
||||
|
||||
fn process_response_chunk(&mut self, mut payload: Vec<u8>) {
|
||||
self.data.append(&mut payload);
|
||||
}
|
||||
|
||||
fn process_response_eof(&mut self, _response: Result<(), NetworkError>) {
|
||||
let node = self.node.root();
|
||||
let document = document_from_node(&*node);
|
||||
let window = document.window();
|
||||
let image_cache = window.image_cache_thread();
|
||||
image_cache.store_complete_image_bytes(self.id, self.data.clone());
|
||||
document.finish_load(LoadType::Image(self.url.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
impl PreInvoke for LayoutImageContext {}
|
||||
|
||||
fn fetch_image_for_layout(url: ServoUrl, node: &Node, id: PendingImageId) {
|
||||
let context = Arc::new(Mutex::new(LayoutImageContext {
|
||||
node: Trusted::new(node),
|
||||
data: vec![],
|
||||
id: id,
|
||||
url: url.clone(),
|
||||
}));
|
||||
|
||||
let document = document_from_node(node);
|
||||
let window = window_from_node(node);
|
||||
|
||||
let (action_sender, action_receiver) = ipc::channel().unwrap();
|
||||
let listener = NetworkListener {
|
||||
context: context,
|
||||
task_source: window.networking_task_source(),
|
||||
wrapper: Some(window.get_runnable_wrapper()),
|
||||
};
|
||||
ROUTER.add_route(action_receiver.to_opaque(), box move |message| {
|
||||
listener.notify_fetch(message.to().unwrap());
|
||||
});
|
||||
|
||||
let request = FetchRequestInit {
|
||||
url: url.clone(),
|
||||
origin: document.url().clone(),
|
||||
type_: RequestType::Image,
|
||||
pipeline_id: Some(document.global().pipeline_id()),
|
||||
.. FetchRequestInit::default()
|
||||
};
|
||||
|
||||
//XXXjdm should not block load event
|
||||
document.fetch_async(LoadType::Image(url), request, action_sender);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue