script: Implement <meta http-equiv="refresh"> (#31468)

* script: Implement <meta http-equiv="refresh">

* Address review comments
This commit is contained in:
Smitty 2024-03-01 02:42:18 -05:00 committed by GitHub
parent ee122acdf4
commit 0beec63c86
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 227 additions and 396 deletions

View file

@ -140,6 +140,7 @@ use crate::dom::htmlhtmlelement::HTMLHtmlElement;
use crate::dom::htmliframeelement::HTMLIFrameElement;
use crate::dom::htmlimageelement::HTMLImageElement;
use crate::dom::htmlinputelement::HTMLInputElement;
use crate::dom::htmlmetaelement::RefreshRedirectDue;
use crate::dom::htmlscriptelement::{HTMLScriptElement, ScriptResult};
use crate::dom::htmltextareaelement::HTMLTextAreaElement;
use crate::dom::htmltitleelement::HTMLTitleElement;
@ -233,6 +234,17 @@ enum FocusTransaction {
InTransaction(Option<Dom<Element>>),
}
/// Information about a declarative refresh
#[derive(JSTraceable, MallocSizeOf)]
pub enum DeclarativeRefresh {
PendingLoad {
#[no_trace]
url: ServoUrl,
time: u64,
},
CreatedAfterLoad,
}
/// <https://dom.spec.whatwg.org/#document>
#[dom_struct]
pub struct Document {
@ -435,6 +447,8 @@ pub struct Document {
animations: DomRefCell<Animations>,
/// The nearest inclusive ancestors to all the nodes that require a restyle.
dirty_root: MutNullableDom<Element>,
/// <https://html.spec.whatwg.org/multipage/#will-declaratively-refresh>
declarative_refresh: DomRefCell<Option<DeclarativeRefresh>>,
}
#[derive(JSTraceable, MallocSizeOf)]
@ -2399,6 +2413,19 @@ impl Document {
task!(completely_loaded: move || {
let document = document.root();
document.completely_loaded.set(true);
if let Some(DeclarativeRefresh::PendingLoad {
url,
time
}) = &*document.declarative_refresh.borrow() {
// https://html.spec.whatwg.org/multipage/#shared-declarative-refresh-steps
document.window.upcast::<GlobalScope>().schedule_callback(
OneshotTimerCallback::RefreshRedirectDue(RefreshRedirectDue {
window: window_from_node(&*document),
url: url.clone(),
}),
MsDuration::new(time.saturating_mul(1000)),
);
}
// Note: this will, among others, result in the "iframe-load-event-steps" being run.
// https://html.spec.whatwg.org/multipage/#iframe-load-event-steps
document.notify_constellation_load();
@ -2409,6 +2436,10 @@ impl Document {
}
}
pub fn completely_loaded(&self) -> bool {
self.completely_loaded.get()
}
// https://html.spec.whatwg.org/multipage/#pending-parsing-blocking-script
pub fn set_pending_parsing_blocking_script(
&self,
@ -3195,6 +3226,7 @@ impl Document {
},
animations: DomRefCell::new(Animations::new()),
dirty_root: Default::default(),
declarative_refresh: Default::default(),
}
}
@ -3940,6 +3972,13 @@ impl Document {
pub(crate) fn cancel_animations_for_node(&self, node: &Node) {
self.animations.borrow().cancel_animations_for_node(node);
}
pub(crate) fn will_declaratively_refresh(&self) -> bool {
self.declarative_refresh.borrow().is_some()
}
pub(crate) fn set_declarative_refresh(&self, refresh: DeclarativeRefresh) {
*self.declarative_refresh.borrow_mut() = Some(refresh);
}
}
impl Element {

View file

@ -2,29 +2,57 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::str::FromStr;
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix};
use js::rust::HandleObject;
use regex::bytes::Regex;
use script_traits::{HistoryEntryReplacement, MsDuration};
use servo_url::ServoUrl;
use style::str::HTML_SPACE_CHARACTERS;
use crate::dom::attr::Attr;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::HTMLMetaElementBinding::HTMLMetaElementMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::document::Document;
use crate::dom::document::{DeclarativeRefresh, Document};
use crate::dom::element::{AttributeMutation, Element};
use crate::dom::globalscope::GlobalScope;
use crate::dom::htmlelement::HTMLElement;
use crate::dom::htmlheadelement::HTMLHeadElement;
use crate::dom::node::{BindContext, Node, UnbindContext};
use crate::dom::location::NavigationType;
use crate::dom::node::{document_from_node, window_from_node, BindContext, Node, UnbindContext};
use crate::dom::virtualmethods::VirtualMethods;
use crate::dom::window::Window;
use crate::timers::OneshotTimerCallback;
#[dom_struct]
pub struct HTMLMetaElement {
htmlelement: HTMLElement,
}
#[derive(JSTraceable, MallocSizeOf)]
pub struct RefreshRedirectDue {
#[no_trace]
pub url: ServoUrl,
#[ignore_malloc_size_of = "non-owning"]
pub window: DomRoot<Window>,
}
impl RefreshRedirectDue {
pub fn invoke(self) {
self.window.Location().navigate(
self.url.clone(),
HistoryEntryReplacement::Enabled,
NavigationType::DeclarativeRefresh,
);
}
}
impl HTMLMetaElement {
fn new_inherited(
local_name: LocalName,
@ -58,6 +86,8 @@ impl HTMLMetaElement {
if name == "referrer" {
self.apply_referrer();
}
} else if &*self.HttpEquiv() != "" {
self.declarative_refresh();
}
}
@ -81,6 +111,96 @@ impl HTMLMetaElement {
}
}
}
/// <https://html.spec.whatwg.org/multipage/#shared-declarative-refresh-steps>
fn declarative_refresh(&self) {
// 2
let content = self.Content();
// 1
if !content.is_empty() {
// 3
self.shared_declarative_refresh_steps(content);
}
}
/// <https://html.spec.whatwg.org/multipage/#shared-declarative-refresh-steps>
fn shared_declarative_refresh_steps(&self, content: DOMString) {
// 1
let document = document_from_node(self);
if document.will_declaratively_refresh() {
return;
}
// 2-11
lazy_static::lazy_static! {
static ref REFRESH_REGEX: Regex = Regex::new(
r#"(?x)
^
\s* # 3
((?<time>\d+)\.?|\.) # 5-6
[0-9.]* # 8
(
(;|,| ) # 10.1
\s* # 10.2
(;|,)? # 10.3
\s* # 10.4
(
(U|u)(R|r)(L|l) # 11.2-11.4
\s*=\s* # 11.5-11.7
('(?<url1>.*?)'?|"(?<url2>.*?)"?|(?<url3>[^'"].*)) # 11.8 - 11.10
|
(?<url4>.*)
)?
)?
$
"#,
)
.unwrap();
}
let mut url_record = document.url();
let captures = if let Some(captures) = REFRESH_REGEX.captures(content.as_bytes()) {
captures
} else {
return;
};
let time = if let Some(time_string) = captures.name("time") {
u64::from_str(&String::from_utf8_lossy(time_string.as_bytes())).unwrap_or(0)
} else {
0
};
let captured_url = captures.name("url1").or(captures
.name("url2")
.or(captures.name("url3").or(captures.name("url4"))));
if let Some(url_match) = captured_url {
url_record = if let Ok(url) = ServoUrl::parse_with_base(
Some(&url_record),
&String::from_utf8_lossy(url_match.as_bytes()),
) {
url
} else {
return;
}
}
// 12-13
if document.completely_loaded() {
// TODO: handle active sandboxing flag
let window = window_from_node(self);
window.upcast::<GlobalScope>().schedule_callback(
OneshotTimerCallback::RefreshRedirectDue(RefreshRedirectDue {
window: window.clone(),
url: url_record,
}),
MsDuration::new(time.saturating_mul(1000)),
);
document.set_declarative_refresh(DeclarativeRefresh::CreatedAfterLoad);
} else {
document.set_declarative_refresh(DeclarativeRefresh::PendingLoad {
url: url_record,
time,
});
}
}
}
impl HTMLMetaElementMethods for HTMLMetaElement {
@ -95,6 +215,11 @@ impl HTMLMetaElementMethods for HTMLMetaElement {
// https://html.spec.whatwg.org/multipage/#dom-meta-content
make_setter!(SetContent, "content");
// https://html.spec.whatwg.org/multipage/#dom-meta-httpequiv
make_getter!(HttpEquiv, "http-equiv");
// https://html.spec.whatwg.org/multipage/#dom-meta-httpequiv
make_atomic_setter!(SetHttpEquiv, "http-equiv");
}
impl VirtualMethods for HTMLMetaElement {

View file

@ -19,7 +19,7 @@ use crate::dom::urlhelper::UrlHelper;
use crate::dom::window::Window;
#[derive(PartialEq)]
enum NavigationType {
pub enum NavigationType {
/// The "[`Location`-object navigate][1]" steps.
///
/// [1]: https://html.spec.whatwg.org/multipage/#location-object-navigate
@ -35,6 +35,11 @@ enum NavigationType {
///
/// [1]: https://html.spec.whatwg.org/multipage/#dom-location-reload
ReloadByConstellation,
/// Reload triggered by a [declarative refresh][1].
///
/// [1]: https://html.spec.whatwg.org/multipage/#shared-declarative-refresh-steps
DeclarativeRefresh,
}
#[dom_struct]
@ -56,7 +61,7 @@ impl Location {
}
/// Navigate the relevant `Document`'s browsing context.
fn navigate(
pub fn navigate(
&self,
url: ServoUrl,
replacement_flag: HistoryEntryReplacement,
@ -70,7 +75,9 @@ impl Location {
// The active document of the source browsing context used for
// navigation determines the request's referrer and referrer policy.
let source_window = match navigation_type {
NavigationType::ReloadByScript | NavigationType::ReloadByConstellation => {
NavigationType::ReloadByScript |
NavigationType::ReloadByConstellation |
NavigationType::DeclarativeRefresh => {
// > Navigate the browsing context [...] the source browsing context
// > set to the browsing context being navigated.
DomRoot::from_ref(&*self.window)
@ -92,7 +99,9 @@ impl Location {
// > node document of the element that initiated the navigation.
let navigation_origin_window = match navigation_type {
NavigationType::Normal | NavigationType::ReloadByScript => incumbent_window(),
NavigationType::ReloadByConstellation => DomRoot::from_ref(&*self.window),
NavigationType::ReloadByConstellation | NavigationType::DeclarativeRefresh => {
DomRoot::from_ref(&*self.window)
},
};
let (load_origin, creator_pipeline_id) = (
navigation_origin_window.origin().immutable().clone(),
@ -102,7 +111,7 @@ impl Location {
// Is `historyHandling` `reload`?
let reload_triggered = match navigation_type {
NavigationType::ReloadByScript | NavigationType::ReloadByConstellation => true,
NavigationType::Normal => false,
NavigationType::Normal | NavigationType::DeclarativeRefresh => false,
};
// Initiate navigation

View file

@ -9,8 +9,8 @@ interface HTMLMetaElement : HTMLElement {
[CEReactions]
attribute DOMString name;
// [CEReactions]
// attribute DOMString httpEquiv;
[CEReactions]
attribute DOMString httpEquiv;
[CEReactions]
attribute DOMString content;

View file

@ -28,6 +28,7 @@ use crate::dom::bindings::str::DOMString;
use crate::dom::document::FakeRequestAnimationFrameCallback;
use crate::dom::eventsource::EventSourceTimeoutCallback;
use crate::dom::globalscope::GlobalScope;
use crate::dom::htmlmetaelement::RefreshRedirectDue;
use crate::dom::testbinding::TestBindingCallback;
use crate::dom::xmlhttprequest::XHRTimeoutCallback;
use crate::script_module::ScriptFetchOptions;
@ -89,6 +90,7 @@ pub enum OneshotTimerCallback {
JsTimer(JsTimerTask),
TestBindingCallback(TestBindingCallback),
FakeRequestAnimationFrame(FakeRequestAnimationFrameCallback),
RefreshRedirectDue(RefreshRedirectDue),
}
impl OneshotTimerCallback {
@ -99,6 +101,7 @@ impl OneshotTimerCallback {
OneshotTimerCallback::JsTimer(task) => task.invoke(this, js_timers),
OneshotTimerCallback::TestBindingCallback(callback) => callback.invoke(),
OneshotTimerCallback::FakeRequestAnimationFrame(callback) => callback.invoke(),
OneshotTimerCallback::RefreshRedirectDue(callback) => callback.invoke(),
}
}
}