diff --git a/dom/base/nsINode.cpp b/dom/base/nsINode.cpp index 5218938ca3..1766e9b920 100644 --- a/dom/base/nsINode.cpp +++ b/dom/base/nsINode.cpp @@ -16,6 +16,7 @@ #include "mozilla/CORSMode.h" #include "mozilla/EventDispatcher.h" #include "mozilla/EventListenerManager.h" +#include "mozilla/HTMLEditor.h" #include "mozilla/InternalMutationEvent.h" #include "mozilla/Likely.h" #include "mozilla/MemoryReporting.h" diff --git a/dom/url/URL.cpp b/dom/url/URL.cpp index d33860ab82..5acaeb46fb 100644 --- a/dom/url/URL.cpp +++ b/dom/url/URL.cpp @@ -57,7 +57,84 @@ CreateObjectURLInternal(const GlobalObject& aGlobal, T aObject, CopyASCIItoUTF16(url, aResult); } -// The URL implementation for the main-thread +bool +IsRootRelativePathInput(const nsAString& aURL) +{ + return !aURL.IsEmpty() && aURL.CharAt(0) == '/' && + (aURL.Length() == 1 || aURL.CharAt(1) != '/'); +} + +bool +BuildRootRelativeFallbackSpec(const nsAString& aURL, nsIURI* aBase, + nsACString& aFallbackSpec) +{ + MOZ_ASSERT(aBase); + MOZ_ASSERT(IsRootRelativePathInput(aURL)); + + nsAutoCString baseSpec; + if (NS_FAILED(aBase->GetSpec(baseSpec)) || baseSpec.IsEmpty()) { + return false; + } + + int32_t colon = baseSpec.FindChar(':'); + if (colon <= 0) { + return false; + } + + uint32_t afterColon = static_cast(colon + 1); + if (afterColon >= baseSpec.Length() || baseSpec.CharAt(afterColon) != '/') { + // Opaque paths (for example mailto:test@example.com) are not valid bases + // for root-relative URL input. + return false; + } + + nsAutoCString input; + if (!AppendUTF16toUTF8(aURL, input, fallible)) { + return false; + } + + // Preserve authority when present (scheme://authority/path -> scheme://authority/input) + if (afterColon + 1 < baseSpec.Length() && baseSpec.CharAt(afterColon + 1) == '/') { + uint32_t authorityEnd = afterColon + 2; + while (authorityEnd < baseSpec.Length()) { + char c = baseSpec.CharAt(authorityEnd); + if (c == '/' || c == '?' || c == '#') { + break; + } + ++authorityEnd; + } + + aFallbackSpec.Assign(Substring(baseSpec, 0, authorityEnd)); + aFallbackSpec.Append(input); + return true; + } + + // Single-slash hierarchical base (scheme:/path -> scheme:/input) + aFallbackSpec.Assign(Substring(baseSpec, 0, afterColon)); + aFallbackSpec.Append(input); + return true; +} + +bool +TryResolveRootRelativeAgainstBase(const nsAString& aURL, nsIURI* aBase, + nsIURI** aOutURI) +{ + MOZ_ASSERT(aOutURI); + + if (!aBase || !IsRootRelativePathInput(aURL)) { + return false; + } + + nsAutoCString fallbackSpec; + if (!BuildRootRelativeFallbackSpec(aURL, aBase, fallbackSpec)) { + return false; + } + + nsresult rv = NS_NewURI(aOutURI, fallbackSpec, nullptr, nullptr, + nsContentUtils::GetIOService()); + return NS_SUCCEEDED(rv); +} + class URLMainThread final : public URL { public: @@ -229,7 +306,8 @@ URLMainThread::Constructor(nsISupports* aParent, const nsAString& aURL, nsCOMPtr uri; nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, nullptr, aBase, nsContentUtils::GetIOService()); - if (NS_FAILED(rv)) { + if (NS_FAILED(rv) && + !TryResolveRootRelativeAgainstBase(aURL, aBase, getter_AddRefs(uri))) { // No need to warn in this case. It's common to use the URL constructor // to determine if a URL is valid and an exception will be propagated. aRv.ThrowTypeError(aURL); @@ -1714,30 +1792,14 @@ URL::IsValidURL(const GlobalObject& aGlobal, const nsAString& aURL, bool URL::CanParse(const GlobalObject& aGlobal, const nsAString& aURL, const Optional& aBase) { - nsCOMPtr baseUri; - if (aBase.WasPassed()) { - // Don't use NS_ConvertUTF16toUTF8 because that doesn't let us handle OOM. - nsAutoCString base; - if (!AppendUTF16toUTF8(aBase.Value(), base, fallible)) { - // Just return false with OOM errors as no ErrorResult. - return false; - } - - nsresult rv = NS_NewURI(getter_AddRefs(baseUri), base); - if (NS_FAILED(rv)) { - // Invalid base URL, return false. - return false; - } - } - - nsAutoCString urlStr; - if (!AppendUTF16toUTF8(aURL, urlStr, fallible)) { - // Just return false with OOM errors as no ErrorResult. + ErrorResult rv; + RefPtr parsed = URL::Constructor(aGlobal, aURL, aBase, rv); + if (rv.Failed() || !parsed) { + rv.SuppressException(); return false; } - nsCOMPtr uri; - return NS_SUCCEEDED(NS_NewURI(getter_AddRefs(uri), urlStr, nullptr, baseUri)); + return true; } URLSearchParams* diff --git a/dom/url/tests/test_url.html b/dom/url/tests/test_url.html index 73e75667d8..a637efff25 100644 --- a/dom/url/tests/test_url.html +++ b/dom/url/tests/test_url.html @@ -1,460 +1,462 @@ - + - - - Test URL API - - - - -Mozilla Bug 887364 -Mozilla Bug 991471 -Mozilla Bug 996055 -

- -
-
- + + + + Mozilla Bug 887364 + Mozilla Bug 991471 + Mozilla Bug 996055 +

+ +

+        
 
-    if ('href' in test) is (test.href, url + '', 'stringify works');
-  }
+        
 
-  
+        
 
-  
+        
+            url.hostname = "[::192.9.5.5]";
+            is(url.hostname, "[::192.9.5.5]", "IPv6 hostname");
+            is(url.href, "http://[::192.9.5.5]/");
 
-  
 
-    url = new URL("http://localhost/");
-    url.host = "[2001::1]:30";
-    is(url.hostname, "[2001::1]", "IPv6 hostname");
-    is(url.port, "30", "Port");
-    is(url.host, "[2001::1]:30", "IPv6 host");
+        
+            var u = new URL(url);
+            ok(u.origin, "http://mochi.test:8888", "The URL generated from a blob URI has an origin");
+        
 
-  
+            var a = document.createElement("A");
+            a.href = url;
+            ok(a.origin, "http://mochi.test:8888", "The 'a' element has the correct origin");
+        
 
-  
 
-    var a = document.createElement('A');
-    a.href = url;
-    ok(a.origin, 'http://mochi.test:8888', "The 'a' element has the correct origin");
-  
+        
 
-  
+        
+            var url = new URL("..\\", base);
+            is(url.href, "http://test.com/path/");
 
-  
 
-    url = new URL("ftp:\\\\tmp\\test", base);
-    is(url.href, "ftp://tmp/test");
+        
+            var url = new URL("file:");
+            is(url.href, "file:///", "Parsing file: should work.");
 
-  
 
-    var url = new URL("file:");
-    is(url.href, "file:///", "Parsing file: should work.");
+        
+            // pathname cannot be overwritten.
+            url.pathname = "new/path?newquery#newhash";
+            is(url.href, "scheme:path/to/file?query#hash");
 
-  
 
