diff --git a/testing/web-platform/tests/html/dom/elements/global-attributes/dir-assorted.window.js b/testing/web-platform/tests/html/dom/elements/global-attributes/dir-assorted.window.js
index 64f99724a95ca..a4b30f0e4ac72 100644
--- a/testing/web-platform/tests/html/dom/elements/global-attributes/dir-assorted.window.js
+++ b/testing/web-platform/tests/html/dom/elements/global-attributes/dir-assorted.window.js
@@ -27,3 +27,21 @@ test(() => {
ele.append(ele2);
assert_true(ele2.matches(":dir(rtl)"));
}, "Non-HTML element without direction has parent element direction");
+
+test(() => {
+ let container1 = document.createElement("div");
+ document.body.appendChild(container1);
+ let container2 = document.createElement("div");
+
+ for (let container of [container1, container2]) {
+ container.dir = "rtl";
+ let e = document.createElement("div");
+ assert_true(e.matches(":dir(ltr)"));
+ container.appendChild(e);
+ assert_false(e.matches(":dir(ltr)"));
+ e.remove();
+ assert_true(e.matches(":dir(ltr)"));
+ }
+
+ container1.remove();
+}, "dir inheritance is correct after insertion and removal from document");
diff --git a/testing/web-platform/tests/html/dom/elements/global-attributes/dir-auto-dynamic-changes.window.js b/testing/web-platform/tests/html/dom/elements/global-attributes/dir-auto-dynamic-changes.window.js
new file mode 100644
index 0000000000000..5df996d285f46
--- /dev/null
+++ b/testing/web-platform/tests/html/dom/elements/global-attributes/dir-auto-dynamic-changes.window.js
@@ -0,0 +1,169 @@
+function setup_tree(light_tree, shadow_tree) {
+ let body = document.body;
+ let old_length = body.childNodes.length;
+ body.insertAdjacentHTML("beforeend", light_tree.trim());
+ if (body.childNodes.length != old_length + 1) {
+ throw "unexpected markup";
+ }
+ let result = body.lastChild;
+ if (shadow_tree) {
+ let shadow = result.querySelector("#root").attachShadow({mode: "open"});
+ shadow.innerHTML = shadow_tree.trim();
+ return [result, shadow];
+ }
+ return result;
+}
+
+test(t => {
+ let a = setup_tree(`
+
+ `);
+
+ let acs = getComputedStyle(a);
+ assert_true(a.matches(":dir(ltr)"), ":dir(ltr) matches before insertion");
+ assert_false(a.matches(":dir(rtl)"), ":dir(rtl) does not match before insertion");
+ assert_equals(acs.direction, "ltr", "CSSdirection before insertion");
+ b.innerHTML = "\u05D0";
+ assert_false(a.matches(":dir(ltr)"), ":dir(ltr) does not match after insertion");
+ assert_true(a.matches(":dir(rtl)"), ":dir(rtl) matches after insertion");
+ assert_equals(acs.direction, "rtl", "CSSdirection after insertion");
+
+ a.remove();
+}, "dynamic insertion of RTL text in a child element");
+
+test(() => {
+ let div_rtlchar = document.createElement("div");
+ div_rtlchar.innerHTML = "\u05D0";
+
+ let container1 = document.createElement("div");
+ document.body.appendChild(container1);
+ let container2 = document.createElement("div");
+
+ for (let container of [container1, container2]) {
+ container.dir = "auto";
+ assert_true(container.matches(":dir(ltr)"));
+ container.appendChild(div_rtlchar);
+ assert_false(container.matches(":dir(ltr)"));
+ div_rtlchar.remove();
+ assert_true(container.matches(":dir(ltr)"));
+ }
+
+ container1.remove();
+}, "dir=auto changes for content insertion and removal, in and out of document");
+
+test(() => {
+ let tree, shadow;
+ [tree, shadow] = setup_tree(`
+
+ `, `
+ \u05D0
+
+ `);
+
+ let one = shadow.getElementById("one");
+ let two = shadow.getElementById("two");
+ let l = tree.querySelector("#l");
+ let r = tree.querySelector("#r");
+ assert_false(one.matches(":dir(ltr)"), "#one while empty");
+ assert_true(two.matches(":dir(ltr)"), "#two with both spans");
+ l.slot = "one";
+ assert_true(one.matches(":dir(ltr)"), "#one with LTR child span");
+ assert_false(two.matches(":dir(ltr)"), "#two with RTL child span");
+ r.slot = "one";
+ assert_true(one.matches(":dir(ltr)"), "#one with both child spans");
+ assert_true(two.matches(":dir(ltr)"), "#two while empty");
+ l.slot = "";
+ assert_false(one.matches(":dir(ltr)"), "#one with RTL child span");
+ assert_true(two.matches(":dir(ltr)"), "#two with LTR child span");
+
+ tree.remove();
+}, "dir=auto changes for slot reassignment");
+
+test(() => {
+ let tree, shadow;
+ [tree, shadow] = setup_tree(`
+
+ `, `
+
+
+
+ `);
+
+ let text = tree.querySelector("#text");
+ let slot = shadow.querySelector("#slot");
+
+ assert_true(tree.matches(":dir(ltr)"), "node tree ancestor before first text change");
+ assert_true(slot.matches(":dir(ltr)"), "slot before first text change");
+ text.innerText = "\u05D0";
+ assert_false(tree.matches(":dir(ltr)"), "node tree ancestor after first text change");
+ assert_false(slot.matches(":dir(ltr)"), "slot after first text change");
+ tree.dir = "rtl";
+ assert_false(tree.matches(":dir(ltr)"), "node tree ancestor before second text change");
+ assert_false(slot.matches(":dir(ltr)"), "slot before second text change");
+ text.innerText = "A";
+ assert_false(tree.matches(":dir(ltr)"), "node tree ancestor after second text change");
+ assert_true(slot.matches(":dir(ltr)"), "slot after second text change");
+ slot.dir = "ltr";
+ assert_false(tree.matches(":dir(ltr)"), "node tree ancestor before third text change");
+ assert_true(slot.matches(":dir(ltr)"), "slot before third text change");
+ text.innerText = "\u05D0";
+ assert_false(tree.matches(":dir(ltr)"), "node tree ancestor after third text change");
+ assert_true(slot.matches(":dir(ltr)"), "slot after third text change");
+ slot.dir = "auto";
+ tree.dir = "auto";
+ assert_false(tree.matches(":dir(ltr)"), "node tree ancestor after fourth text change");
+ assert_false(slot.matches(":dir(ltr)"), "slot after fourth text change");
+ text.innerText = "A";
+ assert_true(tree.matches(":dir(ltr)"), "node tree ancestor before fourth text change");
+ assert_true(slot.matches(":dir(ltr)"), "slot before fourth text change");
+ slot.dir = "rtl";
+ assert_true(tree.matches(":dir(ltr)"), "node tree ancestor before fifth text change");
+ assert_false(slot.matches(":dir(ltr)"), "slot before fifth text change");
+ text.innerText = "\u05D0";
+ assert_false(tree.matches(":dir(ltr)"), "node tree ancestor before fifth text change");
+ assert_false(slot.matches(":dir(ltr)"), "slot before fifth text change");
+}, "text changes affecting both slot and ancestor with dir=auto");
+
+test(() => {
+ let tree = setup_tree(`
+
+ A
+ \u05D0
+ A
+ \u05D0
+
+ `);
+
+ let a1 = tree.querySelector("#a1");
+ let aleph1 = tree.querySelector("#aleph1");
+ assert_true(tree.matches(":dir(ltr)"), "initial state");
+ assert_false(tree.matches(":dir(rtl)"), "initial state");
+ a1.dir = "ltr";
+ assert_false(tree.matches(":dir(ltr)"), "after change 1");
+ a1.dir = "invalid";
+ assert_true(tree.matches(":dir(ltr)"), "after change 2");
+ a1.dir = "rtl";
+ assert_false(tree.matches(":dir(ltr)"), "after change 3");
+ a1.removeAttribute("dir");
+ assert_true(tree.matches(":dir(ltr)"), "after change 4");
+ a1.dir = "invalid";
+ assert_true(tree.matches(":dir(ltr)"), "after change 5");
+ a1.dir = "rtl";
+ assert_false(tree.matches(":dir(ltr)"), "after change 6");
+ aleph1.dir = "auto";
+ assert_true(tree.matches(":dir(ltr)"), "after change 7");
+ aleph1.dir = "invalid";
+ assert_false(tree.matches(":dir(ltr)"), "after change 8");
+}, "dynamic changes to subtrees excluded as a result of the dir attribute");