aboutsummaryrefslogtreecommitdiffstats
path: root/components/script/dom/window.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/script/dom/window.rs')
-rw-r--r--components/script/dom/window.rs2541
1 files changed, 1624 insertions, 917 deletions
diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs
index 18b4b1311b9..b6d2c114388 100644
--- a/components/script/dom/window.rs
+++ b/components/script/dom/window.rs
@@ -1,138 +1,162 @@
/* 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/. */
-
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use crate::dom::bindings::cell::{DomRefCell, Ref};
+use crate::dom::bindings::codegen::Bindings::DocumentBinding::{
+ DocumentMethods, DocumentReadyState,
+};
+use crate::dom::bindings::codegen::Bindings::HistoryBinding::HistoryBinding::HistoryMethods;
+use crate::dom::bindings::codegen::Bindings::ImageBitmapBinding::{
+ ImageBitmapOptions, ImageBitmapSource,
+};
+use crate::dom::bindings::codegen::Bindings::MediaQueryListBinding::MediaQueryListBinding::MediaQueryListMethods;
+use crate::dom::bindings::codegen::Bindings::RequestBinding::RequestInit;
+use crate::dom::bindings::codegen::Bindings::VoidFunctionBinding::VoidFunction;
+use crate::dom::bindings::codegen::Bindings::WindowBinding::{
+ self, FrameRequestCallback, WindowMethods, WindowPostMessageOptions,
+};
+use crate::dom::bindings::codegen::Bindings::WindowBinding::{ScrollBehavior, ScrollToOptions};
+use crate::dom::bindings::codegen::UnionTypes::{RequestOrUSVString, StringOrFunction};
+use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
+use crate::dom::bindings::inheritance::Castable;
+use crate::dom::bindings::num::Finite;
+use crate::dom::bindings::refcounted::Trusted;
+use crate::dom::bindings::reflector::DomObject;
+use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
+use crate::dom::bindings::str::{DOMString, USVString};
+use crate::dom::bindings::structuredclone;
+use crate::dom::bindings::trace::{JSTraceable, RootedTraceableBox};
+use crate::dom::bindings::utils::{GlobalStaticData, WindowProxyHandler};
+use crate::dom::bindings::weakref::DOMTracker;
+use crate::dom::bluetooth::BluetoothExtraPermissionData;
+use crate::dom::crypto::Crypto;
+use crate::dom::cssstyledeclaration::{CSSModificationAccess, CSSStyleDeclaration, CSSStyleOwner};
+use crate::dom::customelementregistry::CustomElementRegistry;
+use crate::dom::document::{AnimationFrameCallback, Document, ReflowTriggerCondition};
+use crate::dom::element::Element;
+use crate::dom::event::{Event, EventStatus};
+use crate::dom::eventtarget::EventTarget;
+use crate::dom::globalscope::GlobalScope;
+use crate::dom::hashchangeevent::HashChangeEvent;
+use crate::dom::history::History;
+use crate::dom::identityhub::Identities;
+use crate::dom::location::Location;
+use crate::dom::mediaquerylist::{MediaQueryList, MediaQueryListMatchState};
+use crate::dom::mediaquerylistevent::MediaQueryListEvent;
+use crate::dom::messageevent::MessageEvent;
+use crate::dom::navigator::Navigator;
+use crate::dom::node::{document_from_node, from_untrusted_node_address, Node, NodeDamage};
+use crate::dom::performance::Performance;
+use crate::dom::promise::Promise;
+use crate::dom::screen::Screen;
+use crate::dom::selection::Selection;
+use crate::dom::storage::Storage;
+use crate::dom::testrunner::TestRunner;
+use crate::dom::webglrenderingcontext::WebGLCommandSender;
+use crate::dom::windowproxy::WindowProxy;
+use crate::dom::worklet::Worklet;
+use crate::dom::workletglobalscope::WorkletGlobalScopeType;
+use crate::fetch;
+use crate::layout_image::fetch_image_for_layout;
+use crate::malloc_size_of::MallocSizeOf;
+use crate::microtask::MicrotaskQueue;
+use crate::realms::InRealm;
+use crate::script_runtime::{
+ CommonScriptMsg, JSContext, Runtime, ScriptChan, ScriptPort, ScriptThreadEventCategory,
+};
+use crate::script_thread::{ImageCacheMsg, MainThreadScriptChan, MainThreadScriptMsg};
+use crate::script_thread::{ScriptThread, SendableMainThreadScriptChan};
+use crate::task_manager::TaskManager;
+use crate::task_source::{TaskSource, TaskSourceName};
+use crate::timers::{IsInterval, TimerCallback};
+use crate::webdriver_handlers::jsval_to_webdriver;
use app_units::Au;
+use backtrace::Backtrace;
use base64;
use bluetooth_traits::BluetoothRequest;
-use cssparser::Parser;
+use canvas_traits::webgl::WebGLChan;
+use crossbeam_channel::{unbounded, Sender, TryRecvError};
+use cssparser::{Parser, ParserInput, SourceLocation};
use devtools_traits::{ScriptToDevtoolsControlMsg, TimelineMarker, TimelineMarkerType};
-use dom::bindings::cell::DOMRefCell;
-use dom::bindings::codegen::Bindings::DocumentBinding::{DocumentMethods, DocumentReadyState};
-use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull;
-use dom::bindings::codegen::Bindings::EventHandlerBinding::OnBeforeUnloadEventHandlerNonNull;
-use dom::bindings::codegen::Bindings::EventHandlerBinding::OnErrorEventHandlerNonNull;
-use dom::bindings::codegen::Bindings::FunctionBinding::Function;
-use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
-use dom::bindings::codegen::Bindings::PermissionStatusBinding::PermissionState;
-use dom::bindings::codegen::Bindings::RequestBinding::RequestInit;
-use dom::bindings::codegen::Bindings::WindowBinding::{self, FrameRequestCallback, WindowMethods};
-use dom::bindings::codegen::Bindings::WindowBinding::{ScrollBehavior, ScrollToOptions};
-use dom::bindings::codegen::UnionTypes::RequestOrUSVString;
-use dom::bindings::error::{Error, ErrorResult, Fallible};
-use dom::bindings::inheritance::Castable;
-use dom::bindings::js::{JS, MutNullableJS, Root};
-use dom::bindings::num::Finite;
-use dom::bindings::refcounted::Trusted;
-use dom::bindings::reflector::DomObject;
-use dom::bindings::str::DOMString;
-use dom::bindings::structuredclone::StructuredCloneData;
-use dom::bindings::trace::RootedTraceableBox;
-use dom::bindings::utils::{GlobalStaticData, WindowProxyHandler};
-use dom::bluetooth::BluetoothExtraPermissionData;
-use dom::browsingcontext::BrowsingContext;
-use dom::crypto::Crypto;
-use dom::cssstyledeclaration::{CSSModificationAccess, CSSStyleDeclaration, CSSStyleOwner};
-use dom::document::{AnimationFrameCallback, Document};
-use dom::element::Element;
-use dom::event::Event;
-use dom::globalscope::GlobalScope;
-use dom::history::History;
-use dom::htmliframeelement::build_mozbrowser_custom_event;
-use dom::location::Location;
-use dom::mediaquerylist::{MediaQueryList, WeakMediaQueryListVec};
-use dom::messageevent::MessageEvent;
-use dom::navigator::Navigator;
-use dom::node::{Node, NodeDamage, document_from_node, from_untrusted_node_address};
-use dom::performance::Performance;
-use dom::promise::Promise;
-use dom::screen::Screen;
-use dom::storage::Storage;
-use dom::testrunner::TestRunner;
use dom_struct::dom_struct;
-use euclid::{Point2D, Rect, Size2D};
-use fetch;
-use ipc_channel::ipc::{self, IpcSender};
+use embedder_traits::{EmbedderMsg, EventLoopWaker, PromptDefinition, PromptOrigin, PromptResult};
+use euclid::default::{Point2D as UntypedPoint2D, Rect as UntypedRect};
+use euclid::{Point2D, Rect, Scale, Size2D, Vector2D};
+use ipc_channel::ipc::IpcSender;
use ipc_channel::router::ROUTER;
-use js::jsapi::{HandleObject, HandleValue, JSAutoCompartment, JSContext};
-use js::jsapi::{JS_GC, JS_GetRuntime};
+use js::conversions::ToJSValConvertible;
+use js::jsapi::Heap;
+use js::jsapi::JSAutoRealm;
+use js::jsapi::JSObject;
+use js::jsapi::JSPROP_ENUMERATE;
+use js::jsapi::{GCReason, StackFormat, JS_GC};
use js::jsval::UndefinedValue;
-use js::rust::Runtime;
-use layout_image::fetch_image_for_layout;
-use msg::constellation_msg::{FrameType, PipelineId};
-use net_traits::{ResourceThreads, ReferrerPolicy};
+use js::jsval::{JSVal, NullValue};
+use js::rust::wrappers::JS_DefineProperty;
+use js::rust::{CustomAutoRooter, CustomAutoRooterGuard, HandleValue};
+use media::WindowGLContext;
+use msg::constellation_msg::{BrowsingContextId, PipelineId};
use net_traits::image_cache::{ImageCache, ImageResponder, ImageResponse};
use net_traits::image_cache::{PendingImageId, PendingImageResponse};
use net_traits::storage_thread::StorageType;
+use net_traits::ResourceThreads;
use num_traits::ToPrimitive;
-use open;
+use parking_lot::Mutex as ParkMutex;
+use profile_traits::ipc as ProfiledIpc;
use profile_traits::mem::ProfilerChan as MemProfilerChan;
-use profile_traits::time::ProfilerChan as TimeProfilerChan;
-use script_layout_interface::{TrustedNodeAddress, PendingImageState};
-use script_layout_interface::message::{Msg, Reflow, ReflowQueryType, ScriptReflow};
-use script_layout_interface::reporter::CSSErrorReporter;
+use profile_traits::time::{ProfilerChan as TimeProfilerChan, ProfilerMsg};
+use script_layout_interface::message::{Msg, QueryMsg, Reflow, ReflowGoal, ScriptReflow};
use script_layout_interface::rpc::{ContentBoxResponse, ContentBoxesResponse, LayoutRPC};
-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, ImageCacheMsg};
-use script_traits::{ConstellationControlMsg, LoadData, MozBrowserEvent, UntrustedNodeAddress};
-use script_traits::{DocumentState, TimerEvent, TimerEventId};
-use script_traits::{ScriptMsg as ConstellationMsg, TimerSchedulerMsg, WindowSizeData, WindowSizeType};
+use script_layout_interface::rpc::{
+ NodeScrollIdResponse, ResolvedStyleResponse, TextIndexResponse,
+};
+use script_layout_interface::{PendingImageState, TrustedNodeAddress};
use script_traits::webdriver_msg::{WebDriverJSError, WebDriverJSResult};
-use servo_atoms::Atom;
-use servo_config::opts;
-use servo_config::prefs::PREFS;
-use servo_geometry::{f32_rect_to_au_rect, max_rect};
-use servo_url::{Host, ImmutableOrigin, MutableOrigin, ServoUrl};
-use std::ascii::AsciiExt;
+use script_traits::{ConstellationControlMsg, DocumentState, HistoryEntryReplacement, LoadData};
+use script_traits::{
+ ScriptMsg, ScriptToConstellationChan, ScrollState, StructuredSerializedData, TimerEventId,
+};
+use script_traits::{TimerSchedulerMsg, WebrenderIpcSender, WindowSizeData, WindowSizeType};
+use selectors::attr::CaseSensitivity;
+use servo_arc::Arc as ServoArc;
+use servo_geometry::{f32_rect_to_au_rect, MaxRect};
+use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
+use std::borrow::Cow;
use std::borrow::ToOwned;
use std::cell::Cell;
-use std::collections::{HashMap, HashSet};
use std::collections::hash_map::Entry;
+use std::collections::{HashMap, HashSet};
use std::default::Default;
use std::env;
-use std::fs;
-use std::io::{Write, stderr, stdout};
+use std::io::{stderr, stdout, Write};
use std::mem;
use std::rc::Rc;
-use std::sync::{Arc, Mutex};
use std::sync::atomic::{AtomicBool, Ordering};
-use std::sync::mpsc::{Sender, channel};
-use std::sync::mpsc::TryRecvError::{Disconnected, Empty};
-use style::context::ReflowGoal;
-use style::error_reporting::ParseErrorReporter;
+use std::sync::{Arc, Mutex};
+use style::dom::OpaqueNode;
+use style::error_reporting::{ContextualParseError, ParseErrorReporter};
use style::media_queries;
-use style::parser::{LengthParsingMode, ParserContext as CssParserContext};
-use style::properties::PropertyId;
-use style::properties::longhands::overflow_x;
+use style::parser::ParserContext as CssParserContext;
+use style::properties::style_structs::Font;
+use style::properties::{PropertyId, ShorthandId};
use style::selector_parser::PseudoElement;
use style::str::HTML_SPACE_CHARACTERS;
-use style::stylesheets::CssRuleType;
-use task_source::dom_manipulation::DOMManipulationTaskSource;
-use task_source::file_reading::FileReadingTaskSource;
-use task_source::history_traversal::HistoryTraversalTaskSource;
-use task_source::networking::NetworkingTaskSource;
-use task_source::user_interaction::UserInteractionTaskSource;
-use time;
-use timers::{IsInterval, TimerCallback};
-#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))]
-use tinyfiledialogs::{self, MessageBoxIcon};
+use style::stylesheets::{CssRuleType, Origin};
+use style_traits::{CSSPixel, DevicePixel, ParsingMode};
use url::Position;
-use webdriver_handlers::jsval_to_webdriver;
-use webrender_traits::ClipId;
-use webvr_traits::WebVRMsg;
+use webrender_api::units::{DeviceIntPoint, DeviceIntSize, LayoutPixel};
+use webrender_api::{DocumentId, ExternalScrollId};
/// Current state of the window object
-#[derive(JSTraceable, Copy, Clone, Debug, PartialEq, HeapSizeOf)]
+#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
enum WindowState {
Alive,
- Zombie, // Pipeline is closed, but the window hasn't been GCed yet.
+ Zombie, // Pipeline is closed, but the window hasn't been GCed yet.
}
/// Extra information concerning the reason for reflowing.
-#[derive(Debug, HeapSizeOf)]
+#[derive(Debug, MallocSizeOf)]
pub enum ReflowReason {
CachedPageNeededReflow,
RefreshTick,
@@ -149,82 +173,80 @@ pub enum ReflowReason {
ImageLoaded,
RequestAnimationFrame,
WebFontLoaded,
+ WorkletLoaded,
FramedContentChanged,
IFrameLoadEvent,
MissingExplicitReflow,
ElementStateChanged,
+ PendingReflow,
}
#[dom_struct]
pub struct Window {
globalscope: GlobalScope,
- #[ignore_heap_size_of = "trait objects are hard"]
+ #[ignore_malloc_size_of = "trait objects are hard"]
script_chan: MainThreadScriptChan,
- #[ignore_heap_size_of = "task sources are hard"]
- dom_manipulation_task_source: DOMManipulationTaskSource,
- #[ignore_heap_size_of = "task sources are hard"]
- user_interaction_task_source: UserInteractionTaskSource,
- #[ignore_heap_size_of = "task sources are hard"]
- networking_task_source: NetworkingTaskSource,
- #[ignore_heap_size_of = "task sources are hard"]
- history_traversal_task_source: HistoryTraversalTaskSource,
- #[ignore_heap_size_of = "task sources are hard"]
- file_reading_task_source: FileReadingTaskSource,
- navigator: MutNullableJS<Navigator>,
- #[ignore_heap_size_of = "Arc"]
- image_cache: Arc<ImageCache>,
- #[ignore_heap_size_of = "channels are hard"]
+ task_manager: TaskManager,
+ navigator: MutNullableDom<Navigator>,
+ #[ignore_malloc_size_of = "Arc"]
+ image_cache: Arc<dyn ImageCache>,
+ #[ignore_malloc_size_of = "channels are hard"]
image_cache_chan: Sender<ImageCacheMsg>,
- browsing_context: MutNullableJS<BrowsingContext>,
- document: MutNullableJS<Document>,
- history: MutNullableJS<History>,
- performance: MutNullableJS<Performance>,
- navigation_start: u64,
- navigation_start_precise: f64,
- screen: MutNullableJS<Screen>,
- session_storage: MutNullableJS<Storage>,
- local_storage: MutNullableJS<Storage>,
- status: DOMRefCell<DOMString>,
+ window_proxy: MutNullableDom<WindowProxy>,
+ document: MutNullableDom<Document>,
+ location: MutNullableDom<Location>,
+ history: MutNullableDom<History>,
+ custom_element_registry: MutNullableDom<CustomElementRegistry>,
+ performance: MutNullableDom<Performance>,
+ navigation_start: Cell<u64>,
+ navigation_start_precise: Cell<u64>,
+ screen: MutNullableDom<Screen>,
+ session_storage: MutNullableDom<Storage>,
+ local_storage: MutNullableDom<Storage>,
+ status: DomRefCell<DOMString>,
/// For sending timeline markers. Will be ignored if
/// no devtools server
- devtools_markers: DOMRefCell<HashSet<TimelineMarkerType>>,
- #[ignore_heap_size_of = "channels are hard"]
- devtools_marker_sender: DOMRefCell<Option<IpcSender<Option<TimelineMarker>>>>,
+ devtools_markers: DomRefCell<HashSet<TimelineMarkerType>>,
+ #[ignore_malloc_size_of = "channels are hard"]
+ devtools_marker_sender: DomRefCell<Option<IpcSender<Option<TimelineMarker>>>>,
/// Pending resize event, if any.
resize_event: Cell<Option<(WindowSizeData, WindowSizeType)>>,
/// Parent id associated with this page, if any.
- parent_info: Option<(PipelineId, FrameType)>,
+ parent_info: Option<PipelineId>,
/// Global static data related to the DOM.
dom_static: GlobalStaticData,
/// The JavaScript runtime.
- #[ignore_heap_size_of = "Rc<T> is hard"]
- js_runtime: DOMRefCell<Option<Rc<Runtime>>>,
+ #[ignore_malloc_size_of = "Rc<T> is hard"]
+ js_runtime: DomRefCell<Option<Rc<Runtime>>>,
/// A handle for communicating messages to the layout thread.
- #[ignore_heap_size_of = "channels are hard"]
+ ///
+ /// This channel shouldn't be accessed directly, but through `Window::layout_chan()`,
+ /// which returns `None` if there's no layout thread anymore.
+ #[ignore_malloc_size_of = "channels are hard"]
layout_chan: Sender<Msg>,
/// A handle to perform RPC calls into the layout, quickly.
- #[ignore_heap_size_of = "trait objects are hard"]
- layout_rpc: Box<LayoutRPC + Send + 'static>,
+ #[ignore_malloc_size_of = "trait objects are hard"]
+ layout_rpc: Box<dyn LayoutRPC + Send + 'static>,
/// The current size of the window, in pixels.
- window_size: Cell<Option<WindowSizeData>>,
+ window_size: Cell<WindowSizeData>,
/// A handle for communicating messages to the bluetooth thread.
- #[ignore_heap_size_of = "channels are hard"]
+ #[ignore_malloc_size_of = "channels are hard"]
bluetooth_thread: IpcSender<BluetoothRequest>,
bluetooth_extra_permission_data: BluetoothExtraPermissionData,
/// An enlarged rectangle around the page contents visible in the viewport, used
/// to prevent creating display list items for content that is far away from the viewport.
- page_clip_rect: Cell<Rect<Au>>,
+ page_clip_rect: Cell<UntypedRect<Au>>,
/// Flag to suppress reflows. The first reflow will come either with
/// RefreshTick or with FirstLoad. Until those first reflows, we want to
@@ -235,109 +257,203 @@ pub struct Window {
pending_reflow_count: Cell<u32>,
/// A channel for communicating results of async scripts back to the webdriver server
- #[ignore_heap_size_of = "channels are hard"]
- webdriver_script_chan: DOMRefCell<Option<IpcSender<WebDriverJSResult>>>,
+ #[ignore_malloc_size_of = "channels are hard"]
+ webdriver_script_chan: DomRefCell<Option<IpcSender<WebDriverJSResult>>>,
/// The current state of the window object
current_state: Cell<WindowState>,
- current_viewport: Cell<Rect<Au>>,
-
- /// A flag to prevent async events from attempting to interact with this window.
- #[ignore_heap_size_of = "defined in std"]
- ignore_further_async_events: DOMRefCell<Arc<AtomicBool>>,
+ current_viewport: Cell<UntypedRect<Au>>,
error_reporter: CSSErrorReporter,
/// A list of scroll offsets for each scrollable element.
- scroll_offsets: DOMRefCell<HashMap<UntrustedNodeAddress, Point2D<f32>>>,
+ scroll_offsets: DomRefCell<HashMap<OpaqueNode, Vector2D<f32, LayoutPixel>>>,
/// All the MediaQueryLists we need to update
- media_query_lists: WeakMediaQueryListVec,
+ media_query_lists: DOMTracker<MediaQueryList>,
- test_runner: MutNullableJS<TestRunner>,
+ test_runner: MutNullableDom<TestRunner>,
- /// A handle for communicating messages to the webvr thread, if available.
- #[ignore_heap_size_of = "channels are hard"]
- webvr_thread: Option<IpcSender<WebVRMsg>>,
+ /// A handle for communicating messages to the WebGL thread, if available.
+ #[ignore_malloc_size_of = "channels are hard"]
+ webgl_chan: Option<WebGLChan>,
- /// A map for storing the previous permission state read results.
- permission_state_invocation_results: DOMRefCell<HashMap<String, PermissionState>>,
+ #[ignore_malloc_size_of = "defined in webxr"]
+ webxr_registry: webxr_api::Registry,
/// 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>>>>,
+ pending_layout_images: DomRefCell<HashMap<PendingImageId, Vec<Dom<Node>>>>,
/// Directory to store unminified scripts for this window if unminify-js
/// opt is enabled.
- unminified_js_dir: DOMRefCell<Option<String>>,
+ unminified_js_dir: DomRefCell<Option<String>>,
+
+ /// Directory with stored unminified scripts
+ local_script_source: Option<String>,
+
+ /// Worklets
+ test_worklet: MutNullableDom<Worklet>,
+ /// <https://drafts.css-houdini.org/css-paint-api-1/#paint-worklet>
+ paint_worklet: MutNullableDom<Worklet>,
+ /// The Webrender Document id associated with this window.
+ #[ignore_malloc_size_of = "defined in webrender_api"]
+ webrender_document: DocumentId,
+
+ /// Flag to identify whether mutation observers are present(true)/absent(false)
+ exists_mut_observer: Cell<bool>,
+
+ /// Webrender API Sender
+ #[ignore_malloc_size_of = "Wraps an IpcSender"]
+ webrender_api_sender: WebrenderIpcSender,
+
+ /// Indicate whether a SetDocumentStatus message has been sent after a reflow is complete.
+ /// It is used to avoid sending idle message more than once, which is unneccessary.
+ has_sent_idle_message: Cell<bool>,
+
+ /// Flag that indicates if the layout thread is busy handling a request.
+ #[ignore_malloc_size_of = "Arc<T> is hard"]
+ layout_is_busy: Arc<AtomicBool>,
+
+ /// Emits notifications when there is a relayout.
+ relayout_event: bool,
+
+ /// True if it is safe to write to the image.
+ prepare_for_screenshot: bool,
+
+ /// Unminify Javascript.
+ unminify_js: bool,
+
+ /// Where to load userscripts from, if any. An empty string will load from
+ /// the resources/user-agent-js directory, and if the option isn't passed userscripts
+ /// won't be loaded.
+ userscripts_path: Option<String>,
+
+ /// Replace unpaired surrogates in DOM strings with U+FFFD.
+ /// See <https://github.com/servo/servo/issues/6564>
+ replace_surrogates: bool,
+
+ /// Window's GL context from application
+ #[ignore_malloc_size_of = "defined in script_thread"]
+ player_context: WindowGLContext,
+
+ /// A mechanism to force the compositor to process events.
+ #[ignore_malloc_size_of = "traits are cumbersome"]
+ event_loop_waker: Option<Box<dyn EventLoopWaker>>,
+
+ visible: Cell<bool>,
+
+ /// A shared marker for the validity of any cached layout values. A value of true
+ /// indicates that any such values remain valid; any new layout that invalidates
+ /// those values will cause the marker to be set to false.
+ #[ignore_malloc_size_of = "Rc is hard"]
+ layout_marker: DomRefCell<Rc<Cell<bool>>>,
+
+ /// https://dom.spec.whatwg.org/#window-current-event
+ current_event: DomRefCell<Option<Dom<Event>>>,
}
impl Window {
+ pub fn task_manager(&self) -> &TaskManager {
+ &self.task_manager
+ }
+
+ pub fn get_exists_mut_observer(&self) -> bool {
+ self.exists_mut_observer.get()
+ }
+
+ pub fn set_exists_mut_observer(&self) {
+ self.exists_mut_observer.set(true);
+ }
+
#[allow(unsafe_code)]
pub fn clear_js_runtime_for_script_deallocation(&self) {
unsafe {
*self.js_runtime.borrow_for_script_deallocation() = None;
- self.browsing_context.set(None);
+ self.window_proxy.set(None);
self.current_state.set(WindowState::Zombie);
- self.ignore_further_async_events.borrow().store(true, Ordering::Relaxed);
+ self.ignore_all_tasks();
}
}
- pub fn origin(&self) -> &MutableOrigin {
- self.globalscope.origin()
- }
-
- pub fn get_cx(&self) -> *mut JSContext {
- self.js_runtime.borrow().as_ref().unwrap().cx()
- }
-
- pub fn dom_manipulation_task_source(&self) -> DOMManipulationTaskSource {
- self.dom_manipulation_task_source.clone()
+ /// A convenience method for
+ /// https://html.spec.whatwg.org/multipage/#a-browsing-context-is-discarded
+ pub fn discard_browsing_context(&self) {
+ let proxy = match self.window_proxy.get() {
+ Some(proxy) => proxy,
+ None => panic!("Discarding a BC from a window that has none"),
+ };
+ proxy.discard_browsing_context();
+ // Step 4 of https://html.spec.whatwg.org/multipage/#discard-a-document
+ // Other steps performed when the `PipelineExit` message
+ // is handled by the ScriptThread.
+ self.ignore_all_tasks();
+ }
+
+ /// Cancel all current, and ignore all subsequently queued, tasks.
+ pub fn ignore_all_tasks(&self) {
+ let mut ignore_flags = self.task_manager.task_cancellers.borrow_mut();
+ for task_source_name in TaskSourceName::all() {
+ let flag = ignore_flags
+ .entry(task_source_name)
+ .or_insert(Default::default());
+ flag.store(true, Ordering::SeqCst);
+ }
}
- pub fn user_interaction_task_source(&self) -> UserInteractionTaskSource {
- self.user_interaction_task_source.clone()
+ /// Get a sender to the time profiler thread.
+ pub fn time_profiler_chan(&self) -> &TimeProfilerChan {
+ self.globalscope.time_profiler_chan()
}
- pub fn networking_task_source(&self) -> NetworkingTaskSource {
- self.networking_task_source.clone()
+ pub fn origin(&self) -> &MutableOrigin {
+ self.globalscope.origin()
}
- pub fn history_traversal_task_source(&self) -> Box<ScriptChan + Send> {
- self.history_traversal_task_source.clone()
+ #[allow(unsafe_code)]
+ pub fn get_cx(&self) -> JSContext {
+ unsafe { JSContext::from_ptr(self.js_runtime.borrow().as_ref().unwrap().cx()) }
}
- pub fn file_reading_task_source(&self) -> FileReadingTaskSource {
- self.file_reading_task_source.clone()
+ pub fn get_js_runtime(&self) -> Ref<Option<Rc<Runtime>>> {
+ self.js_runtime.borrow()
}
pub fn main_thread_script_chan(&self) -> &Sender<MainThreadScriptMsg> {
&self.script_chan.0
}
- pub fn parent_info(&self) -> Option<(PipelineId, FrameType)> {
+ pub fn parent_info(&self) -> Option<PipelineId> {
self.parent_info
}
- pub fn new_script_pair(&self) -> (Box<ScriptChan + Send>, Box<ScriptPort + Send>) {
- let (tx, rx) = channel();
- (box SendableMainThreadScriptChan(tx), box rx)
+ pub fn new_script_pair(&self) -> (Box<dyn ScriptChan + Send>, Box<dyn ScriptPort + Send>) {
+ let (tx, rx) = unbounded();
+ (Box::new(SendableMainThreadScriptChan(tx)), Box::new(rx))
}
- pub fn image_cache(&self) -> Arc<ImageCache> {
+ pub fn image_cache(&self) -> Arc<dyn ImageCache> {
self.image_cache.clone()
}
/// This can panic if it is called after the browsing context has been discarded
- pub fn browsing_context(&self) -> Root<BrowsingContext> {
- self.browsing_context.get().unwrap()
- }
-
- pub fn maybe_browsing_context(&self) -> Option<Root<BrowsingContext>> {
- self.browsing_context.get()
+ pub fn window_proxy(&self) -> DomRoot<WindowProxy> {
+ self.window_proxy.get().unwrap()
+ }
+
+ /// Returns the window proxy if it has not been discarded.
+ /// <https://html.spec.whatwg.org/multipage/#a-browsing-context-is-discarded>
+ pub fn undiscarded_window_proxy(&self) -> Option<DomRoot<WindowProxy>> {
+ self.window_proxy.get().and_then(|window_proxy| {
+ if window_proxy.is_browsing_context_discarded() {
+ None
+ } else {
+ Some(window_proxy)
+ }
+ })
}
pub fn bluetooth_thread(&self) -> IpcSender<BluetoothRequest> {
@@ -345,30 +461,37 @@ impl Window {
}
pub fn bluetooth_extra_permission_data(&self) -> &BluetoothExtraPermissionData {
- &self.bluetooth_extra_permission_data
+ &self.bluetooth_extra_permission_data
}
- pub fn css_error_reporter(&self) -> &ParseErrorReporter {
- &self.error_reporter
+ pub fn css_error_reporter(&self) -> Option<&dyn ParseErrorReporter> {
+ Some(&self.error_reporter)
}
/// Sets a new list of scroll offsets.
///
/// This is called when layout gives us new ones and WebRender is in use.
- pub fn set_scroll_offsets(&self, offsets: HashMap<UntrustedNodeAddress, Point2D<f32>>) {
+ pub fn set_scroll_offsets(&self, offsets: HashMap<OpaqueNode, Vector2D<f32, LayoutPixel>>) {
*self.scroll_offsets.borrow_mut() = offsets
}
- pub fn current_viewport(&self) -> Rect<Au> {
+ pub fn current_viewport(&self) -> UntypedRect<Au> {
self.current_viewport.clone().get()
}
- pub fn webvr_thread(&self) -> Option<IpcSender<WebVRMsg>> {
- self.webvr_thread.clone()
+ pub(crate) fn webgl_chan(&self) -> Option<WebGLCommandSender> {
+ self.webgl_chan
+ .as_ref()
+ .map(|chan| WebGLCommandSender::new(chan.clone(), self.get_event_loop_waker()))
+ }
+
+ pub fn webxr_registry(&self) -> webxr_api::Registry {
+ self.webxr_registry.clone()
}
- pub fn permission_state_invocation_results(&self) -> &DOMRefCell<HashMap<String, PermissionState>> {
- &self.permission_state_invocation_results
+ fn new_paint_worklet(&self) -> DomRoot<Worklet> {
+ debug!("Creating new paint worklet.");
+ Worklet::new(self, WorkletGlobalScopeType::Paint)
}
pub fn pending_image_notification(&self, response: PendingImageResponse) {
@@ -385,25 +508,47 @@ impl Window {
node.dirty(NodeDamage::OtherNodeDamage);
}
match response.response {
- ImageResponse::MetadataLoaded(_) => {}
- ImageResponse::Loaded(_) |
- ImageResponse::PlaceholderLoaded(_) |
- ImageResponse::None => { nodes.remove(); }
+ 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"))]
-fn display_alert_dialog(message: &str) {
- if !opts::get().headless {
- tinyfiledialogs::message_box_ok("Alert!", message, MessageBoxIcon::Warning);
+ pub fn get_webrender_api_sender(&self) -> WebrenderIpcSender {
+ self.webrender_api_sender.clone()
+ }
+
+ pub fn get_userscripts_path(&self) -> Option<String> {
+ self.userscripts_path.clone()
+ }
+
+ pub fn replace_surrogates(&self) -> bool {
+ self.replace_surrogates
+ }
+
+ pub fn unminify_js(&self) -> bool {
+ self.unminify_js
+ }
+
+ pub fn get_player_context(&self) -> WindowGLContext {
+ self.player_context.clone()
}
-}
-#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
-fn display_alert_dialog(_message: &str) {
- // tinyfiledialogs not supported on Android
+ pub fn get_event_loop_waker(&self) -> Option<Box<dyn EventLoopWaker>> {
+ self.event_loop_waker.as_ref().map(|w| (*w).clone_box())
+ }
+
+ // see note at https://dom.spec.whatwg.org/#concept-event-dispatch step 2
+ pub fn dispatch_event_with_target_override(&self, event: &Event) -> EventStatus {
+ if self.has_document() {
+ assert!(self.Document().can_invoke_script());
+ }
+ event.dispatch(self.upcast(), true)
+ }
}
// https://html.spec.whatwg.org/multipage/#atob
@@ -432,7 +577,8 @@ pub fn base64_atob(input: DOMString) -> Fallible<DOMString> {
fn is_html_space(c: char) -> bool {
HTML_SPACE_CHARACTERS.iter().any(|&m| m == c)
}
- let without_spaces = input.chars()
+ let without_spaces = input
+ .chars()
.filter(|&c| !is_html_space(c))
.collect::<String>();
let mut input = &*without_spaces;
@@ -451,7 +597,7 @@ pub fn base64_atob(input: DOMString) -> Fallible<DOMString> {
// "If the length of input divides by 4 leaving a remainder of 1,
// throw an InvalidCharacterError exception and abort these steps."
if input.len() % 4 == 1 {
- return Err(Error::InvalidCharacter)
+ return Err(Error::InvalidCharacter);
}
// "If input contains a character that is not in the following list of
@@ -461,14 +607,16 @@ pub fn base64_atob(input: DOMString) -> Fallible<DOMString> {
// U+002B PLUS SIGN (+)
// U+002F SOLIDUS (/)
// Alphanumeric ASCII characters"
- if input.chars().any(|c| c != '+' && c != '/' && !c.is_alphanumeric()) {
- return Err(Error::InvalidCharacter)
+ if input
+ .chars()
+ .any(|c| c != '+' && c != '/' && !c.is_alphanumeric())
+ {
+ return Err(Error::InvalidCharacter);
}
- match base64::decode(&input) {
- Ok(data) => Ok(DOMString::from(data.iter().map(|&b| b as char).collect::<String>())),
- Err(..) => Err(Error::InvalidCharacter)
- }
+ let data = base64::decode_config(&input, base64::STANDARD.decode_allow_trailing_bits(true))
+ .map_err(|_| Error::InvalidCharacter)?;
+ Ok(data.iter().map(|&b| b as char).collect::<String>().into())
}
impl WindowMethods for Window {
@@ -479,7 +627,7 @@ impl WindowMethods for Window {
// https://html.spec.whatwg.org/multipage/#dom-alert
fn Alert(&self, s: DOMString) {
- // Right now, just print to the console
+ // Print to the console.
// Ensure that stderr doesn't trample through the alert() we use to
// communicate test results (see executorservo.py in wptrunner).
{
@@ -487,135 +635,269 @@ impl WindowMethods for Window {
let mut stderr = stderr.lock();
let stdout = stdout();
let mut stdout = stdout.lock();
- writeln!(&mut stdout, "ALERT: {}", s).unwrap();
+ writeln!(&mut stdout, "\nALERT: {}", s).unwrap();
stdout.flush().unwrap();
stderr.flush().unwrap();
}
+ let (sender, receiver) =
+ ProfiledIpc::channel(self.global().time_profiler_chan().clone()).unwrap();
+ let prompt = PromptDefinition::Alert(s.to_string(), sender);
+ let msg = EmbedderMsg::Prompt(prompt, PromptOrigin::Untrusted);
+ self.send_to_embedder(msg);
+ receiver.recv().unwrap();
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-confirm
+ fn Confirm(&self, s: DOMString) -> bool {
+ let (sender, receiver) =
+ ProfiledIpc::channel(self.global().time_profiler_chan().clone()).unwrap();
+ let prompt = PromptDefinition::OkCancel(s.to_string(), sender);
+ let msg = EmbedderMsg::Prompt(prompt, PromptOrigin::Untrusted);
+ self.send_to_embedder(msg);
+ receiver.recv().unwrap() == PromptResult::Primary
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-prompt
+ fn Prompt(&self, message: DOMString, default: DOMString) -> Option<DOMString> {
+ let (sender, receiver) =
+ ProfiledIpc::channel(self.global().time_profiler_chan().clone()).unwrap();
+ let prompt = PromptDefinition::Input(message.to_string(), default.to_string(), sender);
+ let msg = EmbedderMsg::Prompt(prompt, PromptOrigin::Untrusted);
+ self.send_to_embedder(msg);
+ receiver.recv().unwrap().map(|s| s.into())
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-window-stop
+ fn Stop(&self) {
+ // TODO: Cancel ongoing navigation.
+ let doc = self.Document();
+ doc.abort();
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-open
+ fn Open(
+ &self,
+ url: USVString,
+ target: DOMString,
+ features: DOMString,
+ ) -> Fallible<Option<DomRoot<WindowProxy>>> {
+ self.window_proxy().open(url, target, features)
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-opener
+ fn Opener(&self, cx: JSContext, in_realm_proof: InRealm) -> JSVal {
+ // Step 1, Let current be this Window object's browsing context.
+ let current = match self.window_proxy.get() {
+ Some(proxy) => proxy,
+ // Step 2, If current is null, then return null.
+ None => return NullValue(),
+ };
+ // Still step 2, since the window's BC is the associated doc's BC,
+ // see https://html.spec.whatwg.org/multipage/#window-bc
+ // and a doc's BC is null if it has been discarded.
+ // see https://html.spec.whatwg.org/multipage/#concept-document-bc
+ if current.is_browsing_context_discarded() {
+ return NullValue();
+ }
+ // Step 3 to 5.
+ current.opener(*cx, in_realm_proof)
+ }
- let (sender, receiver) = ipc::channel().unwrap();
- let global_scope = self.upcast::<GlobalScope>();
- global_scope
- .constellation_chan()
- .send(ConstellationMsg::Alert(global_scope.pipeline_id(), s.to_string(), sender))
- .unwrap();
-
- let should_display_alert_dialog = receiver.recv().unwrap();
- if should_display_alert_dialog {
- display_alert_dialog(&s);
+ #[allow(unsafe_code)]
+ // https://html.spec.whatwg.org/multipage/#dom-opener
+ fn SetOpener(&self, cx: JSContext, value: HandleValue) {
+ // Step 1.
+ if value.is_null() {
+ return self.window_proxy().disown();
+ }
+ // Step 2.
+ let obj = self.reflector().get_jsobject();
+ unsafe {
+ assert!(JS_DefineProperty(
+ *cx,
+ obj,
+ "opener\0".as_ptr() as *const libc::c_char,
+ value,
+ JSPROP_ENUMERATE as u32
+ ));
}
}
+ // https://html.spec.whatwg.org/multipage/#dom-window-closed
+ fn Closed(&self) -> bool {
+ self.window_proxy
+ .get()
+ .map(|ref proxy| proxy.is_browsing_context_discarded() || proxy.is_closing())
+ .unwrap_or(true)
+ }
+
// https://html.spec.whatwg.org/multipage/#dom-window-close
fn Close(&self) {
- self.main_thread_script_chan()
- .send(MainThreadScriptMsg::ExitWindow(self.upcast::<GlobalScope>().pipeline_id()))
- .unwrap();
+ // Step 1, Let current be this Window object's browsing context.
+ // Step 2, If current is null or its is closing is true, then return.
+ let window_proxy = match self.window_proxy.get() {
+ Some(proxy) => proxy,
+ None => return,
+ };
+ if window_proxy.is_closing() {
+ return;
+ }
+ // Note: check the length of the "session history", as opposed to the joint session history?
+ // see https://github.com/whatwg/html/issues/3734
+ if let Ok(history_length) = self.History().GetLength() {
+ let is_auxiliary = window_proxy.is_auxiliary();
+
+ // https://html.spec.whatwg.org/multipage/#script-closable
+ let is_script_closable = (self.is_top_level() && history_length == 1) || is_auxiliary;
+
+ // TODO: rest of Step 3:
+ // Is the incumbent settings object's responsible browsing context familiar with current?
+ // Is the incumbent settings object's responsible browsing context allowed to navigate current?
+ if is_script_closable {
+ // Step 3.1, set current's is closing to true.
+ window_proxy.close();
+
+ // Step 3.2, queue a task on the DOM manipulation task source to close current.
+ let this = Trusted::new(self);
+ let task = task!(window_close_browsing_context: move || {
+ let window = this.root();
+ let document = window.Document();
+ // https://html.spec.whatwg.org/multipage/#closing-browsing-contexts
+ // Step 1, prompt to unload.
+ if document.prompt_to_unload(false) {
+ // Step 2, unload.
+ document.unload(false);
+ // Step 3, remove from the user interface
+ let _ = window.send_to_embedder(EmbedderMsg::CloseBrowser);
+ // Step 4, discard browsing context.
+ // https://html.spec.whatwg.org/multipage/#a-browsing-context-is-discarded
+ // which calls into https://html.spec.whatwg.org/multipage/#discard-a-document.
+ window.discard_browsing_context();
+
+ let _ = window.send_to_constellation(ScriptMsg::DiscardTopLevelBrowsingContext);
+ }
+ });
+ self.task_manager()
+ .dom_manipulation_task_source()
+ .queue(task, &self.upcast::<GlobalScope>())
+ .expect("Queuing window_close_browsing_context task to work");
+ }
+ }
}
// https://html.spec.whatwg.org/multipage/#dom-document-2
- fn Document(&self) -> Root<Document> {
- self.document.get().expect("Document accessed before initialization.")
+ fn Document(&self) -> DomRoot<Document> {
+ self.document
+ .get()
+ .expect("Document accessed before initialization.")
}
// https://html.spec.whatwg.org/multipage/#dom-history
- fn History(&self) -> Root<History> {
+ fn History(&self) -> DomRoot<History> {
self.history.or_init(|| History::new(self))
}
+ // https://html.spec.whatwg.org/multipage/#dom-window-customelements
+ fn CustomElements(&self) -> DomRoot<CustomElementRegistry> {
+ self.custom_element_registry
+ .or_init(|| CustomElementRegistry::new(self))
+ }
+
// https://html.spec.whatwg.org/multipage/#dom-location
- fn Location(&self) -> Root<Location> {
- self.Document().GetLocation().unwrap()
+ fn Location(&self) -> DomRoot<Location> {
+ self.location.or_init(|| Location::new(self))
}
// https://html.spec.whatwg.org/multipage/#dom-sessionstorage
- fn SessionStorage(&self) -> Root<Storage> {
- self.session_storage.or_init(|| Storage::new(self, StorageType::Session))
+ fn SessionStorage(&self) -> DomRoot<Storage> {
+ self.session_storage
+ .or_init(|| Storage::new(self, StorageType::Session))
}
// https://html.spec.whatwg.org/multipage/#dom-localstorage
- fn LocalStorage(&self) -> Root<Storage> {
- self.local_storage.or_init(|| Storage::new(self, StorageType::Local))
+ fn LocalStorage(&self) -> DomRoot<Storage> {
+ self.local_storage
+ .or_init(|| Storage::new(self, StorageType::Local))
}
// https://dvcs.w3.org/hg/webcrypto-api/raw-file/tip/spec/Overview.html#dfn-GlobalCrypto
- fn Crypto(&self) -> Root<Crypto> {
+ fn Crypto(&self) -> DomRoot<Crypto> {
self.upcast::<GlobalScope>().crypto()
}
// https://html.spec.whatwg.org/multipage/#dom-frameelement
- fn GetFrameElement(&self) -> Option<Root<Element>> {
+ fn GetFrameElement(&self) -> Option<DomRoot<Element>> {
// Steps 1-3.
- let context = match self.browsing_context.get() {
- None => return None,
- Some(context) => context,
- };
+ let window_proxy = self.window_proxy.get()?;
+
// Step 4-5.
- let container = match context.frame_element() {
- None => return None,
- Some(container) => container,
- };
+ let container = window_proxy.frame_element()?;
+
// Step 6.
let container_doc = document_from_node(container);
- let current_doc = GlobalScope::current().as_window().Document();
- if !current_doc.origin().same_origin_domain(container_doc.origin()) {
+ let current_doc = GlobalScope::current()
+ .expect("No current global object")
+ .as_window()
+ .Document();
+ if !current_doc
+ .origin()
+ .same_origin_domain(container_doc.origin())
+ {
return None;
}
// Step 7.
- Some(Root::from_ref(container))
+ Some(DomRoot::from_ref(container))
}
// https://html.spec.whatwg.org/multipage/#dom-navigator
- fn Navigator(&self) -> Root<Navigator> {
+ fn Navigator(&self) -> DomRoot<Navigator> {
self.navigator.or_init(|| Navigator::new(self))
}
- #[allow(unsafe_code)]
- // https://html.spec.whatwg.org/multipage/#dom-windowtimers-settimeout
- unsafe fn SetTimeout(&self, _cx: *mut JSContext, callback: Rc<Function>, timeout: i32,
- args: Vec<HandleValue>) -> i32 {
- self.upcast::<GlobalScope>().set_timeout_or_interval(
- TimerCallback::FunctionTimerCallback(callback),
- args,
- timeout,
- IsInterval::NonInterval)
- }
-
- #[allow(unsafe_code)]
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-settimeout
- unsafe fn SetTimeout_(&self, _cx: *mut JSContext, callback: DOMString,
- timeout: i32, args: Vec<HandleValue>) -> i32 {
+ fn SetTimeout(
+ &self,
+ _cx: JSContext,
+ callback: StringOrFunction,
+ timeout: i32,
+ args: Vec<HandleValue>,
+ ) -> i32 {
+ let callback = match callback {
+ StringOrFunction::String(i) => TimerCallback::StringTimerCallback(i),
+ StringOrFunction::Function(i) => TimerCallback::FunctionTimerCallback(i),
+ };
self.upcast::<GlobalScope>().set_timeout_or_interval(
- TimerCallback::StringTimerCallback(callback),
+ callback,
args,
timeout,
- IsInterval::NonInterval)
+ IsInterval::NonInterval,
+ )
}
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-cleartimeout
fn ClearTimeout(&self, handle: i32) {
- self.upcast::<GlobalScope>().clear_timeout_or_interval(handle);
- }
-
- #[allow(unsafe_code)]
- // https://html.spec.whatwg.org/multipage/#dom-windowtimers-setinterval
- unsafe fn SetInterval(&self, _cx: *mut JSContext, callback: Rc<Function>,
- timeout: i32, args: Vec<HandleValue>) -> i32 {
- self.upcast::<GlobalScope>().set_timeout_or_interval(
- TimerCallback::FunctionTimerCallback(callback),
- args,
- timeout,
- IsInterval::Interval)
+ self.upcast::<GlobalScope>()
+ .clear_timeout_or_interval(handle);
}
- #[allow(unsafe_code)]
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-setinterval
- unsafe fn SetInterval_(&self, _cx: *mut JSContext, callback: DOMString,
- timeout: i32, args: Vec<HandleValue>) -> i32 {
+ fn SetInterval(
+ &self,
+ _cx: JSContext,
+ callback: StringOrFunction,
+ timeout: i32,
+ args: Vec<HandleValue>,
+ ) -> i32 {
+ let callback = match callback {
+ StringOrFunction::String(i) => TimerCallback::StringTimerCallback(i),
+ StringOrFunction::Function(i) => TimerCallback::FunctionTimerCallback(i),
+ };
self.upcast::<GlobalScope>().set_timeout_or_interval(
- TimerCallback::StringTimerCallback(callback),
+ callback,
args,
timeout,
- IsInterval::Interval)
+ IsInterval::Interval,
+ )
}
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-clearinterval
@@ -623,60 +905,74 @@ impl WindowMethods for Window {
self.ClearTimeout(handle);
}
+ // https://html.spec.whatwg.org/multipage/#dom-queuemicrotask
+ fn QueueMicrotask(&self, callback: Rc<VoidFunction>) {
+ self.upcast::<GlobalScope>()
+ .queue_function_as_microtask(callback);
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-createimagebitmap
+ fn CreateImageBitmap(
+ &self,
+ image: ImageBitmapSource,
+ options: &ImageBitmapOptions,
+ ) -> Rc<Promise> {
+ let p = self
+ .upcast::<GlobalScope>()
+ .create_image_bitmap(image, options);
+ p
+ }
+
// https://html.spec.whatwg.org/multipage/#dom-window
- fn Window(&self) -> Root<BrowsingContext> {
- self.browsing_context()
+ fn Window(&self) -> DomRoot<WindowProxy> {
+ self.window_proxy()
}
// https://html.spec.whatwg.org/multipage/#dom-self
- fn Self_(&self) -> Root<BrowsingContext> {
- self.browsing_context()
+ fn Self_(&self) -> DomRoot<WindowProxy> {
+ self.window_proxy()
}
// https://html.spec.whatwg.org/multipage/#dom-frames
- fn Frames(&self) -> Root<BrowsingContext> {
+ fn Frames(&self) -> DomRoot<WindowProxy> {
println!("frames!");
- self.browsing_context()
+ self.window_proxy()
+ }
+
+ // https://html.spec.whatwg.org/multipage/#accessing-other-browsing-contexts
+ fn Length(&self) -> u32 {
+ let doc = self.Document();
+ doc.iter_iframes().count() as u32
}
// https://html.spec.whatwg.org/multipage/#dom-parent
- fn GetParent(&self) -> Option<Root<BrowsingContext>> {
+ fn GetParent(&self) -> Option<DomRoot<WindowProxy>> {
// Steps 1-3.
- let context = match self.maybe_browsing_context() {
- Some(context) => context,
- None => return None,
- };
- if context.is_discarded() {
- return None;
- }
+ let window_proxy = self.undiscarded_window_proxy()?;
+
// Step 4.
- if let Some(parent) = context.parent() {
- return Some(Root::from_ref(parent));
+ if let Some(parent) = window_proxy.parent() {
+ return Some(DomRoot::from_ref(parent));
}
// Step 5.
- Some(context)
+ Some(window_proxy)
}
// https://html.spec.whatwg.org/multipage/#dom-top
- fn GetTop(&self) -> Option<Root<BrowsingContext>> {
+ fn GetTop(&self) -> Option<DomRoot<WindowProxy>> {
// Steps 1-3.
- let context = match self.maybe_browsing_context() {
- Some(context) => context,
- None => return None,
- };
- if context.is_discarded() {
- return None;
- }
+ let window_proxy = self.undiscarded_window_proxy()?;
+
// Steps 4-5.
- Some(Root::from_ref(context.top()))
+ Some(DomRoot::from_ref(window_proxy.top()))
}
// https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/
// NavigationTiming/Overview.html#sec-window.performance-attribute
- fn Performance(&self) -> Root<Performance> {
+ fn Performance(&self) -> DomRoot<Performance> {
self.performance.or_init(|| {
- Performance::new(self, self.navigation_start,
- self.navigation_start_precise)
+ let global_scope = self.upcast::<GlobalScope>();
+ Performance::new(global_scope, self.navigation_start_precise.get())
})
}
@@ -687,7 +983,7 @@ impl WindowMethods for Window {
window_event_handlers!();
// https://developer.mozilla.org/en-US/docs/Web/API/Window/screen
- fn Screen(&self) -> Root<Screen> {
+ fn Screen(&self) -> DomRoot<Screen> {
self.screen.or_init(|| Screen::new(self))
}
@@ -701,46 +997,63 @@ impl WindowMethods for Window {
base64_atob(atob)
}
- /// https://html.spec.whatwg.org/multipage/#dom-window-requestanimationframe
+ /// <https://html.spec.whatwg.org/multipage/#dom-window-requestanimationframe>
fn RequestAnimationFrame(&self, callback: Rc<FrameRequestCallback>) -> u32 {
self.Document()
.request_animation_frame(AnimationFrameCallback::FrameRequestCallback { callback })
}
- /// https://html.spec.whatwg.org/multipage/#dom-window-cancelanimationframe
+ /// <https://html.spec.whatwg.org/multipage/#dom-window-cancelanimationframe>
fn CancelAnimationFrame(&self, ident: u32) {
let doc = self.Document();
doc.cancel_animation_frame(ident);
}
- #[allow(unsafe_code)]
// https://html.spec.whatwg.org/multipage/#dom-window-postmessage
- unsafe fn PostMessage(&self,
- cx: *mut JSContext,
- message: HandleValue,
- origin: DOMString)
- -> ErrorResult {
- // Step 3-5.
- let origin = match &origin[..] {
- "*" => None,
- "/" => {
- // TODO(#12715): Should be the origin of the incumbent settings
- // object, not self's.
- Some(self.Document().origin().immutable().clone())
- },
- url => match ServoUrl::parse(&url) {
- Ok(url) => Some(url.origin().clone()),
- Err(_) => return Err(Error::Syntax),
- }
- };
-
- // Step 1-2, 6-8.
- // TODO(#12717): Should implement the `transfer` argument.
- let data = try!(StructuredCloneData::write(cx, message));
-
- // Step 9.
- self.post_message(origin, data);
- Ok(())
+ fn PostMessage(
+ &self,
+ cx: JSContext,
+ message: HandleValue,
+ target_origin: USVString,
+ transfer: CustomAutoRooterGuard<Vec<*mut JSObject>>,
+ ) -> ErrorResult {
+ let incumbent = GlobalScope::incumbent().expect("no incumbent global?");
+ let source = incumbent.as_window();
+ let source_origin = source.Document().origin().immutable().clone();
+
+ self.post_message_impl(&target_origin, source_origin, source, cx, message, transfer)
+ }
+
+ /// <https://html.spec.whatwg.org/multipage/#dom-messageport-postmessage>
+ fn PostMessage_(
+ &self,
+ cx: JSContext,
+ message: HandleValue,
+ options: RootedTraceableBox<WindowPostMessageOptions>,
+ ) -> ErrorResult {
+ let mut rooted = CustomAutoRooter::new(
+ options
+ .parent
+ .transfer
+ .iter()
+ .map(|js: &RootedTraceableBox<Heap<*mut JSObject>>| js.get())
+ .collect(),
+ );
+ let transfer = CustomAutoRooterGuard::new(*cx, &mut rooted);
+
+ let incumbent = GlobalScope::incumbent().expect("no incumbent global?");
+ let source = incumbent.as_window();
+
+ let source_origin = source.Document().origin().immutable().clone();
+
+ self.post_message_impl(
+ &options.targetOrigin,
+ source_origin,
+ source,
+ cx,
+ message,
+ transfer,
+ )
}
// https://html.spec.whatwg.org/multipage/#dom-window-captureevents
@@ -761,7 +1074,7 @@ impl WindowMethods for Window {
#[allow(unsafe_code)]
fn Gc(&self) {
unsafe {
- JS_GC(JS_GetRuntime(self.get_cx()));
+ JS_GC(*self.get_cx(), GCReason::API);
}
}
@@ -771,8 +1084,22 @@ impl WindowMethods for Window {
}
#[allow(unsafe_code)]
- unsafe fn WebdriverCallback(&self, cx: *mut JSContext, val: HandleValue) {
- let rv = jsval_to_webdriver(cx, val);
+ fn Js_backtrace(&self) {
+ unsafe {
+ capture_stack!(in(*self.get_cx()) let stack);
+ let js_stack = stack.and_then(|s| s.as_string(None, StackFormat::SpiderMonkey));
+ let rust_stack = Backtrace::new();
+ println!(
+ "Current JS stack:\n{}\nCurrent Rust stack:\n{:?}",
+ js_stack.unwrap_or(String::new()),
+ rust_stack
+ );
+ }
+ }
+
+ #[allow(unsafe_code)]
+ fn WebdriverCallback(&self, cx: JSContext, val: HandleValue) {
+ let rv = unsafe { jsval_to_webdriver(*cx, &self.globalscope, val) };
let opt_chan = self.webdriver_script_chan.borrow_mut().take();
if let Some(chan) = opt_chan {
chan.send(rv).unwrap();
@@ -787,39 +1114,54 @@ impl WindowMethods for Window {
}
// https://drafts.csswg.org/cssom/#dom-window-getcomputedstyle
- fn GetComputedStyle(&self,
- element: &Element,
- pseudo: Option<DOMString>) -> Root<CSSStyleDeclaration> {
+ fn GetComputedStyle(
+ &self,
+ element: &Element,
+ pseudo: Option<DOMString>,
+ ) -> DomRoot<CSSStyleDeclaration> {
// Steps 1-4.
- let pseudo = match pseudo.map(|mut s| { s.make_ascii_lowercase(); s }) {
- Some(ref pseudo) if pseudo == ":before" || pseudo == "::before" =>
- Some(PseudoElement::Before),
- Some(ref pseudo) if pseudo == ":after" || pseudo == "::after" =>
- Some(PseudoElement::After),
- _ => None
+ let pseudo = match pseudo.map(|mut s| {
+ s.make_ascii_lowercase();
+ s
+ }) {
+ Some(ref pseudo) if pseudo == ":before" || pseudo == "::before" => {
+ Some(PseudoElement::Before)
+ },
+ Some(ref pseudo) if pseudo == ":after" || pseudo == "::after" => {
+ Some(PseudoElement::After)
+ },
+ _ => None,
};
// Step 5.
- CSSStyleDeclaration::new(self,
- CSSStyleOwner::Element(JS::from_ref(element)),
- pseudo,
- CSSModificationAccess::Readonly)
+ CSSStyleDeclaration::new(
+ self,
+ CSSStyleOwner::Element(Dom::from_ref(element)),
+ pseudo,
+ CSSModificationAccess::Readonly,
+ )
}
// https://drafts.csswg.org/cssom-view/#dom-window-innerheight
//TODO Include Scrollbar
fn InnerHeight(&self) -> i32 {
- self.window_size.get()
- .and_then(|e| e.initial_viewport.height.to_i32())
- .unwrap_or(0)
+ self.window_size
+ .get()
+ .initial_viewport
+ .height
+ .to_i32()
+ .unwrap_or(0)
}
// https://drafts.csswg.org/cssom-view/#dom-window-innerwidth
//TODO Include Scrollbar
fn InnerWidth(&self) -> i32 {
- self.window_size.get()
- .and_then(|e| e.initial_viewport.width.to_i32())
- .unwrap_or(0)
+ self.window_size
+ .get()
+ .initial_viewport
+ .width
+ .to_i32()
+ .unwrap_or(0)
}
// https://drafts.csswg.org/cssom-view/#dom-window-scrollx
@@ -848,7 +1190,6 @@ impl WindowMethods for Window {
let left = options.left.unwrap_or(0.0f64);
let top = options.top.unwrap_or(0.0f64);
self.scroll(left, top, options.parent.behavior);
-
}
// https://drafts.csswg.org/cssom-view/#dom-window-scroll
@@ -867,7 +1208,7 @@ impl WindowMethods for Window {
}
// https://drafts.csswg.org/cssom-view/#dom-window-scrollby
- fn ScrollBy(&self, options: &ScrollToOptions) {
+ fn ScrollBy(&self, options: &ScrollToOptions) {
// Step 1
let x = options.left.unwrap_or(0.0f64);
let y = options.top.unwrap_or(0.0f64);
@@ -876,43 +1217,43 @@ impl WindowMethods for Window {
}
// https://drafts.csswg.org/cssom-view/#dom-window-scrollby
- fn ScrollBy_(&self, x: f64, y: f64) {
+ fn ScrollBy_(&self, x: f64, y: f64) {
// Step 3
let left = x + self.ScrollX() as f64;
// Step 4
- let top = y + self.ScrollY() as f64;
+ let top = y + self.ScrollY() as f64;
// Step 5
self.scroll(left, top, ScrollBehavior::Auto);
}
// https://drafts.csswg.org/cssom-view/#dom-window-resizeto
- fn ResizeTo(&self, x: i32, y: i32) {
+ fn ResizeTo(&self, width: i32, height: i32) {
// Step 1
//TODO determine if this operation is allowed
- let size = Size2D::new(x.to_u32().unwrap_or(1), y.to_u32().unwrap_or(1));
- self.upcast::<GlobalScope>()
- .constellation_chan()
- .send(ConstellationMsg::ResizeTo(size))
- .unwrap()
+ let dpr = self.device_pixel_ratio();
+ let size = Size2D::new(width, height).to_f32() * dpr;
+ self.send_to_embedder(EmbedderMsg::ResizeTo(size.to_i32()));
}
// https://drafts.csswg.org/cssom-view/#dom-window-resizeby
fn ResizeBy(&self, x: i32, y: i32) {
let (size, _) = self.client_window();
// Step 1
- self.ResizeTo(x + size.width.to_i32().unwrap_or(1), y + size.height.to_i32().unwrap_or(1))
+ self.ResizeTo(
+ x + size.width.to_i32().unwrap_or(1),
+ y + size.height.to_i32().unwrap_or(1),
+ )
}
// https://drafts.csswg.org/cssom-view/#dom-window-moveto
fn MoveTo(&self, x: i32, y: i32) {
// Step 1
//TODO determine if this operation is allowed
- let point = Point2D::new(x, y);
- self.upcast::<GlobalScope>()
- .constellation_chan()
- .send(ConstellationMsg::MoveTo(point))
- .unwrap()
+ let dpr = self.device_pixel_ratio();
+ let point = Point2D::new(x, y).to_f32() * dpr;
+ let msg = EmbedderMsg::MoveTo(point.to_i32());
+ self.send_to_embedder(msg);
}
// https://drafts.csswg.org/cssom-view/#dom-window-moveby
@@ -948,8 +1289,7 @@ impl WindowMethods for Window {
// https://drafts.csswg.org/cssom-view/#dom-window-devicepixelratio
fn DevicePixelRatio(&self) -> Finite<f64> {
- let dpr = self.window_size.get().map_or(1.0f32, |data| data.device_pixel_ratio.get());
- Finite::wrap(dpr as f64)
+ Finite::wrap(self.device_pixel_ratio().get() as f64)
}
// https://html.spec.whatwg.org/multipage/#dom-window-status
@@ -962,62 +1302,181 @@ impl WindowMethods for Window {
*self.status.borrow_mut() = status
}
- // check-tidy: no specs after this line
- fn OpenURLInDefaultBrowser(&self, href: DOMString) -> ErrorResult {
- let url = try!(ServoUrl::parse(&href).map_err(|e| {
- Error::Type(format!("Couldn't parse URL: {}", e))
- }));
- match open::that(url.as_str()) {
- Ok(_) => Ok(()),
- Err(e) => Err(Error::Type(format!("Couldn't open URL: {}", e))),
- }
- }
-
// https://drafts.csswg.org/cssom-view/#dom-window-matchmedia
- fn MatchMedia(&self, query: DOMString) -> Root<MediaQueryList> {
- let mut parser = Parser::new(&query);
+ fn MatchMedia(&self, query: DOMString) -> DomRoot<MediaQueryList> {
+ let mut input = ParserInput::new(&query);
+ let mut parser = Parser::new(&mut input);
let url = self.get_url();
- let context = CssParserContext::new_for_cssom(&url, self.css_error_reporter(), Some(CssRuleType::Media),
- LengthParsingMode::Default);
- let media_query_list = media_queries::parse_media_query_list(&context, &mut parser);
+ let quirks_mode = self.Document().quirks_mode();
+ let context = CssParserContext::new(
+ Origin::Author,
+ &url,
+ Some(CssRuleType::Media),
+ ParsingMode::DEFAULT,
+ quirks_mode,
+ self.css_error_reporter(),
+ None,
+ );
+ let media_query_list = media_queries::MediaList::parse(&context, &mut parser);
let document = self.Document();
let mql = MediaQueryList::new(&document, media_query_list);
- self.media_query_lists.push(&*mql);
+ self.media_query_lists.track(&*mql);
mql
}
- #[allow(unrooted_must_root)]
// https://fetch.spec.whatwg.org/#fetch-method
- fn Fetch(&self, input: RequestOrUSVString, init: RootedTraceableBox<RequestInit>) -> Rc<Promise> {
- fetch::Fetch(&self.upcast(), input, init)
+ fn Fetch(
+ &self,
+ input: RequestOrUSVString,
+ init: RootedTraceableBox<RequestInit>,
+ comp: InRealm,
+ ) -> Rc<Promise> {
+ fetch::Fetch(&self.upcast(), input, init, comp)
}
- fn TestRunner(&self) -> Root<TestRunner> {
+ fn TestRunner(&self) -> DomRoot<TestRunner> {
self.test_runner.or_init(|| TestRunner::new(self.upcast()))
}
+
+ fn RunningAnimationCount(&self) -> u32 {
+ self.document
+ .get()
+ .map_or(0, |d| d.animations().running_animation_count() as u32)
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-name
+ fn SetName(&self, name: DOMString) {
+ if let Some(proxy) = self.undiscarded_window_proxy() {
+ proxy.set_name(name);
+ }
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-name
+ fn Name(&self) -> DOMString {
+ match self.undiscarded_window_proxy() {
+ Some(proxy) => proxy.get_name(),
+ None => "".into(),
+ }
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-origin
+ fn Origin(&self) -> USVString {
+ USVString(self.origin().immutable().ascii_serialization())
+ }
+
+ // https://w3c.github.io/selection-api/#dom-window-getselection
+ fn GetSelection(&self) -> Option<DomRoot<Selection>> {
+ self.document.get().and_then(|d| d.GetSelection())
+ }
+
+ // https://dom.spec.whatwg.org/#dom-window-event
+ #[allow(unsafe_code)]
+ fn Event(&self, cx: JSContext) -> JSVal {
+ rooted!(in(*cx) let mut rval = UndefinedValue());
+ if let Some(ref event) = *self.current_event.borrow() {
+ unsafe {
+ event
+ .reflector()
+ .get_jsobject()
+ .to_jsval(*cx, rval.handle_mut());
+ }
+ }
+ rval.get()
+ }
+
+ fn IsSecureContext(&self) -> bool {
+ self.upcast::<GlobalScope>().is_secure_context()
+ }
}
impl Window {
- pub fn get_runnable_wrapper(&self) -> RunnableWrapper {
- RunnableWrapper {
- cancelled: Some(self.ignore_further_async_events.borrow().clone()),
- }
+ pub(crate) fn set_current_event(&self, event: Option<&Event>) -> Option<DomRoot<Event>> {
+ let current = self
+ .current_event
+ .borrow()
+ .as_ref()
+ .map(|e| DomRoot::from_ref(&**e));
+ *self.current_event.borrow_mut() = event.map(|e| Dom::from_ref(e));
+ current
+ }
+
+ /// https://html.spec.whatwg.org/multipage/#window-post-message-steps
+ fn post_message_impl(
+ &self,
+ target_origin: &USVString,
+ source_origin: ImmutableOrigin,
+ source: &Window,
+ cx: JSContext,
+ message: HandleValue,
+ transfer: CustomAutoRooterGuard<Vec<*mut JSObject>>,
+ ) -> ErrorResult {
+ // Step 1-2, 6-8.
+ let data = structuredclone::write(cx, message, Some(transfer))?;
+
+ // Step 3-5.
+ let target_origin = match target_origin.0[..].as_ref() {
+ "*" => None,
+ "/" => Some(source_origin.clone()),
+ url => match ServoUrl::parse(&url) {
+ Ok(url) => Some(url.origin().clone()),
+ Err(_) => return Err(Error::Syntax),
+ },
+ };
+
+ // Step 9.
+ self.post_message(target_origin, source_origin, &*source.window_proxy(), data);
+ Ok(())
+ }
+
+ // https://drafts.css-houdini.org/css-paint-api-1/#paint-worklet
+ pub fn paint_worklet(&self) -> DomRoot<Worklet> {
+ self.paint_worklet.or_init(|| self.new_paint_worklet())
+ }
+
+ pub fn get_navigation_start(&self) -> u64 {
+ self.navigation_start_precise.get()
+ }
+
+ pub fn has_document(&self) -> bool {
+ self.document.get().is_some()
}
/// Cancels all the tasks associated with that window.
///
- /// This sets the current `ignore_further_async_events` sentinel value to
+ /// This sets the current `task_manager.task_cancellers` sentinel value to
/// `true` and replaces it with a brand new one for future tasks.
pub fn cancel_all_tasks(&self) {
- let cancelled = mem::replace(&mut *self.ignore_further_async_events.borrow_mut(), Default::default());
- cancelled.store(true, Ordering::Relaxed);
+ let mut ignore_flags = self.task_manager.task_cancellers.borrow_mut();
+ for task_source_name in TaskSourceName::all() {
+ let flag = ignore_flags
+ .entry(task_source_name)
+ .or_insert(Default::default());
+ let cancelled = mem::replace(&mut *flag, Default::default());
+ cancelled.store(true, Ordering::SeqCst);
+ }
+ }
+
+ /// Cancels all the tasks from a given task source.
+ /// This sets the current sentinel value to
+ /// `true` and replaces it with a brand new one for future tasks.
+ pub fn cancel_all_tasks_from_source(&self, task_source_name: TaskSourceName) {
+ let mut ignore_flags = self.task_manager.task_cancellers.borrow_mut();
+ let flag = ignore_flags
+ .entry(task_source_name)
+ .or_insert(Default::default());
+ let cancelled = mem::replace(&mut *flag, Default::default());
+ cancelled.store(true, Ordering::SeqCst);
}
pub fn clear_js_runtime(&self) {
- // We tear down the active document, which causes all the attached
- // nodes to dispose of their layout data. This messages the layout
- // thread, informing it that it can safely free the memory.
- self.Document().upcast::<Node>().teardown();
+ self.upcast::<GlobalScope>()
+ .remove_web_messaging_and_dedicated_workers_infra();
+
+ // Clean up any active promises
+ // https://github.com/servo/servo/issues/15318
+ if let Some(custom_elements) = self.custom_element_registry.get() {
+ custom_elements.teardown();
+ }
// The above code may not catch all DOM objects (e.g. DOM
// objects removed from the tree that haven't been collected
@@ -1035,20 +1494,31 @@ impl Window {
self.current_state.set(WindowState::Zombie);
*self.js_runtime.borrow_mut() = None;
- self.browsing_context.set(None);
- self.ignore_further_async_events.borrow().store(true, Ordering::SeqCst);
+
+ // If this is the currently active pipeline,
+ // nullify the window_proxy.
+ if let Some(proxy) = self.window_proxy.get() {
+ let pipeline_id = self.upcast::<GlobalScope>().pipeline_id();
+ if let Some(currently_active) = proxy.currently_active() {
+ if currently_active == pipeline_id {
+ self.window_proxy.set(None);
+ }
+ }
+ }
+
+ if let Some(performance) = self.performance.get() {
+ performance.clear_and_disable_performance_entry_buffer();
+ }
+ self.ignore_all_tasks();
}
- /// https://drafts.csswg.org/cssom-view/#dom-window-scroll
+ /// <https://drafts.csswg.org/cssom-view/#dom-window-scroll>
pub fn scroll(&self, x_: f64, y_: f64, behavior: ScrollBehavior) {
// Step 3
let xfinite = if x_.is_finite() { x_ } else { 0.0f64 };
let yfinite = if y_.is_finite() { y_ } else { 0.0f64 };
- // Step 4
- if self.window_size.get().is_none() {
- return;
- }
+ // TODO Step 4 - determine if a window has a viewport
// Step 5
//TODO remove scrollbar width
@@ -1065,12 +1535,12 @@ impl Window {
let content_size = e.upcast::<Node>().bounding_content_box_or_zero();
let content_height = content_size.size.height.to_f64_px();
let content_width = content_size.size.width.to_f64_px();
- (xfinite.max(0.0f64).min(content_width - width),
- yfinite.max(0.0f64).min(content_height - height))
+ (
+ xfinite.min(content_width - width).max(0.0f64),
+ yfinite.min(content_height - height).max(0.0f64),
+ )
},
- None => {
- (xfinite.max(0.0f64), yfinite.max(0.0f64))
- }
+ None => (xfinite.max(0.0f64), yfinite.max(0.0f64)),
};
// Step 10
@@ -1083,39 +1553,39 @@ impl Window {
//let document = self.Document();
// Step 12
let global_scope = self.upcast::<GlobalScope>();
- self.perform_a_scroll(x.to_f32().unwrap_or(0.0f32),
- y.to_f32().unwrap_or(0.0f32),
- global_scope.pipeline_id().root_scroll_node(),
- behavior,
- None);
- }
-
- /// https://drafts.csswg.org/cssom-view/#perform-a-scroll
- pub fn perform_a_scroll(&self,
- x: f32,
- y: f32,
- scroll_root_id: ClipId,
- behavior: ScrollBehavior,
- element: Option<&Element>) {
- //TODO Step 1
- let point = Point2D::new(x, y);
- let smooth = match behavior {
- ScrollBehavior::Auto => {
- element.map_or(false, |_element| {
- // TODO check computed scroll-behaviour CSS property
- true
- })
- }
- ScrollBehavior::Instant => false,
- ScrollBehavior::Smooth => true
- };
-
- // TODO (farodin91): Raise an event to stop the current_viewport
+ let x = x.to_f32().unwrap_or(0.0f32);
+ let y = y.to_f32().unwrap_or(0.0f32);
self.update_viewport_for_scroll(x, y);
-
- let global_scope = self.upcast::<GlobalScope>();
- let message = ConstellationMsg::ScrollFragmentPoint(scroll_root_id, point, smooth);
- global_scope.constellation_chan().send(message).unwrap();
+ self.perform_a_scroll(
+ x,
+ y,
+ global_scope.pipeline_id().root_scroll_id(),
+ behavior,
+ None,
+ );
+ }
+
+ /// <https://drafts.csswg.org/cssom-view/#perform-a-scroll>
+ pub fn perform_a_scroll(
+ &self,
+ x: f32,
+ y: f32,
+ scroll_id: ExternalScrollId,
+ _behavior: ScrollBehavior,
+ _element: Option<&Element>,
+ ) {
+ // TODO Step 1
+ // TODO(mrobinson, #18709): Add smooth scrolling support to WebRender so that we can
+ // properly process ScrollBehavior here.
+ match self.layout_chan() {
+ Some(chan) => chan
+ .send(Msg::UpdateScrollStateFromScript(ScrollState {
+ scroll_id,
+ scroll_offset: Vector2D::new(-x, -y),
+ }))
+ .unwrap(),
+ None => warn!("Layout channel unavailable"),
+ }
}
pub fn update_viewport_for_scroll(&self, x: f32, y: f32) {
@@ -1124,19 +1594,31 @@ impl Window {
self.current_viewport.set(new_viewport)
}
- pub fn client_window(&self) -> (Size2D<u32>, Point2D<i32>) {
- let (send, recv) = ipc::channel::<(Size2D<u32>, Point2D<i32>)>().unwrap();
- self.upcast::<GlobalScope>()
- .constellation_chan()
- .send(ConstellationMsg::GetClientWindow(send))
- .unwrap();
- recv.recv().unwrap_or((Size2D::zero(), Point2D::zero()))
+ pub fn device_pixel_ratio(&self) -> Scale<f32, CSSPixel, DevicePixel> {
+ self.window_size.get().device_pixel_ratio
}
- /// Advances the layout animation clock by `delta` milliseconds, and then
- /// forces a reflow if `tick` is true.
- pub fn advance_animation_clock(&self, delta: i32, tick: bool) {
- self.layout_chan.send(Msg::AdvanceClockMs(delta, tick)).unwrap();
+ fn client_window(&self) -> (Size2D<u32, CSSPixel>, Point2D<i32, CSSPixel>) {
+ let timer_profile_chan = self.global().time_profiler_chan().clone();
+ let (send, recv) =
+ ProfiledIpc::channel::<(DeviceIntSize, DeviceIntPoint)>(timer_profile_chan).unwrap();
+ self.send_to_constellation(ScriptMsg::GetClientWindow(send));
+ let (size, point) = recv.recv().unwrap_or((Size2D::zero(), Point2D::zero()));
+ let dpr = self.device_pixel_ratio();
+ (
+ (size.to_f32() / dpr).to_u32(),
+ (point.to_f32() / dpr).to_i32(),
+ )
+ }
+
+ /// Prepares to tick animations and then does a reflow which also advances the
+ /// layout animation clock.
+ #[allow(unsafe_code)]
+ pub fn advance_animation_clock(&self, delta_ms: i32) {
+ let pipeline_id = self.upcast::<GlobalScope>().pipeline_id();
+ self.Document()
+ .advance_animation_timeline_for_testing(delta_ms as f64 / 1000.);
+ ScriptThread::handle_tick_all_animations_for_testing(pipeline_id);
}
/// Reflows the page unconditionally if possible and not suppressed. This
@@ -1149,32 +1631,45 @@ impl Window {
/// off-main-thread layout.
///
/// Returns true if layout actually happened, false otherwise.
- pub fn force_reflow(&self,
- goal: ReflowGoal,
- query_type: ReflowQueryType,
- reason: ReflowReason) -> bool {
+ #[allow(unsafe_code)]
+ pub fn force_reflow(
+ &self,
+ reflow_goal: ReflowGoal,
+ reason: ReflowReason,
+ condition: Option<ReflowTriggerCondition>,
+ ) -> bool {
+ self.Document().ensure_safe_to_run_script_or_layout();
// Check if we need to unsuppress reflow. Note that this needs to be
// *before* any early bailouts, or reflow might never be unsuppresed!
match reason {
- ReflowReason::FirstLoad |
- ReflowReason::RefreshTick => self.suppress_reflow.set(false),
+ ReflowReason::FirstLoad | ReflowReason::RefreshTick => self.suppress_reflow.set(false),
_ => (),
}
- // If there is no window size, we have nothing to do.
- let window_size = match self.window_size.get() {
- Some(window_size) => window_size,
- None => return false,
- };
-
- let for_display = query_type == ReflowQueryType::NoQuery;
+ let for_display = reflow_goal == ReflowGoal::Full;
+ let pipeline_id = self.upcast::<GlobalScope>().pipeline_id();
if for_display && self.suppress_reflow.get() {
- debug!("Suppressing reflow pipeline {} for goal {:?} reason {:?} before FirstLoad or RefreshTick",
- self.upcast::<GlobalScope>().pipeline_id(), goal, reason);
+ debug!(
+ "Suppressing reflow pipeline {} for reason {:?} before FirstLoad or RefreshTick",
+ pipeline_id, reason
+ );
return false;
}
- debug!("script: performing reflow for goal {:?} reason {:?}", goal, reason);
+ if condition != Some(ReflowTriggerCondition::PaintPostponed) {
+ debug!(
+ "Invalidating layout cache due to reflow condition {:?}",
+ condition
+ );
+ // Invalidate any existing cached layout values.
+ self.layout_marker.borrow().set(false);
+ // Create a new layout caching token.
+ *self.layout_marker.borrow_mut() = Rc::new(Cell::new(true));
+ } else {
+ debug!("Not invalidating cached layout values for paint-only reflow.");
+ }
+
+ debug!("script: performing reflow for reason {:?}", reason);
let marker = if self.need_emit_timeline_marker(TimelineMarkerType::Reflow) {
Some(TimelineMarker::start("Reflow".to_owned()))
@@ -1183,51 +1678,76 @@ impl Window {
};
// Layout will let us know when it's done.
- let (join_chan, join_port) = channel();
+ let (join_chan, join_port) = unbounded();
// On debug mode, print the reflow event information.
- if opts::get().relayout_event {
- debug_reflow_events(self.upcast::<GlobalScope>().pipeline_id(), &goal, &query_type, &reason);
+ if self.relayout_event {
+ debug_reflow_events(pipeline_id, &reflow_goal, &reason);
}
let document = self.Document();
- let stylesheets_changed = document.get_and_reset_stylesheets_changed_since_reflow();
+
+ let stylesheets_changed = document.flush_stylesheets_for_reflow();
+
+ // If this reflow is for display, ensure webgl canvases are composited with
+ // up-to-date contents.
+ if for_display {
+ document.flush_dirty_webgpu_canvases();
+ document.flush_dirty_webgl_canvases();
+ }
+
+ let pending_restyles = document.drain_pending_restyles();
+
+ let dirty_root = document
+ .take_dirty_root()
+ .filter(|_| !stylesheets_changed)
+ .or_else(|| document.GetDocumentElement())
+ .map(|root| root.upcast::<Node>().to_trusted_node_address());
// Send new document and relevant styles to layout.
+ let needs_display = reflow_goal.needs_display();
let reflow = ScriptReflow {
reflow_info: Reflow {
- goal: goal,
page_clip_rect: self.page_clip_rect.get(),
},
- document: self.Document().upcast::<Node>().to_trusted_node_address(),
- document_stylesheets: document.stylesheets(),
- stylesheets_changed: stylesheets_changed,
- window_size: window_size,
+ document: document.upcast::<Node>().to_trusted_node_address(),
+ dirty_root,
+ stylesheets_changed,
+ window_size: self.window_size.get(),
+ origin: self.origin().immutable().clone(),
+ reflow_goal,
script_join_chan: join_chan,
- query_type: query_type,
- dom_count: self.Document().dom_count(),
+ dom_count: document.dom_count(),
+ pending_restyles,
+ animation_timeline_value: document.current_animation_timeline_value(),
+ animations: document.animations().sets.clone(),
};
- self.layout_chan.send(Msg::Reflow(reflow)).unwrap();
+ match self.layout_chan() {
+ Some(layout_chan) => layout_chan
+ .send(Msg::Reflow(reflow))
+ .expect("Layout thread disconnected"),
+ None => return false,
+ };
debug!("script: layout forked");
- match join_port.try_recv() {
- Err(Empty) => {
- info!("script: waiting on layout");
- join_port.recv().unwrap();
- }
- Ok(_) => {}
- Err(Disconnected) => {
+ let complete = match join_port.try_recv() {
+ Err(TryRecvError::Empty) => {
+ debug!("script: waiting on layout");
+ join_port.recv().unwrap()
+ },
+ Ok(reflow_complete) => reflow_complete,
+ Err(TryRecvError::Disconnected) => {
panic!("Layout thread failed while script was waiting for a result.");
- }
- }
+ },
+ };
debug!("script: layout joined");
// Pending reflows require display, so only reset the pending reflow count if this reflow
// was to be displayed.
- if goal == ReflowGoal::ForDisplay {
+ if needs_display {
self.pending_reflow_count.set(0);
}
@@ -1235,12 +1755,9 @@ impl Window {
self.emit_timeline_marker(marker.end());
}
- let pending_images = self.layout_rpc.pending_images();
- for image in pending_images {
+ for image in complete.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);
+ let node = unsafe { from_untrusted_node_address(image.node) };
if let PendingImageState::Unrequested(ref url) = image.state {
fetch_image_for_layout(url.clone(), &*node, id, self.image_cache.clone());
@@ -1248,18 +1765,28 @@ impl Window {
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();
+ if nodes
+ .iter()
+ .find(|n| &***n as *const _ == &*node as *const _)
+ .is_none()
+ {
+ let (responder, responder_listener) =
+ ProfiledIpc::channel(self.global().time_profiler_chan().clone()).unwrap();
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.add_listener(id, ImageResponder::new(responder, id));
- nodes.push(JS::from_ref(&*node));
+ ROUTER.add_route(
+ responder_listener.to_opaque(),
+ Box::new(move |message| {
+ let _ = image_cache_chan.send((pipeline_id, message.to().unwrap()));
+ }),
+ );
+ self.image_cache
+ .add_listener(id, ImageResponder::new(responder, id));
+ nodes.push(Dom::from_ref(&*node));
}
}
+ document.update_animations_post_reflow();
+
true
}
@@ -1276,25 +1803,33 @@ impl Window {
/// that layout might hold if the first layout hasn't happened yet (which
/// may happen in the only case a query reflow may bail out, that is, if the
/// viewport size is not present). See #11223 for an example of that.
- pub fn reflow(&self,
- goal: ReflowGoal,
- query_type: ReflowQueryType,
- reason: ReflowReason) -> bool {
- let for_display = query_type == ReflowQueryType::NoQuery;
+ pub fn reflow(&self, reflow_goal: ReflowGoal, reason: ReflowReason) -> bool {
+ self.Document().ensure_safe_to_run_script_or_layout();
+ let for_display = reflow_goal == ReflowGoal::Full;
let mut issued_reflow = false;
- if !for_display || self.Document().needs_reflow() {
- issued_reflow = self.force_reflow(goal, query_type, reason);
+ let condition = self.Document().needs_reflow();
+ if !for_display || condition.is_some() {
+ issued_reflow = self.force_reflow(reflow_goal, reason, condition);
- // If window_size is `None`, we don't reflow, so the document stays
- // dirty. Otherwise, we shouldn't need a reflow immediately after a
+ // We shouldn't need a reflow immediately after a
// reflow, except if we're waiting for a deferred paint.
- assert!(!self.Document().needs_reflow() ||
- (!for_display && self.Document().needs_paint()) ||
- self.window_size.get().is_none() ||
- self.suppress_reflow.get());
+ let condition = self.Document().needs_reflow();
+ assert!(
+ {
+ condition.is_none() ||
+ (!for_display &&
+ condition == Some(ReflowTriggerCondition::PaintPostponed)) ||
+ self.suppress_reflow.get()
+ },
+ "condition was {:?}",
+ condition
+ );
} else {
- debug!("Document doesn't need reflow - skipping it (reason {:?})", reason);
+ debug!(
+ "Document doesn't need reflow - skipping it (reason {:?})",
+ reason
+ );
}
// If writing a screenshot, check if the script has reached a state
@@ -1305,194 +1840,176 @@ impl Window {
// When all these conditions are met, notify the constellation
// that this pipeline is ready to write the image (from the script thread
// perspective at least).
- if (opts::get().output_file.is_some() ||
- opts::get().exit_after_load ||
- opts::get().webdriver_port.is_some()) && for_display {
+ if self.prepare_for_screenshot && for_display {
let document = self.Document();
// Checks if the html element has reftest-wait attribute present.
// See http://testthewebforward.org/docs/reftests.html
let html_element = document.GetDocumentElement();
let reftest_wait = html_element.map_or(false, |elem| {
- elem.has_class(&Atom::from("reftest-wait"))
+ elem.has_class(&atom!("reftest-wait"), CaseSensitivity::CaseSensitive)
});
- let ready_state = document.ReadyState();
-
+ let has_sent_idle_message = self.has_sent_idle_message.get();
+ let is_ready_state_complete = document.ReadyState() == DocumentReadyState::Complete;
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();
+
+ if !has_sent_idle_message && is_ready_state_complete && !reftest_wait && pending_images
+ {
+ let event = ScriptMsg::SetDocumentState(DocumentState::Idle);
+ self.send_to_constellation(event);
+ self.has_sent_idle_message.set(true);
}
}
issued_reflow
}
- pub fn layout(&self) -> &LayoutRPC {
+ pub fn layout_reflow(&self, query_msg: QueryMsg) -> bool {
+ if self.layout_is_busy.load(Ordering::Relaxed) {
+ let url = self.get_url().into_string();
+ self.time_profiler_chan()
+ .send(ProfilerMsg::BlockedLayoutQuery(url));
+ }
+
+ self.reflow(
+ ReflowGoal::LayoutQuery(query_msg, time::precise_time_ns()),
+ ReflowReason::Query,
+ )
+ }
+
+ pub fn resolved_font_style_query(&self, node: &Node, value: String) -> Option<ServoArc<Font>> {
+ let id = PropertyId::Shorthand(ShorthandId::Font);
+ if !self.layout_reflow(QueryMsg::ResolvedFontStyleQuery(
+ node.to_trusted_node_address(),
+ id,
+ value,
+ )) {
+ return None;
+ }
+ self.layout_rpc.resolved_font_style()
+ }
+
+ pub fn layout(&self) -> &dyn LayoutRPC {
&*self.layout_rpc
}
- pub fn content_box_query(&self, content_box_request: TrustedNodeAddress) -> Option<Rect<Au>> {
- if !self.reflow(ReflowGoal::ForScriptQuery,
- ReflowQueryType::ContentBoxQuery(content_box_request),
- ReflowReason::Query) {
+ pub fn content_box_query(&self, node: &Node) -> Option<UntypedRect<Au>> {
+ if !self.layout_reflow(QueryMsg::ContentBoxQuery(node.to_opaque())) {
return None;
}
let ContentBoxResponse(rect) = self.layout_rpc.content_box();
rect
}
- pub fn content_boxes_query(&self, content_boxes_request: TrustedNodeAddress) -> Vec<Rect<Au>> {
- if !self.reflow(ReflowGoal::ForScriptQuery,
- ReflowQueryType::ContentBoxesQuery(content_boxes_request),
- ReflowReason::Query) {
+ pub fn content_boxes_query(&self, node: &Node) -> Vec<UntypedRect<Au>> {
+ if !self.layout_reflow(QueryMsg::ContentBoxesQuery(node.to_opaque())) {
return vec![];
}
let ContentBoxesResponse(rects) = self.layout_rpc.content_boxes();
rects
}
- pub fn client_rect_query(&self, node_geometry_request: TrustedNodeAddress) -> Rect<i32> {
- if !self.reflow(ReflowGoal::ForScriptQuery,
- ReflowQueryType::NodeGeometryQuery(node_geometry_request),
- ReflowReason::Query) {
+ pub fn client_rect_query(&self, node: &Node) -> UntypedRect<i32> {
+ if !self.layout_reflow(QueryMsg::ClientRectQuery(node.to_opaque())) {
return Rect::zero();
}
self.layout_rpc.node_geometry().client_rect
}
- pub fn hit_test_query(&self,
- client_point: Point2D<f32>,
- update_cursor: bool)
- -> Option<UntrustedNodeAddress> {
- let translated_point =
- Point2D::new(client_point.x + self.PageXOffset() as f32,
- client_point.y + self.PageYOffset() as f32);
-
- if !self.reflow(ReflowGoal::ForScriptQuery,
- ReflowQueryType::HitTestQuery(translated_point,
- client_point,
- update_cursor),
- ReflowReason::Query) {
- return None
- }
-
- self.layout_rpc.hit_test().node_address
- }
-
- pub fn scroll_area_query(&self, node: TrustedNodeAddress) -> Rect<i32> {
- if !self.reflow(ReflowGoal::ForScriptQuery,
- ReflowQueryType::NodeScrollGeometryQuery(node),
- ReflowReason::Query) {
+ pub fn scroll_area_query(&self, node: &Node) -> UntypedRect<i32> {
+ if !self.layout_reflow(QueryMsg::NodeScrollGeometryQuery(node.to_opaque())) {
return Rect::zero();
}
self.layout_rpc.node_scroll_area().client_rect
}
- pub fn overflow_query(&self,
- node: TrustedNodeAddress) -> Point2D<overflow_x::computed_value::T> {
- // NB: This is only called if the document is fully active, and the only
- // reason to bail out from a query is if there's no viewport, so this
- // *must* issue a reflow.
- assert!(self.reflow(ReflowGoal::ForScriptQuery,
- ReflowQueryType::NodeOverflowQuery(node),
- ReflowReason::Query));
-
- self.layout_rpc.node_overflow().0.unwrap()
+ pub fn scroll_offset_query(&self, node: &Node) -> Vector2D<f32, LayoutPixel> {
+ if let Some(scroll_offset) = self.scroll_offsets.borrow().get(&node.to_opaque()) {
+ return *scroll_offset;
+ }
+ Vector2D::new(0.0, 0.0)
}
- pub fn scroll_offset_query(&self, node: &Node) -> Point2D<f32> {
- let mut node = Root::from_ref(node);
- loop {
- if let Some(scroll_offset) = self.scroll_offsets
- .borrow()
- .get(&node.to_untrusted_node_address()) {
- return *scroll_offset
- }
- node = match node.GetParentNode() {
- Some(node) => node,
- None => break,
- }
- }
- let offset = self.current_viewport.get().origin;
- Point2D::new(offset.x.to_f32_px(), offset.y.to_f32_px())
- }
-
- // https://drafts.csswg.org/cssom-view/#dom-element-scroll
- pub fn scroll_node(&self,
- node: TrustedNodeAddress,
- x_: f64,
- y_: f64,
- behavior: ScrollBehavior) {
- if !self.reflow(ReflowGoal::ForScriptQuery,
- ReflowQueryType::NodeScrollRootIdQuery(node),
- ReflowReason::Query) {
+ // https://drafts.csswg.org/cssom-view/#element-scrolling-members
+ pub fn scroll_node(&self, node: &Node, x_: f64, y_: f64, behavior: ScrollBehavior) {
+ if !self.layout_reflow(QueryMsg::NodeScrollIdQuery(node.to_trusted_node_address())) {
return;
}
- let NodeScrollRootIdResponse(scroll_root_id) = self.layout_rpc.node_scroll_root_id();
+
+ // The scroll offsets are immediatly updated since later calls
+ // to topScroll and others may access the properties before
+ // webrender has a chance to update the offsets.
+ self.scroll_offsets
+ .borrow_mut()
+ .insert(node.to_opaque(), Vector2D::new(x_ as f32, y_ as f32));
+
+ let NodeScrollIdResponse(scroll_id) = self.layout_rpc.node_scroll_id();
// Step 12
- self.perform_a_scroll(x_.to_f32().unwrap_or(0.0f32),
- y_.to_f32().unwrap_or(0.0f32),
- scroll_root_id,
- behavior,
- None);
- }
-
- pub fn resolved_style_query(&self,
- element: TrustedNodeAddress,
- pseudo: Option<PseudoElement>,
- property: PropertyId) -> DOMString {
- if !self.reflow(ReflowGoal::ForScriptQuery,
- ReflowQueryType::ResolvedStyleQuery(element, pseudo, property),
- ReflowReason::Query) {
+ self.perform_a_scroll(
+ x_.to_f32().unwrap_or(0.0f32),
+ y_.to_f32().unwrap_or(0.0f32),
+ scroll_id,
+ behavior,
+ None,
+ );
+ }
+
+ pub fn resolved_style_query(
+ &self,
+ element: TrustedNodeAddress,
+ pseudo: Option<PseudoElement>,
+ property: PropertyId,
+ ) -> DOMString {
+ if !self.layout_reflow(QueryMsg::ResolvedStyleQuery(element, pseudo, property)) {
return DOMString::new();
}
let ResolvedStyleResponse(resolved) = self.layout_rpc.resolved_style();
DOMString::from(resolved)
}
- pub fn offset_parent_query(&self, node: TrustedNodeAddress) -> (Option<Root<Element>>, Rect<Au>) {
- if !self.reflow(ReflowGoal::ForScriptQuery,
- ReflowQueryType::OffsetParentQuery(node),
- ReflowReason::Query) {
+ pub fn inner_window_dimensions_query(
+ &self,
+ browsing_context: BrowsingContextId,
+ ) -> Option<Size2D<f32, CSSPixel>> {
+ if !self.layout_reflow(QueryMsg::InnerWindowDimensionsQuery(browsing_context)) {
+ return None;
+ }
+ self.layout_rpc.inner_window_dimensions()
+ }
+
+ #[allow(unsafe_code)]
+ pub fn offset_parent_query(&self, node: &Node) -> (Option<DomRoot<Element>>, UntypedRect<Au>) {
+ if !self.layout_reflow(QueryMsg::OffsetParentQuery(node.to_opaque())) {
return (None, Rect::zero());
}
+ // FIXME(nox): Layout can reply with a garbage value which doesn't
+ // actually correspond to an element, that's unsound.
let response = self.layout_rpc.offset_parent();
- let js_runtime = self.js_runtime.borrow();
- let js_runtime = js_runtime.as_ref().unwrap();
let element = response.node_address.and_then(|parent_node_address| {
- let node = from_untrusted_node_address(js_runtime.rt(), parent_node_address);
- Root::downcast(node)
+ let node = unsafe { from_untrusted_node_address(parent_node_address) };
+ DomRoot::downcast(node)
});
(element, response.rect)
}
- pub fn margin_style_query(&self, node: TrustedNodeAddress) -> MarginStyleResponse {
- if !self.reflow(ReflowGoal::ForScriptQuery,
- ReflowQueryType::MarginStyleQuery(node),
- ReflowReason::Query) {
- return MarginStyleResponse::empty();
- }
- self.layout_rpc.margin_style()
- }
-
- pub fn text_index_query(&self, node: TrustedNodeAddress, mouse_x: i32, mouse_y: i32) -> TextIndexResponse {
- if !self.reflow(ReflowGoal::ForScriptQuery,
- ReflowQueryType::TextIndexQuery(node, mouse_x, mouse_y),
- ReflowReason::Query) {
+ pub fn text_index_query(
+ &self,
+ node: &Node,
+ point_in_node: UntypedPoint2D<f32>,
+ ) -> TextIndexResponse {
+ if !self.layout_reflow(QueryMsg::TextIndexQuery(node.to_opaque(), point_in_node)) {
return TextIndexResponse(None);
}
self.layout_rpc.text_index()
}
#[allow(unsafe_code)]
- pub fn init_browsing_context(&self, browsing_context: &BrowsingContext) {
- assert!(self.browsing_context.get().is_none());
- self.browsing_context.set(Some(&browsing_context));
+ pub fn init_window_proxy(&self, window_proxy: &WindowProxy) {
+ assert!(self.window_proxy.get().is_none());
+ self.window_proxy.set(Some(&window_proxy));
}
#[allow(unsafe_code)]
@@ -1500,60 +2017,110 @@ impl Window {
assert!(self.document.get().is_none());
assert!(document.window() == self);
self.document.set(Some(&document));
- if !opts::get().unminify_js {
+ if !self.unminify_js {
return;
}
- // Create a folder for the document host to store unminified scripts.
- if let Some(&Host::Domain(ref host)) = document.url().origin().host() {
- let mut path = env::current_dir().unwrap();
- path.push("unminified-js");
- path.push(host);
- let _ = fs::remove_dir_all(&path);
- match fs::create_dir_all(&path) {
- Ok(_) => {
- *self.unminified_js_dir.borrow_mut() = Some(path.into_os_string().into_string().unwrap());
- debug!("Created folder for {:?} unminified scripts {:?}", host, self.unminified_js_dir.borrow());
- },
- Err(_) => warn!("Could not create unminified dir for {:?}", host),
- }
- }
+ // Set a path for the document host to store unminified scripts.
+ let mut path = env::current_dir().unwrap();
+ path.push("unminified-js");
+ *self.unminified_js_dir.borrow_mut() = Some(path.into_os_string().into_string().unwrap());
}
/// Commence a new URL load which will either replace this window or scroll to a fragment.
- pub fn load_url(&self, url: ServoUrl, replace: bool, force_reload: bool,
- referrer_policy: Option<ReferrerPolicy>) {
+ ///
+ /// https://html.spec.whatwg.org/multipage/#navigating-across-documents
+ pub fn load_url(
+ &self,
+ replace: HistoryEntryReplacement,
+ force_reload: bool,
+ load_data: LoadData,
+ ) {
let doc = self.Document();
- let referrer_policy = referrer_policy.or(doc.get_referrer_policy());
-
- // https://html.spec.whatwg.org/multipage/#navigating-across-documents
- if !force_reload && url.as_url()[..Position::AfterQuery] ==
- doc.url().as_url()[..Position::AfterQuery] {
- // Step 5
- if let Some(fragment) = url.fragment() {
- doc.check_and_scroll_fragment(fragment);
- doc.set_url(url.clone());
- return
+ // TODO: Important re security. See https://github.com/servo/servo/issues/23373
+ // Step 3: check that the source browsing-context is "allowed to navigate" this window.
+ if !force_reload &&
+ load_data.url.as_url()[..Position::AfterQuery] ==
+ doc.url().as_url()[..Position::AfterQuery]
+ {
+ // Step 6
+ if let Some(fragment) = load_data.url.fragment() {
+ self.send_to_constellation(ScriptMsg::NavigatedToFragment(
+ load_data.url.clone(),
+ replace,
+ ));
+ doc.check_and_scroll_fragment(fragment);
+ let this = Trusted::new(self);
+ let old_url = doc.url().into_string();
+ let new_url = load_data.url.clone().into_string();
+ let task = task!(hashchange_event: move || {
+ let this = this.root();
+ let event = HashChangeEvent::new(
+ &this,
+ atom!("hashchange"),
+ false,
+ false,
+ old_url,
+ new_url);
+ event.upcast::<Event>().fire(this.upcast::<EventTarget>());
+ });
+ // FIXME(nox): Why are errors silenced here?
+ let _ = self.script_chan.send(CommonScriptMsg::Task(
+ ScriptThreadEventCategory::DomEvent,
+ Box::new(
+ self.task_manager
+ .task_canceller(TaskSourceName::DOMManipulation)
+ .wrap_task(task),
+ ),
+ Some(self.pipeline_id()),
+ TaskSourceName::DOMManipulation,
+ ));
+ doc.set_url(load_data.url.clone());
+ return;
+ }
+ }
+
+ let pipeline_id = self.upcast::<GlobalScope>().pipeline_id();
+
+ // Step 4 and 5
+ let window_proxy = self.window_proxy();
+ if let Some(active) = window_proxy.currently_active() {
+ if pipeline_id == active {
+ if doc.is_prompting_or_unloading() {
+ return;
}
+ }
}
- self.main_thread_script_chan().send(
- MainThreadScriptMsg::Navigate(self.upcast::<GlobalScope>().pipeline_id(),
- LoadData::new(url, referrer_policy, Some(doc.url())),
- replace)).unwrap();
+ // Step 8
+ if doc.prompt_to_unload(false) {
+ let window_proxy = self.window_proxy();
+ if window_proxy.parent().is_some() {
+ // Step 10
+ // If browsingContext is a nested browsing context,
+ // then put it in the delaying load events mode.
+ window_proxy.start_delaying_load_events_mode();
+ }
+ // TODO: step 11, navigationType.
+ // Step 12, 13
+ ScriptThread::navigate(
+ window_proxy.browsing_context_id(),
+ pipeline_id,
+ load_data,
+ replace,
+ );
+ };
}
pub fn handle_fire_timer(&self, timer_id: TimerEventId) {
self.upcast::<GlobalScope>().fire_timer(timer_id);
- self.reflow(ReflowGoal::ForDisplay,
- ReflowQueryType::NoQuery,
- ReflowReason::Timer);
+ self.reflow(ReflowGoal::Full, ReflowReason::Timer);
}
pub fn set_window_size(&self, size: WindowSizeData) {
- self.window_size.set(Some(size));
+ self.window_size.set(size);
}
- pub fn window_size(&self) -> Option<WindowSizeData> {
+ pub fn window_size(&self) -> WindowSizeData {
self.window_size.get()
}
@@ -1561,8 +2128,12 @@ impl Window {
self.Document().url()
}
- pub fn layout_chan(&self) -> &Sender<Msg> {
- &self.layout_chan
+ pub fn layout_chan(&self) -> Option<&Sender<Msg>> {
+ if self.is_alive() {
+ Some(&self.layout_chan)
+ } else {
+ None
+ }
}
pub fn windowproxy_handler(&self) -> WindowProxyHandler {
@@ -1574,7 +2145,8 @@ impl Window {
}
pub fn add_pending_reflow(&self) {
- self.pending_reflow_count.set(self.pending_reflow_count.get() + 1);
+ self.pending_reflow_count
+ .set(self.pending_reflow_count.get() + 1);
}
pub fn set_resize_event(&self, event: WindowSizeData, event_type: WindowSizeType) {
@@ -1587,22 +2159,23 @@ impl Window {
event
}
- pub fn set_page_clip_rect_with_new_viewport(&self, viewport: Rect<f32>) -> bool {
+ pub fn set_page_clip_rect_with_new_viewport(&self, viewport: UntypedRect<f32>) -> bool {
let rect = f32_rect_to_au_rect(viewport.clone());
self.current_viewport.set(rect);
// We use a clipping rectangle that is five times the size of the of the viewport,
// so that we don't collect display list items for areas too far outside the viewport,
// but also don't trigger reflows every time the viewport changes.
static VIEWPORT_EXPANSION: f32 = 2.0; // 2 lengths on each side plus original length is 5 total.
- let proposed_clip_rect = f32_rect_to_au_rect(
- viewport.inflate(viewport.size.width * VIEWPORT_EXPANSION,
- viewport.size.height * VIEWPORT_EXPANSION));
+ let proposed_clip_rect = f32_rect_to_au_rect(viewport.inflate(
+ viewport.size.width * VIEWPORT_EXPANSION,
+ viewport.size.height * VIEWPORT_EXPANSION,
+ ));
let clip_rect = self.page_clip_rect.get();
if proposed_clip_rect == clip_rect {
return false;
}
- let had_clip_rect = clip_rect != max_rect();
+ let had_clip_rect = clip_rect != MaxRect::max_rect();
if had_clip_rect && !should_move_clip_rect(clip_rect, viewport) {
return false;
}
@@ -1610,22 +2183,17 @@ impl Window {
self.page_clip_rect.set(proposed_clip_rect);
// If we didn't have a clip rect, the previous display doesn't need rebuilding
- // because it was built for infinite clip (max_rect()).
+ // because it was built for infinite clip (MaxRect::amax_rect()).
had_clip_rect
}
- // https://html.spec.whatwg.org/multipage/#accessing-other-browsing-contexts
- pub fn IndexedGetter(&self, _index: u32, _found: &mut bool) -> Option<Root<Window>> {
- None
- }
-
pub fn suspend(&self) {
// Suspend timer events.
self.upcast::<GlobalScope>().suspend();
// Set the window proxy to be a cross-origin window.
- if self.browsing_context().currently_active() == Some(self.global().pipeline_id()) {
- self.browsing_context().unset_currently_active();
+ if self.window_proxy().currently_active() == Some(self.global().pipeline_id()) {
+ self.window_proxy().unset_currently_active();
}
// A hint to the JS runtime that now would be a good time to
@@ -1640,7 +2208,7 @@ impl Window {
self.upcast::<GlobalScope>().resume();
// Set the window proxy to be this object.
- self.browsing_context().set_currently_active(self);
+ self.window_proxy().set_currently_active(self);
// Push the document title to the compositor since we are
// activating this document due to a navigation.
@@ -1658,11 +2226,15 @@ impl Window {
sender.send(Some(marker)).unwrap();
}
- pub fn set_devtools_timeline_markers(&self,
- markers: Vec<TimelineMarkerType>,
- reply: IpcSender<Option<TimelineMarker>>) {
+ pub fn set_devtools_timeline_markers(
+ &self,
+ markers: Vec<TimelineMarkerType>,
+ reply: IpcSender<Option<TimelineMarker>>,
+ ) {
*self.devtools_marker_sender.borrow_mut() = Some(reply);
- self.devtools_markers.borrow_mut().extend(markers.into_iter());
+ self.devtools_markers
+ .borrow_mut()
+ .extend(markers.into_iter());
}
pub fn drop_devtools_timeline_markers(&self, markers: Vec<TimelineMarkerType>) {
@@ -1685,39 +2257,37 @@ impl Window {
// https://html.spec.whatwg.org/multipage/#top-level-browsing-context
pub fn is_top_level(&self) -> bool {
- match self.parent_info {
- Some((_, FrameType::IFrame)) => false,
- _ => true,
- }
- }
-
- /// Returns whether this window is mozbrowser.
- pub fn is_mozbrowser(&self) -> bool {
- PREFS.is_mozbrowser_enabled() && self.parent_info().is_none()
- }
-
- /// Returns whether mozbrowser is enabled and `obj` has been created
- /// in a top-level `Window` global.
- #[allow(unsafe_code)]
- pub unsafe fn global_is_mozbrowser(_: *mut JSContext, obj: HandleObject) -> bool {
- GlobalScope::from_object(obj.get())
- .downcast::<Window>()
- .map_or(false, |window| window.is_mozbrowser())
- }
-
- #[allow(unsafe_code)]
- pub fn dispatch_mozbrowser_event(&self, event: MozBrowserEvent) {
- assert!(PREFS.is_mozbrowser_enabled());
- let custom_event = build_mozbrowser_custom_event(&self, event);
- custom_event.upcast::<Event>().fire(self.upcast());
+ self.parent_info.is_none()
}
+ /// Evaluate media query lists and report changes
+ /// <https://drafts.csswg.org/cssom-view/#evaluate-media-queries-and-report-changes>
pub fn evaluate_media_queries_and_report_changes(&self) {
- self.media_query_lists.evaluate_and_report_changes();
+ rooted_vec!(let mut mql_list);
+ self.media_query_lists.for_each(|mql| {
+ if let MediaQueryListMatchState::Changed(_) = mql.evaluate_changes() {
+ // Recording list of changed Media Queries
+ mql_list.push(Dom::from_ref(&*mql));
+ }
+ });
+ // Sending change events for all changed Media Queries
+ for mql in mql_list.iter() {
+ let event = MediaQueryListEvent::new(
+ &mql.global(),
+ atom!("change"),
+ false,
+ false,
+ mql.Media(),
+ mql.Matches(),
+ );
+ event.upcast::<Event>().fire(mql.upcast::<EventTarget>());
+ }
+ self.Document().react_to_environment_changes();
}
/// Slow down/speed up timers based on visibility.
pub fn alter_resource_utilization(&self, visible: bool) {
+ self.visible.set(visible);
if visible {
self.upcast::<GlobalScope>().speed_up_timers();
} else {
@@ -1725,118 +2295,235 @@ impl Window {
}
}
+ pub fn visible(&self) -> bool {
+ self.visible.get()
+ }
+
pub fn unminified_js_dir(&self) -> Option<String> {
self.unminified_js_dir.borrow().clone()
}
+
+ pub fn local_script_source(&self) -> &Option<String> {
+ &self.local_script_source
+ }
+
+ pub fn set_navigation_start(&self) {
+ let current_time = time::get_time();
+ let now = (current_time.sec * 1000 + current_time.nsec as i64 / 1000000) as u64;
+ self.navigation_start.set(now);
+ self.navigation_start_precise.set(time::precise_time_ns());
+ }
+
+ pub fn send_to_embedder(&self, msg: EmbedderMsg) {
+ self.send_to_constellation(ScriptMsg::ForwardToEmbedder(msg));
+ }
+
+ pub fn send_to_constellation(&self, msg: ScriptMsg) {
+ self.upcast::<GlobalScope>()
+ .script_to_constellation_chan()
+ .send(msg)
+ .unwrap();
+ }
+
+ pub fn webrender_document(&self) -> DocumentId {
+ self.webrender_document
+ }
+
+ pub fn in_immersive_xr_session(&self) -> bool {
+ self.navigator
+ .get()
+ .as_ref()
+ .and_then(|nav| nav.xr())
+ .map_or(false, |xr| xr.pending_or_active_session())
+ }
}
impl Window {
#[allow(unsafe_code)]
- pub fn new(runtime: Rc<Runtime>,
- script_chan: MainThreadScriptChan,
- dom_task_source: DOMManipulationTaskSource,
- user_task_source: UserInteractionTaskSource,
- network_task_source: NetworkingTaskSource,
- history_task_source: HistoryTraversalTaskSource,
- file_task_source: FileReadingTaskSource,
- image_cache_chan: Sender<ImageCacheMsg>,
- image_cache: Arc<ImageCache>,
- resource_threads: ResourceThreads,
- bluetooth_thread: IpcSender<BluetoothRequest>,
- mem_profiler_chan: MemProfilerChan,
- time_profiler_chan: TimeProfilerChan,
- devtools_chan: Option<IpcSender<ScriptToDevtoolsControlMsg>>,
- constellation_chan: IpcSender<ConstellationMsg>,
- control_chan: IpcSender<ConstellationControlMsg>,
- scheduler_chan: IpcSender<TimerSchedulerMsg>,
- timer_event_chan: IpcSender<TimerEvent>,
- layout_chan: Sender<Msg>,
- id: PipelineId,
- parent_info: Option<(PipelineId, FrameType)>,
- window_size: Option<WindowSizeData>,
- origin: MutableOrigin,
- webvr_thread: Option<IpcSender<WebVRMsg>>)
- -> Root<Window> {
- let layout_rpc: Box<LayoutRPC + Send> = {
- let (rpc_send, rpc_recv) = channel();
+ pub fn new(
+ runtime: Rc<Runtime>,
+ script_chan: MainThreadScriptChan,
+ task_manager: TaskManager,
+ image_cache_chan: Sender<ImageCacheMsg>,
+ image_cache: Arc<dyn ImageCache>,
+ resource_threads: ResourceThreads,
+ bluetooth_thread: IpcSender<BluetoothRequest>,
+ mem_profiler_chan: MemProfilerChan,
+ time_profiler_chan: TimeProfilerChan,
+ devtools_chan: Option<IpcSender<ScriptToDevtoolsControlMsg>>,
+ constellation_chan: ScriptToConstellationChan,
+ control_chan: IpcSender<ConstellationControlMsg>,
+ scheduler_chan: IpcSender<TimerSchedulerMsg>,
+ layout_chan: Sender<Msg>,
+ pipelineid: PipelineId,
+ parent_info: Option<PipelineId>,
+ window_size: WindowSizeData,
+ origin: MutableOrigin,
+ creator_url: ServoUrl,
+ navigation_start: u64,
+ navigation_start_precise: u64,
+ webgl_chan: Option<WebGLChan>,
+ webxr_registry: webxr_api::Registry,
+ microtask_queue: Rc<MicrotaskQueue>,
+ webrender_document: DocumentId,
+ webrender_api_sender: WebrenderIpcSender,
+ layout_is_busy: Arc<AtomicBool>,
+ relayout_event: bool,
+ prepare_for_screenshot: bool,
+ unminify_js: bool,
+ local_script_source: Option<String>,
+ userscripts_path: Option<String>,
+ is_headless: bool,
+ replace_surrogates: bool,
+ user_agent: Cow<'static, str>,
+ player_context: WindowGLContext,
+ event_loop_waker: Option<Box<dyn EventLoopWaker>>,
+ gpu_id_hub: Arc<ParkMutex<Identities>>,
+ inherited_secure_context: Option<bool>,
+ ) -> DomRoot<Self> {
+ let layout_rpc: Box<dyn LayoutRPC + Send> = {
+ let (rpc_send, rpc_recv) = unbounded();
layout_chan.send(Msg::GetRPC(rpc_send)).unwrap();
rpc_recv.recv().unwrap()
};
let error_reporter = CSSErrorReporter {
- pipelineid: id,
+ pipelineid,
script_chan: Arc::new(Mutex::new(control_chan)),
};
- let current_time = time::get_time();
- let win = box Window {
- globalscope:
- GlobalScope::new_inherited(
- id,
- devtools_chan,
- mem_profiler_chan,
- time_profiler_chan,
- constellation_chan,
- scheduler_chan,
- resource_threads,
- timer_event_chan,
- origin),
- script_chan: script_chan,
- dom_manipulation_task_source: dom_task_source,
- user_interaction_task_source: user_task_source,
- networking_task_source: network_task_source,
- history_traversal_task_source: history_task_source,
- file_reading_task_source: file_task_source,
- image_cache_chan: image_cache_chan,
- image_cache: image_cache.clone(),
+ let win = Box::new(Self {
+ globalscope: GlobalScope::new_inherited(
+ pipelineid,
+ devtools_chan,
+ mem_profiler_chan,
+ time_profiler_chan,
+ constellation_chan,
+ scheduler_chan,
+ resource_threads,
+ origin,
+ Some(creator_url),
+ microtask_queue,
+ is_headless,
+ user_agent,
+ gpu_id_hub,
+ inherited_secure_context,
+ ),
+ script_chan,
+ task_manager,
+ image_cache_chan,
+ image_cache,
navigator: Default::default(),
+ location: Default::default(),
history: Default::default(),
- browsing_context: Default::default(),
+ custom_element_registry: Default::default(),
+ window_proxy: Default::default(),
document: Default::default(),
performance: Default::default(),
- navigation_start: (current_time.sec * 1000 + current_time.nsec as i64 / 1000000) as u64,
- navigation_start_precise: time::precise_time_ns() as f64,
+ navigation_start: Cell::new(navigation_start),
+ navigation_start_precise: Cell::new(navigation_start_precise),
screen: Default::default(),
session_storage: Default::default(),
local_storage: Default::default(),
- status: DOMRefCell::new(DOMString::new()),
- parent_info: parent_info,
+ status: DomRefCell::new(DOMString::new()),
+ parent_info,
dom_static: GlobalStaticData::new(),
- js_runtime: DOMRefCell::new(Some(runtime.clone())),
- bluetooth_thread: bluetooth_thread,
+ js_runtime: DomRefCell::new(Some(runtime.clone())),
+ bluetooth_thread,
bluetooth_extra_permission_data: BluetoothExtraPermissionData::new(),
- page_clip_rect: Cell::new(max_rect()),
- resize_event: Cell::new(None),
- layout_chan: layout_chan,
- layout_rpc: layout_rpc,
+ page_clip_rect: Cell::new(MaxRect::max_rect()),
+ resize_event: Default::default(),
+ layout_chan,
+ layout_rpc,
window_size: Cell::new(window_size),
current_viewport: Cell::new(Rect::zero()),
suppress_reflow: Cell::new(true),
- pending_reflow_count: Cell::new(0),
+ pending_reflow_count: Default::default(),
current_state: Cell::new(WindowState::Alive),
- devtools_marker_sender: DOMRefCell::new(None),
- devtools_markers: DOMRefCell::new(HashSet::new()),
- webdriver_script_chan: DOMRefCell::new(None),
- ignore_further_async_events: Default::default(),
- error_reporter: error_reporter,
- scroll_offsets: DOMRefCell::new(HashMap::new()),
- media_query_lists: WeakMediaQueryListVec::new(),
+ devtools_marker_sender: Default::default(),
+ devtools_markers: Default::default(),
+ webdriver_script_chan: Default::default(),
+ error_reporter,
+ scroll_offsets: Default::default(),
+ media_query_lists: DOMTracker::new(),
test_runner: Default::default(),
- webvr_thread: webvr_thread,
- permission_state_invocation_results: DOMRefCell::new(HashMap::new()),
- pending_layout_images: DOMRefCell::new(HashMap::new()),
- unminified_js_dir: DOMRefCell::new(None),
- };
+ webgl_chan,
+ webxr_registry,
+ pending_layout_images: Default::default(),
+ unminified_js_dir: Default::default(),
+ local_script_source,
+ test_worklet: Default::default(),
+ paint_worklet: Default::default(),
+ webrender_document,
+ exists_mut_observer: Cell::new(false),
+ webrender_api_sender,
+ has_sent_idle_message: Cell::new(false),
+ layout_is_busy,
+ relayout_event,
+ prepare_for_screenshot,
+ unminify_js,
+ userscripts_path,
+ replace_surrogates,
+ player_context,
+ event_loop_waker,
+ visible: Cell::new(true),
+ layout_marker: DomRefCell::new(Rc::new(Cell::new(true))),
+ current_event: DomRefCell::new(None),
+ });
- unsafe {
- WindowBinding::Wrap(runtime.cx(), win)
+ unsafe { WindowBinding::Wrap(JSContext::from_ptr(runtime.cx()), win) }
+ }
+
+ pub fn pipeline_id(&self) -> PipelineId {
+ self.upcast::<GlobalScope>().pipeline_id()
+ }
+
+ /// Create a new cached instance of the given value.
+ pub fn cache_layout_value<T>(&self, value: T) -> LayoutValue<T>
+ where
+ T: Copy + JSTraceable + MallocSizeOf,
+ {
+ LayoutValue::new(self.layout_marker.borrow().clone(), value)
+ }
+}
+
+/// An instance of a value associated with a particular snapshot of layout. This stored
+/// value can only be read as long as the associated layout marker that is considered
+/// valid. It will automatically become unavailable when the next layout operation is
+/// performed.
+#[derive(JSTraceable, MallocSizeOf)]
+pub struct LayoutValue<T: JSTraceable + MallocSizeOf> {
+ #[ignore_malloc_size_of = "Rc is hard"]
+ is_valid: Rc<Cell<bool>>,
+ value: T,
+}
+
+impl<T: Copy + JSTraceable + MallocSizeOf> LayoutValue<T> {
+ fn new(marker: Rc<Cell<bool>>, value: T) -> Self {
+ LayoutValue {
+ is_valid: marker,
+ value,
}
}
+
+ /// Retrieve the stored value if it is still valid.
+ pub fn get(&self) -> Result<T, ()> {
+ if self.is_valid.get() {
+ return Ok(self.value);
+ }
+ Err(())
+ }
}
-fn should_move_clip_rect(clip_rect: Rect<Au>, new_viewport: Rect<f32>) -> bool {
- let clip_rect = Rect::new(Point2D::new(clip_rect.origin.x.to_f32_px(),
- clip_rect.origin.y.to_f32_px()),
- Size2D::new(clip_rect.size.width.to_f32_px(),
- clip_rect.size.height.to_f32_px()));
+fn should_move_clip_rect(clip_rect: UntypedRect<Au>, new_viewport: UntypedRect<f32>) -> bool {
+ let clip_rect = UntypedRect::new(
+ Point2D::new(
+ clip_rect.origin.x.to_f32_px(),
+ clip_rect.origin.y.to_f32_px(),
+ ),
+ Size2D::new(
+ clip_rect.size.width.to_f32_px(),
+ clip_rect.size.height.to_f32_px(),
+ ),
+ );
// We only need to move the clip rect if the viewport is getting near the edge of
// our preexisting clip rect. We use half of the size of the viewport as a heuristic
@@ -1845,111 +2532,131 @@ fn should_move_clip_rect(clip_rect: Rect<Au>, new_viewport: Rect<f32>) -> bool {
let viewport_scroll_margin = new_viewport.size * VIEWPORT_SCROLL_MARGIN_SIZE;
(clip_rect.origin.x - new_viewport.origin.x).abs() <= viewport_scroll_margin.width ||
- (clip_rect.max_x() - new_viewport.max_x()).abs() <= viewport_scroll_margin.width ||
- (clip_rect.origin.y - new_viewport.origin.y).abs() <= viewport_scroll_margin.height ||
- (clip_rect.max_y() - new_viewport.max_y()).abs() <= viewport_scroll_margin.height
+ (clip_rect.max_x() - new_viewport.max_x()).abs() <= viewport_scroll_margin.width ||
+ (clip_rect.origin.y - new_viewport.origin.y).abs() <= viewport_scroll_margin.height ||
+ (clip_rect.max_y() - new_viewport.max_y()).abs() <= viewport_scroll_margin.height
}
-fn debug_reflow_events(id: PipelineId, goal: &ReflowGoal, query_type: &ReflowQueryType, reason: &ReflowReason) {
- let mut debug_msg = format!("**** pipeline={}", id);
- debug_msg.push_str(match *goal {
- ReflowGoal::ForDisplay => "\tForDisplay",
- ReflowGoal::ForScriptQuery => "\tForScriptQuery",
- });
-
- debug_msg.push_str(match *query_type {
- ReflowQueryType::NoQuery => "\tNoQuery",
- ReflowQueryType::ContentBoxQuery(_n) => "\tContentBoxQuery",
- ReflowQueryType::ContentBoxesQuery(_n) => "\tContentBoxesQuery",
- ReflowQueryType::HitTestQuery(..) => "\tHitTestQuery",
- ReflowQueryType::NodesFromPoint(..) => "\tNodesFromPoint",
- ReflowQueryType::NodeGeometryQuery(_n) => "\tNodeGeometryQuery",
- ReflowQueryType::NodeOverflowQuery(_n) => "\tNodeOverFlowQuery",
- ReflowQueryType::NodeScrollGeometryQuery(_n) => "\tNodeScrollGeometryQuery",
- ReflowQueryType::NodeScrollRootIdQuery(_n) => "\tNodeScrollRootIdQuery",
- ReflowQueryType::ResolvedStyleQuery(_, _, _) => "\tResolvedStyleQuery",
- ReflowQueryType::OffsetParentQuery(_n) => "\tOffsetParentQuery",
- ReflowQueryType::MarginStyleQuery(_n) => "\tMarginStyleQuery",
- ReflowQueryType::TextIndexQuery(..) => "\tTextIndexQuery",
- });
-
- debug_msg.push_str(match *reason {
- ReflowReason::CachedPageNeededReflow => "\tCachedPageNeededReflow",
- ReflowReason::RefreshTick => "\tRefreshTick",
- ReflowReason::FirstLoad => "\tFirstLoad",
- ReflowReason::KeyEvent => "\tKeyEvent",
- ReflowReason::MouseEvent => "\tMouseEvent",
- ReflowReason::Query => "\tQuery",
- ReflowReason::Timer => "\tTimer",
- ReflowReason::Viewport => "\tViewport",
- ReflowReason::WindowResize => "\tWindowResize",
- ReflowReason::DOMContentLoaded => "\tDOMContentLoaded",
- ReflowReason::DocumentLoaded => "\tDocumentLoaded",
- ReflowReason::StylesheetLoaded => "\tStylesheetLoaded",
- ReflowReason::ImageLoaded => "\tImageLoaded",
- ReflowReason::RequestAnimationFrame => "\tRequestAnimationFrame",
- ReflowReason::WebFontLoaded => "\tWebFontLoaded",
- ReflowReason::FramedContentChanged => "\tFramedContentChanged",
- ReflowReason::IFrameLoadEvent => "\tIFrameLoadEvent",
- ReflowReason::MissingExplicitReflow => "\tMissingExplicitReflow",
- ReflowReason::ElementStateChanged => "\tElementStateChanged",
- });
-
- println!("{}", debug_msg);
+fn debug_reflow_events(id: PipelineId, reflow_goal: &ReflowGoal, reason: &ReflowReason) {
+ let goal_string = match *reflow_goal {
+ ReflowGoal::Full => "\tFull",
+ ReflowGoal::TickAnimations => "\tTickAnimations",
+ ReflowGoal::LayoutQuery(ref query_msg, _) => match query_msg {
+ &QueryMsg::ContentBoxQuery(_n) => "\tContentBoxQuery",
+ &QueryMsg::ContentBoxesQuery(_n) => "\tContentBoxesQuery",
+ &QueryMsg::NodesFromPointQuery(..) => "\tNodesFromPointQuery",
+ &QueryMsg::ClientRectQuery(_n) => "\tClientRectQuery",
+ &QueryMsg::NodeScrollGeometryQuery(_n) => "\tNodeScrollGeometryQuery",
+ &QueryMsg::NodeScrollIdQuery(_n) => "\tNodeScrollIdQuery",
+ &QueryMsg::ResolvedStyleQuery(_, _, _) => "\tResolvedStyleQuery",
+ &QueryMsg::ResolvedFontStyleQuery(..) => "\nResolvedFontStyleQuery",
+ &QueryMsg::OffsetParentQuery(_n) => "\tOffsetParentQuery",
+ &QueryMsg::StyleQuery => "\tStyleQuery",
+ &QueryMsg::TextIndexQuery(..) => "\tTextIndexQuery",
+ &QueryMsg::ElementInnerTextQuery(_) => "\tElementInnerTextQuery",
+ &QueryMsg::InnerWindowDimensionsQuery(_) => "\tInnerWindowDimensionsQuery",
+ },
+ };
+
+ println!("**** pipeline={}\t{}\t{:?}", id, goal_string, reason);
}
-struct PostMessageHandler {
- destination: Trusted<Window>,
- origin: Option<ImmutableOrigin>,
- message: StructuredCloneData,
-}
+impl Window {
+ // https://html.spec.whatwg.org/multipage/#dom-window-postmessage step 7.
+ pub fn post_message(
+ &self,
+ target_origin: Option<ImmutableOrigin>,
+ source_origin: ImmutableOrigin,
+ source: &WindowProxy,
+ data: StructuredSerializedData,
+ ) {
+ let this = Trusted::new(self);
+ let source = Trusted::new(source);
+ let task = task!(post_serialised_message: move || {
+ let this = this.root();
+ let source = source.root();
+ let document = this.Document();
+
+ // Step 7.1.
+ if let Some(ref target_origin) = target_origin {
+ if !target_origin.same_origin(document.origin()) {
+ return;
+ }
+ }
-impl PostMessageHandler {
- fn new(window: &Window,
- origin: Option<ImmutableOrigin>,
- message: StructuredCloneData) -> PostMessageHandler {
- PostMessageHandler {
- destination: Trusted::new(window),
- origin: origin,
- message: message,
- }
+ // Steps 7.2.-7.5.
+ let cx = this.get_cx();
+ let obj = this.reflector().get_jsobject();
+ let _ac = JSAutoRealm::new(*cx, obj.get());
+ rooted!(in(*cx) let mut message_clone = UndefinedValue());
+ if let Ok(ports) = structuredclone::read(this.upcast(), data, message_clone.handle_mut()) {
+ // Step 7.6, 7.7
+ MessageEvent::dispatch_jsval(
+ this.upcast(),
+ this.upcast(),
+ message_clone.handle(),
+ Some(&source_origin.ascii_serialization()),
+ Some(&*source),
+ ports,
+ );
+ } else {
+ // Step 4, fire messageerror.
+ MessageEvent::dispatch_error(
+ this.upcast(),
+ this.upcast(),
+ );
+ }
+ });
+ // FIXME(nox): Why are errors silenced here?
+ // TODO(#12718): Use the "posted message task source".
+ // TODO: When switching to the right task source, update the task_canceller call too.
+ let _ = self.script_chan.send(CommonScriptMsg::Task(
+ ScriptThreadEventCategory::DomEvent,
+ Box::new(
+ self.task_manager
+ .task_canceller(TaskSourceName::DOMManipulation)
+ .wrap_task(task),
+ ),
+ Some(self.pipeline_id()),
+ TaskSourceName::DOMManipulation,
+ ));
}
}
-impl Runnable for PostMessageHandler {
- // https://html.spec.whatwg.org/multipage/#dom-window-postmessage steps 10-12.
- fn handler(self: Box<PostMessageHandler>) {
- let this = *self;
- let window = this.destination.root();
-
- // Step 10.
- let doc = window.Document();
- if let Some(source) = this.origin {
- if !source.same_origin(doc.origin()) {
- return;
- }
- }
-
- let cx = window.get_cx();
- let globalhandle = window.reflector().get_jsobject();
- let _ac = JSAutoCompartment::new(cx, globalhandle.get());
-
- rooted!(in(cx) let mut message = UndefinedValue());
- this.message.read(window.upcast(), message.handle_mut());
-
- // Step 11-12.
- // TODO(#12719): set the other attributes.
- MessageEvent::dispatch_jsval(window.upcast(),
- window.upcast(),
- message.handle());
- }
+#[derive(Clone, MallocSizeOf)]
+pub struct CSSErrorReporter {
+ pub pipelineid: PipelineId,
+ // Arc+Mutex combo is necessary to make this struct Sync,
+ // which is necessary to fulfill the bounds required by the
+ // uses of the ParseErrorReporter trait.
+ #[ignore_malloc_size_of = "Arc is defined in libstd"]
+ pub script_chan: Arc<Mutex<IpcSender<ConstellationControlMsg>>>,
}
+unsafe_no_jsmanaged_fields!(CSSErrorReporter);
+
+impl ParseErrorReporter for CSSErrorReporter {
+ fn report_error(&self, url: &ServoUrl, location: SourceLocation, error: ContextualParseError) {
+ if log_enabled!(log::Level::Info) {
+ info!(
+ "Url:\t{}\n{}:{} {}",
+ url.as_str(),
+ location.line,
+ location.column,
+ error
+ )
+ }
-impl Window {
- pub fn post_message(&self, origin: Option<ImmutableOrigin>, data: StructuredCloneData) {
- let runnable = PostMessageHandler::new(self, origin, data);
- let msg = CommonScriptMsg::RunnableMsg(ScriptThreadEventCategory::DomEvent, box runnable);
- // TODO(#12718): Use the "posted message task source".
- let _ = self.script_chan.send(msg);
+ //TODO: report a real filename
+ let _ = self
+ .script_chan
+ .lock()
+ .unwrap()
+ .send(ConstellationControlMsg::ReportCSSError(
+ self.pipelineid,
+ url.to_string(),
+ location.line,
+ location.column,
+ error.to_string(),
+ ));
}
}