Compare commits

...

14 Commits

Author SHA1 Message Date
Euclid Ye
626af595f2 replaceWith <document> in a cycle should not panic (linux-wpt)
{"fail_fast": false, "matrix": [{"name": "Linux (WPT)", "workflow": "linux", "wpt": true, "profile": "release", "unit_tests": false, "build_libservo": false, "bencher": false, "coverage": false, "build_args": "", "wpt_args": "", "number_of_wpt_chunks": 20}]}
2026-04-25 21:09:07 +08:00
Euclid Ye
a09c334a52 replaceWith <document> in a cycle should not panic
Signed-off-by: Euclid Ye <yezhizhenjiakang@gmail.com>
2026-04-25 21:08:13 +08:00
Euclid Ye
35671d9dde pls don't panic
Signed-off-by: Euclid Ye <yezhizhenjiakang@gmail.com>
2026-04-25 17:12:45 +08:00
Alice
8ced3d1b8e layout: Expose some non-interactive accessibility roles based on DOM node type. (#44255)
Sets the accessibility role of nodes based on their DOM node type
(ignoring ARIA roles and computed styles for now), for a small subset of
roles.

This change also re-works some details of accessibility tree building:
- Set the `root` property for the accesskit tree to the node ID for the
root DOM node, rather than creating a placeholder root node which
contains the root DOM node.
- Since we now map the `<body>` node to the `RootWebArea` role, this
required a small change to the accessibility test logic to do a tree
walk to find the `RootWebArea` node, which used to be the first child
since we artificially set the placeholder root node to be the
`RootWebArea` node.
- Have `update_node_and_children()` return a bool indicating whether any
node was updated.
- Split the code updating the node itself into its own function,
`update_node()`, which takes a DOM node and returns a tuple containing
the accessibility node ID and a bool indicating whether the node
required updating
- Add an `assert_node_by_id()` method to retrieve an accessibility node
based on its ID.

Testing: See new tests added in this PR.

---------

Signed-off-by: Alice Boxhall <alice@igalia.com>
Co-authored-by: delan azabani <dazabani@igalia.com>
2026-04-25 06:25:20 +00:00
Gae24
1464ffd68a script: pass &mut JSContext to WritableStreamDefaultController::setup (#44490)
Also port `TextDecoderStream`, `TextEncoderStream`, `CompressionStream`
and `DecompressionStream` to `reflect_dom_object_with_proto_and_cx`.

Testing: It compiles
Part of #40600

Signed-off-by: Gae24 <96017547+Gae24@users.noreply.github.com>
2026-04-25 03:52:36 +00:00
dependabot[bot]
6e4a9e85a2 build: bump grid from 1.0.0 to 1.0.1 (#44487)
Bumps [grid](https://github.com/becheran/grid) from 1.0.0 to 1.0.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/becheran/grid/releases">grid's
releases</a>.</em></p>
<blockquote>
<h2>v1.0.1</h2>
<h2>What's Changed</h2>
<ul>
<li>Add non-allocating delete_row and delete_col methods by <a
href="https://github.com/Copilot"><code>@​Copilot</code></a> in <a
href="https://redirect.github.com/becheran/grid/pull/70">becheran/grid#70</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/Copilot"><code>@​Copilot</code></a> made
their first contribution in <a
href="https://redirect.github.com/becheran/grid/pull/70">becheran/grid#70</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/becheran/grid/compare/v1.0.0...v1.0.1">https://github.com/becheran/grid/compare/v1.0.0...v1.0.1</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="8caa81908f"><code>8caa819</code></a>
chore: Release grid version 1.0.1</li>
<li><a
href="e879d4100b"><code>e879d41</code></a>
Add safety checks for empty input in insert_row and insert_col
methods</li>
<li><a
href="be213bd352"><code>be213bd</code></a>
Add safety checks for grid dimension overflow in expand and prepend
methods</li>
<li><a
href="e77e0c0241"><code>e77e0c0</code></a>
style: format test assertions for better readability</li>
<li><a
href="781d36c5d8"><code>781d36c</code></a>
Add comprehensive tests for delete methods including equivalence
tests</li>
<li><a
href="9bf8d0b96f"><code>9bf8d0b</code></a>
Add delete_row and delete_col methods to avoid allocation</li>
<li><a
href="da542e1ca6"><code>da542e1</code></a>
chore: fix pipeline formatting</li>
<li><a
href="875f418b55"><code>875f418</code></a>
chore: auto generate release notes</li>
<li>See full diff in <a
href="https://github.com/becheran/grid/compare/v1.0.0...v1.0.1">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=grid&package-manager=cargo&previous-version=1.0.0&new-version=1.0.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts page](https://github.com/servo/servo/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-24 17:44:35 +00:00
rovertrack
71a5dfabdd Devtools: Enable Stylesheets Resource and Add Basic Fields to StyleSheetsActor (#44462)
Added basic fields required by firefox remote debugging protocol for
stylesheets- `StyleSheetData`
Enabled stylesheets in devtools watcher, made stylesheet request return
an empty array for now.

Testing: No change in behaviour and tests 

Fixes: part of #44315

Signed-off-by: Rover track <rishan.pgowda@gmail.com>
2026-04-24 15:14:48 +00:00
Kingsley Yung
430105468f script: Add or remove elements in sanitizer (#44481)
Implement the `allowElement`, `removeElement` and
`replaceElementWithChildren` methods of the `Sanitizer` interface, which
add or remove elements in a sanitizer.

Specification:
- https://wicg.github.io/sanitizer-api/#dom-sanitizer-allowelement
- https://wicg.github.io/sanitizer-api/#dom-sanitizer-removeelement
-
https://wicg.github.io/sanitizer-api/#dom-sanitizer-replaceelementwithchildren

Testing: Covered by WPT tests in `sanitizer-api/` subdirectory.
Additionally, some tests in `sanitizer-basic-filtering.tentative.html`
are changed from ERROR to FAIL because of this implementation.
Fixes: Part of #43948

---------

Signed-off-by: Kingsley Yung <kingsley@kkoyung.dev>
2026-04-24 15:07:01 +00:00
elomscansio
93ae710c07 script: propagate &mut JSContext in DebuggerGlobalScope::fire_eval (#44477)
Propagate `&mut JSContext` in `DebuggerGlobalScope::fire_eval`

Testing: Successful build is enough
Fixes: Part of #42638 

Opened to reduces the complexity of #44254

---------

Signed-off-by: Emmanuel Paul Elom <elomemmanuel007@gmail.com>
Signed-off-by: elomscansio <163124154+elomscansio@users.noreply.github.com>
Co-authored-by: Gae24 <96017547+Gae24@users.noreply.github.com>
2026-04-24 14:53:28 +00:00
elomscansio
8590dd74c9 script: propagate &mut JSContext in DebuggerGlobalScope::fire_add_debugee (#44476)
Propagate `&mut JSContext` in `DebuggerGlobalScope::fire_add_debugee`

Testing: Successful build is enough
Fixes: Part of #42638 

Opened to reduces the complexity of #44254

Signed-off-by: Emmanuel Paul Elom <elomemmanuel007@gmail.com>
2026-04-24 14:53:08 +00:00
rovertrack
8470478270 pass &mut JSContext to remove_script_and_layout_blocker (#44484)
This PR makes the `remove_script_and_layout_blocker` in
ee72fca571/components/script/dom/document/document.rs (L3727)
take `&mut JSContext` as argument, removing the dependency of the
previous `temp_cx` being created!

Testing:  No behaviour changes , all tests passes.
Fixes: #44479

Signed-off-by: Rover track <rishan.pgowda@gmail.com>
2026-04-24 14:27:55 +00:00
Jonathan Schwender
ee72fca571 devtools: Respect SOURCE_DATE_EPOCH for build_id (#44459)
We are probably not anywhere close to reproducible builds, but we should
try to follow best practices where trivially possible.
See also: https://reproducible-builds.org/docs/source-date-epoch/

Firefox devtools expects the build_id to be provided in a datetime
specific format.
This PR also switches the time to UTC instead of `Local`, since there
seems to be no clear reason to use Local, and SOURCE_DATE_EPOCH is UTC.

Testing: No functional changes, the devtools build_id is not covered by
any tests.
Fixes: #44458

---------

Signed-off-by: Jonathan Schwender <schwenderjonathan@gmail.com>
2026-04-24 12:09:44 +00:00
elomscansio
551100c8bb script: propagate &mut JSContext in DebuggerGlobalScope::fire_get_possible_breakpoints (#44478)
Propagate `&mut JSContext` in
`DebuggerGlobalScope::fire_get_possible_breakpoints` and related call
sites.

Testing: Successful build is enough
Fixes: Part of #42638 

Opened to reduces the complexity of #44254

Signed-off-by: Emmanuel Paul Elom <elomemmanuel007@gmail.com>
2026-04-24 11:34:00 +00:00
Furkan Erdem
22da16139d script: Use frozen array in gamepad axes for spec complience (#44411)
To comply with the Gamepad spec, this commit changes the type of Gamepad
axes from Float64Array to CachedFrozenArray as per [Gamepad
Interface](https://w3c.github.io/gamepad/#gamepad-interface).

Fixes: #44336
Testing: automated tests are not possible because the axes values can
only be accessed with a real gamepad attached.

Signed-off-by: log101 <ffrknerdm@gmail.com>
2026-04-24 11:11:44 +00:00
30 changed files with 1270 additions and 369 deletions

4
Cargo.lock generated
View File

@@ -3098,9 +3098,9 @@ dependencies = [
[[package]]
name = "grid"
version = "1.0.0"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9e2d4c0a8296178d8802098410ca05d86b17a10bb5ab559b3fb404c1f948220"
checksum = "b40ca9252762c466af32d0b1002e91e4e1bc5398f77455e55474deb466355ff5"
[[package]]
name = "group"

View File

@@ -10,11 +10,41 @@ use crate::StreamId;
use crate::actor::{Actor, ActorError, ActorRegistry};
use crate::protocol::ClientRequest;
/// <https://searchfox.org/mozilla-central/source/devtools/server/actors/resources/stylesheets.js>
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct StyleSheetData {
/// Unique identifier for this stylesheet.
resource_id: String,
/// The URL of the stylesheet. Optional for inline stylesheets.
href: Option<String>,
/// The URL of the document that owns this stylesheet.
node_href: String,
/// Whether the stylesheet is disabled.
disabled: bool,
/// The title of the stylesheet.
title: String,
/// Whether this is a browser stylesheet.
system: bool,
/// Whether this stylesheet was created by DevTools.
is_new: bool,
/// Optional source map URL.
source_map_url: Option<String>,
/// The index of this stylesheet in the document's stylesheet list.
style_sheet_index: i32,
// TODO: the following fields will be implemented later once we fetch the stylesheets
// constructed: bool,
// file_name: Option<String>,
// at_rules: Vec<Rule>,
// rule_count: u32,
// source_map_base_url: String,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct GetStyleSheetsReply {
from: String,
style_sheets: Vec<u32>, // TODO: real JSON structure.
style_sheets: Vec<StyleSheetData>,
}
#[derive(MallocSizeOf)]
@@ -38,6 +68,7 @@ impl Actor for StyleSheetsActor {
"getStyleSheets" => {
let msg = GetStyleSheetsReply {
from: self.name(),
// TODO: Fetch actual stylesheets from the script thread.
style_sheets: vec![],
};
request.reply_final(&msg)?

View File

@@ -84,7 +84,7 @@ impl SessionContext {
("network-event", true),
("network-event-stacktrace", false),
("reflow", true),
("stylesheet", false),
("stylesheet", true),
("source", true),
("thread-state", false),
("server-sent-event", false),
@@ -386,6 +386,17 @@ impl Actor for WatcherActor {
);
}
},
"stylesheet" => {
// TODO: Fetch actual stylesheets from the script thread.
// For now, send an empty array.
let empty: Vec<serde_json::Value> = vec![];
browsing_context_actor.resources_array(
empty,
resource.into(),
ResourceArrayType::Available,
&mut request,
);
},
"network-event" => {},
_ => warn!("resource {} not handled yet", resource),
}

View File

@@ -5,16 +5,29 @@
use std::path::Path;
use std::{env, fs};
use chrono::Local;
use chrono::{TimeZone, Utc};
fn main() {
println!("cargo:rerun-if-env-changed=SOURCE_DATE_EPOCH");
// Parsing as suggested on <https://reproducible-builds.org/docs/source-date-epoch/>
let now = match env::var("SOURCE_DATE_EPOCH") {
Ok(val) => Utc
.timestamp_opt(
val.parse::<i64>()
.expect("SOURCE_DATE_EPOCH should be a valid integer"),
0,
)
.unwrap(),
Err(_) => Utc::now(),
};
let build_id = now.format("%Y%m%d%H%M%S").to_string();
let path = Path::new(&env::var_os("OUT_DIR").unwrap()).join("build_id.rs");
fs::write(
path,
format!(
"const BUILD_ID: &str = \"{}\";",
Local::now().format("%Y%m%d%H%M%S")
),
)
.unwrap();
// The build ID is used in Firefox devtools, `getDateFromBuildID` function:
// <https://searchfox.org/firefox-main/rev/be31b3948198286e39a9855e414823cb17b6e94c/devtools/client/shared/remote-debugging/version-checker.js#21-24>
// The expected format is: yyyyMMddHHmmss.
// The date is than later used to check devtools compatibility:
// <https://searchfox.org/firefox-main/rev/be31b3948198286e39a9855e414823cb17b6e94c/devtools/client/shared/remote-debugging/version-checker.js#133-139>
fs::write(path, format!("const BUILD_ID: &str = \"{build_id}\";")).unwrap();
}

View File

@@ -1,14 +1,16 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use std::sync::LazyLock;
use accesskit::Role;
use layout_api::LayoutNode;
use layout_api::{LayoutElement, LayoutNode, LayoutNodeType};
use log::trace;
use rustc_hash::FxHashMap;
use script::layout_dom::ServoLayoutNode;
use servo_base::Epoch;
use style::dom::{NodeInfo, OpaqueNode};
use style::dom::OpaqueNode;
use web_atoms::{LocalName, local_name};
use crate::ArcRefCell;
@@ -35,47 +37,28 @@ impl AccessibilityTree {
const ROOT_NODE_ID: accesskit::NodeId = accesskit::NodeId(0);
pub(super) fn new(tree_id: accesskit::TreeId, epoch: Epoch) -> Self {
// The root node doesn't correspond to a DOM node, but contains the root DOM node.
let mut root_node = AccessibilityNode::new(AccessibilityTree::ROOT_NODE_ID);
root_node
.accesskit_node
.set_role(accesskit::Role::RootWebArea);
root_node
.accesskit_node
.add_action(accesskit::Action::Focus);
let mut tree = Self {
nodes: Default::default(),
accesskit_tree: accesskit::Tree::new(root_node.id),
Self {
nodes: FxHashMap::default(),
accesskit_tree: accesskit::Tree::new(AccessibilityTree::ROOT_NODE_ID),
tree_id,
epoch,
};
tree.nodes.insert(root_node.id, ArcRefCell::new(root_node));
tree
}
}
pub(super) fn update_tree(
&mut self,
root_dom_node: &ServoLayoutNode<'_>,
) -> Option<accesskit::TreeUpdate> {
let root_node = self.get_or_create_node(root_dom_node);
self.accesskit_tree.root = root_node.borrow().id;
let mut tree_update = AccessibilityUpdate::new(self.accesskit_tree.clone(), self.tree_id);
let any_node_updated = self.update_node_and_children(root_dom_node, &mut tree_update);
{
let root_dom_node_id = Self::to_accesskit_id(&root_dom_node.opaque());
let root_node = self
.nodes
.get_mut(&AccessibilityTree::ROOT_NODE_ID)
.expect("Guaranteed by Self::new");
let mut root_node = root_node.borrow_mut();
root_node
.accesskit_node
.set_children(vec![root_dom_node_id]);
tree_update.add(&root_node);
if !any_node_updated {
return None;
}
self.update_node_and_children(root_dom_node, &mut tree_update);
Some(tree_update.finalize())
}
@@ -83,9 +66,26 @@ impl AccessibilityTree {
&mut self,
dom_node: &ServoLayoutNode<'_>,
tree_update: &mut AccessibilityUpdate,
) {
// TODO: read accessibility damage from dom_node (right now, assume damage is complete)
) -> bool {
// TODO: read accessibility damage (right now, assume damage is complete)
let (node_id, updated) = self.update_node(dom_node);
let mut any_descendant_updated = false;
for dom_child in dom_node.flat_tree_children() {
// TODO: We actually need to propagate damage within the accessibility tree, rather than
// assuming it matches the DOM tree, but this will do for now.
any_descendant_updated |= self.update_node_and_children(&dom_child, tree_update);
}
if updated {
let node = self.assert_node_by_id(node_id);
tree_update.add(&node.borrow());
}
updated || any_descendant_updated
}
fn update_node(&mut self, dom_node: &ServoLayoutNode<'_>) -> (accesskit::NodeId, bool) {
let node = self.get_or_create_node(dom_node);
let mut node = node.borrow_mut();
let accesskit_node = &mut node.accesskit_node;
@@ -99,21 +99,26 @@ impl AccessibilityTree {
accesskit_node.set_children(new_children);
}
if dom_node.is_text_node() {
if let Some(dom_element) = dom_node.as_element() {
accesskit_node.set_role(Role::GenericContainer);
let local_name = dom_element.local_name().to_ascii_lowercase();
accesskit_node.set_html_tag(&*local_name);
let role = HTML_ELEMENT_ROLE_MAPPINGS
.get(&local_name)
.unwrap_or(&Role::GenericContainer);
accesskit_node.set_role(*role);
} else if dom_node.type_id() == Some(LayoutNodeType::Text) {
accesskit_node.set_role(Role::TextRun);
let text_content = dom_node.text_content();
trace!("node text content = {text_content:?}");
// FIXME: this should take into account editing selection units (grapheme clusters?)
accesskit_node.set_value(&*text_content);
} else if dom_node.as_element().is_some() {
} else {
accesskit_node.set_role(Role::GenericContainer);
}
tree_update.add(&node);
for dom_child in dom_node.flat_tree_children() {
self.update_node_and_children(&dom_child, tree_update);
}
// TODO: only return true if any update actually happened
(node.id, true)
}
fn get_or_create_node(
@@ -129,6 +134,16 @@ impl AccessibilityTree {
node.clone()
}
fn assert_node_by_id(&self, id: accesskit::NodeId) -> ArcRefCell<AccessibilityNode> {
let Some(node) = self.nodes.get(&id) else {
panic!("Stale node ID found: {id:?}");
};
node.clone()
}
// TODO: Using the OpaqueNode as the identifier for the accessibility node will inevitably
// create issues as OpaqueNodes will be reused when DOM nodes are destroyed. Instead, we should
// make a monotonically increasing ID, and have some other way to retrieve it based on DOM node.
fn to_accesskit_id(opaque: &OpaqueNode) -> accesskit::NodeId {
accesskit::NodeId(opaque.0 as u64)
}
@@ -229,3 +244,25 @@ fn test_accessibility_update_add_some_nodes_twice() {
}
);
}
static HTML_ELEMENT_ROLE_MAPPINGS: LazyLock<FxHashMap<LocalName, Role>> = LazyLock::new(|| {
[
(local_name!("article"), Role::Article),
(local_name!("aside"), Role::Complementary),
(local_name!("body"), Role::RootWebArea),
(local_name!("footer"), Role::ContentInfo),
(local_name!("h1"), Role::Heading),
(local_name!("h2"), Role::Heading),
(local_name!("h3"), Role::Heading),
(local_name!("h4"), Role::Heading),
(local_name!("h5"), Role::Heading),
(local_name!("h6"), Role::Heading),
(local_name!("header"), Role::Banner),
(local_name!("hr"), Role::Splitter),
(local_name!("main"), Role::Main),
(local_name!("nav"), Role::Navigation),
(local_name!("p"), Role::Paragraph),
]
.into_iter()
.collect()
});

View File

@@ -156,30 +156,34 @@ impl DebuggerGlobalScope {
pub(crate) fn fire_add_debuggee(
&self,
can_gc: CanGc,
cx: &mut JSContext,
debuggee_global: &GlobalScope,
debuggee_pipeline_id: PipelineId,
debuggee_worker_id: Option<WorkerId>,
) {
let _realm = enter_realm(self);
let debuggee_pipeline_id =
crate::dom::pipelineid::PipelineId::new(self.upcast(), debuggee_pipeline_id, can_gc);
let mut realm = enter_auto_realm(cx, self);
let cx = &mut realm;
let debuggee_pipeline_id = crate::dom::pipelineid::PipelineId::new(
self.upcast(),
debuggee_pipeline_id,
CanGc::from_cx(cx),
);
let event = DomRoot::upcast::<Event>(DebuggerAddDebuggeeEvent::new(
self.upcast(),
debuggee_global,
&debuggee_pipeline_id,
debuggee_worker_id.map(|id| id.to_string().into()),
can_gc,
CanGc::from_cx(cx),
));
assert!(
event.fire(self.upcast(), can_gc),
event.fire(self.upcast(), CanGc::from_cx(cx)),
"Guaranteed by DebuggerAddDebuggeeEvent::new"
);
}
pub(crate) fn fire_eval(
&self,
can_gc: CanGc,
cx: &mut JSContext,
code: DOMString,
debuggee_pipeline_id: PipelineId,
debuggee_worker_id: Option<WorkerId>,
@@ -191,26 +195,30 @@ impl DebuggerGlobalScope {
.replace(Some(result_sender))
.is_none()
);
let _realm = enter_realm(self);
let debuggee_pipeline_id =
crate::dom::pipelineid::PipelineId::new(self.upcast(), debuggee_pipeline_id, can_gc);
let mut realm = enter_auto_realm(cx, self);
let cx = &mut realm;
let debuggee_pipeline_id = crate::dom::pipelineid::PipelineId::new(
self.upcast(),
debuggee_pipeline_id,
CanGc::from_cx(cx),
);
let event = DomRoot::upcast::<Event>(DebuggerEvalEvent::new(
self.upcast(),
code,
&debuggee_pipeline_id,
debuggee_worker_id.map(|id| id.to_string().into()),
frame_actor_id.map(|id| id.into()),
can_gc,
CanGc::from_cx(cx),
));
assert!(
event.fire(self.upcast(), can_gc),
event.fire(self.upcast(), CanGc::from_cx(cx)),
"Guaranteed by DebuggerEvalEvent::new"
);
}
pub(crate) fn fire_get_possible_breakpoints(
&self,
can_gc: CanGc,
cx: &mut JSContext,
spidermonkey_id: u32,
result_sender: GenericSender<Vec<devtools_traits::RecommendedBreakpointLocation>>,
) {
@@ -223,17 +231,17 @@ impl DebuggerGlobalScope {
let event = DomRoot::upcast::<Event>(DebuggerGetPossibleBreakpointsEvent::new(
self.upcast(),
spidermonkey_id,
can_gc,
CanGc::from_cx(cx),
));
assert!(
event.fire(self.upcast(), can_gc),
event.fire(self.upcast(), CanGc::from_cx(cx)),
"Guaranteed by DebuggerGetPossibleBreakpointsEvent::new"
);
}
pub(crate) fn fire_set_breakpoint(
&self,
can_gc: CanGc,
cx: &mut JSContext,
spidermonkey_id: u32,
script_id: u32,
offset: u32,
@@ -243,10 +251,10 @@ impl DebuggerGlobalScope {
spidermonkey_id,
script_id,
offset,
can_gc,
CanGc::from_cx(cx),
));
assert!(
event.fire(self.upcast(), can_gc),
event.fire(self.upcast(), CanGc::from_cx(cx)),
"Guaranteed by DebuggerSetBreakpointEvent::new"
);
}
@@ -290,9 +298,9 @@ impl DebuggerGlobalScope {
pub(crate) fn fire_get_environment(
&self,
cx: &mut JSContext,
frame_actor_id: String,
result_sender: GenericSender<String>,
can_gc: CanGc,
) {
assert!(
self.get_environment_result_sender
@@ -303,35 +311,35 @@ impl DebuggerGlobalScope {
let event = DomRoot::upcast::<Event>(DebuggerGetEnvironmentEvent::new(
self.upcast(),
frame_actor_id.into(),
can_gc,
CanGc::from_cx(cx),
));
assert!(
event.fire(self.upcast(), can_gc),
event.fire(self.upcast(), CanGc::from_cx(cx)),
"Guaranteed by DebuggerGetEnvironmentEvent::new"
);
}
pub(crate) fn fire_resume(
&self,
cx: &mut JSContext,
resume_limit_type: Option<String>,
frame_actor_id: Option<String>,
can_gc: CanGc,
) {
let event = DomRoot::upcast::<Event>(DebuggerResumeEvent::new(
self.upcast(),
resume_limit_type.map(DOMString::from),
frame_actor_id.map(DOMString::from),
can_gc,
CanGc::from_cx(cx),
));
assert!(
event.fire(self.upcast(), can_gc),
event.fire(self.upcast(), CanGc::from_cx(cx)),
"Guaranteed by DebuggerResumeEvent::new"
);
}
pub(crate) fn fire_clear_breakpoint(
&self,
can_gc: CanGc,
cx: &mut JSContext,
spidermonkey_id: u32,
script_id: u32,
offset: u32,
@@ -341,10 +349,10 @@ impl DebuggerGlobalScope {
spidermonkey_id,
script_id,
offset,
can_gc,
CanGc::from_cx(cx),
));
assert!(
event.fire(self.upcast(), can_gc),
event.fire(self.upcast(), CanGc::from_cx(cx)),
"Guaranteed by DebuggerClearBreakpointEvent::new"
);
}

View File

@@ -710,14 +710,16 @@ impl Document {
self.root_removal_noted.set(false);
if let Some(dirty_root) = self.dirty_root.get() {
// There was an existing dirty root so we mark its
// ancestors as dirty until the document element.
for ancestor in dirty_root
.upcast::<Node>()
.inclusive_ancestors_in_flat_tree()
{
if ancestor.is::<Element>() {
ancestor.set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, true);
if dirty_root.is_connected() {
// There was an existing dirty root so we mark its
// ancestors as dirty until the document element.
for ancestor in dirty_root
.upcast::<Node>()
.inclusive_ancestors_in_flat_tree()
{
if ancestor.is::<Element>() {
ancestor.set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, true);
}
}
}
}
@@ -3720,19 +3722,17 @@ impl Document {
.set(self.script_and_layout_blockers.get() + 1);
}
#[expect(unsafe_code)]
/// Terminate the period in which JS or layout is disallowed from running.
/// If no further blockers remain, any delayed tasks in the queue will
/// be executed in queue order until the queue is empty.
pub(crate) fn remove_script_and_layout_blocker(&self) {
pub(crate) fn remove_script_and_layout_blocker(&self, cx: &mut js::context::JSContext) {
assert!(self.script_and_layout_blockers.get() > 0);
self.script_and_layout_blockers
.set(self.script_and_layout_blockers.get() - 1);
while self.script_and_layout_blockers.get() == 0 && !self.delayed_tasks.borrow().is_empty()
{
let task = self.delayed_tasks.borrow_mut().remove(0);
let mut cx = unsafe { script_bindings::script_runtime::temp_cx() };
task.run_box(&mut cx);
task.run_box(cx);
}
}

View File

@@ -15,14 +15,14 @@ use crate::dom::bindings::codegen::Bindings::TextDecoderBinding;
use crate::dom::bindings::codegen::Bindings::TextDecoderStreamBinding::TextDecoderStreamMethods;
use crate::dom::bindings::codegen::UnionTypes::ArrayBufferViewOrArrayBuffer;
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto};
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto_and_cx};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::DOMString;
use crate::dom::encoding::textdecodercommon::TextDecoderCommon;
use crate::dom::globalscope::GlobalScope;
use crate::dom::stream::transformstreamdefaultcontroller::TransformerType;
use crate::dom::types::{TransformStream, TransformStreamDefaultController};
use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
use crate::script_runtime::CanGc;
/// <https://encoding.spec.whatwg.org/#decode-and-enqueue-a-chunk>
#[expect(unsafe_code)]
@@ -122,25 +122,24 @@ impl TextDecoderStream {
}
fn new_with_proto(
cx: SafeJSContext,
cx: &mut js::context::JSContext,
global: &GlobalScope,
proto: Option<SafeHandleObject>,
encoding: &'static Encoding,
fatal: bool,
ignoreBOM: bool,
can_gc: CanGc,
) -> Fallible<DomRoot<Self>> {
let decoder = Rc::new(TextDecoderCommon::new_inherited(encoding, fatal, ignoreBOM));
let transformer_type = TransformerType::Decoder(decoder.clone());
let transform_stream = TransformStream::new_with_proto(global, None, can_gc);
transform_stream.set_up(cx, global, transformer_type, can_gc)?;
let transform_stream = TransformStream::new_with_proto(global, None, CanGc::from_cx(cx));
transform_stream.set_up(cx, global, transformer_type)?;
Ok(reflect_dom_object_with_proto(
Ok(reflect_dom_object_with_proto_and_cx(
Box::new(TextDecoderStream::new_inherited(decoder, &transform_stream)),
global,
proto,
can_gc,
cx,
))
}
}
@@ -148,9 +147,9 @@ impl TextDecoderStream {
impl TextDecoderStreamMethods<crate::DomTypeHolder> for TextDecoderStream {
/// <https://encoding.spec.whatwg.org/#dom-textdecoderstream>
fn Constructor(
cx: &mut js::context::JSContext,
global: &GlobalScope,
proto: Option<SafeHandleObject>,
can_gc: CanGc,
label: DOMString,
options: &TextDecoderBinding::TextDecoderOptions,
) -> Fallible<DomRoot<TextDecoderStream>> {
@@ -164,13 +163,12 @@ impl TextDecoderStreamMethods<crate::DomTypeHolder> for TextDecoderStream {
};
Self::new_with_proto(
GlobalScope::get_cx(),
cx,
global,
proto,
encoding,
options.fatal,
options.ignoreBOM,
can_gc,
)
}

View File

@@ -23,13 +23,14 @@ use script_bindings::conversions::SafeToJSValConvertible;
use crate::dom::bindings::buffer_source::create_buffer_source;
use crate::dom::bindings::codegen::Bindings::TextEncoderStreamBinding::TextEncoderStreamMethods;
use crate::dom::bindings::error::{Error, Fallible, throw_dom_exception};
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto};
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto_and_cx};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::DOMString;
use crate::dom::stream::readablestream::ReadableStream;
use crate::dom::stream::transformstreamdefaultcontroller::TransformerType;
use crate::dom::stream::writablestream::WritableStream;
use crate::dom::types::{GlobalScope, TransformStream, TransformStreamDefaultController};
use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
use crate::{DomTypeHolder, DomTypes};
/// String converted from an input JS Value
enum ConvertedInput<'a> {
@@ -341,10 +342,9 @@ impl TextEncoderStream {
/// <https://encoding.spec.whatwg.org/#dom-textencoderstream>
fn new_with_proto(
cx: SafeJSContext,
cx: &mut js::context::JSContext,
global: &GlobalScope,
proto: Option<SafeHandleObject>,
can_gc: CanGc,
) -> Fallible<DomRoot<TextEncoderStream>> {
// Step 1. Set thiss encoder to an instance of the UTF-8 encoder.
let encoder = Encoder::default();
@@ -356,29 +356,29 @@ impl TextEncoderStream {
let transformer_type = TransformerType::Encoder(encoder);
// Step 4. Let transformStream be a new TransformStream.
let transform = TransformStream::new_with_proto(global, None, can_gc);
let transform = TransformStream::new_with_proto(global, None, CanGc::from_cx(cx));
// Step 5. Set up transformStream with transformAlgorithm set to transformAlgorithm
// and flushAlgorithm set to flushAlgorithm.
transform.set_up(cx, global, transformer_type, can_gc)?;
transform.set_up(cx, global, transformer_type)?;
// Step 6. Set thiss transform to transformStream.
Ok(reflect_dom_object_with_proto(
Ok(reflect_dom_object_with_proto_and_cx(
Box::new(TextEncoderStream::new_inherited(&transform)),
global,
proto,
can_gc,
cx,
))
}
}
impl TextEncoderStreamMethods<DomTypeHolder> for TextEncoderStream {
impl TextEncoderStreamMethods<crate::DomTypeHolder> for TextEncoderStream {
/// <https://encoding.spec.whatwg.org/#dom-textencoderstream>
fn Constructor(
global: &<DomTypeHolder as DomTypes>::GlobalScope,
cx: &mut js::context::JSContext,
global: &GlobalScope,
proto: Option<SafeHandleObject>,
can_gc: CanGc,
) -> Fallible<DomRoot<<DomTypeHolder as DomTypes>::TextEncoderStream>> {
TextEncoderStream::new_with_proto(GlobalScope::get_cx(), global, proto, can_gc)
) -> Fallible<DomRoot<TextEncoderStream>> {
TextEncoderStream::new_with_proto(cx, global, proto)
}
/// <https://encoding.spec.whatwg.org/#dom-textencoder-encoding>
@@ -388,12 +388,12 @@ impl TextEncoderStreamMethods<DomTypeHolder> for TextEncoderStream {
}
/// <https://streams.spec.whatwg.org/#dom-generictransformstream-readable>
fn Readable(&self) -> DomRoot<<DomTypeHolder as DomTypes>::ReadableStream> {
fn Readable(&self) -> DomRoot<ReadableStream> {
self.transform.get_readable()
}
/// <https://streams.spec.whatwg.org/#dom-generictransformstream-writable>
fn Writable(&self) -> DomRoot<<DomTypeHolder as DomTypes>::WritableStream> {
fn Writable(&self) -> DomRoot<WritableStream> {
self.transform.get_writable()
}
}

View File

@@ -2,18 +2,15 @@
* 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::cell::Cell;
use std::cell::{Cell, RefCell};
use dom_struct::dom_struct;
use embedder_traits::{GamepadSupportedHapticEffects, GamepadUpdateType};
use js::rust::MutableHandleValue;
use js::typedarray::{Float64, HeapFloat64Array};
use script_bindings::trace::RootedTraceableBox;
use super::gamepadbutton::GamepadButton;
use super::gamepadhapticactuator::GamepadHapticActuator;
use super::gamepadpose::GamepadPose;
use crate::dom::bindings::buffer_source::HeapBufferSource;
use crate::dom::bindings::codegen::Bindings::GamepadBinding::{GamepadHand, GamepadMethods};
use crate::dom::bindings::frozenarray::CachedFrozenArray;
use crate::dom::bindings::inheritance::Castable;
@@ -24,7 +21,6 @@ use crate::dom::bindings::str::DOMString;
use crate::dom::event::Event;
use crate::dom::eventtarget::EventTarget;
use crate::dom::gamepadevent::{GamepadEvent, GamepadEventType};
use crate::dom::globalscope::GlobalScope;
use crate::dom::window::Window;
use crate::script_runtime::{CanGc, JSContext};
@@ -45,10 +41,11 @@ pub(crate) struct Gamepad {
timestamp: Cell<f64>,
mapping_type: String,
#[ignore_malloc_size_of = "mozjs"]
axes: HeapBufferSource<Float64>,
#[ignore_malloc_size_of = "mozjs"]
frozen_buttons: CachedFrozenArray,
buttons: Vec<Dom<GamepadButton>>,
#[ignore_malloc_size_of = "mozjs"]
frozen_axes: CachedFrozenArray,
axes: RefCell<Vec<f64>>,
pose: Option<Dom<GamepadPose>>,
#[ignore_malloc_size_of = "Defined in rust-webvr"]
hand: GamepadHand,
@@ -82,12 +79,13 @@ impl Gamepad {
connected: Cell::new(connected),
timestamp: Cell::new(timestamp),
mapping_type,
axes: HeapBufferSource::default(),
frozen_buttons: CachedFrozenArray::new(),
buttons: buttons
.iter()
.map(|button| Dom::from_ref(*button))
.collect(),
frozen_axes: CachedFrozenArray::new(),
axes: RefCell::new(Vec::new()),
pose: pose.map(Dom::from_ref),
hand,
axis_bounds,
@@ -137,7 +135,7 @@ impl Gamepad {
window,
can_gc,
);
gamepad.init_axes(can_gc);
gamepad.init_axes();
gamepad
}
}
@@ -169,10 +167,9 @@ impl GamepadMethods<crate::DomTypeHolder> for Gamepad {
}
/// <https://w3c.github.io/gamepad/#dom-gamepad-axes>
fn Axes(&self, _cx: JSContext) -> RootedTraceableBox<HeapFloat64Array> {
self.axes
.get_typed_array()
.expect("Failed to get gamepad axes.")
fn Axes(&self, cx: JSContext, can_gc: CanGc, retval: MutableHandleValue) {
self.frozen_axes
.get_or_init(|| self.axes.borrow().clone(), cx, retval, can_gc);
}
/// <https://w3c.github.io/gamepad/#dom-gamepad-buttons>
@@ -275,19 +272,15 @@ impl Gamepad {
/// Initialize the number of axes in the "standard" gamepad mapping.
/// <https://www.w3.org/TR/gamepad/#dfn-initializing-axes>
fn init_axes(&self, can_gc: CanGc) {
let initial_axes: Vec<f64> = vec![
fn init_axes(&self) {
*self.axes.borrow_mut() = vec![
0., // Horizontal axis for left stick (negative left/positive right)
0., // Vertical axis for left stick (negative up/positive down)
0., // Horizontal axis for right stick (negative left/positive right)
0., // Vertical axis for right stick (negative up/positive down)
];
self.axes
.set_data(GlobalScope::get_cx(), &initial_axes, can_gc)
.expect("Failed to set axes data on gamepad.")
}
#[expect(unsafe_code)]
/// <https://www.w3.org/TR/gamepad/#dfn-map-and-normalize-axes>
pub(crate) fn map_and_normalize_axes(&self, axis_index: usize, value: f64) {
// Let normalizedValue be 2 (logicalValue logicalMinimum) / (logicalMaximum logicalMinimum) 1.
@@ -296,13 +289,8 @@ impl Gamepad {
if denominator != 0.0 && denominator.is_finite() {
let normalized_value: f64 = 2.0 * numerator / denominator - 1.0;
if normalized_value.is_finite() {
let mut axis_vec = self
.axes
.typed_array_to_option()
.expect("Axes have not been initialized!");
unsafe {
axis_vec.as_mut_slice()[axis_index] = normalized_value;
}
self.axes.borrow_mut()[axis_index] = normalized_value;
self.frozen_axes.clear();
} else {
warn!("Axis value is not finite!");
}

View File

@@ -2881,8 +2881,8 @@ impl Node {
}
}
old_doc.remove_script_and_layout_blocker();
document.remove_script_and_layout_blocker();
old_doc.remove_script_and_layout_blocker(cx);
document.remove_script_and_layout_blocker(cx);
}
/// <https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity>
@@ -3251,8 +3251,8 @@ impl Node {
}),
);
parent_document.remove_script_and_layout_blocker();
from_document.remove_script_and_layout_blocker();
parent_document.remove_script_and_layout_blocker(cx);
from_document.remove_script_and_layout_blocker(cx);
}
/// <https://dom.spec.whatwg.org/#concept-node-replace-all>
@@ -3303,7 +3303,7 @@ impl Node {
});
MutationObserver::queue_a_mutation_record(parent, mutation);
}
parent.owner_doc().remove_script_and_layout_blocker();
parent.owner_doc().remove_script_and_layout_blocker(cx);
}
/// <https://dom.spec.whatwg.org/multipage/#string-replace-all>
@@ -3417,7 +3417,7 @@ impl Node {
});
MutationObserver::queue_a_mutation_record(parent, mutation);
}
parent.owner_doc().remove_script_and_layout_blocker();
parent.owner_doc().remove_script_and_layout_blocker(cx);
}
/// <https://dom.spec.whatwg.org/#live-range-pre-remove-steps>

View File

@@ -19,9 +19,10 @@ use crate::dom::bindings::codegen::Bindings::SanitizerBinding::{
use crate::dom::bindings::codegen::UnionTypes::SanitizerConfigOrSanitizerPresets;
use crate::dom::bindings::domname::is_valid_attribute_local_name;
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto_and_cx};
use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object_with_proto_and_cx};
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::types::Console;
use crate::dom::window::Window;
#[dom_struct]
@@ -190,12 +191,291 @@ impl SanitizerMethods<crate::DomTypeHolder> for Sanitizer {
// Step 10. Return config.
(*config).clone()
}
/// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-allowelement>
fn AllowElement(&self, element: SanitizerElementWithAttributes) -> bool {
// Step 1. Let configuration be thiss configuration.
let mut configuration = self.configuration.borrow_mut();
// Step 2. Assert: configuration is valid.
assert!(configuration.is_valid());
// Step 3. Set element to the result of canonicalize a sanitizer element with attributes
// with element.
let mut element = element.canonicalize();
// Step 4. If configuration["elements"] exists:
if configuration.elements.is_some() {
// Step 4.1. Set modified to the result of remove element from
// configuration["replaceWithChildrenElements"].
let modified = if let Some(replace_with_children_elements) =
&mut configuration.replaceWithChildrenElements
{
replace_with_children_elements.remove_item(&element)
} else {
false
};
// Step 4.2. Comment: We need to make sure the per-element attributes do not overlap
// with global attributes.
match &configuration.attributes {
// Step 4.3. If configuration["attributes"] exists:
Some(configuration_attributes) => {
// Step 4.3.1. If element["attributes"] exists:
if let Some(element_attributes) = element.attributes_mut() {
// Step 4.3.1.1. Set element["attributes"] to remove duplicates from
// element["attributes"].
element_attributes.remove_duplicates();
// Step 4.3.1.2. Set element["attributes"] to the difference of
// element["attributes"] and configuration["attributes"].
element_attributes.difference(configuration_attributes);
// Step 4.3.1.3. If configuration["dataAttributes"] is true:
if configuration.dataAttributes == Some(true) {
// Step 4.3.1.3.1. Remove all items item from element["attributes"]
// where item is a custom data attribute.
element_attributes.retain(|attribute| {
!is_custom_data_attribute(
&attribute.name().str(),
attribute
.namespace()
.map(|namespace| namespace.str())
.as_deref(),
)
});
}
}
// Step 4.3.2. If element["removeAttributes"] exists:
if let Some(element_remove_attributes) = element.remove_attributes_mut() {
// Step 4.3.2.1. set element["removeattributes"] to remove duplicates from
// element["removeattributes"].
element_remove_attributes.remove_duplicates();
// Step 4.3.2.2. set element["removeattributes"] to the intersection of
// element["removeattributes"] and configuration["attributes"].
element_remove_attributes.intersection(configuration_attributes);
}
},
// Step 4.4. Otherwise:
None => {
// NOTE: To avoid borrowing `element` again at Step 4.4.1.2 and 4.4.1.3 after
// borrowing `element` mutably at the beginning of Step 4.4.1, we clone
// element["attributes"] first, and call `set_attributes` at the end of Step
// 4.4.1 to put it back into `element`.
// Step 4.4.1. If element["attributes"] exists:
if let Some(mut element_attributes) = element.attributes_mut().cloned() {
// Step 4.4.1.1. Set element["attributes"] to remove duplicates from
// element["attributes"].
element_attributes.remove_duplicates();
// Step 4.4.1.2. Set element["attributes"] to the difference of
// element["attributes"] and element["removeAttributes"] with default « ».
element_attributes
.difference(element.remove_attributes().unwrap_or_default());
// Step 4.4.1.3. Remove element["removeAttributes"].
element.set_remove_attributes(None);
// Step 4.4.1.4. Set element["attributes"] to the difference of
// element["attributes"] and configuration["removeAttributes"].
element_attributes.difference(
configuration
.removeAttributes
.as_deref()
.unwrap_or_default(),
);
element.set_attributes(Some(element_attributes));
}
// Step 4.4.2. If element["removeAttributes"] exists:
if let Some(mut element_remove_attributes) = element.remove_attributes_mut() {
// Step 4.4.2.1. Set element["removeAttributes"] to remove duplicates from
// element["removeAttributes"].
element_remove_attributes = element_remove_attributes.remove_duplicates();
// Step 4.4.2.2. Set element["removeAttributes"] to the difference of
// element["removeAttributes"] and configuration["removeAttributes"].
element_remove_attributes.difference(
configuration
.removeAttributes
.as_deref()
.unwrap_or_default(),
);
}
},
}
// Step 4.5. If configuration["elements"] does not contain element:
let configuration_elements = configuration
.elements
.as_mut()
.expect("Guaranteed by Step 4");
if !configuration_elements.contains_item(&element) {
// Step 4.5.1. Comment: This is the case with a global allow-list that does not yet
// contain element.
// Step 4.5.2. Append element to configuration["elements"].
configuration_elements.push(element.clone());
// Step 4.5.3. Return true.
return true;
}
// Step 4.6. Comment: This is the case with a global allow-list that already contains
// element.
// Step 4.7. Let current element be the item in configuration["elements"] where
// item["name"] equals element["name"] and item["namespace"] equals
// element["namespace"].
let current_element = configuration_elements
.iter()
.find(|item| {
item.name() == element.name() && item.namespace() == element.namespace()
})
.expect("Guaranteed by Step 4.5 and Step 4.5.2");
// Step 4.8. If element equals current element then return modified.
if element == *current_element {
return modified;
}
// Step 4.9. Remove element from configuration["elements"].
configuration_elements.remove_item(&element);
// Step 4.10. Append element to configuration["elements"]
configuration_elements.push(element);
// Step 4.11. Return true.
true
}
// Step 5. Otherwise:
else {
// Step 5.1. If element["attributes"] exists or element["removeAttributes"] with default
// « » is not empty:
if element.attributes().is_some() ||
!element.remove_attributes().unwrap_or_default().is_empty()
{
// Step 5.1.1. The user agent may report a warning to the console that this
// operation is not supported.
Console::internal_warn(
&self.global(),
"Do not support adding an element with attributes to a sanitizer \
whose configuration[\"elements\"] does not exist."
.into(),
);
// Step 5.1.2. Return false.
return false;
}
// Step 5.2. Set modified to the result of remove element from
// configuration["replaceWithChildrenElements"].
let modified = if let Some(replace_with_children_elements) =
&mut configuration.replaceWithChildrenElements
{
replace_with_children_elements.remove_item(&element)
} else {
false
};
// Step 5.3. If configuration["removeElements"] does not contain element:
if !configuration
.removeElements
.as_ref()
.is_some_and(|configuration_remove_elements| {
configuration_remove_elements.contains_item(&element)
})
{
// Step 5.3.1. Comment: This is the case with a global remove-list that does not
// contain element.
// Step 5.3.2. Return modified.
return modified;
}
// Step 5.4. Comment: This is the case with a global remove-list that contains element.
// Step 5.5. Remove element from configuration["removeElements"].
if let Some(configuration_remove_elements) = &mut configuration.removeElements {
configuration_remove_elements.remove_item(&element);
}
// Step 5.6. Return true.
true
}
}
/// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-removeelement>
fn RemoveElement(&self, element: SanitizerElement) -> bool {
// Remove an element with element and thiss configuration.
self.configuration.borrow_mut().remove_element(element)
}
/// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-replaceelementwithchildren>
fn ReplaceElementWithChildren(&self, element: SanitizerElement) -> bool {
// Step 1. Let configuration be thiss configuration.
let mut configuration = self.configuration.borrow_mut();
// Step 2. Assert: configuration is valid.
assert!(configuration.is_valid());
// Step 3. Set element to the result of canonicalize a sanitizer element with element.
let element = element.canonicalize();
// Step 4. If the built-in non-replaceable elements list contains element:
if built_in_non_replaceable_elements_list().contains_item(&element) {
// Step 4.1. Return false.
return false;
}
// Step 5. If configuration["replaceWithChildrenElements"] contains element:
if configuration
.replaceWithChildrenElements
.as_ref()
.is_some_and(|configuration_replace_with_children_elements| {
configuration_replace_with_children_elements.contains_item(&element)
})
{
// Step 5.1. Return false.
return false;
}
// Step 6. Remove element from configuration["removeElements"].
if let Some(configuration_remove_elements) = &mut configuration.removeElements {
configuration_remove_elements.remove_item(&element);
}
// Step 7. Remove element from configuration["elements"] list.
if let Some(configuration_elements) = &mut configuration.elements {
configuration_elements.remove_item(&element);
}
// Step 8. Add element to configuration["replaceWithChildrenElements"].
if let Some(configuration_replace_with_children_elements) =
&mut configuration.replaceWithChildrenElements
{
configuration_replace_with_children_elements.add_item(element);
} else {
configuration.replaceWithChildrenElements = Some(vec![element]);
}
// Step 9. Return true.
true
}
}
trait SanitizerConfigAlgorithm {
/// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-valid>
fn is_valid(&self) -> bool;
/// <https://wicg.github.io/sanitizer-api/#sanitizer-remove-an-element>
fn remove_element(&mut self, element: SanitizerElement) -> bool;
/// <https://wicg.github.io/sanitizer-api/#sanitizer-canonicalize-the-configuration>
fn canonicalize(&mut self, allow_comments_pis_and_data_attributes: bool);
}
@@ -310,7 +590,7 @@ impl SanitizerConfigAlgorithm for SanitizerConfig {
for element in config_replace_with_children_elements {
// Step 15.1.1. If the built-in non-replaceable elements list contains element, then
// return false.
if built_in_non_replaceable_elements_list().contains_name(element) {
if built_in_non_replaceable_elements_list().contains_item(element) {
return false;
}
}
@@ -388,7 +668,7 @@ impl SanitizerConfigAlgorithm for SanitizerConfig {
.remove_attributes()
.unwrap_or_default()
.iter()
.all(|entry| config_attributes.contains_name(entry))
.all(|entry| config_attributes.contains_item(entry))
{
return false;
}
@@ -502,6 +782,72 @@ impl SanitizerConfigAlgorithm for SanitizerConfig {
true
}
/// <https://wicg.github.io/sanitizer-api/#sanitizer-remove-an-element>
fn remove_element(&mut self, element: SanitizerElement) -> bool {
// Step 1. Assert: configuration is valid.
assert!(self.is_valid());
// Step 2. Set element to the result of canonicalize a sanitizer element with element.
let element = element.canonicalize();
// Step 3. Set modified to the result of remove element from
// configuration["replaceWithChildrenElements"].
let modified = if let Some(configuration_replace_with_children_elements) =
&mut self.replaceWithChildrenElements
{
configuration_replace_with_children_elements.remove_item(&element)
} else {
false
};
// Step 4. If configuration["elements"] exists:
if let Some(configuration_elements) = &mut self.elements {
// Step 4.1. If configuration["elements"] contains element:
if configuration_elements.contains_item(&element) {
// Step 4.1.1. Comment: We have a global allow list and it contains element.
// Step 4.1.2. Remove element from configuration["elements"].
configuration_elements.remove_item(&element);
// Step 4.1.3. Return true.
return true;
}
// Step 4.2. Comment: We have a global allow list and it does not contain element.
// Step 4.3. Return modified.
modified
}
// Step 5. Otherwise:
else {
// Step 5.1. If configuration["removeElements"] contains element:
if self
.removeElements
.as_mut()
.is_some_and(|configuration_remove_elements| {
configuration_remove_elements.contains_item(&element)
})
{
// Step 5.1.1. Comment: We have a global remove list and it already contains element.
// Step 5.1.2. Return modified.
return modified;
}
// Step 5.2. Comment: We have a global remove list and it does not contain element.
// Step 5.3. Add element to configuration["removeElements"].
if let Some(configuration_remove_elements) = &mut self.removeElements {
configuration_remove_elements.add_item(element);
} else {
self.removeElements = Some(vec![element]);
}
// Step 5.4. Return true.
true
}
}
/// <https://wicg.github.io/sanitizer-api/#sanitizer-canonicalize-the-configuration>
fn canonicalize(&mut self, allow_comments_pis_and_data_attributes: bool) {
// Step 1. If neither configuration["elements"] nor configuration["removeElements"] exist,
@@ -803,7 +1149,7 @@ where
T: NameMember + Canonicalization + Clone,
{
/// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-contains>
fn contains_name<S: NameMember>(&self, other: &S) -> bool;
fn contains_item<S: NameMember>(&self, other: &S) -> bool;
/// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-has-duplicates>
fn has_duplicates(&self) -> bool;
@@ -821,7 +1167,7 @@ where
T: NameMember + Canonicalization + Clone,
{
/// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-contains>
fn contains_name<S: NameMember>(&self, other: &S) -> bool {
fn contains_item<S: NameMember>(&self, other: &S) -> bool {
// A Sanitizer name list contains an item if there exists an entry of list that is an
// ordered map, and where item["name"] equals entry["name"] and item["namespace"] equals
// entry["namespace"].
@@ -872,6 +1218,107 @@ where
}
}
/// Supporting algorithms on lists of elements and lists of attributes, from the specification.
trait NameVec<T>
where
T: NameMember + Canonicalization + Clone,
{
/// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove>
fn remove_item<S: NameMember>(&mut self, item: &S) -> bool;
/// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-add>
fn add_item(&mut self, name: T);
/// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove-duplicates>
fn remove_duplicates(&mut self) -> &mut Self;
/// Set itself to the set intersection of itself and another list.
///
/// <https://infra.spec.whatwg.org/#set-intersection>
fn intersection<S>(&mut self, others: &[S])
where
S: NameMember + Canonicalization + Clone;
/// <https://infra.spec.whatwg.org/#set-difference>
fn difference(&mut self, others: &[T]);
}
impl<T> NameVec<T> for Vec<T>
where
T: NameMember + Canonicalization + Clone,
{
/// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove>
fn remove_item<S: NameMember>(&mut self, item: &S) -> bool {
// Step 1. Set removed to false.
let mut removed = false;
// Step 2. For each entry of list:
// Step 2.1. If item["name"] equals entry["name"] and item["namespace"] equals entry["namespace"]:
// Step 2.1.1. Remove item entry from list.
// Step 2.1.2. Set removed to true.
self.retain(|entry| {
let matched = item.name() == entry.name() && item.namespace() == entry.namespace();
if matched {
removed = true;
}
!matched
});
// Step 3. Return removed.
removed
}
/// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-add>
fn add_item(&mut self, name: T) {
// Step 1. If list contains name, then return.
if self.contains_item(&name) {
return;
};
// Step 2. Append name to list.
self.push(name);
}
/// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove-duplicates>
fn remove_duplicates(&mut self) -> &mut Self {
// Step 1. Let result be « ».
// Step 2. For each entry of list, add entry to result.
// Step 3. Return result.
self.sort_by(|item_a, item_b| item_a.compare(item_b));
self.dedup_by_key(|item| (item.name().clone(), item.namespace().cloned()));
self
}
/// Set itself to the set intersection of itself and another list.
///
/// <https://infra.spec.whatwg.org/#set-intersection>
fn intersection<S>(&mut self, others: &[S])
where
S: NameMember + Canonicalization + Clone,
{
// The intersection of ordered sets A and B, is the result of creating a new ordered set set
// and, for each item of A, if B contains item, appending item to set.
self.retain(|item| {
others
.iter()
.any(|other| other.name() == item.name() && other.namespace() == item.namespace())
})
}
/// Set itself to the set difference of itself and another list.
///
/// <https://infra.spec.whatwg.org/#set-difference>
fn difference(&mut self, others: &[T]) {
// The difference of ordered sets A and B, is the result of creating a new ordered set set
// and, for each item of A, if B does not contain item, appending item to set.
self.retain(|item| {
!others
.iter()
.any(|other| other.name() == item.name() && other.namespace() == item.namespace())
})
}
}
/// Helper functions for accessing the "name" and "namespace" members of
/// [`SanitizerElementWithAttributes`], [`SanitizerElement`] and [`SanitizerAttribute`].
trait NameMember: Sized {

View File

@@ -24,7 +24,7 @@ use crate::dom::bindings::codegen::Bindings::CompressionStreamBinding::{
use crate::dom::bindings::codegen::UnionTypes::ArrayBufferViewOrArrayBuffer;
use crate::dom::bindings::conversions::{SafeFromJSValConvertible, SafeToJSValConvertible};
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto};
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto_and_cx};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::stream::transformstreamdefaultcontroller::TransformerType;
use crate::dom::types::{
@@ -63,17 +63,17 @@ impl CompressionStream {
}
fn new_with_proto(
cx: &mut js::context::JSContext,
global: &GlobalScope,
proto: Option<SafeHandleObject>,
transform: &TransformStream,
format: CompressionFormat,
can_gc: CanGc,
) -> DomRoot<CompressionStream> {
reflect_dom_object_with_proto(
reflect_dom_object_with_proto_and_cx(
Box::new(CompressionStream::new_inherited(transform, format)),
global,
proto,
can_gc,
cx,
)
}
}
@@ -81,9 +81,9 @@ impl CompressionStream {
impl CompressionStreamMethods<crate::DomTypeHolder> for CompressionStream {
/// <https://compression.spec.whatwg.org/#dom-compressionstream-compressionstream>
fn Constructor(
cx: &mut js::context::JSContext,
global: &GlobalScope,
proto: Option<SafeHandleObject>,
can_gc: CanGc,
format: CompressionFormat,
) -> Fallible<DomRoot<CompressionStream>> {
// Step 1. If format is unsupported in CompressionStream, then throw a TypeError.
@@ -91,9 +91,9 @@ impl CompressionStreamMethods<crate::DomTypeHolder> for CompressionStream {
// Step 2. Set thiss format to format.
// Step 5. Set thiss transform to a new TransformStream.
let transform = TransformStream::new_with_proto(global, None, can_gc);
let transform = TransformStream::new_with_proto(global, None, CanGc::from_cx(cx));
let compression_stream =
CompressionStream::new_with_proto(global, proto, &transform, format, can_gc);
CompressionStream::new_with_proto(cx, global, proto, &transform, format);
// Step 3. Let transformAlgorithm be an algorithm which takes a chunk argument and runs the
// compress and enqueue a chunk algorithm with this and chunk.
@@ -103,8 +103,7 @@ impl CompressionStreamMethods<crate::DomTypeHolder> for CompressionStream {
// Step 6. Set up thiss transform with transformAlgorithm set to transformAlgorithm and
// flushAlgorithm set to flushAlgorithm.
let cx = GlobalScope::get_cx();
transform.set_up(cx, global, transformer_type, can_gc)?;
transform.set_up(cx, global, transformer_type)?;
Ok(compression_stream)
}

View File

@@ -20,7 +20,7 @@ use crate::dom::bindings::codegen::Bindings::CompressionStreamBinding::Compressi
use crate::dom::bindings::codegen::Bindings::DecompressionStreamBinding::DecompressionStreamMethods;
use crate::dom::bindings::conversions::SafeToJSValConvertible;
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto};
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto_and_cx};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::stream::compressionstream::{BROTLI_BUFFER_SIZE, convert_chunk_to_vec};
use crate::dom::stream::transformstreamdefaultcontroller::TransformerType;
@@ -59,17 +59,17 @@ impl DecompressionStream {
}
fn new_with_proto(
cx: &mut js::context::JSContext,
global: &GlobalScope,
proto: Option<SafeHandleObject>,
transform: &TransformStream,
format: CompressionFormat,
can_gc: CanGc,
) -> DomRoot<DecompressionStream> {
reflect_dom_object_with_proto(
reflect_dom_object_with_proto_and_cx(
Box::new(DecompressionStream::new_inherited(transform, format)),
global,
proto,
can_gc,
cx,
)
}
}
@@ -77,9 +77,9 @@ impl DecompressionStream {
impl DecompressionStreamMethods<crate::DomTypeHolder> for DecompressionStream {
/// <https://compression.spec.whatwg.org/#dom-decompressionstream-decompressionstream>
fn Constructor(
cx: &mut js::context::JSContext,
global: &GlobalScope,
proto: Option<SafeHandleObject>,
can_gc: CanGc,
format: CompressionFormat,
) -> Fallible<DomRoot<DecompressionStream>> {
// Step 1. If format is unsupported in DecompressionStream, then throw a TypeError.
@@ -87,9 +87,9 @@ impl DecompressionStreamMethods<crate::DomTypeHolder> for DecompressionStream {
// Step 2. Set thiss format to format.
// Step 5. Set thiss transform to a new TransformStream.
let transform = TransformStream::new_with_proto(global, None, can_gc);
let transform = TransformStream::new_with_proto(global, None, CanGc::from_cx(cx));
let decompression_stream =
DecompressionStream::new_with_proto(global, proto, &transform, format, can_gc);
DecompressionStream::new_with_proto(cx, global, proto, &transform, format);
// Step 3. Let transformAlgorithm be an algorithm which takes a chunk argument and runs the
// decompress and enqueue a chunk algorithm with this and chunk.
@@ -99,8 +99,7 @@ impl DecompressionStreamMethods<crate::DomTypeHolder> for DecompressionStream {
// Step 6. Set up thiss transform with transformAlgorithm set to transformAlgorithm and
// flushAlgorithm set to flushAlgorithm.
let cx = GlobalScope::get_cx();
transform.set_up(cx, global, transformer_type, can_gc)?;
transform.set_up(cx, global, transformer_type)?;
Ok(decompression_stream)
}

View File

@@ -451,22 +451,23 @@ impl TransformStream {
/// <https://streams.spec.whatwg.org/#transformstream-set-up>
pub(crate) fn set_up(
&self,
cx: SafeJSContext,
cx: &mut js::context::JSContext,
global: &GlobalScope,
transformer_type: TransformerType,
can_gc: CanGc,
) -> Fallible<()> {
// Step1. Let writableHighWaterMark be 1.
let writable_high_water_mark = 1.0;
// Step 2. Let writableSizeAlgorithm be an algorithm that returns 1.
let writable_size_algorithm = extract_size_algorithm(&Default::default(), can_gc);
let writable_size_algorithm =
extract_size_algorithm(&Default::default(), CanGc::from_cx(cx));
// Step 3. Let readableHighWaterMark be 0.
let readable_high_water_mark = 0.0;
// Step 4. Let readableSizeAlgorithm be an algorithm that returns 1.
let readable_size_algorithm = extract_size_algorithm(&Default::default(), can_gc);
let readable_size_algorithm =
extract_size_algorithm(&Default::default(), CanGc::from_cx(cx));
// Step 5. Let transformAlgorithmWrapper be an algorithm that runs these steps given a value chunk:
// Step 6. Let flushAlgorithmWrapper be an algorithm that runs these steps:
@@ -474,7 +475,7 @@ impl TransformStream {
// NOTE: These steps are implemented in `TransformStreamDefaultController::new`
// Step 8. Let startPromise be a promise resolved with undefined.
let start_promise = Promise::new_resolved(global, cx, (), can_gc);
let start_promise = Promise::new_resolved(global, cx.into(), (), CanGc::from_cx(cx));
// Step 9. Perform ! InitializeTransformStream(stream, startPromise,
// writableHighWaterMark, writableSizeAlgorithm, readableHighWaterMark,
@@ -487,11 +488,11 @@ impl TransformStream {
writable_size_algorithm,
readable_high_water_mark,
readable_size_algorithm,
can_gc,
)?;
// Step 10. Let controller be a new TransformStreamDefaultController.
let controller = TransformStreamDefaultController::new(global, transformer_type, can_gc);
let controller =
TransformStreamDefaultController::new(global, transformer_type, CanGc::from_cx(cx));
// Step 11. Perform ! SetUpTransformStreamDefaultController(stream,
// controller, transformAlgorithmWrapper, flushAlgorithmWrapper,
@@ -518,17 +519,16 @@ impl TransformStream {
}
/// <https://streams.spec.whatwg.org/#initialize-transform-stream>
#[allow(clippy::too_many_arguments)]
#[expect(clippy::too_many_arguments)]
fn initialize(
&self,
cx: SafeJSContext,
cx: &mut js::context::JSContext,
global: &GlobalScope,
start_promise: Rc<Promise>,
writable_high_water_mark: f64,
writable_size_algorithm: Rc<QueuingStrategySize>,
readable_high_water_mark: f64,
readable_size_algorithm: Rc<QueuingStrategySize>,
can_gc: CanGc,
) -> Fallible<()> {
// Let startAlgorithm be an algorithm that returns startPromise.
// Let writeAlgorithm be the following steps, taking a chunk argument:
@@ -547,7 +547,6 @@ impl TransformStream {
writable_high_water_mark,
writable_size_algorithm,
UnderlyingSinkType::Transform(Dom::from_ref(self), start_promise.clone()),
can_gc,
)?;
self.writable.set(Some(&writable));
@@ -567,7 +566,7 @@ impl TransformStream {
UnderlyingSourceType::Transform(Dom::from_ref(self), start_promise),
Some(readable_size_algorithm),
Some(readable_high_water_mark),
can_gc,
CanGc::from_cx(cx),
);
self.readable.set(Some(&readable));
@@ -575,7 +574,7 @@ impl TransformStream {
// Note: This is done in the constructor.
// Perform ! TransformStreamSetBackpressure(stream, true).
self.set_backpressure(global, true, can_gc);
self.set_backpressure(global, true, CanGc::from_cx(cx));
// Set stream.[[controller]] to undefined.
self.controller.set(None);
@@ -962,22 +961,21 @@ impl TransformStreamMethods<crate::DomTypeHolder> for TransformStream {
/// <https://streams.spec.whatwg.org/#ts-constructor>
#[expect(unsafe_code)]
fn Constructor(
cx: SafeJSContext,
cx: &mut js::context::JSContext,
global: &GlobalScope,
proto: Option<SafeHandleObject>,
can_gc: CanGc,
transformer: Option<*mut JSObject>,
writable_strategy: &QueuingStrategy,
readable_strategy: &QueuingStrategy,
) -> Fallible<DomRoot<TransformStream>> {
// If transformer is missing, set it to null.
rooted!(in(*cx) let transformer_obj = transformer.unwrap_or(ptr::null_mut()));
rooted!(&in(cx) let transformer_obj = transformer.unwrap_or(ptr::null_mut()));
// Let underlyingSinkDict be underlyingSink,
// converted to an IDL value of type UnderlyingSink.
let transformer_dict = if !transformer_obj.is_null() {
rooted!(in(*cx) let obj_val = ObjectValue(transformer_obj.get()));
match Transformer::new(cx, obj_val.handle(), can_gc) {
rooted!(&in(cx) let obj_val = ObjectValue(transformer_obj.get()));
match Transformer::new(cx.into(), obj_val.handle(), CanGc::from_cx(cx)) {
Ok(ConversionResult::Success(val)) => val,
Ok(ConversionResult::Failure(error)) => {
return Err(Error::Type(error.into_owned()));
@@ -1004,20 +1002,20 @@ impl TransformStreamMethods<crate::DomTypeHolder> for TransformStream {
let readable_high_water_mark = extract_high_water_mark(readable_strategy, 0.0)?;
// Let readableSizeAlgorithm be ! ExtractSizeAlgorithm(readableStrategy).
let readable_size_algorithm = extract_size_algorithm(readable_strategy, can_gc);
let readable_size_algorithm = extract_size_algorithm(readable_strategy, CanGc::from_cx(cx));
// Let writableHighWaterMark be ? ExtractHighWaterMark(writableStrategy, 1).
let writable_high_water_mark = extract_high_water_mark(writable_strategy, 1.0)?;
// Let writableSizeAlgorithm be ! ExtractSizeAlgorithm(writableStrategy).
let writable_size_algorithm = extract_size_algorithm(writable_strategy, can_gc);
let writable_size_algorithm = extract_size_algorithm(writable_strategy, CanGc::from_cx(cx));
// Let startPromise be a new promise.
let start_promise = Promise::new(global, can_gc);
let start_promise = Promise::new2(cx, global);
// Perform ! InitializeTransformStream(this, startPromise, writableHighWaterMark,
// writableSizeAlgorithm, readableHighWaterMark, readableSizeAlgorithm).
let stream = TransformStream::new_with_proto(global, proto, can_gc);
let stream = TransformStream::new_with_proto(global, proto, CanGc::from_cx(cx));
stream.initialize(
cx,
global,
@@ -1026,7 +1024,6 @@ impl TransformStreamMethods<crate::DomTypeHolder> for TransformStream {
writable_size_algorithm,
readable_high_water_mark,
readable_size_algorithm,
can_gc,
)?;
// Perform ? SetUpTransformStreamDefaultControllerFromTransformer(this, transformer, transformerDict).
@@ -1034,22 +1031,22 @@ impl TransformStreamMethods<crate::DomTypeHolder> for TransformStream {
global,
transformer_obj.handle(),
&transformer_dict,
can_gc,
CanGc::from_cx(cx),
);
// If transformerDict["start"] exists, then resolve startPromise with the
// result of invoking transformerDict["start"]
// with argument list « this.[[controller]] » and callback this value transformer.
if let Some(start) = &transformer_dict.start {
rooted!(in(*cx) let mut result_object = ptr::null_mut::<JSObject>());
rooted!(in(*cx) let mut result: JSVal);
rooted!(in(*cx) let this_object = transformer_obj.get());
rooted!(&in(cx) let mut result_object = ptr::null_mut::<JSObject>());
rooted!(&in(cx) let mut result: JSVal);
rooted!(&in(cx) let this_object = transformer_obj.get());
start.Call_(
&this_object.handle(),
&stream.get_controller(),
result.handle_mut(),
ExceptionHandling::Rethrow,
can_gc,
CanGc::from_cx(cx),
)?;
let is_promise = unsafe {
if result.is_object() {
@@ -1060,14 +1057,14 @@ impl TransformStreamMethods<crate::DomTypeHolder> for TransformStream {
}
};
let promise = if is_promise {
Promise::new_with_js_promise(result_object.handle(), cx)
Promise::new_with_js_promise(result_object.handle(), cx.into())
} else {
Promise::new_resolved(global, cx, result.get(), can_gc)
Promise::new_resolved(global, cx.into(), result.get(), CanGc::from_cx(cx))
};
start_promise.resolve_native(&promise, can_gc);
start_promise.resolve_native(&promise, CanGc::from_cx(cx));
} else {
// Otherwise, resolve startPromise with undefined.
start_promise.resolve_native(&(), can_gc);
start_promise.resolve_native(&(), CanGc::from_cx(cx));
};
Ok(stream)

View File

@@ -914,21 +914,20 @@ impl WritableStream {
// Perform ! SetUpWritableStreamDefaultController
controller
.setup(cx.into(), &global, self, CanGc::from_cx(cx))
.setup(cx, &global, self)
.expect("Setup for transfer cannot fail");
}
/// <https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller-from-underlying-sink>
#[allow(clippy::too_many_arguments)]
pub(crate) fn setup_from_underlying_sink(
fn setup_from_underlying_sink(
&self,
cx: SafeJSContext,
cx: &mut js::context::JSContext,
global: &GlobalScope,
stream: &WritableStream,
underlying_sink_obj: SafeHandleObject,
underlying_sink: &UnderlyingSink,
strategy_hwm: f64,
strategy_size: Rc<QueuingStrategySize>,
can_gc: CanGc,
) -> Result<(), Error> {
// Let controller be a new WritableStreamDefaultController.
@@ -965,7 +964,7 @@ impl WritableStream {
),
strategy_hwm,
strategy_size,
can_gc,
CanGc::from_cx(cx),
);
// Note: this must be done before `setup`,
@@ -973,26 +972,25 @@ impl WritableStream {
controller.set_underlying_sink_this_object(underlying_sink_obj);
// Perform ? SetUpWritableStreamDefaultController
controller.setup(cx, global, stream, can_gc)
controller.setup(cx, global, stream)
}
}
/// <https://streams.spec.whatwg.org/#create-writable-stream>
#[cfg_attr(crown, expect(crown::unrooted_must_root))]
pub(crate) fn create_writable_stream(
cx: SafeJSContext,
cx: &mut js::context::JSContext,
global: &GlobalScope,
writable_high_water_mark: f64,
writable_size_algorithm: Rc<QueuingStrategySize>,
underlying_sink_type: UnderlyingSinkType,
can_gc: CanGc,
) -> Fallible<DomRoot<WritableStream>> {
// Assert: ! IsNonNegativeNumber(highWaterMark) is true.
assert!(writable_high_water_mark >= 0.0);
// Let stream be a new WritableStream.
// Perform ! InitializeWritableStream(stream).
let stream = WritableStream::new_with_proto(global, None, can_gc);
let stream = WritableStream::new_with_proto(global, None, CanGc::from_cx(cx));
// Let controller be a new WritableStreamDefaultController.
let controller = WritableStreamDefaultController::new(
@@ -1000,12 +998,12 @@ pub(crate) fn create_writable_stream(
underlying_sink_type,
writable_high_water_mark,
writable_size_algorithm,
can_gc,
CanGc::from_cx(cx),
);
// Perform ? SetUpWritableStreamDefaultController(stream, controller, startAlgorithm, writeAlgorithm,
// closeAlgorithm, abortAlgorithm, highWaterMark, sizeAlgorithm).
controller.setup(cx, global, &stream, can_gc)?;
controller.setup(cx, global, &stream)?;
// Return stream.
Ok(stream)
@@ -1014,21 +1012,20 @@ pub(crate) fn create_writable_stream(
impl WritableStreamMethods<crate::DomTypeHolder> for WritableStream {
/// <https://streams.spec.whatwg.org/#ws-constructor>
fn Constructor(
cx: SafeJSContext,
cx: &mut js::context::JSContext,
global: &GlobalScope,
proto: Option<SafeHandleObject>,
can_gc: CanGc,
underlying_sink: Option<*mut JSObject>,
strategy: &QueuingStrategy,
) -> Fallible<DomRoot<WritableStream>> {
// If underlyingSink is missing, set it to null.
rooted!(in(*cx) let underlying_sink_obj = underlying_sink.unwrap_or(ptr::null_mut()));
rooted!(&in(cx) let underlying_sink_obj = underlying_sink.unwrap_or(ptr::null_mut()));
// Let underlyingSinkDict be underlyingSink,
// converted to an IDL value of type UnderlyingSink.
let underlying_sink_dict = if !underlying_sink_obj.is_null() {
rooted!(in(*cx) let obj_val = ObjectValue(underlying_sink_obj.get()));
match UnderlyingSink::new(cx, obj_val.handle(), can_gc) {
rooted!(&in(cx) let obj_val = ObjectValue(underlying_sink_obj.get()));
match UnderlyingSink::new(cx.into(), obj_val.handle(), CanGc::from_cx(cx)) {
Ok(ConversionResult::Success(val)) => val,
Ok(ConversionResult::Failure(error)) => {
return Err(Error::Type(error.into_owned()));
@@ -1047,10 +1044,10 @@ impl WritableStreamMethods<crate::DomTypeHolder> for WritableStream {
}
// Perform ! InitializeWritableStream(this).
let stream = WritableStream::new_with_proto(global, proto, can_gc);
let stream = WritableStream::new_with_proto(global, proto, CanGc::from_cx(cx));
// Let sizeAlgorithm be ! ExtractSizeAlgorithm(strategy).
let size_algorithm = extract_size_algorithm(strategy, can_gc);
let size_algorithm = extract_size_algorithm(strategy, CanGc::from_cx(cx));
// Let highWaterMark be ? ExtractHighWaterMark(strategy, 1).
let high_water_mark = extract_high_water_mark(strategy, 1.0)?;
@@ -1065,7 +1062,6 @@ impl WritableStreamMethods<crate::DomTypeHolder> for WritableStream {
&underlying_sink_dict,
high_water_mark,
size_algorithm,
can_gc,
)?;
Ok(stream)

View File

@@ -29,7 +29,7 @@ use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
use crate::dom::readablestreamdefaultcontroller::{EnqueuedValue, QueueWithSizes, ValueWithSize};
use crate::dom::stream::writablestream::WritableStream;
use crate::dom::types::{AbortController, AbortSignal, TransformStream};
use crate::realms::{InRealm, enter_auto_realm, enter_realm};
use crate::realms::{InRealm, enter_auto_realm};
use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
impl js::gc::Rootable for CloseAlgorithmFulfillmentHandler {}
@@ -442,10 +442,9 @@ impl WritableStreamDefaultController {
/// <https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller>
pub(crate) fn setup(
&self,
cx: SafeJSContext,
cx: &mut js::context::JSContext,
global: &GlobalScope,
stream: &WritableStream,
can_gc: CanGc,
) -> Result<(), Error> {
// Assert: stream implements WritableStream.
// Implied by stream type.
@@ -481,21 +480,21 @@ impl WritableStreamDefaultController {
let backpressure = self.get_backpressure();
// Perform ! WritableStreamUpdateBackpressure(stream, backpressure).
stream.update_backpressure(backpressure, global, can_gc);
stream.update_backpressure(backpressure, global, CanGc::from_cx(cx));
// Let startResult be the result of performing startAlgorithm. (This may throw an exception.)
// Let startPromise be a promise resolved with startResult.
let start_promise = self.start_algorithm(cx, global, can_gc)?;
let start_promise = self.start_algorithm(cx.into(), global, CanGc::from_cx(cx))?;
let rooted_default_controller = DomRoot::from_ref(self);
// Upon fulfillment of startPromise,
rooted!(in(*cx) let mut fulfillment_handler = Some(StartAlgorithmFulfillmentHandler {
rooted!(&in(cx) let mut fulfillment_handler = Some(StartAlgorithmFulfillmentHandler {
controller: Dom::from_ref(&rooted_default_controller),
}));
// Upon rejection of startPromise with reason r,
rooted!(in(*cx) let mut rejection_handler = Some(StartAlgorithmRejectionHandler {
rooted!(&in(cx) let mut rejection_handler = Some(StartAlgorithmRejectionHandler {
controller: Dom::from_ref(&rooted_default_controller),
}));
@@ -503,11 +502,14 @@ impl WritableStreamDefaultController {
global,
fulfillment_handler.take().map(|h| Box::new(h) as Box<_>),
rejection_handler.take().map(|h| Box::new(h) as Box<_>),
can_gc,
CanGc::from_cx(cx),
);
let realm = enter_realm(global);
let comp = InRealm::Entered(&realm);
start_promise.append_native_handler(&handler, comp, can_gc);
let mut realm = enter_auto_realm(cx, global);
let cx = &mut realm.current_realm();
let in_realm_proof = cx.into();
let comp = InRealm::Already(&in_realm_proof);
start_promise.append_native_handler(&handler, comp, CanGc::from_cx(cx));
Ok(())
}

View File

@@ -494,7 +494,7 @@ impl DedicatedWorkerGlobalScope {
cx,
);
debugger_global.fire_add_debuggee(
CanGc::from_cx(cx),
cx,
global.upcast(),
pipeline_id,
Some(worker_id),
@@ -675,7 +675,7 @@ impl DedicatedWorkerGlobalScope {
DevtoolScriptControlMsg::WantsLiveNotifications(_pipe_id, _bool_val) => {},
DevtoolScriptControlMsg::Eval(code, id, frame_actor_id, reply) => {
self.debugger_global.fire_eval(
CanGc::from_cx(cx),
cx,
code.into(),
id,
Some(self.upcast::<WorkerGlobalScope>().worker_id()),

View File

@@ -399,7 +399,7 @@ impl ServiceWorkerGlobalScope {
if let Some(debugger_global) = debugger_global.as_deref() {
debugger_global.fire_add_debuggee(
CanGc::from_cx(cx),
cx,
global_scope,
pipeline_id,
Some(worker_scope.worker_id()),
@@ -500,7 +500,7 @@ impl ServiceWorkerGlobalScope {
DevtoolScriptControlMsg::Eval(code, id, frame_actor_id, reply) => {
if let Some(debugger_global) = self.debugger_global.as_deref() {
debugger_global.fire_eval(
CanGc::from_cx(cx),
cx,
code.into(),
id,
Some(self.upcast::<WorkerGlobalScope>().worker_id()),

View File

@@ -2237,37 +2237,23 @@ impl ScriptThread {
)
},
DevtoolScriptControlMsg::Eval(code, id, frame_actor_id, reply) => {
self.debugger_global.fire_eval(
CanGc::from_cx(cx),
code.into(),
id,
None,
frame_actor_id,
reply,
);
self.debugger_global
.fire_eval(cx, code.into(), id, None, frame_actor_id, reply);
},
DevtoolScriptControlMsg::GetPossibleBreakpoints(spidermonkey_id, result_sender) => {
self.debugger_global.fire_get_possible_breakpoints(
CanGc::from_cx(cx),
cx,
spidermonkey_id,
result_sender,
);
},
DevtoolScriptControlMsg::SetBreakpoint(spidermonkey_id, script_id, offset) => {
self.debugger_global.fire_set_breakpoint(
CanGc::from_cx(cx),
spidermonkey_id,
script_id,
offset,
);
self.debugger_global
.fire_set_breakpoint(cx, spidermonkey_id, script_id, offset);
},
DevtoolScriptControlMsg::ClearBreakpoint(spidermonkey_id, script_id, offset) => {
self.debugger_global.fire_clear_breakpoint(
CanGc::from_cx(cx),
spidermonkey_id,
script_id,
offset,
);
self.debugger_global
.fire_clear_breakpoint(cx, spidermonkey_id, script_id, offset);
},
DevtoolScriptControlMsg::Interrupt => {
self.debugger_global.fire_interrupt(CanGc::from_cx(cx));
@@ -2282,18 +2268,12 @@ impl ScriptThread {
);
},
DevtoolScriptControlMsg::GetEnvironment(frame_actor_id, result_sender) => {
self.debugger_global.fire_get_environment(
frame_actor_id,
result_sender,
CanGc::from_cx(cx),
);
self.debugger_global
.fire_get_environment(cx, frame_actor_id, result_sender);
},
DevtoolScriptControlMsg::Resume(resume_limit_type, frame_actor_id) => {
self.debugger_global.fire_resume(
resume_limit_type,
frame_actor_id,
CanGc::from_cx(cx),
);
self.debugger_global
.fire_resume(cx, resume_limit_type, frame_actor_id);
self.debugger_paused.set(false);
},
}
@@ -3460,12 +3440,8 @@ impl ScriptThread {
incomplete.theme,
self.this.clone(),
);
self.debugger_global.fire_add_debuggee(
CanGc::from_cx(cx),
window.upcast(),
incomplete.pipeline_id,
None,
);
self.debugger_global
.fire_add_debuggee(cx, window.upcast(), incomplete.pipeline_id, None);
let mut realm = enter_auto_realm(cx, &*window);
let cx = &mut realm;

View File

@@ -114,6 +114,10 @@ DOMInterfaces = {
'cx': ['Constructor']
},
'CompressionStream': {
'cx': ['Constructor'],
},
'CookieStore': {
'cx': ['Set', 'Set_', 'Get', 'Get_', 'GetAll', 'GetAll_', 'Delete', 'Delete_'],
},
@@ -197,6 +201,10 @@ DOMInterfaces = {
'useSystemCompartment': True,
},
'DecompressionStream': {
'cx': ['Constructor'],
},
'DedicatedWorkerGlobalScope': {
'cx': ['PostMessage', 'PostMessage_'],
},
@@ -337,6 +345,10 @@ DOMInterfaces = {
'canGc': ['Load'],
},
'Gamepad': {
'canGc': ['Axes']
},
'GamepadHapticActuator': {
'inRealms': ['PlayEffect', 'Reset'],
'canGc': ['PlayEffect', 'Reset', 'Effects'],
@@ -873,10 +885,22 @@ DOMInterfaces = {
'cx_no_gc': ['WholeText']
},
'TextDecoderStream': {
'cx': ['Constructor'],
},
'TextEncoder': {
'canGc': ['Encode']
},
'TextEncoderStream': {
'cx': ['Constructor'],
},
'TransformStream': {
'cx': ['Constructor'],
},
'TreeWalker': {
'cx': ['ParentNode', 'PreviousNode', 'NextNode', 'FirstChild', 'LastChild', 'PreviousSibling', 'NextSibling']
},
@@ -1069,6 +1093,7 @@ DOMInterfaces = {
'WritableStream': {
'canGc': ['Close', 'GetWriter'],
'cx': ['Constructor'],
'inRealms': ['GetWriter'],
'realm': ['Abort', 'Close']
},
@@ -1167,21 +1192,21 @@ Dictionaries = {
},
'SanitizerAttributeNamespace': {
'derives': ['Clone', 'MallocSizeOf']
},
'SanitizerElementNamespace': {
'derives': ['Clone', 'MallocSizeOf'],
},
'SanitizerElementNamespaceWithAttributes': {
'derives': ['Clone', 'MallocSizeOf'],
'derives': ['Clone', 'MallocSizeOf', 'PartialEq']
},
'SanitizerConfig': {
'derives': ['Clone', 'MallocSizeOf']
},
'SanitizerElementNamespace': {
'derives': ['Clone', 'MallocSizeOf', 'PartialEq'],
},
'SanitizerElementNamespaceWithAttributes': {
'derives': ['Clone', 'MallocSizeOf', 'PartialEq'],
},
'ScrollOptions': {
'derives': ['Clone'],
},
@@ -1226,15 +1251,15 @@ Unions = {
},
'StringOrSanitizerAttributeNamespace': {
'derives': ['Clone', 'MallocSizeOf'],
'derives': ['Clone', 'MallocSizeOf', 'PartialEq'],
},
'StringOrSanitizerElementNamespace': {
'derives': ['Clone', 'MallocSizeOf'],
'derives': ['Clone', 'MallocSizeOf', 'PartialEq'],
},
'StringOrSanitizerElementNamespaceWithAttributes': {
'derives': ['Clone', 'MallocSizeOf'],
'derives': ['Clone', 'MallocSizeOf', 'PartialEq'],
},
'RangeEnforcedUnsignedLongSequenceOrGPUExtent3DDict': {

View File

@@ -12,8 +12,8 @@ interface Gamepad {
readonly attribute boolean connected;
readonly attribute DOMHighResTimeStamp timestamp;
readonly attribute DOMString mapping;
readonly attribute Float64Array axes;
readonly attribute any buttons;
readonly attribute /* FrozenArray<double> */ any axes;
[SameObject] readonly attribute GamepadHapticActuator vibrationActuator;
};

View File

@@ -20,9 +20,9 @@ interface Sanitizer {
SanitizerConfig get();
// Modify a Sanitizer's lists and fields:
// boolean allowElement(SanitizerElementWithAttributes element);
// boolean removeElement(SanitizerElement element);
// boolean replaceElementWithChildren(SanitizerElement element);
boolean allowElement(SanitizerElementWithAttributes element);
boolean removeElement(SanitizerElement element);
boolean replaceElementWithChildren(SanitizerElement element);
// boolean allowAttribute(SanitizerAttribute attribute);
// boolean removeAttribute(SanitizerAttribute attribute);
// boolean setComments(boolean allow);

View File

@@ -201,6 +201,63 @@ fn test_accessibility_after_navigate_and_back() {
// TODO(accessibility): write test for resend a11y tree when clicking back or forward
#[test]
fn test_accessibility_basic_mapping() {
let servo_test = ServoTest::new_with_builder(|builder| {
let mut preferences = Preferences::default();
preferences.accessibility_enabled = true;
builder.preferences(preferences)
});
let delegate = Rc::new(WebViewDelegateImpl::default());
let mut element_role_pairs = VecDeque::from([
("article", Role::Article),
("aside", Role::Complementary),
("footer", Role::ContentInfo),
("h1", Role::Heading),
("h2", Role::Heading),
("h3", Role::Heading),
("h4", Role::Heading),
("h5", Role::Heading),
("h6", Role::Heading),
("header", Role::Banner),
("hr", Role::Splitter),
("main", Role::Main),
("nav", Role::Navigation),
("p", Role::Paragraph),
]);
let mut url: String = "data:text/html,<!DOCTYPE html>".to_owned();
for (element, _) in element_role_pairs.iter() {
url.push_str(format!("<{element}></{element}>").as_str());
}
let webview = WebViewBuilder::new(servo_test.servo(), servo_test.rendering_context.clone())
.delegate(delegate.clone())
.url(Url::parse(url.as_str()).unwrap())
.build();
webview.set_accessibility_active(true);
let load_webview = webview.clone();
servo_test.spin(move || load_webview.load_status() != LoadStatus::Complete);
let updates = wait_for_min_updates(&servo_test, delegate.clone(), 2);
let tree = build_tree(updates);
let root = assert_tree_structure_and_get_root_web_area(&tree);
assert_eq!(root.children().len(), element_role_pairs.len());
for child in root.children() {
let Some((tag, role)) = element_role_pairs.pop_front() else {
panic!("Number of children of root node should match number of tag/role pairs");
};
assert_eq!(child.data().html_tag(), Some(tag));
assert_eq!(child.role(), role);
}
assert!(
element_role_pairs.is_empty(),
"Number of children of root node should match number of tag/role pairs"
);
}
fn wait_for_min_updates(
servo_test: &ServoTest,
delegate: Rc<WebViewDelegateImpl>,
@@ -266,13 +323,8 @@ fn assert_tree_structure_and_get_root_web_area<'tree>(
let graft_node = scroll_view_children[0];
assert!(graft_node.is_graft());
let graft_node_children: Vec<accesskit_consumer::Node<'_>> = graft_node.children().collect();
assert_eq!(graft_node_children.len(), 1);
let root_web_area = graft_node_children[0];
assert_eq!(root_web_area.role(), Role::RootWebArea);
root_web_area
find_first_matching_node(graft_node, |node| node.role() == Role::RootWebArea)
.expect("Should have a RootWebArea")
}
fn find_first_matching_node(

View File

@@ -7739,6 +7739,13 @@
null,
{}
]
],
"replaceWith-document-element-crash.html": [
"299542341ad27da315951f7863c3f0198e2ecfcb",
[
null,
{}
]
]
},
"ranges": {

View File

@@ -1,13 +1,4 @@
[idlharness.https.window.html]
[Sanitizer interface: operation allowElement(SanitizerElementWithAttributes)]
expected: FAIL
[Sanitizer interface: operation removeElement(SanitizerElement)]
expected: FAIL
[Sanitizer interface: operation replaceElementWithChildren(SanitizerElement)]
expected: FAIL
[Sanitizer interface: operation allowAttribute(SanitizerAttribute)]
expected: FAIL
@@ -23,24 +14,6 @@
[Sanitizer interface: operation removeUnsafe()]
expected: FAIL
[Sanitizer interface: new Sanitizer({}) must inherit property "allowElement(SanitizerElementWithAttributes)" with the proper type]
expected: FAIL
[Sanitizer interface: calling allowElement(SanitizerElementWithAttributes) on new Sanitizer({}) with too few arguments must throw TypeError]
expected: FAIL
[Sanitizer interface: new Sanitizer({}) must inherit property "removeElement(SanitizerElement)" with the proper type]
expected: FAIL
[Sanitizer interface: calling removeElement(SanitizerElement) on new Sanitizer({}) with too few arguments must throw TypeError]
expected: FAIL
[Sanitizer interface: new Sanitizer({}) must inherit property "replaceElementWithChildren(SanitizerElement)" with the proper type]
expected: FAIL
[Sanitizer interface: calling replaceElementWithChildren(SanitizerElement) on new Sanitizer({}) with too few arguments must throw TypeError]
expected: FAIL
[Sanitizer interface: new Sanitizer({}) must inherit property "allowAttribute(SanitizerAttribute)" with the proper type]
expected: FAIL

View File

@@ -1,5 +1,4 @@
[sanitizer-basic-filtering.tentative.html]
expected: ERROR
[setHTML testcase text/0, "text"]
expected: FAIL
@@ -29,3 +28,378 @@
[ShadowRoot.setHTMLUnsafe testcase elements/1, "<div><p>Hello <b>World!</b>"]
expected: FAIL
[parseHTML testcase elements/1, "<div><p>Hello <b>World!</b>"]
expected: FAIL
[parseHTMLUnsafe testcase elements/1, "<div><p>Hello <b>World!</b>"]
expected: FAIL
[setHTML testcase elements/2, "<div><p>Hello <b>World!</b>"]
expected: FAIL
[setHTMLUnsafe testcase elements/2, "<div><p>Hello <b>World!</b>"]
expected: FAIL
[ShadowRoot.setHTML testcase elements/2, "<div><p>Hello <b>World!</b>"]
expected: FAIL
[ShadowRoot.setHTMLUnsafe testcase elements/2, "<div><p>Hello <b>World!</b>"]
expected: FAIL
[parseHTML testcase elements/2, "<div><p>Hello <b>World!</b>"]
expected: FAIL
[parseHTMLUnsafe testcase elements/2, "<div><p>Hello <b>World!</b>"]
expected: FAIL
[setHTML testcase elements/3, "<div><p>Hello <b>World!</b>"]
expected: FAIL
[setHTMLUnsafe testcase elements/3, "<div><p>Hello <b>World!</b>"]
expected: FAIL
[ShadowRoot.setHTML testcase elements/3, "<div><p>Hello <b>World!</b>"]
expected: FAIL
[ShadowRoot.setHTMLUnsafe testcase elements/3, "<div><p>Hello <b>World!</b>"]
expected: FAIL
[parseHTML testcase elements/3, "<div><p>Hello <b>World!</b>"]
expected: FAIL
[parseHTMLUnsafe testcase elements/3, "<div><p>Hello <b>World!</b>"]
expected: FAIL
[setHTML testcase elements/4, "<div><p>Hello <b>World!</b>"]
expected: FAIL
[setHTMLUnsafe testcase elements/4, "<div><p>Hello <b>World!</b>"]
expected: FAIL
[ShadowRoot.setHTML testcase elements/4, "<div><p>Hello <b>World!</b>"]
expected: FAIL
[ShadowRoot.setHTMLUnsafe testcase elements/4, "<div><p>Hello <b>World!</b>"]
expected: FAIL
[parseHTML testcase elements/4, "<div><p>Hello <b>World!</b>"]
expected: FAIL
[parseHTMLUnsafe testcase elements/4, "<div><p>Hello <b>World!</b>"]
expected: FAIL
[setHTML testcase attributes/0, "<p id="hello" style="font-weight: bold">x"]
expected: FAIL
[ShadowRoot.setHTML testcase attributes/0, "<p id="hello" style="font-weight: bold">x"]
expected: FAIL
[parseHTML testcase attributes/0, "<p id="hello" style="font-weight: bold">x"]
expected: FAIL
[setHTML testcase attributes/1, "<p id="hello" style="font-weight: bold">x"]
expected: FAIL
[setHTMLUnsafe testcase attributes/1, "<p id="hello" style="font-weight: bold">x"]
expected: FAIL
[ShadowRoot.setHTML testcase attributes/1, "<p id="hello" style="font-weight: bold">x"]
expected: FAIL
[ShadowRoot.setHTMLUnsafe testcase attributes/1, "<p id="hello" style="font-weight: bold">x"]
expected: FAIL
[parseHTML testcase attributes/1, "<p id="hello" style="font-weight: bold">x"]
expected: FAIL
[parseHTMLUnsafe testcase attributes/1, "<p id="hello" style="font-weight: bold">x"]
expected: FAIL
[setHTML testcase attributes/2, "<p id="hello" style="font-weight: bold">x"]
expected: FAIL
[setHTMLUnsafe testcase attributes/2, "<p id="hello" style="font-weight: bold">x"]
expected: FAIL
[ShadowRoot.setHTML testcase attributes/2, "<p id="hello" style="font-weight: bold">x"]
expected: FAIL
[ShadowRoot.setHTMLUnsafe testcase attributes/2, "<p id="hello" style="font-weight: bold">x"]
expected: FAIL
[parseHTML testcase attributes/2, "<p id="hello" style="font-weight: bold">x"]
expected: FAIL
[parseHTMLUnsafe testcase attributes/2, "<p id="hello" style="font-weight: bold">x"]
expected: FAIL
[setHTML testcase attributes-per-element/0, "<div style="font-weight: bold" class="bourgeoisie">"]
expected: FAIL
[setHTMLUnsafe testcase attributes-per-element/0, "<div style="font-weight: bold" class="bourgeoisie">"]
expected: FAIL
[ShadowRoot.setHTML testcase attributes-per-element/0, "<div style="font-weight: bold" class="bourgeoisie">"]
expected: FAIL
[ShadowRoot.setHTMLUnsafe testcase attributes-per-element/0, "<div style="font-weight: bold" class="bourgeoisie">"]
expected: FAIL
[parseHTML testcase attributes-per-element/0, "<div style="font-weight: bold" class="bourgeoisie">"]
expected: FAIL
[parseHTMLUnsafe testcase attributes-per-element/0, "<div style="font-weight: bold" class="bourgeoisie">"]
expected: FAIL
[setHTML testcase attributes-per-element/1, "<div style="font-weight: bold" class="bourgeoisie">"]
expected: FAIL
[setHTMLUnsafe testcase attributes-per-element/1, "<div style="font-weight: bold" class="bourgeoisie">"]
expected: FAIL
[ShadowRoot.setHTML testcase attributes-per-element/1, "<div style="font-weight: bold" class="bourgeoisie">"]
expected: FAIL
[ShadowRoot.setHTMLUnsafe testcase attributes-per-element/1, "<div style="font-weight: bold" class="bourgeoisie">"]
expected: FAIL
[parseHTML testcase attributes-per-element/1, "<div style="font-weight: bold" class="bourgeoisie">"]
expected: FAIL
[parseHTMLUnsafe testcase attributes-per-element/1, "<div style="font-weight: bold" class="bourgeoisie">"]
expected: FAIL
[setHTML testcase comments/0, "a <!-- comment --> b"]
expected: FAIL
[ShadowRoot.setHTML testcase comments/0, "a <!-- comment --> b"]
expected: FAIL
[parseHTML testcase comments/0, "a <!-- comment --> b"]
expected: FAIL
[setHTML testcase comments/1, "a <!-- comment --> b"]
expected: FAIL
[setHTMLUnsafe testcase comments/1, "a <!-- comment --> b"]
expected: FAIL
[ShadowRoot.setHTML testcase comments/1, "a <!-- comment --> b"]
expected: FAIL
[ShadowRoot.setHTMLUnsafe testcase comments/1, "a <!-- comment --> b"]
expected: FAIL
[parseHTML testcase comments/1, "a <!-- comment --> b"]
expected: FAIL
[parseHTMLUnsafe testcase comments/1, "a <!-- comment --> b"]
expected: FAIL
[setHTML testcase dataAttributes/0, "<p data-x="1" data-y="2" data-z="3">"]
expected: FAIL
[ShadowRoot.setHTML testcase dataAttributes/0, "<p data-x="1" data-y="2" data-z="3">"]
expected: FAIL
[parseHTML testcase dataAttributes/0, "<p data-x="1" data-y="2" data-z="3">"]
expected: FAIL
[setHTML testcase dataAttributes/1, "<p data-x="1" data-y="2" data-z="3">"]
expected: FAIL
[setHTMLUnsafe testcase dataAttributes/1, "<p data-x="1" data-y="2" data-z="3">"]
expected: FAIL
[ShadowRoot.setHTML testcase dataAttributes/1, "<p data-x="1" data-y="2" data-z="3">"]
expected: FAIL
[ShadowRoot.setHTMLUnsafe testcase dataAttributes/1, "<p data-x="1" data-y="2" data-z="3">"]
expected: FAIL
[parseHTML testcase dataAttributes/1, "<p data-x="1" data-y="2" data-z="3">"]
expected: FAIL
[parseHTMLUnsafe testcase dataAttributes/1, "<p data-x="1" data-y="2" data-z="3">"]
expected: FAIL
[setHTML testcase dataAttributes/2, "<p data-x="1" data-y="2" data-z="3">"]
expected: FAIL
[setHTMLUnsafe testcase dataAttributes/2, "<p data-x="1" data-y="2" data-z="3">"]
expected: FAIL
[ShadowRoot.setHTML testcase dataAttributes/2, "<p data-x="1" data-y="2" data-z="3">"]
expected: FAIL
[ShadowRoot.setHTMLUnsafe testcase dataAttributes/2, "<p data-x="1" data-y="2" data-z="3">"]
expected: FAIL
[parseHTML testcase dataAttributes/2, "<p data-x="1" data-y="2" data-z="3">"]
expected: FAIL
[parseHTMLUnsafe testcase dataAttributes/2, "<p data-x="1" data-y="2" data-z="3">"]
expected: FAIL
[setHTML testcase namespaces/0, "<svg><rect></svg><math><mi>x"]
expected: FAIL
[ShadowRoot.setHTML testcase namespaces/0, "<svg><rect></svg><math><mi>x"]
expected: FAIL
[parseHTML testcase namespaces/0, "<svg><rect></svg><math><mi>x"]
expected: FAIL
[setHTML testcase namespaces/1, "<svg><rect>"]
expected: FAIL
[setHTMLUnsafe testcase namespaces/1, "<svg><rect>"]
expected: FAIL
[ShadowRoot.setHTML testcase namespaces/1, "<svg><rect>"]
expected: FAIL
[ShadowRoot.setHTMLUnsafe testcase namespaces/1, "<svg><rect>"]
expected: FAIL
[parseHTML testcase namespaces/1, "<svg><rect>"]
expected: FAIL
[parseHTMLUnsafe testcase namespaces/1, "<svg><rect>"]
expected: FAIL
[setHTML testcase namespaces/2, "<svg><rect>"]
expected: FAIL
[ShadowRoot.setHTML testcase namespaces/2, "<svg><rect>"]
expected: FAIL
[parseHTML testcase namespaces/2, "<svg><rect>"]
expected: FAIL
[setHTML testcase namespaces/3, "<svg><rect>"]
expected: FAIL
[setHTMLUnsafe testcase namespaces/3, "<svg><rect>"]
expected: FAIL
[ShadowRoot.setHTML testcase namespaces/3, "<svg><rect>"]
expected: FAIL
[ShadowRoot.setHTMLUnsafe testcase namespaces/3, "<svg><rect>"]
expected: FAIL
[parseHTML testcase namespaces/3, "<svg><rect>"]
expected: FAIL
[parseHTMLUnsafe testcase namespaces/3, "<svg><rect>"]
expected: FAIL
[setHTML testcase namespaces/4, "<math><mi>x"]
expected: FAIL
[setHTMLUnsafe testcase namespaces/4, "<math><mi>x"]
expected: FAIL
[ShadowRoot.setHTML testcase namespaces/4, "<math><mi>x"]
expected: FAIL
[ShadowRoot.setHTMLUnsafe testcase namespaces/4, "<math><mi>x"]
expected: FAIL
[parseHTML testcase namespaces/4, "<math><mi>x"]
expected: FAIL
[parseHTMLUnsafe testcase namespaces/4, "<math><mi>x"]
expected: FAIL
[setHTML testcase namespaces/5, "<math><mi>x"]
expected: FAIL
[ShadowRoot.setHTML testcase namespaces/5, "<math><mi>x"]
expected: FAIL
[parseHTML testcase namespaces/5, "<math><mi>x"]
expected: FAIL
[setHTML testcase namespaces/6, "<math><mi>x"]
expected: FAIL
[setHTMLUnsafe testcase namespaces/6, "<math><mi>x"]
expected: FAIL
[ShadowRoot.setHTML testcase namespaces/6, "<math><mi>x"]
expected: FAIL
[ShadowRoot.setHTMLUnsafe testcase namespaces/6, "<math><mi>x"]
expected: FAIL
[parseHTML testcase namespaces/6, "<math><mi>x"]
expected: FAIL
[parseHTMLUnsafe testcase namespaces/6, "<math><mi>x"]
expected: FAIL
[setHTML testcase namespaces/7, "<svg xml:space="default" xlink:href="about:blank" xmlns:foo="barspace">"]
expected: FAIL
[ShadowRoot.setHTML testcase namespaces/7, "<svg xml:space="default" xlink:href="about:blank" xmlns:foo="barspace">"]
expected: FAIL
[parseHTML testcase namespaces/7, "<svg xml:space="default" xlink:href="about:blank" xmlns:foo="barspace">"]
expected: FAIL
[setHTML testcase namespaces/8, "<svg xml:space="default" xlink:href="about:blank" xmlns:foo="barspace">"]
expected: FAIL
[setHTMLUnsafe testcase namespaces/8, "<svg xml:space="default" xlink:href="about:blank" xmlns:foo="barspace">"]
expected: FAIL
[ShadowRoot.setHTML testcase namespaces/8, "<svg xml:space="default" xlink:href="about:blank" xmlns:foo="barspace">"]
expected: FAIL
[ShadowRoot.setHTMLUnsafe testcase namespaces/8, "<svg xml:space="default" xlink:href="about:blank" xmlns:foo="barspace">"]
expected: FAIL
[parseHTML testcase namespaces/8, "<svg xml:space="default" xlink:href="about:blank" xmlns:foo="barspace">"]
expected: FAIL
[parseHTMLUnsafe testcase namespaces/8, "<svg xml:space="default" xlink:href="about:blank" xmlns:foo="barspace">"]
expected: FAIL
[setHTML testcase namespaces/9, "<svg xml:space="default" xlink:href="about:blank" xmlns:foo="barspace">"]
expected: FAIL
[setHTMLUnsafe testcase namespaces/9, "<svg xml:space="default" xlink:href="about:blank" xmlns:foo="barspace">"]
expected: FAIL
[ShadowRoot.setHTML testcase namespaces/9, "<svg xml:space="default" xlink:href="about:blank" xmlns:foo="barspace">"]
expected: FAIL
[ShadowRoot.setHTMLUnsafe testcase namespaces/9, "<svg xml:space="default" xlink:href="about:blank" xmlns:foo="barspace">"]
expected: FAIL
[parseHTML testcase namespaces/9, "<svg xml:space="default" xlink:href="about:blank" xmlns:foo="barspace">"]
expected: FAIL
[parseHTMLUnsafe testcase namespaces/9, "<svg xml:space="default" xlink:href="about:blank" xmlns:foo="barspace">"]
expected: FAIL
[setHTML testcase defaults-with-attributes-per-element/0, "<div start=1 value=5 lang=en><ol start=1 value=5 lang=en><li start=1 value=5 lang=en>xx"]
expected: FAIL
[setHTMLUnsafe testcase defaults-with-attributes-per-element/0, "<div start=1 value=5 lang=en><ol start=1 value=5 lang=en><li start=1 value=5 lang=en>xx"]
expected: FAIL
[ShadowRoot.setHTML testcase defaults-with-attributes-per-element/0, "<div start=1 value=5 lang=en><ol start=1 value=5 lang=en><li start=1 value=5 lang=en>xx"]
expected: FAIL
[ShadowRoot.setHTMLUnsafe testcase defaults-with-attributes-per-element/0, "<div start=1 value=5 lang=en><ol start=1 value=5 lang=en><li start=1 value=5 lang=en>xx"]
expected: FAIL
[parseHTML testcase defaults-with-attributes-per-element/0, "<div start=1 value=5 lang=en><ol start=1 value=5 lang=en><li start=1 value=5 lang=en>xx"]
expected: FAIL
[parseHTMLUnsafe testcase defaults-with-attributes-per-element/0, "<div start=1 value=5 lang=en><ol start=1 value=5 lang=en><li start=1 value=5 lang=en>xx"]
expected: FAIL

View File

@@ -5,23 +5,8 @@
[SanitizerConfig dataAttributes field.]
expected: FAIL
[Test elements addition.]
expected: FAIL
[Test elements removal.]
expected: FAIL
[Test elements replacewithchildren.]
expected: FAIL
[Test attribute addition.]
expected: FAIL
[Test attribute removal.]
expected: FAIL
[Test attribute-per-element sets (i.e. overwrites).]
expected: FAIL
[Test removeAttribute-per-element sets (i.e. overwrites).]
expected: FAIL

View File

@@ -28,30 +28,3 @@
[sanitizer.removeAttribute() with global removeAttributes and element's removeAttributes]
expected: FAIL
[sanitizer.removeElement() with global elements]
expected: FAIL
[sanitizer.removeElement() with global removeElements]
expected: FAIL
[sanitizer.replaceElementWithChildren() with global elements]
expected: FAIL
[sanitizer.replaceElementWithChildren() with global removeElements]
expected: FAIL
[sanitizer.allowElement() with global elements]
expected: FAIL
[sanitizer.allowElement() with global removeElements]
expected: FAIL
[sanitizer.allowElement() with global elements and attributes]
expected: FAIL
[sanitizer.allowElement() with global elements and removeAttributes]
expected: FAIL
[sanitizer.replaceElementWithChildren does not allow 'html' element.]
expected: FAIL

View File

@@ -0,0 +1,10 @@
<!DOCTYPE html>
<meta name="assert" content="Test replaceWith in a cycle">
<link rel=help href="https://github.com/servo/servo/issues/35698">
<link rel="author" title="Euclid Ye" href="mailto:yezhizhenjiakang@gmail.com">
<script>
var oldRoot = document.documentElement;
var newRoot = document.createElement("html");
oldRoot.replaceWith(newRoot);
newRoot.replaceWith(oldRoot);
</script>