-    url = new URL("scheme:path#hash");
-    is(url.href, "scheme:path#hash");
-    url.search = "query";
-    is(url.href, "scheme:path?query#hash");
-    url.hash = "";
-    is(url.href, "scheme:path?query");
-    url.hash = "newhash";
-    is(url.href, "scheme:path?query#newhash");
-    url.search = "";
-    is(url.href, "scheme:path#newhash");
+        
 
-    // we don't implement a spec-compliant parser yet.
-    // make sure we are bug compatible with existing implementations.
-    url = new URL("data:text/html,Link");
-    is(url.href, "data:text/html,Link");
-  
-
-  
-
+        
+    
 
diff --git a/dom/url/tests/urlApi_worker.js b/dom/url/tests/urlApi_worker.js
index a8b88e046f..bc26d8a0a9 100644
--- a/dom/url/tests/urlApi_worker.js
+++ b/dom/url/tests/urlApi_worker.js
@@ -1,212 +1,224 @@
 function ok(a, msg) {
   dump("OK: " + !!a + "  =>  " + a + " " + msg + "\n");
-  postMessage({type: 'status', status: !!a, msg: a + ": " + msg });
+  postMessage({ type: "status", status: !!a, msg: a + ": " + msg });
 }
 
 function is(a, b, msg) {
-  dump("IS: " + (a===b) + "  =>  " + a + " | " + b + " " + msg + "\n");
-  postMessage({type: 'status', status: a === b, msg: a + " === " + b + ": " + msg });
+  dump("IS: " + (a === b) + "  =>  " + a + " | " + b + " " + msg + "\n");
+  postMessage({
+    type: "status",
+    status: a === b,
+    msg: a + " === " + b + ": " + msg,
+  });
 }
 
-onmessage = function() {
+onmessage = function () {
   status = false;
   try {
-    if ((URL instanceof Object)) {
+    if (URL instanceof Object) {
       status = true;
     }
-  } catch(e) {
-  }
+  } catch (e) {}
 
   var tests = [
-    { url: 'http://www.abc.com',
+    {
+      url: "http://www.abc.com",
       base: undefined,
       error: false,
-      href: 'http://www.abc.com/',
-      origin: 'http://www.abc.com',
-      protocol: 'http:',
-      username: '',
-      password: '',
-      host: 'www.abc.com',
-      hostname: 'www.abc.com',
-      port: '',
-      pathname: '/',
-      search: '',
-      hash: ''
+      href: "http://www.abc.com/",
+      origin: "http://www.abc.com",
+      protocol: "http:",
+      username: "",
+      password: "",
+      host: "www.abc.com",
+      hostname: "www.abc.com",
+      port: "",
+      pathname: "/",
+      search: "",
+      hash: "",
     },
-    { url: 'ftp://auser:apw@www.abc.com',
+    {
+      url: "ftp://auser:apw@www.abc.com",
       base: undefined,
       error: false,
-      href: 'ftp://auser:apw@www.abc.com/',
-      origin: 'ftp://www.abc.com',
-      protocol: 'ftp:',
-      username: 'auser',
-      password: 'apw',
-      host: 'www.abc.com',
-      hostname: 'www.abc.com',
-      port: '',
-      pathname: '/',
-      search: '',
-      hash: ''
+      href: "ftp://auser:apw@www.abc.com/",
+      origin: "ftp://www.abc.com",
+      protocol: "ftp:",
+      username: "auser",
+      password: "apw",
+      host: "www.abc.com",
+      hostname: "www.abc.com",
+      port: "",
+      pathname: "/",
+      search: "",
+      hash: "",
     },
-    { url: 'http://www.abc.com:90/apath/',
+    {
+      url: "http://www.abc.com:90/apath/",
       base: undefined,
       error: false,
-      href: 'http://www.abc.com:90/apath/',
-      origin: 'http://www.abc.com:90',
-      protocol: 'http:',
-      username: '',
-      password: '',
-      host: 'www.abc.com:90',
-      hostname: 'www.abc.com',
-      port: '90',
-      pathname: '/apath/',
-      search: '',
-      hash: ''
+      href: "http://www.abc.com:90/apath/",
+      origin: "http://www.abc.com:90",
+      protocol: "http:",
+      username: "",
+      password: "",
+      host: "www.abc.com:90",
+      hostname: "www.abc.com",
+      port: "90",
+      pathname: "/apath/",
+      search: "",
+      hash: "",
     },
-    { url: 'http://www.abc.com/apath/afile.txt#ahash',
+    {
+      url: "http://www.abc.com/apath/afile.txt#ahash",
       base: undefined,
       error: false,
-      href: 'http://www.abc.com/apath/afile.txt#ahash',
-      origin: 'http://www.abc.com',
-      protocol: 'http:',
-      username: '',
-      password: '',
-      host: 'www.abc.com',
-      hostname: 'www.abc.com',
-      port: '',
-      pathname: '/apath/afile.txt',
-      search: '',
-      hash: '#ahash'
+      href: "http://www.abc.com/apath/afile.txt#ahash",
+      origin: "http://www.abc.com",
+      protocol: "http:",
+      username: "",
+      password: "",
+      host: "www.abc.com",
+      hostname: "www.abc.com",
+      port: "",
+      pathname: "/apath/afile.txt",
+      search: "",
+      hash: "#ahash",
     },
-    { url: 'http://example.com/?test#hash',
+    {
+      url: "http://example.com/?test#hash",
       base: undefined,
       error: false,
-      href: 'http://example.com/?test#hash',
-      origin: 'http://example.com',
-      protocol: 'http:',
-      username: '',
-      password: '',
-      host: 'example.com',
-      hostname: 'example.com',
-      port: '',
-      pathname: '/',
-      search: '?test',
-      hash: '#hash'
+      href: "http://example.com/?test#hash",
+      origin: "http://example.com",
+      protocol: "http:",
+      username: "",
+      password: "",
+      host: "example.com",
+      hostname: "example.com",
+      port: "",
+      pathname: "/",
+      search: "?test",
+      hash: "#hash",
     },
-    { url: 'http://example.com/?test',
+    {
+      url: "http://example.com/?test",
       base: undefined,
       error: false,
-      href: 'http://example.com/?test',
-      origin: 'http://example.com',
-      protocol: 'http:',
-      username: '',
-      password: '',
-      host: 'example.com',
-      hostname: 'example.com',
-      port: '',
-      pathname: '/',
-      search: '?test',
-      hash: ''
+      href: "http://example.com/?test",
+      origin: "http://example.com",
+      protocol: "http:",
+      username: "",
+      password: "",
+      host: "example.com",
+      hostname: "example.com",
+      port: "",
+      pathname: "/",
+      search: "?test",
+      hash: "",
     },
-    { url: 'http://example.com/carrot#question%3f',
+    {
+      url: "http://example.com/carrot#question%3f",
       base: undefined,
       error: false,
-      hash: '#question%3f'
+      hash: "#question%3f",
     },
-    { url: 'https://example.com:4443?',
+    {
+      url: "https://example.com:4443?",
       base: undefined,
       error: false,
-      protocol: 'https:',
-      port: '4443',
-      pathname: '/',
-      hash: '',
-      search: ''
+      protocol: "https:",
+      port: "4443",
+      pathname: "/",
+      hash: "",
+      search: "",
     },
-    { url: 'http://www.abc.com/apath/afile.txt#ahash?asearch',
+    {
+      url: "http://www.abc.com/apath/afile.txt#ahash?asearch",
       base: undefined,
       error: false,
-      href: 'http://www.abc.com/apath/afile.txt#ahash?asearch',
-      protocol: 'http:',
-      pathname: '/apath/afile.txt',
-      hash: '#ahash?asearch',
-      search: ''
+      href: "http://www.abc.com/apath/afile.txt#ahash?asearch",
+      protocol: "http:",
+      pathname: "/apath/afile.txt",
+      hash: "#ahash?asearch",
+      search: "",
     },
-    { url: 'http://www.abc.com/apath/afile.txt?asearch#ahash',
+    {
+      url: "http://www.abc.com/apath/afile.txt?asearch#ahash",
       base: undefined,
       error: false,
-      href: 'http://www.abc.com/apath/afile.txt?asearch#ahash',
-      protocol: 'http:',
-      pathname: '/apath/afile.txt',
-      hash: '#ahash',
-      search: '?asearch'
+      href: "http://www.abc.com/apath/afile.txt?asearch#ahash",
+      protocol: "http:",
+      pathname: "/apath/afile.txt",
+      hash: "#ahash",
+      search: "?asearch",
     },
-    { url: 'http://abc.com/apath/afile.txt?#ahash',
+    {
+      url: "http://abc.com/apath/afile.txt?#ahash",
       base: undefined,
       error: false,
-      pathname: '/apath/afile.txt',
-      hash: '#ahash',
-      search: ''
+      pathname: "/apath/afile.txt",
+      hash: "#ahash",
+      search: "",
     },
-    { url: 'http://auser:apassword@www.abc.com:90/apath/afile.txt?asearch#ahash',
+    {
+      url: "http://auser:apassword@www.abc.com:90/apath/afile.txt?asearch#ahash",
       base: undefined,
       error: false,
-      protocol: 'http:',
-      username: 'auser',
-      password: 'apassword',
-      host: 'www.abc.com:90',
-      hostname: 'www.abc.com',
-      port: '90',
-      pathname: '/apath/afile.txt',
-      hash: '#ahash',
-      search: '?asearch',
-      origin: 'http://www.abc.com:90'
+      protocol: "http:",
+      username: "auser",
+      password: "apassword",
+      host: "www.abc.com:90",
+      hostname: "www.abc.com",
+      port: "90",
+      pathname: "/apath/afile.txt",
+      hash: "#ahash",
+      search: "?asearch",
+      origin: "http://www.abc.com:90",
     },
 
-    { url: '/foo#bar',
-      base: 'www.test.org',
-      error: true,
-    },
-    { url: '/foo#bar',
-      base: null,
-      error: true,
-    },
-    { url: '/foo#bar',
-      base: 42,
-      error: true,
-    },
-    { url: 'ftp://ftp.something.net',
+    { url: "/foo#bar", base: "www.test.org", error: true },
+    { url: "/foo#bar", base: null, error: true },
+    { url: "/foo#bar", base: 42, error: true },
+    {
+      url: "ftp://ftp.something.net",
       base: undefined,
       error: false,
-      protocol: 'ftp:',
+      protocol: "ftp:",
     },
-    { url: 'file:///tmp/file',
+    {
+      url: "file:///tmp/file",
       base: undefined,
       error: false,
-      protocol: 'file:',
+      protocol: "file:",
     },
-    { url: 'gopher://gopher.something.net',
+    {
+      url: "gopher://gopher.something.net",
       base: undefined,
       error: false,
-      protocol: 'gopher:',
+      protocol: "gopher:",
     },
-    { url: 'ws://ws.something.net',
+    {
+      url: "ws://ws.something.net",
       base: undefined,
       error: false,
-      protocol: 'ws:',
+      protocol: "ws:",
     },
-    { url: 'wss://ws.something.net',
+    {
+      url: "wss://ws.something.net",
       base: undefined,
       error: false,
-      protocol: 'wss:',
+      protocol: "wss:",
     },
-    { url: 'foo://foo.something.net',
+    {
+      url: "foo://foo.something.net",
       base: undefined,
       error: false,
-      protocol: 'foo:',
+      protocol: "foo:",
     },
   ];
 
-  while(tests.length) {
+  while (tests.length) {
     var test = tests.shift();
 
     var error = false;
@@ -217,7 +229,7 @@ onmessage = function() {
       } else {
         url = new URL(test.url);
       }
-    } catch(e) {
+    } catch (e) {
       error = true;
     }
 
@@ -226,47 +238,79 @@ onmessage = function() {
       continue;
     }
 
-    if ('href' in test) is(url.href, test.href, "href");
-    if ('origin' in test) is(url.origin, test.origin, "origin");
-    if ('protocol' in test) is(url.protocol, test.protocol, "protocol");
-    if ('username' in test) is(url.username, test.username, "username");
-    if ('password' in test) is(url.password, test.password, "password");
-    if ('host' in test) is(url.host, test.host, "host");
-    if ('hostname' in test) is(url.hostname, test.hostname, "hostname");
-    if ('port' in test) is(url.port, test.port, "port");
-    if ('pathname' in test) is(url.pathname, test.pathname, "pathname");
-    if ('search' in test) is(url.search, test.search, "search");
-    if ('hash' in test) is(url.hash, test.hash, "hash");
+    if ("href" in test) is(url.href, test.href, "href");
+    if ("origin" in test) is(url.origin, test.origin, "origin");
+    if ("protocol" in test) is(url.protocol, test.protocol, "protocol");
+    if ("username" in test) is(url.username, test.username, "username");
+    if ("password" in test) is(url.password, test.password, "password");
+    if ("host" in test) is(url.host, test.host, "host");
+    if ("hostname" in test) is(url.hostname, test.hostname, "hostname");
+    if ("port" in test) is(url.port, test.port, "port");
+    if ("pathname" in test) is(url.pathname, test.pathname, "pathname");
+    if ("search" in test) is(url.search, test.search, "search");
+    if ("hash" in test) is(url.hash, test.hash, "hash");
 
-    url = new URL('https://www.example.net/what#foo?bar');
+    url = new URL("https://www.example.net/what#foo?bar");
     ok(url, "Url exists!");
 
-    if ('href' in test) url.href = test.href;
-    if ('protocol' in test) url.protocol = test.protocol;
-    if ('username' in test && test.username) url.username = test.username;
-    if ('password' in test && test.password) url.password = test.password;
-    if ('host' in test) url.host = test.host;
-    if ('hostname' in test) url.hostname = test.hostname;
-    if ('port' in test) url.port = test.port;
-    if ('pathname' in test) url.pathname = test.pathname;
-    if ('search' in test) url.search = test.search;
-    if ('hash' in test) url.hash = test.hash;
+    if ("href" in test) url.href = test.href;
+    if ("protocol" in test) url.protocol = test.protocol;
+    if ("username" in test && test.username) url.username = test.username;
+    if ("password" in test && test.password) url.password = test.password;
+    if ("host" in test) url.host = test.host;
+    if ("hostname" in test) url.hostname = test.hostname;
+    if ("port" in test) url.port = test.port;
+    if ("pathname" in test) url.pathname = test.pathname;
+    if ("search" in test) url.search = test.search;
+    if ("hash" in test) url.hash = test.hash;
 
-    if ('href' in test) is(url.href, test.href, "href");
-    if ('origin' in test) is(url.origin, test.origin, "origin");
-    if ('protocol' in test) is(url.protocol, test.protocol, "protocol");
-    if ('username' in test) is(url.username, test.username, "username");
-    if ('password' in test) is(url.password, test.password, "password");
-    if ('host' in test) is(url.host, test.host, "host");
-    if ('hostname' in test) is(test.hostname, url.hostname, "hostname");
-    if ('port' in test) is(test.port, url.port, "port");
-    if ('pathname' in test) is(test.pathname, url.pathname, "pathname");
-    if ('search' in test) is(test.search, url.search, "search");
-    if ('hash' in test) is(test.hash, url.hash, "hash");
+    if ("href" in test) is(url.href, test.href, "href");
+    if ("origin" in test) is(url.origin, test.origin, "origin");
+    if ("protocol" in test) is(url.protocol, test.protocol, "protocol");
+    if ("username" in test) is(url.username, test.username, "username");
+    if ("password" in test) is(url.password, test.password, "password");
+    if ("host" in test) is(url.host, test.host, "host");
+    if ("hostname" in test) is(test.hostname, url.hostname, "hostname");
+    if ("port" in test) is(test.port, url.port, "port");
+    if ("pathname" in test) is(test.pathname, url.pathname, "pathname");
+    if ("search" in test) is(test.search, url.search, "search");
+    if ("hash" in test) is(test.hash, url.hash, "hash");
 
-    if ('href' in test) is (test.href, url + '', 'stringify works');
+    if ("href" in test) is(test.href, url + "", "stringify works");
   }
 
-  postMessage({type: 'finish' });
-}
+  is(
+    new URL("/static/client/runtime.js", "x:/").href,
+    "x:/static/client/runtime.js",
+    "URL constructor accepts root-relative input with x:/ base",
+  );
+  ok("canParse" in URL, "URL.canParse exists");
+  ok(
+    URL.canParse("/static/client/runtime.js", "x:/"),
+    "URL.canParse accepts root-relative input with x:/ base",
+  );
+  ok(
+    URL.canParse("/static/client/runtime.js", self.location.href),
+    "URL.canParse accepts root-relative input with worker location base",
+  );
+  ok(
+    !URL.canParse("/static/client/runtime.js"),
+    "URL.canParse rejects root-relative input without base",
+  );
+  ok(
+    !URL.canParse("/static/client/runtime.js", "mailto:test@example.com"),
+    "URL.canParse rejects root-relative input against opaque mailto: base",
+  );
+  let mailtoRelativeFailed = false;
+  try {
+    new URL("/static/client/runtime.js", "mailto:test@example.com");
+  } catch (e) {
+    mailtoRelativeFailed = true;
+  }
+  ok(
+    mailtoRelativeFailed,
+    "constructor rejects root-relative input against opaque mailto: base",
+  );
 
+  postMessage({ type: "finish" });
+};
diff --git a/editor/libeditor/EditorBase.cpp b/editor/libeditor/EditorBase.cpp
index ef5845ffe0..de6b319f5b 100644
--- a/editor/libeditor/EditorBase.cpp
+++ b/editor/libeditor/EditorBase.cpp
@@ -142,6 +142,7 @@ EditorBase::EditorBase()
   , mDispatchInputEvent(true)
   , mIsInEditAction(false)
   , mHidingCaret(false)
+  , mIsHTMLEditorClass(false)
 {
 }
 
diff --git a/editor/libeditor/EditorBase.h b/editor/libeditor/EditorBase.h
index 08a895dcdc..2d1a60dba1 100644
--- a/editor/libeditor/EditorBase.h
+++ b/editor/libeditor/EditorBase.h
@@ -114,6 +114,7 @@ class DeleteNodeTransaction;
 class DeleteTextTransaction;
 class EditAggregateTransaction;
 class ErrorResult;
+class HTMLEditor;
 class InsertNodeTransaction;
 class InsertTextTransaction;
 class JoinNodeTransaction;
@@ -121,6 +122,7 @@ class PlaceholderTransaction;
 class RemoveStyleSheetTransaction;
 class SplitNodeTransaction;
 class TextComposition;
+class TextEditor;
 struct EditorDOMPoint;
 
 namespace dom {
@@ -1113,14 +1115,32 @@ protected:
   bool mIsInEditAction;
   // Whether caret is hidden forcibly.
   bool mHidingCaret;
+  // Whether we are an HTML editor class.
+  bool mIsHTMLEditorClass;
 
   friend bool NSCanUnload(nsISupports* serviceMgr);
   friend class AutoRules;
   friend class AutoSelectionRestorer;
   friend class AutoTransactionsConserveSelection;
   friend class RangeUpdater;
+  friend class nsIEditor;
 };
 
 } // namespace mozilla
 
+// nsIEditor helper functions.
+// Here because of code context.
+mozilla::EditorBase*
+nsIEditor::AsEditorBase()
+{
+  return static_cast(this);
+}
+
+const mozilla::EditorBase*
+nsIEditor::AsEditorBase() const
+{
+  return static_cast(this);
+}
+
+
 #endif // #ifndef mozilla_EditorBase_h
diff --git a/editor/libeditor/HTMLEditRules.cpp b/editor/libeditor/HTMLEditRules.cpp
index a5b284d82f..c1f90d4483 100644
--- a/editor/libeditor/HTMLEditRules.cpp
+++ b/editor/libeditor/HTMLEditRules.cpp
@@ -233,9 +233,16 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLEditRules, TextEditRules,
 NS_IMETHODIMP
 HTMLEditRules::Init(TextEditor* aTextEditor)
 {
+  if (NS_WARN_IF(!aTextEditor)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
   InitFields();
 
-  mHTMLEditor = static_cast(aTextEditor);
+  mHTMLEditor = aTextEditor->AsHTMLEditor();
+  if (NS_WARN_IF(!mHTMLEditor)) {
+    return NS_ERROR_INVALID_ARG;
+  }
 
   // call through to base class Init
   nsresult rv = TextEditRules::Init(aTextEditor);
diff --git a/editor/libeditor/HTMLEditor.cpp b/editor/libeditor/HTMLEditor.cpp
index 767856a1bc..1faccd1e15 100644
--- a/editor/libeditor/HTMLEditor.cpp
+++ b/editor/libeditor/HTMLEditor.cpp
@@ -132,6 +132,7 @@ HTMLEditor::HTMLEditor()
   , mPositionedObjectBorderTop(0)
   , mGridSize(0)
 {
+  mIsHTMLEditorClass = true;
 }
 
 HTMLEditor::~HTMLEditor()
@@ -501,7 +502,7 @@ HTMLEditor::InitRules()
     // instantiate the rules for the html editor
     mRules = new HTMLEditRules();
   }
-  return mRules->Init(static_cast(this));
+  return mRules->Init(this);
 }
 
 NS_IMETHODIMP
diff --git a/editor/libeditor/HTMLEditor.h b/editor/libeditor/HTMLEditor.h
index e90e7ebe6e..f314d24d00 100644
--- a/editor/libeditor/HTMLEditor.h
+++ b/editor/libeditor/HTMLEditor.h
@@ -1108,4 +1108,20 @@ private:
 
 } // namespace mozilla
 
+// nsIEditor helper functions.
+// Here because of code context.
+mozilla::HTMLEditor*
+nsIEditor::AsHTMLEditor()
+{
+  return static_cast(this)->mIsHTMLEditorClass ?
+           static_cast(this) : nullptr;
+}
+
+const mozilla::HTMLEditor*
+nsIEditor::AsHTMLEditor() const
+{
+  return static_cast(this)->mIsHTMLEditorClass ?
+           static_cast(this) : nullptr;
+}
+
 #endif // #ifndef mozilla_HTMLEditor_h
diff --git a/editor/libeditor/HTMLEditorEventListener.cpp b/editor/libeditor/HTMLEditorEventListener.cpp
index aa767519c7..3699e11acb 100644
--- a/editor/libeditor/HTMLEditorEventListener.cpp
+++ b/editor/libeditor/HTMLEditorEventListener.cpp
@@ -30,24 +30,18 @@ namespace mozilla {
 
 using namespace dom;
 
-#ifdef DEBUG
 nsresult
 HTMLEditorEventListener::Connect(EditorBase* aEditorBase)
 {
-  nsCOMPtr htmlEditor = do_QueryObject(aEditorBase);
-  nsCOMPtr htmlInlineTableEditor =
-    do_QueryObject(aEditorBase);
-  NS_PRECONDITION(htmlEditor && htmlInlineTableEditor,
-                  "Set HTMLEditor or its sub class");
-  return EditorEventListener::Connect(aEditorBase);
-}
-#endif
-
-HTMLEditor*
-HTMLEditorEventListener::GetHTMLEditor()
-{
-  // mEditor must be HTMLEditor or its subclass.
-  return static_cast(mEditorBase);
+  if (NS_WARN_IF(!aEditorBase)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+  // Guarantee that mEditorBase is always HTMLEditor.
+  HTMLEditor* htmlEditor = aEditorBase->AsHTMLEditor();
+  if (NS_WARN_IF(!htmlEditor)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+  return EditorEventListener::Connect(htmlEditor);
 }
 
 nsresult
@@ -59,7 +53,8 @@ HTMLEditorEventListener::MouseUp(nsIDOMMouseEvent* aMouseEvent)
 
   // FYI: We need to notify HTML editor of mouseup even if it's consumed
   //      because HTML editor always needs to release grabbing resizer.
-  HTMLEditor* htmlEditor = GetHTMLEditor();
+  HTMLEditor* htmlEditor = mEditorBase->AsHTMLEditor();
+  MOZ_ASSERT(htmlEditor);
 
   nsCOMPtr target;
   nsresult rv = aMouseEvent->AsEvent()->GetTarget(getter_AddRefs(target));
@@ -85,7 +80,9 @@ HTMLEditorEventListener::MouseDown(nsIDOMMouseEvent* aMouseEvent)
   WidgetMouseEvent* mousedownEvent =
     aMouseEvent->AsEvent()->WidgetEventPtr()->AsMouseEvent();
 
-  HTMLEditor* htmlEditor = GetHTMLEditor();
+  HTMLEditor* htmlEditor = mEditorBase->AsHTMLEditor();
+  MOZ_ASSERT(htmlEditor);
+
   // Contenteditable should disregard mousedowns outside it.
   // IsAcceptableInputEvent() checks it for a mouse event.
   if (!htmlEditor->IsAcceptableInputEvent(mousedownEvent)) {
@@ -221,13 +218,19 @@ HTMLEditorEventListener::MouseDown(nsIDOMMouseEvent* aMouseEvent)
 nsresult
 HTMLEditorEventListener::MouseClick(nsIDOMMouseEvent* aMouseEvent)
 {
+  if (NS_WARN_IF(DetachedFromEditor())) {
+    return NS_OK;
+  }
+
   nsCOMPtr target;
   nsresult rv = aMouseEvent->AsEvent()->GetTarget(getter_AddRefs(target));
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ENSURE_TRUE(target, NS_ERROR_NULL_POINTER);
   nsCOMPtr element = do_QueryInterface(target);
 
-  GetHTMLEditor()->DoInlineTableEditingAction(element);
+  HTMLEditor* htmlEditor = mEditorBase->AsHTMLEditor();
+  MOZ_ASSERT(htmlEditor);
+  htmlEditor->DoInlineTableEditingAction(element);
 
   return EditorEventListener::MouseClick(aMouseEvent);
 }
diff --git a/editor/libeditor/HTMLEditorEventListener.h b/editor/libeditor/HTMLEditorEventListener.h
index b97b675b51..69bbf705d9 100644
--- a/editor/libeditor/HTMLEditorEventListener.h
+++ b/editor/libeditor/HTMLEditorEventListener.h
@@ -25,17 +25,13 @@ public:
   {
   }
 
-#ifdef DEBUG
-  // WARNING: You must be use HTMLEditor or its sub class for this class.
+  // Connect() fails if aEditorBase isn't an HTMLEditor instance.
   virtual nsresult Connect(EditorBase* aEditorBase) override;
-#endif
 
 protected:
   virtual nsresult MouseDown(nsIDOMMouseEvent* aMouseEvent) override;
   virtual nsresult MouseUp(nsIDOMMouseEvent* aMouseEvent) override;
   virtual nsresult MouseClick(nsIDOMMouseEvent* aMouseEvent) override;
-
-  inline HTMLEditor* GetHTMLEditor();
 };
 
 } // namespace mozilla
diff --git a/editor/libeditor/TextEditor.cpp b/editor/libeditor/TextEditor.cpp
index 5f75e7e7e0..2b56ae4fc4 100644
--- a/editor/libeditor/TextEditor.cpp
+++ b/editor/libeditor/TextEditor.cpp
@@ -10,6 +10,7 @@
 #include "gfxFontUtils.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/EditorUtils.h" // AutoEditBatch, AutoRules
+#include "mozilla/HTMLEditor.h"
 #include "mozilla/mozalloc.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/TextEditRules.h"
diff --git a/editor/libeditor/TextEditor.h b/editor/libeditor/TextEditor.h
index 7bb594931b..f4a744d759 100644
--- a/editor/libeditor/TextEditor.h
+++ b/editor/libeditor/TextEditor.h
@@ -247,4 +247,18 @@ protected:
 
 } // namespace mozilla
 
+// nsIEditor helper functions.
+// Here because of code context.
+mozilla::TextEditor*
+nsIEditor::AsTextEditor()
+{
+  return static_cast(this);
+}
+
+const mozilla::TextEditor*
+nsIEditor::AsTextEditor() const
+{
+  return static_cast(this);
+}
+
 #endif // #ifndef mozilla_TextEditor_h
diff --git a/editor/nsIEditor.idl b/editor/nsIEditor.idl
index bb9026d0ee..d96a3954e9 100644
--- a/editor/nsIEditor.idl
+++ b/editor/nsIEditor.idl
@@ -21,6 +21,14 @@ interface nsIEditActionListener;
 interface nsIInlineSpellChecker;
 interface nsITransferable;
 
+%{C++
+namespace mozilla {
+class EditorBase;
+class HTMLEditor;
+class TextEditor;
+} // namespace mozilla
+%}
+ 
 [scriptable, uuid(094be624-f0bf-400f-89e2-6a84baab9474)]
 interface nsIEditor  : nsISupports
 {
@@ -564,4 +572,34 @@ interface nsIEditor  : nsISupports
    * or nsIEditorObserver::CancelEditAction().  Otherwise, false.
    */
   [noscript] readonly attribute boolean isInEditAction;
+
+%{C++
+  /**
+   * AsEditorBase() returns a pointer to EditorBase class.
+   *
+   * In order to avoid circular dependency issues, this method is defined
+   * in mozilla/EditorBase.h.  Consumers need to #include that header.
+   */
+  inline mozilla::EditorBase* AsEditorBase();
+  inline const mozilla::EditorBase* AsEditorBase() const;
+
+  /**
+   * AsTextEditor() returns a pointer to TextEditor class.
+   *
+   * In order to avoid circular dependency issues, this method is defined
+   * in mozilla/TextEditor.h.  Consumers need to #include that header.
+   */
+  inline mozilla::TextEditor* AsTextEditor();
+  inline const mozilla::TextEditor* AsTextEditor() const;
+
+  /**
+   * AsHTMLEditor() returns a pointer to HTMLEditor class.
+   *
+   * In order to avoid circular dependency issues, this method is defined
+   * in mozilla/HTMLEditor.h.  Consumers need to #include that header.
+   */
+  inline mozilla::HTMLEditor* AsHTMLEditor();
+  inline const mozilla::HTMLEditor* AsHTMLEditor() const;
+%}
+
 };
diff --git a/extensions/spellcheck/src/mozInlineSpellChecker.cpp b/extensions/spellcheck/src/mozInlineSpellChecker.cpp
index 398059fc9b..f62c1852e0 100644
--- a/extensions/spellcheck/src/mozInlineSpellChecker.cpp
+++ b/extensions/spellcheck/src/mozInlineSpellChecker.cpp
@@ -97,9 +97,6 @@ using namespace mozilla::dom;
 #define INLINESPELL_STARTED_TOPIC "inlineSpellChecker-spellCheck-started"
 #define INLINESPELL_ENDED_TOPIC "inlineSpellChecker-spellCheck-ended"
 
-static bool ContentIsDescendantOf(nsINode* aPossibleDescendant,
-                                    nsINode* aPossibleAncestor);
-
 static const char kMaxSpellCheckSelectionSize[] = "extensions.spellcheck.inline.max-misspellings";
 
 mozInlineSpellStatus::mozInlineSpellStatus(mozInlineSpellChecker* aSpellChecker)
@@ -238,7 +235,7 @@ mozInlineSpellStatus::InitForNavigation(
   NS_ENSURE_SUCCESS(rv, rv);
   nsCOMPtr currentAnchor = do_QueryInterface(aOldAnchorNode, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
-  if (root && currentAnchor && ! ContentIsDescendantOf(currentAnchor, root)) {
+  if (root && currentAnchor && !nsContentUtils::ContentIsShadowIncludingDescendantOf(currentAnchor, root)) {
     *aContinue = false;
     return NS_OK;
   }
@@ -1490,8 +1487,11 @@ nsresult mozInlineSpellChecker::DoSpellCheck(mozInlineSpellWordUtil& aWordUtil,
       return NS_OK;
     }
 
-    aWordUtil.SetEnd(endNode, endOffset);
-    aWordUtil.SetPosition(beginNode, beginOffset);
+    nsresult rv = aWordUtil.SetPositionAndEnd(beginNode, beginOffset, endNode, endOffset);
+    if (NS_FAILED(rv)) {
+      // Just bail out and don't try to spell-check this
+      return NS_OK;
+    }
   }
 
   // aWordUtil.SetPosition flushes pending notifications, check editor again.
@@ -1840,24 +1840,6 @@ nsresult mozInlineSpellChecker::SaveCurrentSelectionPosition()
   return NS_OK;
 }
 
-// This is a copy of nsContentUtils::ContentIsDescendantOf. Another crime
-// for XPCOM's rap sheet
-bool // static
-ContentIsDescendantOf(nsINode* aPossibleDescendant,
-                      nsINode* aPossibleAncestor)
-{
-  NS_PRECONDITION(aPossibleDescendant, "The possible descendant is null!");
-  NS_PRECONDITION(aPossibleAncestor, "The possible ancestor is null!");
-
-  do {
-    if (aPossibleDescendant == aPossibleAncestor)
-      return true;
-    aPossibleDescendant = aPossibleDescendant->GetParentNode();
-  } while (aPossibleDescendant);
-
-  return false;
-}
-
 // mozInlineSpellChecker::HandleNavigationEvent
 //
 //    Acts upon mouse clicks and keyboard navigation changes, spell checking
diff --git a/extensions/spellcheck/src/mozInlineSpellWordUtil.cpp b/extensions/spellcheck/src/mozInlineSpellWordUtil.cpp
index 460ac46b85..13e621c870 100644
--- a/extensions/spellcheck/src/mozInlineSpellWordUtil.cpp
+++ b/extensions/spellcheck/src/mozInlineSpellWordUtil.cpp
@@ -23,6 +23,8 @@
 #include "nsIFrame.h"
 #include 
 #include "mozilla/BinarySearch.h"
+#include "mozilla/HTMLEditor.h"
+#include "mozilla/dom/ShadowRoot.h"
 
 using namespace mozilla;
 
@@ -69,8 +71,10 @@ mozInlineSpellWordUtil::Init(nsWeakPtr aWeakEditor)
   mDOMDocument = domDoc;
   mDocument = do_QueryInterface(domDoc);
 
-  // Find the root node for the editor. For contenteditable we'll need something
-  // cleverer here.
+  mIsContentEditableOrDesignMode = !!editor->AsHTMLEditor();
+
+  // Find the root node for the editor. For contenteditable the mRootNode could
+  // change to shadow root if the begin and end are inside the shadowDOM.
   nsCOMPtr rootElt;
   rv = editor->GetRootElement(getter_AddRefs(rootElt));
   NS_ENSURE_SUCCESS(rv, rv);
@@ -154,7 +158,7 @@ FindNextTextNode(nsINode* aNode, int32_t aOffset, nsINode* aRoot)
   return checkNode;
 }
 
-// mozInlineSpellWordUtil::SetEnd
+// mozInlineSpellWordUtil::SetPositionAndEnd
 //
 //    We have two ranges "hard" and "soft". The hard boundary is simply
 //    the scope of the root node. The soft boundary is that which is set
@@ -172,34 +176,44 @@ FindNextTextNode(nsINode* aNode, int32_t aOffset, nsINode* aRoot)
 //    position.
 
 nsresult
-mozInlineSpellWordUtil::SetEnd(nsINode* aEndNode, int32_t aEndOffset)
+mozInlineSpellWordUtil::SetPositionAndEnd(nsINode* aPositionNode,
+                                          int32_t aPositionOffset,
+                                          nsINode* aEndNode,
+                                          int32_t aEndOffset)
 {
+  MOZ_ASSERT(aPositionNode, "Null begin node?");
   NS_PRECONDITION(aEndNode, "Null end node?");
 
   NS_ASSERTION(mRootNode, "Not initialized");
 
+  // Find a appropriate root if we are dealing with contenteditable nodes which
+  // are in the shadow DOM. See UXP Issue #3011
+  if (mIsContentEditableOrDesignMode) {
+    nsINode* rootNode = aPositionNode->SubtreeRoot();
+    if (rootNode != aEndNode->SubtreeRoot()) {
+      return NS_ERROR_FAILURE;
+    }
+
+    if (mozilla::dom::ShadowRoot::FromNode(rootNode)) {
+      mRootNode = rootNode;
+    }
+  }
+
   InvalidateWords();
 
+  if (!IsTextNode(aPositionNode)) {
+    // Start at the start of the first text node after aNode/aOffset.
+    aPositionNode = FindNextTextNode(aPositionNode, aPositionOffset, mRootNode);
+    aPositionOffset = 0;
+  }
+  mSoftBegin = NodeOffset(aPositionNode, aPositionOffset);
+
   if (!IsTextNode(aEndNode)) {
     // End at the start of the first text node after aEndNode/aEndOffset.
     aEndNode = FindNextTextNode(aEndNode, aEndOffset, mRootNode);
     aEndOffset = 0;
   }
   mSoftEnd = NodeOffset(aEndNode, aEndOffset);
-  return NS_OK;
-}
-
-nsresult
-mozInlineSpellWordUtil::SetPosition(nsINode* aNode, int32_t aOffset)
-{
-  InvalidateWords();
-
-  if (!IsTextNode(aNode)) {
-    // Start at the start of the first text node after aNode/aOffset.
-    aNode = FindNextTextNode(aNode, aOffset, mRootNode);
-    aOffset = 0;
-  }
-  mSoftBegin = NodeOffset(aNode, aOffset);
 
   nsresult rv = EnsureWords();
   if (NS_FAILED(rv)) {
@@ -207,8 +221,10 @@ mozInlineSpellWordUtil::SetPosition(nsINode* aNode, int32_t aOffset)
   }
   
   int32_t textOffset = MapDOMPositionToSoftTextOffset(mSoftBegin);
-  if (textOffset < 0)
+  if (textOffset < 0) {
     return NS_OK;
+  }
+
   mNextWordIndex = FindRealWordContaining(textOffset, HINT_END, true);
   return NS_OK;
 }
diff --git a/extensions/spellcheck/src/mozInlineSpellWordUtil.h b/extensions/spellcheck/src/mozInlineSpellWordUtil.h
index b28d24ae5f..213fa52a16 100644
--- a/extensions/spellcheck/src/mozInlineSpellWordUtil.h
+++ b/extensions/spellcheck/src/mozInlineSpellWordUtil.h
@@ -29,12 +29,11 @@ class nsINode;
  *    The basic operation is:
  *
  *    1. Call Init with the weak pointer to the editor that you're using.
- *    2. Call SetEnd to set where you want to stop spellchecking. We'll stop
- *       at the word boundary after that. If SetEnd is not called, we'll stop
- *       at the end of the document's root element.
- *    3. Call SetPosition to initialize the current position inside the
- *       previously given range.
- *    4. Call GetNextWord over and over until it returns false.
+ *    2. Call SetPositionAndEnd to to initialize the current position inside the
+ *       previously given range and set where you want to stop spellchecking. 
+ *       We'll stop at the word boundary after that. If SetEnd is not called,
+ *       we'll stop at the end of the root element.
+ *    3. Call GetNextWord over and over until it returns false.
  */
 
 class mozInlineSpellWordUtil
@@ -57,17 +56,22 @@ public:
   };
 
   mozInlineSpellWordUtil()
-    : mRootNode(nullptr),
-      mSoftBegin(nullptr, 0), mSoftEnd(nullptr, 0),
-      mNextWordIndex(-1), mSoftTextValid(false) {}
+    : mIsContentEditableOrDesignMode(false)
+    , mRootNode(nullptr)
+    , mSoftBegin(nullptr, 0)
+    , mSoftEnd(nullptr, 0)
+    , mNextWordIndex(-1)
+    , mSoftTextValid(false)
+  {}
 
   nsresult Init(nsWeakPtr aWeakEditor);
 
-  nsresult SetEnd(nsINode* aEndNode, int32_t aEndOffset);
-
-  // sets the current position, this should be inside the range. If we are in
-  // the middle of a word, we'll move to its start.
-  nsresult SetPosition(nsINode* aNode, int32_t aOffset);
+  // Sets the current position and end. This should be inside the range.
+  // If we are in the middle of a word, we'll move to its start.
+  nsresult SetPositionAndEnd(nsINode* aPositionNode,
+                             int32_t aPositionOffset,
+                             nsINode* aEndNode,
+                             int32_t aEndOffset);
 
   // Given a point inside or immediately following a word, this returns the
   // DOM range that exactly encloses that word's characters. The current
@@ -100,7 +104,8 @@ private:
 
   // cached stuff for the editor, set by Init
   nsCOMPtr mDOMDocument;
-  nsCOMPtr         mDocument;
+  nsCOMPtr mDocument;
+  bool mIsContentEditableOrDesignMode;
 
   // range to check, see SetPosition and SetEnd
   nsINode*    mRootNode;
diff --git a/js/src/builtin/TypedArray.js b/js/src/builtin/TypedArray.js
index 237b47ad8c..9c22c51f2c 100644
--- a/js/src/builtin/TypedArray.js
+++ b/js/src/builtin/TypedArray.js
@@ -37,6 +37,10 @@ function TypedArrayLengthMethod() {
     return TypedArrayLength(this);
 }
 
+function TypedArrayContentTypeIsBigIntMethod() {
+    return IsBigInt64TypedArray(this) || IsBigUint64TypedArray(this);
+}
+
 function GetAttachedArrayBuffer(tarray) {
     var buffer = ViewedArrayBufferIfReified(tarray);
     if (IsDetachedBuffer(buffer))
@@ -895,6 +899,61 @@ function TypedArrayToReversed() {
     return A;
 }
 
+// ES2023 23.2.3.36 %TypedArray%.prototype.with ( index, value )
+function TypedArrayWith(index, value) {
+    // Step 1.
+    var O = this;
+
+    // Step 2.
+    // This function is not generic.
+    // We want to make sure that we have an attached buffer, per spec prose.
+    var isTypedArray = IsTypedArrayEnsuringArrayBuffer(O);
+
+    // If we got here, `this` is either a typed array or a wrapper for one.
+
+    // Step 3.
+    var len;
+    if (isTypedArray)
+        len = TypedArrayLength(O);
+    else
+        len = callFunction(CallTypedArrayMethodIfWrapped, O, "TypedArrayLengthMethod");
+
+    // Steps 4-6.
+    var relativeIndex = ToInteger(index);
+    var actualIndex = relativeIndex >= 0 ? relativeIndex : len + relativeIndex;
+
+    // Steps 7-8.
+    var isBigIntContentType;
+    if (isTypedArray) {
+        isBigIntContentType = callFunction(TypedArrayContentTypeIsBigIntMethod, O);
+    } else {
+        isBigIntContentType = callFunction(CallTypedArrayMethodIfWrapped, O,
+                                           "TypedArrayContentTypeIsBigIntMethod");
+    }
+    var numericValue = isBigIntContentType ? ToBigInt(value) : ToNumber(value);
+
+    // Step 9.
+    if (actualIndex < 0 || actualIndex >= len)
+        ThrowRangeError(JSMSG_BAD_INDEX);
+
+    // Step 10.
+    var A = TypedArrayCreateSameType(O, len);
+
+    // Steps 11-12.
+    for (var k = 0; k < len; k++) {
+        var fromValue;
+        if (k === actualIndex) {
+            fromValue = numericValue;
+        } else {
+            fromValue = O[k];
+        }
+        A[k] = fromValue;
+    }
+
+    // Step 13.
+    return A;
+}
+
 // ES6 draft 20150220 22.2.3.22.1 %TypedArray%.prototype.set(array [, offset])
 function SetFromNonTypedArray(target, array, targetOffset, targetLength, targetBuffer) {
     assert(!IsPossiblyWrappedTypedArray(array),
diff --git a/js/src/tests/ecma_6/TypedArray/detached-array-buffer-checks.js b/js/src/tests/ecma_6/TypedArray/detached-array-buffer-checks.js
index 76a00d886d..c5b58db328 100644
--- a/js/src/tests/ecma_6/TypedArray/detached-array-buffer-checks.js
+++ b/js/src/tests/ecma_6/TypedArray/detached-array-buffer-checks.js
@@ -97,6 +97,10 @@ assertThrowsInstanceOf(() => {
     array.values();
 }, TypeError);
 
+assertThrowsInstanceOf(() => {
+    array.with(POISON, POISON);
+}, TypeError);
+
 assertThrowsInstanceOf(() => {
     array.every(POISON);
 }, TypeError);
diff --git a/js/src/tests/ecma_6/TypedArray/with.js b/js/src/tests/ecma_6/TypedArray/with.js
new file mode 100644
index 0000000000..41ce1e706f
--- /dev/null
+++ b/js/src/tests/ecma_6/TypedArray/with.js
@@ -0,0 +1,84 @@
+for (var constructor of anyTypedArrayConstructors) {
+    assertEq(constructor.prototype.with.length, 2);
+
+    var original = new constructor([1, 2, 3, 4]);
+    var updated = original.with(1, 9);
+    assertDeepEq(updated, new constructor([1, 9, 3, 4]));
+    assertDeepEq(original, new constructor([1, 2, 3, 4]));
+    assertEq(updated === original, false);
+    assertEq(updated.constructor, constructor);
+
+    assertDeepEq(new constructor([1, 2, 3]).with(-1, 7),
+                 new constructor([1, 2, 7]));
+    assertDeepEq(new constructor([1, 2, 3]).with(-0, 7),
+                 new constructor([7, 2, 3]));
+
+    assertThrowsInstanceOf(() => {
+        new constructor([1, 2, 3]).with(3, 9);
+    }, RangeError);
+    assertThrowsInstanceOf(() => {
+        new constructor([1, 2, 3]).with(-4, 9);
+    }, RangeError);
+
+    var valueOrder = [];
+    var value = {
+        valueOf() {
+            valueOrder.push("valueOf");
+            return 9;
+        }
+    };
+    assertThrowsInstanceOf(() => {
+        new constructor([1, 2, 3]).with(9, value);
+    }, RangeError);
+    assertEq(valueOrder.join(","), "valueOf");
+
+    var ctorIgnored = new constructor([5, 6, 7]);
+    Object.defineProperty(ctorIgnored, "constructor", {
+        get() {
+            throw new Error("constructor accessor called");
+        }
+    });
+    assertDeepEq(ctorIgnored.with(0, 4), new constructor([4, 6, 7]));
+
+    if (constructor === Uint8ClampedArray ||
+        (typeof isSharedConstructor === "function" && isSharedConstructor(constructor) &&
+         constructor.name === Uint8ClampedArray.name))
+    {
+        assertDeepEq(new constructor([0, 1, 2]).with(1, 2.6),
+                     new constructor([0, 3, 2]));
+    }
+
+    var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./,
+                            new Proxy(new constructor(), {})];
+    invalidReceivers.forEach(invalidReceiver => {
+        assertThrowsInstanceOf(() => {
+            constructor.prototype.with.call(invalidReceiver, 0, 1);
+        }, TypeError,
+        "Assert that with fails if this value is not a TypedArray");
+    });
+}
+
+for (var constructor of typedArrayConstructors) {
+    if (typeof newGlobal === "function") {
+        var withFn = newGlobal()[constructor.name].prototype.with;
+        var original = new constructor([3, 2, 1]);
+        var updated = withFn.call(original, 1, 8);
+
+        assertDeepEq(updated, new constructor([3, 8, 1]));
+        assertDeepEq(original, new constructor([3, 2, 1]));
+    }
+}
+
+if (typeof BigInt64Array === "function" && typeof BigUint64Array === "function") {
+    var bigIntArray = new BigInt64Array([1n, 2n, 3n]);
+    assertEq(BigInt64Array.prototype.with.length, 2);
+    assertDeepEq(bigIntArray.with(1, 9n), new BigInt64Array([1n, 9n, 3n]));
+    assertThrowsInstanceOf(() => bigIntArray.with(1, 9), TypeError);
+
+    var bigUintArray = new BigUint64Array([1n, 2n, 3n]);
+    assertDeepEq(bigUintArray.with(2, 4n), new BigUint64Array([1n, 2n, 4n]));
+    assertThrowsInstanceOf(() => bigUintArray.with(9, 1), TypeError);
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
diff --git a/js/src/vm/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp
index 068d4ef4a6..fd687946ca 100644
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -1629,6 +1629,7 @@ TypedArrayObject::protoFunctions[] = {
     JS_SELF_HOSTED_FN("reduceRight", "TypedArrayReduceRight", 1, 0),
     JS_SELF_HOSTED_FN("reverse", "TypedArrayReverse", 0, 0),
     JS_SELF_HOSTED_FN("toReversed", "TypedArrayToReversed", 0, 0),
+    JS_SELF_HOSTED_FN("with", "TypedArrayWith", 2, 0),
     JS_SELF_HOSTED_FN("slice", "TypedArraySlice", 2, 0),
     JS_SELF_HOSTED_FN("some", "TypedArraySome", 1, 0),
     JS_SELF_HOSTED_FN("sort", "TypedArraySort", 1, 0),
diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js
index 3042fd0445..9536425b46 100644
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1977,6 +1977,12 @@ pref("network.predictor.max-resources-per-entry", 100);
 pref("network.predictor.max-uri-length", 500);
 pref("network.predictor.cleaned-up", false);
 
+// Cloudflare Image Resizing compatibility.
+// When enabled, URLs containing the "/cdn-cgi/image/" marker will have
+// everything after that marker treated as opaque path data. This matches
+// Cloudflare's expectations for Image Resizing URLs.
+pref("network.url.cloudflare_image_resizing.enabled", true);
+
 // The following prefs pertain to the negotiate-auth extension (see bug 17578),
 // which provides transparent Kerberos or NTLM authentication using the SPNEGO
 // protocol.  Each pref is a comma-separated list of keys, where each key has
diff --git a/netwerk/base/nsStandardURL.cpp b/netwerk/base/nsStandardURL.cpp
index 9334def6c7..92dce5423e 100644
--- a/netwerk/base/nsStandardURL.cpp
+++ b/netwerk/base/nsStandardURL.cpp
@@ -26,6 +26,7 @@
 #include "prprf.h"
 #include "nsReadableUtils.h"
 #include "nsPrintfCString.h"
+#include "mozilla/Preferences.h" //fixes up dependency issues in non-unified building
 
 using mozilla::dom::EncodingUtils;
 using namespace mozilla::ipc;
@@ -1105,6 +1106,58 @@ nsStandardURL::ParseURL(const char *spec, int32_t specLen)
 nsresult
 nsStandardURL::ParsePath(const char *spec, uint32_t pathPos, int32_t pathLen)
 {
+// Cloudflare Image Resizing compatibility (pref-controlled)
+//
+// This feature detects the "/cdn-cgi/image/" marker in the URL path and
+// treats everything after it as opaque path data. Cloudflare's Image
+// Resizing service expects clients to preserve the entire suffix exactly.
+//
+// Because this code runs in a hot path (URL parsing), we avoid calling
+// Preferences::GetBool() repeatedly. Instead, we use AddBoolVarCache()
+// to cache the pref value once and read it cheaply thereafter.
+
+// Cached preference: true = enable Cloudflare Image Resizing fixup
+static bool sCloudflareImageResizingEnabled = true;
+static bool sCloudflareImageResizingPrefCached = false;
+
+if (!sCloudflareImageResizingPrefCached) {
+  Preferences::AddBoolVarCache(
+    &sCloudflareImageResizingEnabled,
+    "network.url.cloudflare_image_resizing.enabled",
+    true // default if pref does not exist
+  );
+  sCloudflareImageResizingPrefCached = true;
+}
+
+if (sCloudflareImageResizingEnabled) {
+
+  // Extract the full path substring from the full URL spec.
+  nsDependentCSubstring fullPath(spec + pathPos, pathLen);
+
+  // Prepare iterators for scanning the path.
+  nsACString::const_iterator begin, end;
+  fullPath.BeginReading(begin);
+  fullPath.EndReading(end);
+
+  // Search for the Cloudflare Image Resizing marker.
+  nsACString::const_iterator cfPos = begin;
+  if (FindInReadable(NS_LITERAL_CSTRING("/cdn-cgi/image/"), cfPos, end)) {
+
+    // Compute how far into the path the marker was found.
+    uint32_t offset = cfPos.get() - begin.get();
+
+    // Rewrite the internal path representation so that the path
+    // begins at the Cloudflare marker. Everything before it is ignored.
+    mPath.mPos = pathPos + offset;
+    mPath.mLen = pathLen - offset;
+
+    // We handled the path; no further parsing needed.
+    return NS_OK;
+  }
+}
+
+
+
     LOG(("ParsePath: %s pathpos %d len %d\n",spec,pathPos,pathLen));
 
     if (pathLen > net_GetURLMaxLength()) {