Bug 1402247 bis - Rework string buffer allocation from Rust. draft
authorHenri Sivonen <hsivonen@hsivonen.fi>
Mon, 18 Jun 2018 13:26:57 +0300
changeset 808103 f83cc54917ca7c6f498d92f68fc21ec68cd073f0
parent 779162 042ad6c05c461f8e3679d76ab91f73ba781bf6c4
push id113276
push userbmo:hsivonen@hsivonen.fi
push dateMon, 18 Jun 2018 12:23:19 +0000
bugs1402247
milestone61.0a1
Bug 1402247 bis - Rework string buffer allocation from Rust. MozReview-Commit-ID: GuPTQIyT9ub
servo/support/gecko/nsstring/src/conversions.rs
servo/support/gecko/nsstring/src/lib.rs
xpcom/string/nsSubstring.cpp
xpcom/string/nsTSubstring.cpp
xpcom/string/nsTSubstring.h
--- a/servo/support/gecko/nsstring/src/conversions.rs
+++ b/servo/support/gecko/nsstring/src/conversions.rs
@@ -1,14 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 extern crate encoding_rs;
 
+use super::BulkWriteOk;
 use super::nsAString;
 use super::nsACString;
 use super::nsCStringLike;
 use super::Latin1StringLike;
 use super::Gecko_FallibleAssignCString;
 
 use conversions::encoding_rs::mem::*;
 use conversions::encoding_rs::Encoding;
@@ -40,29 +41,23 @@ fn plus_one(a: usize) -> Option<usize> {
 /// `$convert` is the underlying `encoding_rs::mem` function to use
 /// `$other_ty` is the type of the input slice
 /// `$math` is the worst-case length math that `$convert` expects
 macro_rules! shrinking_conversion {
     ($name:ident,
      $convert:ident,
      $other_ty:ty,
      $math:ident) => (
-        fn $name(&mut self, other: $other_ty, old_len: usize) -> Result<(), ()> {
-            let written = {
-                let needed = $math(other.len()).ok_or(())?;
-                let buffer = unsafe {
-                    self.fallible_maybe_expand_capacity(old_len.checked_add(needed).ok_or(())?)?
-                };
-                $convert(other, &mut buffer[old_len..])
+        fn $name(&mut self, other: $other_ty, old_len: usize) -> Result<BulkWriteOk, ()> {
+            let needed = $math(other.len()).ok_or(())?;
+            let mut handle = unsafe {
+                self.bulk_write(old_len.checked_add(needed).ok_or(())?, old_len)?
             };
-            unsafe {
-                // TODO: Shrink buffer
-                self.fallible_set_length((old_len + written) as u32)?;
-            }
-            Ok(())
+            let written = $convert(other, &mut handle.as_mut_slice()[old_len..]);
+            Ok(handle.finish(old_len + written)) // XXX maybe shrink buffer
         }
      )
 }
 
 /// A conversion where the number of code units in the output is always equal
 /// to the number of code units in the input.
 ///
 /// Takes the name of the method to be generated, the name of the conversion
@@ -70,58 +65,51 @@ macro_rules! shrinking_conversion {
 ///
 /// `$name` is the name of the function to generate
 /// `$convert` is the underlying `encoding_rs::mem` function to use
 /// `$other_ty` is the type of the input slice
 macro_rules! constant_conversion {
     ($name:ident,
      $convert:ident,
      $other_ty:ty) => (
-        fn $name(&mut self, other: $other_ty, old_len: usize) -> Result<(), ()> {
-            let needed = old_len.checked_add(other.len()).ok_or(())?;
-            {
-                let buffer = unsafe {
-                    self.fallible_maybe_expand_capacity(needed)?
-                };
-                $convert(other, &mut buffer[old_len..])
-            }
-            unsafe {
-                // Truncation to u32 is OK, because `fallible_maybe_expand_capacity()`
-                // would have failed if not.
-                self.fallible_set_length(needed as u32)?;
-            }
-            Ok(())
+        fn $name(&mut self, other: $other_ty, old_len: usize) -> Result<BulkWriteOk, ()> {
+            let new_len = old_len.checked_add(other.len()).ok_or(())?;
+            let mut handle = unsafe {
+                self.bulk_write(new_len, old_len)?
+            };
+            $convert(other, &mut handle.as_mut_slice()[old_len..]);
+            Ok(handle.finish(new_len))
         }
      )
 }
 
 /// An intermediate check for avoiding a copy and having an `nsStringBuffer`
 /// refcount increment instead when both `self` and `other` are `nsACString`s,
 /// `other` is entirely ASCII and all old data in `self` is discarded.
 ///
 /// `$name` is the name of the function to generate
 /// `$impl` is the underlying conversion that takes a slice and that is used
 ///         when we can't just adopt the incoming buffer as-is
 /// `$string_like` is the kind of input taken
 macro_rules! ascii_copy_avoidance {
     ($name:ident,
      $impl:ident,
      $string_like:ident) => (
-        fn $name<T: $string_like + ?Sized>(&mut self, other: &T, old_len: usize) -> Result<(), ()> {
+        fn $name<T: $string_like + ?Sized>(&mut self, other: &T, old_len: usize) -> Result<BulkWriteOk, ()> {
             let adapter = other.adapt();
             let other_slice = adapter.as_ref();
             let num_ascii = if adapter.is_abstract() && old_len == 0 {
                 let up_to = Encoding::ascii_valid_up_to(other_slice);
                 if up_to == other_slice.len() {
                     // Calling something whose argument can be obtained from
                     // the adapter rather than an nsStringLike avoids a huge
                     // lifetime mess by keeping nsStringLike and
                     // Latin1StringLike free of lifetime interdependencies.
                     if unsafe { Gecko_FallibleAssignCString(self, other.adapt().as_ptr()) } {
-                        return Ok(());
+                        return Ok(BulkWriteOk{});
                     } else {
                         return Err(());
                     }
                 }
                 Some(up_to)
             } else {
                 None
             };
@@ -147,32 +135,32 @@ impl nsAString {
     pub fn assign_str(&mut self, other: &str) {
         self.fallible_append_str_impl(other, 0)
             .expect("Out of memory");
     }
 
     /// Convert a valid UTF-8 string into valid UTF-16 and fallibly replace the
     /// content of this string with the conversion result.
     pub fn fallible_assign_str(&mut self, other: &str) -> Result<(), ()> {
-        self.fallible_append_str_impl(other, 0)
+        self.fallible_append_str_impl(other, 0).map(|_| ())
     }
 
     /// Convert a valid UTF-8 string into valid UTF-16 and append the conversion
     /// to this string.
     pub fn append_str(&mut self, other: &str) {
         let len = self.len();
         self.fallible_append_str_impl(other, len)
             .expect("Out of memory");
     }
 
     /// Convert a valid UTF-8 string into valid UTF-16 and fallibly append the
     /// conversion to this string.
     pub fn fallible_append_str(&mut self, other: &str) -> Result<(), ()> {
         let len = self.len();
-        self.fallible_append_str_impl(other, len)
+        self.fallible_append_str_impl(other, len).map(|_| ())
     }
 
     // Potentially-invalid UTF-8 to UTF-16
 
     // Documentation says the destination buffer needs to have
     // one more code unit than the input.
     shrinking_conversion!(
         fallible_append_utf8_impl,
@@ -188,34 +176,34 @@ impl nsAString {
         self.fallible_append_utf8_impl(other, 0)
             .expect("Out of memory");
     }
 
     /// Convert a potentially-invalid UTF-8 string into valid UTF-16
     /// (replacing invalid sequences with the REPLACEMENT CHARACTER) and
     /// fallibly replace the content of this string with the conversion result.
     pub fn fallible_assign_utf8(&mut self, other: &[u8]) -> Result<(), ()> {
-        self.fallible_append_utf8_impl(other, 0)
+        self.fallible_append_utf8_impl(other, 0).map(|_| ())
     }
 
     /// Convert a potentially-invalid UTF-8 string into valid UTF-16
     /// (replacing invalid sequences with the REPLACEMENT CHARACTER) and
     /// append the conversion result to this string.
     pub fn append_utf8(&mut self, other: &[u8]) {
         let len = self.len();
         self.fallible_append_utf8_impl(other, len)
             .expect("Out of memory");
     }
 
     /// Convert a potentially-invalid UTF-8 string into valid UTF-16
     /// (replacing invalid sequences with the REPLACEMENT CHARACTER) and
     /// fallibly append the conversion result to this string.
     pub fn fallible_append_utf8(&mut self, other: &[u8]) -> Result<(), ()> {
         let len = self.len();
-        self.fallible_append_utf8_impl(other, len)
+        self.fallible_append_utf8_impl(other, len).map(|_| ())
     }
 
     // Latin1 to UTF-16
 
     constant_conversion!(fallible_append_latin1_impl, convert_latin1_to_utf16, &[u8]);
 
     /// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!)
     /// into UTF-16 and replace the content of this string with the conversion result.
@@ -223,119 +211,110 @@ impl nsAString {
         self.fallible_append_latin1_impl(other, 0)
             .expect("Out of memory");
     }
 
     /// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!)
     /// into UTF-16 and fallibly replace the content of this string with the
     /// conversion result.
     pub fn fallible_assign_latin1(&mut self, other: &[u8]) -> Result<(), ()> {
-        self.fallible_append_latin1_impl(other, 0)
+        self.fallible_append_latin1_impl(other, 0).map(|_| ())
     }
 
     /// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!)
     /// into UTF-16 and append the conversion result to this string.
     pub fn append_latin1(&mut self, other: &[u8]) {
         let len = self.len();
         self.fallible_append_latin1_impl(other, len)
             .expect("Out of memory");
     }
 
     /// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!)
     /// into UTF-16 and fallibly append the conversion result to this string.
     pub fn fallible_append_latin1(&mut self, other: &[u8]) -> Result<(), ()> {
         let len = self.len();
-        self.fallible_append_latin1_impl(other, len)
+        self.fallible_append_latin1_impl(other, len).map(|_| ())
     }
 }
 
 impl nsACString {
     // UTF-16 to UTF-8
 
     fn fallible_append_utf16_to_utf8_impl(
         &mut self,
         other: &[u16],
         old_len: usize,
-    ) -> Result<(), ()> {
+    ) -> Result<BulkWriteOk, ()> {
         // We first size the buffer for ASCII if the first code unit is ASCII. If that turns out not to
         // be enough, we size for the worst case given the length of the remaining input at that point.
-        // Lexical lifetimes make this a bit messy.
-        let mut written = 0;
-        {
-            if let Some(first) = other.first() {
-                let (needed, filled, num_ascii) = if *first < 0x80 {
-                    let buffer = unsafe {
-                        self.fallible_maybe_expand_capacity(old_len
-                            .checked_add(other.len())
-                            .ok_or(())?)?
-                    };
-                    let num_ascii = copy_basic_latin_to_ascii(other, &mut buffer[old_len..]);
-                    let filled = old_len + num_ascii;
-                    let available = buffer.len() - filled;
-                    let left = other.len() - num_ascii;
-                    let needed = times_three_plus_one(left).ok_or(())?;
-                    if needed <= available {
-                        written = num_ascii +
-                            convert_utf16_to_utf8(&other[num_ascii..], &mut buffer[filled..]);
-                        (0, 0, 0)
-                    } else {
-                        (needed, filled, num_ascii)
-                    }
-                } else {
-                    let needed = times_three_plus_one(other.len()).ok_or(())?;
-                    (needed, old_len, 0)
+        let (filled, num_ascii, mut handle) = if let Some(first) = other.first() {
+            if *first < 0x80 {
+                let new_len_with_ascii = old_len.checked_add(other.len()).ok_or(())?;
+                let mut handle = unsafe {
+                    self.bulk_write(new_len_with_ascii, old_len)?
                 };
-                if needed != 0 {
-                    let buffer = unsafe {
-                        self.fallible_maybe_expand_capacity(filled.checked_add(needed).ok_or(())?)?
-                    };
-                    written = num_ascii +
-                        convert_utf16_to_utf8(&other[num_ascii..], &mut buffer[filled..]);
+                let num_ascii = copy_basic_latin_to_ascii(other, &mut handle.as_mut_slice()[old_len..]);
+                let left = other.len() - num_ascii;
+                if left == 0 {
+                    return Ok(handle.finish(old_len + num_ascii));
                 }
+                let filled = old_len + num_ascii;
+                let needed = times_three_plus_one(left).ok_or(())?;
+                let new_len = filled.checked_add(needed).ok_or(())?;
+                unsafe {
+                    handle.restart_bulk_write(new_len, filled)?;
+                }
+                (filled, num_ascii, handle)
             } else {
-                return Ok(());
+                // Started with non-ASCII. Compute worst case
+                let needed = times_three_plus_one(other.len()).ok_or(())?;
+                let new_len = old_len.checked_add(needed).ok_or(())?;
+                let mut handle = unsafe {
+                    self.bulk_write(new_len, old_len)?
+                };
+                (old_len, 0, handle)
             }
-        }
-        unsafe {
-            // TODO: Shrink buffer
-            self.fallible_set_length((old_len + written) as u32)?;
-        }
-        Ok(())
+        } else {
+            // Other was zero-length slice
+            return Ok(BulkWriteOk{});
+        };
+        let written = convert_utf16_to_utf8(&other[num_ascii..], &mut handle.as_mut_slice()[filled..]);
+        Ok(handle.finish(filled + written))
     }
 
     /// Convert a potentially-invalid UTF-16 string into valid UTF-8
     /// (replacing invalid sequences with the REPLACEMENT CHARACTER) and
     /// replace the content of this string with the conversion result.
     pub fn assign_utf16_to_utf8(&mut self, other: &[u16]) {
         self.fallible_append_utf16_to_utf8_impl(other, 0)
             .expect("Out of memory");
     }
 
     /// Convert a potentially-invalid UTF-16 string into valid UTF-8
     /// (replacing invalid sequences with the REPLACEMENT CHARACTER) and
     /// fallibly replace the content of this string with the conversion result.
     pub fn fallible_assign_utf16_to_utf8(&mut self, other: &[u16]) -> Result<(), ()> {
-        self.fallible_append_utf16_to_utf8_impl(other, 0)
+        self.fallible_append_utf16_to_utf8_impl(other, 0).map(|_| ())
     }
 
     /// Convert a potentially-invalid UTF-16 string into valid UTF-8
     /// (replacing invalid sequences with the REPLACEMENT CHARACTER) and
     /// append the conversion result to this string.
     pub fn append_utf16_to_utf8(&mut self, other: &[u16]) {
         let len = self.len();
         self.fallible_append_utf16_to_utf8_impl(other, len)
             .expect("Out of memory");
     }
 
     /// Convert a potentially-invalid UTF-16 string into valid UTF-8
     /// (replacing invalid sequences with the REPLACEMENT CHARACTER) and
     /// fallibly append the conversion result to this string.
     pub fn fallible_append_utf16_to_utf8(&mut self, other: &[u16]) -> Result<(), ()> {
         let len = self.len();
-        self.fallible_append_utf16_to_utf8_impl(other, len)
+        self.fallible_append_utf16_to_utf8_impl(other, len).map(|_| ())
     }
 
     // UTF-16 to Latin1
 
     constant_conversion!(
         fallible_append_utf16_to_latin1_lossy_impl,
         convert_utf16_to_latin1_lossy,
         &[u16]
@@ -362,17 +341,17 @@ impl nsACString {
     ///
     /// # Panics
     ///
     /// If the input contains code points above U+00FF or is not valid UTF-16,
     /// panics in debug mode and produces garbage in a memory-safe way in
     /// release builds. The nature of the garbage may differ based on CPU
     /// architecture and must not be relied upon.
     pub fn fallible_assign_utf16_to_latin1_lossy(&mut self, other: &[u16]) -> Result<(), ()> {
-        self.fallible_append_utf16_to_latin1_lossy_impl(other, 0)
+        self.fallible_append_utf16_to_latin1_lossy_impl(other, 0).map(|_| ())
     }
 
     /// Convert a UTF-16 string whose all code points are below U+0100 into
     /// a Latin1 (scalar value is byte value; not windows-1252!) string and
     /// append the conversion result to this string.
     ///
     /// # Panics
     ///
@@ -393,50 +372,48 @@ impl nsACString {
     /// # Panics
     ///
     /// If the input contains code points above U+00FF or is not valid UTF-16,
     /// panics in debug mode and produces garbage in a memory-safe way in
     /// release builds. The nature of the garbage may differ based on CPU
     /// architecture and must not be relied upon.
     pub fn fallible_append_utf16_to_latin1_lossy(&mut self, other: &[u16]) -> Result<(), ()> {
         let len = self.len();
-        self.fallible_append_utf16_to_latin1_lossy_impl(other, len)
+        self.fallible_append_utf16_to_latin1_lossy_impl(other, len).map(|_| ())
     }
 
     // UTF-8 to Latin1
 
     ascii_copy_avoidance!(
         fallible_append_utf8_to_latin1_lossy_check,
         fallible_append_utf8_to_latin1_lossy_impl,
         nsCStringLike
     );
 
     fn fallible_append_utf8_to_latin1_lossy_impl(
         &mut self,
         other: &[u8],
         old_len: usize,
         maybe_num_ascii: Option<usize>,
-    ) -> Result<(), ()> {
+    ) -> Result<BulkWriteOk, ()> {
+        let new_len = old_len.checked_add(other.len()).ok_or(())?;
         let num_ascii = maybe_num_ascii.unwrap_or(0);
-        // This may overflow, but if overflow happens here, an overflow also happens where checked.
+        // Already checked for overflow above, so this can't overflow.
         let old_len_plus_num_ascii = old_len + num_ascii;
+        let mut handle = unsafe {
+            self.bulk_write(new_len, old_len)?
+        };
         let written = {
-            let buffer = unsafe {
-                self.fallible_maybe_expand_capacity(old_len.checked_add(other.len()).ok_or(())?)?
-            };
+            let buffer = handle.as_mut_slice();
             if num_ascii != 0 {
                 (&mut buffer[old_len..old_len_plus_num_ascii]).copy_from_slice(&other[..num_ascii]);
             }
             convert_utf8_to_latin1_lossy(&other[num_ascii..], &mut buffer[old_len_plus_num_ascii..])
         };
-        unsafe {
-            // TODO: Shrink buffer
-            self.fallible_set_length((old_len_plus_num_ascii + written) as u32)?;
-        }
-        Ok(())
+        Ok(handle.finish(old_len_plus_num_ascii + written)) // TODO: Shrink buffer
     }
 
     /// Convert a UTF-8 string whose all code points are below U+0100 into
     /// a Latin1 (scalar value is byte value; not windows-1252!) string and
     /// replace the content of this string with the conversion result.
     ///
     /// # Panics
     ///
@@ -458,17 +435,17 @@ impl nsACString {
     /// If the input contains code points above U+00FF or is not valid UTF-8,
     /// panics in debug mode and produces garbage in a memory-safe way in
     /// release builds. The nature of the garbage may differ based on CPU
     /// architecture and must not be relied upon.
     pub fn fallible_assign_utf8_to_latin1_lossy<T: nsCStringLike + ?Sized>(
         &mut self,
         other: &T,
     ) -> Result<(), ()> {
-        self.fallible_append_utf8_to_latin1_lossy_check(other, 0)
+        self.fallible_append_utf8_to_latin1_lossy_check(other, 0).map(|_| ())
     }
 
     /// Convert a UTF-8 string whose all code points are below U+0100 into
     /// a Latin1 (scalar value is byte value; not windows-1252!) string and
     /// append the conversion result to this string.
     ///
     /// # Panics
     ///
@@ -492,94 +469,100 @@ impl nsACString {
     /// panics in debug mode and produces garbage in a memory-safe way in
     /// release builds. The nature of the garbage may differ based on CPU
     /// architecture and must not be relied upon.
     pub fn fallible_append_utf8_to_latin1_lossy<T: nsCStringLike + ?Sized>(
         &mut self,
         other: &T,
     ) -> Result<(), ()> {
         let len = self.len();
-        self.fallible_append_utf8_to_latin1_lossy_check(other, len)
+        self.fallible_append_utf8_to_latin1_lossy_check(other, len).map(|_| ())
     }
 
     // Latin1 to UTF-8 CString
 
     ascii_copy_avoidance!(
         fallible_append_latin1_to_utf8_check,
         fallible_append_latin1_to_utf8_impl,
         Latin1StringLike
     );
 
     fn fallible_append_latin1_to_utf8_impl(
         &mut self,
         other: &[u8],
         old_len: usize,
         maybe_num_ascii: Option<usize>,
-    ) -> Result<(), ()> {
-        // We first size the buffer for ASCII. If that turns out not to be enough, we size for the worst
-        // case given the length of the remaining input at that point. Lexical lifetimes make this a bit
-        // messy.
-        let mut written = 0;
-        {
-            let (needed, filled, num_ascii) = {
-                if let Some(num_ascii) = maybe_num_ascii {
-                    let filled = old_len + num_ascii;
-                    let left = other.len() - num_ascii;
-                    let needed = left.checked_mul(2).ok_or(())?;
-                    (needed, filled, num_ascii)
-                } else {
-                    let buffer = unsafe {
-                        self.fallible_maybe_expand_capacity(old_len
-                            .checked_add(other.len())
-                            .ok_or(())?)?
-                    };
-                    let num_ascii = copy_ascii_to_ascii(other, &mut buffer[old_len..]);
-                    let filled = old_len + num_ascii;
-                    let available = buffer.len() - filled;
-                    let left = other.len() - num_ascii;
-                    let needed = left.checked_mul(2).ok_or(())?;
-                    if needed <= available {
-                        written = num_ascii +
-                            convert_latin1_to_utf8(&other[num_ascii..], &mut buffer[filled..]);
-                        (0, 0, 0)
-                    } else {
-                        (needed, filled, num_ascii)
-                    }
+    ) -> Result<BulkWriteOk, ()> {
+        let (filled, num_ascii, mut handle) = if let Some(num_ascii) = maybe_num_ascii {
+            // Wrapper checked for ASCII
+            let left = other.len() - num_ascii;
+            let filled = old_len + num_ascii;
+            let needed = left.checked_mul(2).ok_or(())?;
+            let new_len = filled.checked_add(needed).ok_or(())?;
+            let mut handle = unsafe {
+                self.bulk_write(new_len, old_len)?
+            };
+            if num_ascii != 0 {
+                (&mut handle.as_mut_slice()[old_len..filled]).copy_from_slice(&other[..num_ascii]);
+            }
+            (filled, num_ascii, handle)
+        } else if let Some(first) = other.first() {
+            // Wrapper didn't check for ASCII, so let's see if `other` starts with ASCII
+            if *first < 0x80 {
+                // `other` starts with ASCII, so let's first size the buffer
+                // with optimism that it's ASCII-only.
+                let new_len_with_ascii = old_len.checked_add(other.len()).ok_or(())?;
+                let mut handle = unsafe {
+                    self.bulk_write(new_len_with_ascii, old_len)?
+                };
+                let num_ascii = copy_ascii_to_ascii(other, &mut handle.as_mut_slice()[old_len..]);
+                let left = other.len() - num_ascii;
+                let filled = old_len + num_ascii;
+                if left == 0 {
+                    // `other` was all ASCII
+                    return Ok(handle.finish(filled));
                 }
-            };
-            if needed != 0 {
-                let buffer = unsafe {
-                    self.fallible_maybe_expand_capacity(filled.checked_add(needed).ok_or(())?)?
+                let needed = left.checked_mul(2).ok_or(())?;
+                let new_len = filled.checked_add(needed).ok_or(())?;
+                unsafe {
+                    handle.restart_bulk_write(new_len, filled)?;
+                }
+                (filled, num_ascii, handle)
+            } else {
+                // Started with non-ASCII. Assume worst case.
+                let needed = other.len().checked_mul(2).ok_or(())?;
+                let new_len = old_len.checked_add(needed).ok_or(())?;
+                let mut handle = unsafe {
+                    self.bulk_write(new_len, old_len)?
                 };
-                written =
-                    num_ascii + convert_latin1_to_utf8(&other[num_ascii..], &mut buffer[filled..]);
+                (old_len, 0, handle)
             }
-        }
-        unsafe {
-            // TODO: Shrink buffer
-            self.fallible_set_length((old_len + written) as u32)?;
-        }
-        Ok(())
+        } else {
+            // Other was zero-length slice
+            return Ok(BulkWriteOk{});
+        };
+        let written = convert_latin1_to_utf8(&other[num_ascii..], &mut handle.as_mut_slice()[filled..]);
+        Ok(handle.finish(filled + written))
     }
 
     /// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!)
     /// into UTF-8 and replace the content of this string with the conversion result.
     pub fn assign_latin1_to_utf8<T: Latin1StringLike + ?Sized>(&mut self, other: &T) {
         self.fallible_append_latin1_to_utf8_check(other, 0)
             .expect("Out of memory");
     }
 
     /// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!)
     /// into UTF-8 and fallibly replace the content of this string with the
     /// conversion result.
     pub fn fallible_assign_latin1_to_utf8<T: Latin1StringLike + ?Sized>(
         &mut self,
         other: &T,
     ) -> Result<(), ()> {
-        self.fallible_append_latin1_to_utf8_check(other, 0)
+        self.fallible_append_latin1_to_utf8_check(other, 0).map(|_| ())
     }
 
     /// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!)
     /// into UTF-8 and append the conversion result to this string.
     pub fn append_latin1_to_utf8<T: Latin1StringLike + ?Sized>(&mut self, other: &T) {
         let len = self.len();
         self.fallible_append_latin1_to_utf8_check(other, len)
             .expect("Out of memory");
@@ -587,17 +570,17 @@ impl nsACString {
 
     /// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!)
     /// into UTF-8 and fallibly append the conversion result to this string.
     pub fn fallible_append_latin1_to_utf8<T: Latin1StringLike + ?Sized>(
         &mut self,
         other: &T,
     ) -> Result<(), ()> {
         let len = self.len();
-        self.fallible_append_latin1_to_utf8_check(other, len)
+        self.fallible_append_latin1_to_utf8_check(other, len).map(|_| ())
     }
 }
 
 #[no_mangle]
 pub unsafe extern "C" fn nsstring_fallible_append_utf8_impl(
     this: *mut nsAString,
     other: *const u8,
     other_len: usize,
--- a/servo/support/gecko/nsstring/src/lib.rs
+++ b/servo/support/gecko/nsstring/src/lib.rs
@@ -132,16 +132,23 @@ mod conversions;
 
 pub use self::conversions::nsstring_fallible_append_utf8_impl;
 pub use self::conversions::nsstring_fallible_append_latin1_impl;
 pub use self::conversions::nscstring_fallible_append_utf16_to_utf8_impl;
 pub use self::conversions::nscstring_fallible_append_utf16_to_latin1_lossy_impl;
 pub use self::conversions::nscstring_fallible_append_utf8_to_latin1_lossy_check;
 pub use self::conversions::nscstring_fallible_append_latin1_to_utf8_check;
 
+/// A type for showing that `finish()` was called on a `BulkWriteHandle`.
+/// Instantiating this type from elsewhere is basically an assertion that
+/// there is no `BulkWriteHandle` around, so be very careful with instantiating
+/// this type!
+pub struct BulkWriteOk {
+}
+
 ///////////////////////////////////
 // Internal Implementation Flags //
 ///////////////////////////////////
 
 mod data_flags {
     bitflags! {
         // While this has the same layout as u16, it cannot be passed
         // over FFI safely as a u16.
@@ -247,61 +254,122 @@ macro_rules! string_like {
         impl $StringLike for Box<[$char_t]> {
             fn adapt(&self) -> $StringAdapter {
                 $StringAdapter::Borrowed($Str::from(&self[..]))
             }
         }
     }
 }
 
+impl<'a> Drop for nsAStringBulkWriteHandle<'a> {
+    /// This only runs in error cases. In success cases, `finish()`
+    /// calles `forget(self)`.
+    fn drop(&mut self) {
+        assert_ne!(self.capacity, 0);
+        // Zero-length strings are special, so we can't make one
+        // that doesn't follow the zero-length rules.
+        // The old zero terminator may be gone by now, so we need
+        // to write a new one somewhere and make length match.
+        // We can use a length between 1 and self.capacity.
+        // Seems prudent to overwrite the uninitialized memory.
+        // Using the length 1 leaves the shortest memory to overwrite.
+        // U+FFFD is the safest placeholder.
+        unsafe {
+            // The borrow checker doesn't allow a reference to reference
+            // transmute, so let's use pointers.
+            let string_ptr = self.string as *mut nsAString;
+            let this: *mut nsStringRepr = mem::transmute(string_ptr);
+            (*this).length = 1u32;
+            *(*this).data = 0xFFFDu16;
+            *((*this).data.offset(1isize)) = 0;
+        }
+    }
+}
+
+impl<'a> Drop for nsACStringBulkWriteHandle<'a> {
+    /// This only runs in error cases. In success cases, `finish()`
+    /// calles `forget(self)`.
+    fn drop(&mut self) {
+        assert_ne!(self.capacity, 0);
+        // Zero-length strings are special, so we can't make one
+        // that doesn't follow the zero-length rules.
+        // The old zero terminator may be gone by now, so we need
+        // to write a new one somewhere and make length match.
+        // We can use a length between 1 and self.capacity.
+        // Seems prudent to overwrite the uninitialized memory.
+        // Using the length 1 leaves the shortest memory to overwrite.
+        // U+FFFD is the safest placeholder, but when it doesn't fit,
+        // let's use ASCII substitute.
+        unsafe {
+            // The borrow checker doesn't allow a reference to reference
+            // transmute, so let's use pointers.
+            let string_ptr = self.string as *mut nsACString;
+            let this: *mut nsCStringRepr = mem::transmute(string_ptr);
+            if self.capacity >= 3 {
+                (*this).length = 3u32;
+                *(*this).data = 0xEFu8; // U+FFFD doesn't fit
+                *((*this).data.offset(1isize)) = 0xBFu8;
+                *((*this).data.offset(2isize)) = 0xBDu8;
+                *((*this).data.offset(3isize)) = 0;
+            } else {
+                (*this).length = 1u32;
+                *(*this).data = 0x1Au8; // U+FFFD doesn't fit
+                *((*this).data.offset(1isize)) = 0;
+            }
+        }
+    }
+}
+
 macro_rules! define_string_types {
     {
         char_t = $char_t: ty;
 
         AString = $AString: ident;
         String = $String: ident;
         Str = $Str: ident;
 
         StringLike = $StringLike: ident;
         StringAdapter = $StringAdapter: ident;
 
         StringRepr = $StringRepr: ident;
 
+        BulkWriteHandle = $BulkWriteHandle: ident;
+
         drop = $drop: ident;
         assign = $assign: ident, $fallible_assign: ident;
         take_from = $take_from: ident, $fallible_take_from: ident;
         append = $append: ident, $fallible_append: ident;
         set_length = $set_length: ident, $fallible_set_length: ident;
         begin_writing = $begin_writing: ident, $fallible_begin_writing: ident;
-        fallible_maybe_expand_capacity = $fallible_maybe_expand_capacity: ident;
+        start_bulk_write = $start_bulk_write: ident;
     } => {
         /// The representation of a ns[C]String type in C++. This type is
         /// used internally by our definition of ns[C]String to ensure layout
         /// compatibility with the C++ ns[C]String type.
         ///
         /// This type may also be used in place of a C++ ns[C]String inside of
         /// struct definitions which are shared with C++, as it has identical
         /// layout to our ns[C]String type.
         ///
         /// This struct will leak its data if dropped from rust. See the module
         /// documentation for more information on this type.
         #[repr(C)]
         #[derive(Debug)]
         pub struct $StringRepr {
-            data: *const $char_t,
+            data: *mut $char_t,
             length: u32,
             dataflags: DataFlags,
             classflags: ClassFlags,
         }
 
         impl $StringRepr {
             fn new(classflags: ClassFlags) -> $StringRepr {
                 static NUL: $char_t = 0;
                 $StringRepr {
-                    data: &NUL,
+                    data: unsafe { mem::transmute(&NUL as *const $char_t) },
                     length: 0,
                     dataflags: DataFlags::TERMINATED | DataFlags::LITERAL,
                     classflags: classflags,
                 }
             }
         }
 
         impl Deref for $StringRepr {
@@ -316,16 +384,66 @@ macro_rules! define_string_types {
         impl DerefMut for $StringRepr {
             fn deref_mut(&mut self) -> &mut $AString {
                 unsafe {
                     mem::transmute(self)
                 }
             }
         }
 
+        pub struct $BulkWriteHandle<'a> {
+            string: &'a mut $AString,
+            capacity: usize,
+        }
+
+        impl<'a> $BulkWriteHandle<'a> {
+            fn new(string: &'a mut $AString, capacity: usize) -> Self {
+                $BulkWriteHandle{ string: string, capacity: capacity }
+            }
+
+            pub unsafe fn restart_bulk_write(&mut self, capacity: usize, units_to_preserve: usize) -> Result<(), ()> {
+                self.capacity = self.string.start_bulk_write_impl(capacity, units_to_preserve)?;
+                Ok(())
+            }
+
+            pub fn finish(self, length: usize) -> BulkWriteOk {
+                // NOTE: Drop is implemented outside the macro earlier in this file
+                assert!(length <= self.capacity);
+                if self.capacity == 0 {
+                    mem::forget(self); // Don't run the failure path in drop()
+                    return BulkWriteOk{};
+                }
+                unsafe {
+                    // The borrow checker doesn't allow a reference to reference
+                    // transmute, so let's use pointers.
+                    let string_ptr = self.string as *mut $AString;
+                    let this: *mut $StringRepr = mem::transmute(string_ptr);
+                    (*this).length = length as u32;
+                    *((*this).data.offset(length as isize)) = 0;
+                }
+                mem::forget(self); // Don't run the failure path in drop()
+                BulkWriteOk{}
+            }
+
+            pub fn as_mut_slice(&mut self) -> &mut [$char_t] {
+                unsafe {
+                    let ptr = if self.capacity == 0 {
+                        ::std::ptr::NonNull::<$char_t>::dangling().as_ptr()
+                    } else {
+                        // The borrow checker doesn't allow a reference to reference
+                        // transmute, so let's use pointers.
+                        let string_ptr = self.string as *mut $AString;
+                        let this: *mut $StringRepr = mem::transmute(string_ptr);
+                        (*this).data
+                    };
+                    ::std::slice::from_raw_parts_mut(ptr, self.capacity)
+                }
+            }
+        }
+
         /// This type is the abstract type which is used for interacting with
         /// strings in rust. Each string type can derefence to an instance of
         /// this type, which provides the useful operations on strings.
         ///
         /// NOTE: Rust thinks this type has a size of 0, because the data
         /// associated with it is not necessarially safe to move. It is not safe
         /// to construct a nsAString yourself, unless it is received by
         /// dereferencing one of these types.
@@ -456,41 +574,46 @@ macro_rules! define_string_types {
                             Err(())
                         } else {
                             Ok(slice::from_raw_parts_mut(ptr, len))
                         }
                     }
                 }
             }
 
+            pub unsafe fn bulk_write(&mut self, capacity: usize, units_to_preserve: usize) -> Result<$BulkWriteHandle, ()> {
+                let capacity = self.start_bulk_write_impl(capacity, units_to_preserve)?;
+                Ok($BulkWriteHandle::new(self, capacity))
+            }
+
             /// Unshares the buffer of the string, sets the capacity to the
             /// allocation size resulting from rounding up `len`. Set the
             /// length of the string to the rounded-up capacity and returns
             /// the buffer as a mutable slice.
             ///
             /// Fails also if the new length doesn't fit in 32 bits.
             ///
             /// # Safety
             ///
             /// Unsafe because of exposure of uninitialized memory.
-            unsafe fn fallible_maybe_expand_capacity(&mut self, len: usize) -> Result<&mut [$char_t], ()> {
-                if len == 0 {
+            unsafe fn start_bulk_write_impl(&mut self, capacity: usize, units_to_preserve: usize) -> Result<usize, ()> {
+                debug_assert!(units_to_preserve <= capacity);
+                if capacity == 0 {
                     self.fallible_set_length(0)?;
-                    // Use an arbitrary non-null value as the pointer
-                    Ok(slice::from_raw_parts_mut(0x1 as *mut $char_t, 0))
-                } else if len > u32::max_value() as usize {
+                    Ok(0)
+                } else if capacity > u32::max_value() as usize {
                     Err(())
                 } else {
-                    let mut len32 = len as u32;
-                    let ptr = $fallible_maybe_expand_capacity(self, &mut len32);
-                    if ptr.is_null() {
-                        Err(())
-                    } else {
-                        Ok(slice::from_raw_parts_mut(ptr, len32 as usize))
+                    let capacity32 = capacity as u32;
+                    let rounded = $start_bulk_write(self, capacity32, units_to_preserve as u32);
+                    if rounded == u32::max_value() {
+                        return Err(())
                     }
+                    debug_assert_ne!(rounded, 0);
+                    Ok(rounded as usize)
                 }
             }
         }
 
         impl Deref for $AString {
             type Target = [$char_t];
             fn deref(&self) -> &[$char_t] {
                 unsafe {
@@ -585,17 +708,17 @@ macro_rules! define_string_types {
         impl<'a> From<&'a [$char_t]> for $Str<'a> {
             fn from(s: &'a [$char_t]) -> $Str<'a> {
                 assert!(s.len() < (u32::MAX as usize));
                 if s.is_empty() {
                     return $Str::new();
                 }
                 $Str {
                     hdr: $StringRepr {
-                        data: s.as_ptr(),
+                        data: unsafe { ::mem::transmute(s.as_ptr()) },
                         length: s.len() as u32,
                         dataflags: DataFlags::empty(),
                         classflags: ClassFlags::empty(),
                     },
                     _marker: PhantomData,
                 }
             }
         }
@@ -742,17 +865,17 @@ macro_rules! define_string_types {
                 let length = s.len() as u32;
                 s.push(0); // null terminator
 
                 // SAFETY NOTE: This method produces an data_flags::OWNED
                 // ns[C]String from a Box<[$char_t]>. this is only safe
                 // because in the Gecko tree, we use the same allocator for
                 // Rust code as for C++ code, meaning that our box can be
                 // legally freed with libc::free().
-                let ptr = s.as_ptr();
+                let ptr = s.as_mut_ptr();
                 mem::forget(s);
                 unsafe {
                     Gecko_IncrementStringAdoptCount(ptr as *mut _);
                 }
                 $String {
                     hdr: $StringRepr {
                         data: ptr,
                         length: length,
@@ -870,23 +993,25 @@ define_string_types! {
     String = nsCString;
     Str = nsCStr;
 
     StringLike = nsCStringLike;
     StringAdapter = nsCStringAdapter;
 
     StringRepr = nsCStringRepr;
 
+    BulkWriteHandle = nsACStringBulkWriteHandle;
+
     drop = Gecko_FinalizeCString;
     assign = Gecko_AssignCString, Gecko_FallibleAssignCString;
     take_from = Gecko_TakeFromCString, Gecko_FallibleTakeFromCString;
     append = Gecko_AppendCString, Gecko_FallibleAppendCString;
     set_length = Gecko_SetLengthCString, Gecko_FallibleSetLengthCString;
     begin_writing = Gecko_BeginWritingCString, Gecko_FallibleBeginWritingCString;
-    fallible_maybe_expand_capacity = Gecko_FallibleMaybeExpandCapacityCString;
+    start_bulk_write = Gecko_StartBulkWriteCString;
 }
 
 impl nsACString {
     pub unsafe fn as_str_unchecked(&self) -> &str {
         str::from_utf8_unchecked(self)
     }
 }
 
@@ -998,23 +1123,25 @@ define_string_types! {
     String = nsString;
     Str = nsStr;
 
     StringLike = nsStringLike;
     StringAdapter = nsStringAdapter;
 
     StringRepr = nsStringRepr;
 
+    BulkWriteHandle = nsAStringBulkWriteHandle;
+
     drop = Gecko_FinalizeString;
     assign = Gecko_AssignString, Gecko_FallibleAssignString;
     take_from = Gecko_TakeFromString, Gecko_FallibleTakeFromString;
     append = Gecko_AppendString, Gecko_FallibleAppendString;
     set_length = Gecko_SetLengthString, Gecko_FallibleSetLengthString;
     begin_writing = Gecko_BeginWritingString, Gecko_FallibleBeginWritingString;
-    fallible_maybe_expand_capacity = Gecko_FallibleMaybeExpandCapacityString;
+    start_bulk_write = Gecko_StartBulkWriteString;
 }
 
 // NOTE: The From impl for a string slice for nsString produces a <'static>
 // lifetime, as it allocates.
 impl<'a> From<&'a str> for nsString {
     fn from(s: &'a str) -> nsString {
         s.encode_utf16().collect::<Vec<u16>>().into()
     }
@@ -1070,31 +1197,31 @@ extern "C" {
     fn Gecko_AppendCString(this: *mut nsACString, other: *const nsACString);
     fn Gecko_SetLengthCString(this: *mut nsACString, length: u32);
     fn Gecko_BeginWritingCString(this: *mut nsACString) -> *mut u8;
     fn Gecko_FallibleAssignCString(this: *mut nsACString, other: *const nsACString) -> bool;
     fn Gecko_FallibleTakeFromCString(this: *mut nsACString, other: *mut nsACString) -> bool;
     fn Gecko_FallibleAppendCString(this: *mut nsACString, other: *const nsACString) -> bool;
     fn Gecko_FallibleSetLengthCString(this: *mut nsACString, length: u32) -> bool;
     fn Gecko_FallibleBeginWritingCString(this: *mut nsACString) -> *mut u8;
-    fn Gecko_FallibleMaybeExpandCapacityCString(this: *mut nsACString, length: *mut u32) -> *mut u8;
+    fn Gecko_StartBulkWriteCString(this: *mut nsACString, capacity: u32, units_to_preserve: u32) -> u32;
 
     fn Gecko_FinalizeString(this: *mut nsAString);
 
     fn Gecko_AssignString(this: *mut nsAString, other: *const nsAString);
     fn Gecko_TakeFromString(this: *mut nsAString, other: *mut nsAString);
     fn Gecko_AppendString(this: *mut nsAString, other: *const nsAString);
     fn Gecko_SetLengthString(this: *mut nsAString, length: u32);
     fn Gecko_BeginWritingString(this: *mut nsAString) -> *mut u16;
     fn Gecko_FallibleAssignString(this: *mut nsAString, other: *const nsAString) -> bool;
     fn Gecko_FallibleTakeFromString(this: *mut nsAString, other: *mut nsAString) -> bool;
     fn Gecko_FallibleAppendString(this: *mut nsAString, other: *const nsAString) -> bool;
     fn Gecko_FallibleSetLengthString(this: *mut nsAString, length: u32) -> bool;
     fn Gecko_FallibleBeginWritingString(this: *mut nsAString) -> *mut u16;
-    fn Gecko_FallibleMaybeExpandCapacityString(this: *mut nsAString, length: *mut u32) -> *mut u16;
+    fn Gecko_StartBulkWriteString(this: *mut nsAString, capacity: u32, units_to_preserve: u32) -> u32;
 }
 
 //////////////////////////////////////
 // Repr Validation Helper Functions //
 //////////////////////////////////////
 
 pub mod test_helpers {
     //! This module only exists to help with ensuring that the layout of the
--- a/xpcom/string/nsSubstring.cpp
+++ b/xpcom/string/nsSubstring.cpp
@@ -450,19 +450,19 @@ char* Gecko_BeginWritingCString(nsACStri
   return aThis->BeginWriting();
 }
 
 char* Gecko_FallibleBeginWritingCString(nsACString* aThis)
 {
   return aThis->BeginWriting(mozilla::fallible);
 }
 
-char* Gecko_FallibleMaybeExpandCapacityCString(nsACString* aThis, uint32_t* aCapacity)
+uint32_t Gecko_StartBulkWriteCString(nsACString* aThis, uint32_t aCapacity, uint32_t aUnitsToPreserve)
 {
-  return aThis->MaybeExpandCapacity(aCapacity, mozilla::fallible);
+  return aThis->StartBulkWrite(aCapacity, aUnitsToPreserve);
 }
 
 void Gecko_FinalizeString(nsAString* aThis)
 {
   aThis->~nsAString();
 }
 
 void Gecko_AssignString(nsAString* aThis, const nsAString* aOther)
@@ -510,14 +510,14 @@ char16_t* Gecko_BeginWritingString(nsASt
   return aThis->BeginWriting();
 }
 
 char16_t* Gecko_FallibleBeginWritingString(nsAString* aThis)
 {
   return aThis->BeginWriting(mozilla::fallible);
 }
 
-char16_t* Gecko_FallibleMaybeExpandCapacityString(nsAString* aThis, uint32_t* aCapacity)
+uint32_t Gecko_StartBulkWriteString(nsAString* aThis, uint32_t aCapacity, uint32_t aUnitsToPreserve)
 {
-  return aThis->MaybeExpandCapacity(aCapacity, mozilla::fallible);
+  return aThis->StartBulkWrite(aCapacity, aUnitsToPreserve);
 }
 
 } // extern "C"
--- a/xpcom/string/nsTSubstring.cpp
+++ b/xpcom/string/nsTSubstring.cpp
@@ -42,16 +42,116 @@ nsTSubstring<T>::nsTSubstring(char_type*
  */
 template <typename T>
 inline const nsTAutoString<T>*
 AsAutoString(const nsTSubstring<T>* aStr)
 {
   return static_cast<const nsTAutoString<T>*>(aStr);
 }
 
+template <typename T>
+uint32_t
+nsTSubstring<T>::StartBulkWrite(size_type aCapacity, size_type aUnitsToPreserve)
+{
+  // capacity does not include room for the terminating null char
+  size_type wantedCapacity = aCapacity;
+  size_type curCapacity = Capacity();
+
+  if (MOZ_UNLIKELY(!wantedCapacity)) {
+    this->mDataFlags &= ~DataFlags::VOIDED;  // mutation clears voided flag
+    return 0;
+  }
+
+  // We've established that wantedCapacity > 0.
+  // |curCapacity == 0| means that the buffer is immutable or 0-sized, so we
+  // need to allocate a new buffer. We cannot use the existing buffer even
+  // though it might be large enough.
+
+  if (wantedCapacity <= curCapacity) {
+    return curCapacity;
+  }
+
+  // If |wantedCapacity > kMaxCapacity|, then our doubling algorithm may not be
+  // able to allocate it.  Just bail out in cases like that.  We don't want
+  // to be allocating 2GB+ strings anyway.
+  static_assert((sizeof(nsStringBuffer) & 0x1) == 0,
+                "bad size for nsStringBuffer");
+  if (MOZ_UNLIKELY(!CheckCapacity(wantedCapacity))) {
+      return UINT32_MAX;
+  }
+
+  // We increase our capacity so that the allocated buffer grows
+  // exponentially, which gives us amortized O(1) appending. Below the
+  // threshold, we use powers-of-two. Above the threshold, we grow by at
+  // least 1.125, rounding up to the nearest MiB.
+  const size_type slowGrowthThreshold = 8 * 1024 * 1024;
+
+  // nsStringBuffer allocates sizeof(nsStringBuffer) + passed size, and
+  // storageSize below wants extra 1 * sizeof(char_type).
+  const size_type neededExtraSpace =
+    sizeof(nsStringBuffer) / sizeof(char_type) + 1;
+
+  size_type temp;
+  if (wantedCapacity >= slowGrowthThreshold) {
+    size_type minNewCapacity = curCapacity + (curCapacity >> 3); // multiply by 1.125
+    temp = XPCOM_MAX(wantedCapacity, minNewCapacity) + neededExtraSpace;
+
+    // Round up to the next multiple of MiB, but ensure the expected
+    // capacity doesn't include the extra space required by nsStringBuffer
+    // and null-termination.
+    const size_t MiB = 1 << 20;
+    temp = (MiB * ((temp + MiB - 1) / MiB)) - neededExtraSpace;
+  } else {
+    // Round up to the next power of two.
+    temp =
+      mozilla::RoundUpPow2(aCapacity + neededExtraSpace) - neededExtraSpace;
+  }
+
+  size_type newCapacity = XPCOM_MIN(temp, kMaxCapacity);
+  MOZ_ASSERT(newCapacity >= wantedCapacity,
+             "should have hit the early return at the top");
+
+  // If the old buffer is a read-only heap buffer and this is an nsTAutoStringN,
+  // it's possible that we can use the inline buffer. Otherwise, we allocate an
+  // nsStringBuffer. We explicitly don't realloc an old nsStringBuffer in order
+  // to control what data is copied.
+
+  char_type* newData;
+  DataFlags newDataFlags;
+  if ((this->mClassFlags & ClassFlags::INLINE) &&
+      (newCapacity <= AsAutoString(this)->mInlineCapacity)) {
+    newCapacity = AsAutoString(this)->mInlineCapacity;
+    newData = (char_type*)AsAutoString(this)->mStorage;
+    newDataFlags = DataFlags::TERMINATED | DataFlags::INLINE;
+  } else {
+    size_type storageSize = (newCapacity + 1) * sizeof(char_type);
+    nsStringBuffer* newHdr =
+      nsStringBuffer::Alloc(storageSize).take();
+    if (!newHdr) {
+      return UINT32_MAX; // we are still in a consistent state
+    }
+
+    newData = (char_type*)newHdr->Data();
+    newDataFlags = DataFlags::TERMINATED | DataFlags::REFCOUNTED;
+  }
+
+  char_type* oldData = this->mData;
+  DataFlags oldFlags = this->mDataFlags;
+
+  this->mData = newData;
+  this->mDataFlags = newDataFlags;
+
+  if (oldData) {
+    char_traits::copy(this->mData, oldData, aUnitsToPreserve);
+    ::ReleaseData(oldData, oldFlags);
+  }
+
+  return newCapacity;
+}
+
 /**
  * this function is called to prepare mData for writing.  the given capacity
  * indicates the required minimum storage size for mData, in sizeof(char_type)
  * increments.  this function returns true if the operation succeeds.  it also
  * returns the old data and old flags members if mData is newly allocated.
  * the old data must be released by the caller.
  */
 template <typename T>
--- a/xpcom/string/nsTSubstring.h
+++ b/xpcom/string/nsTSubstring.h
@@ -590,45 +590,16 @@ public:
   }
 
 
   /**
    * buffer access
    */
 
   /**
-   * If *aCapacity is larger than the current capacity, allocates a
-   * buffer whose length is at least *aCapacity.
-   *
-   * Sets *aCapacity and the string's length to the actual capacity.
-   *
-   * Returns a pointer to the start of the buffer or nullptr if
-   * allocation failed.
-   *
-   * Note that unlike GetMutableData, this rounds the length up to the
-   * capacity.
-   */
-  inline char_type* MaybeExpandCapacity(size_type* aCapacity, const fallible_t& aFallible)
-  {
-    // SetCapacity unshares a shared buffer even then resizing is not
-    // needed.
-    if (!SetCapacity(*aCapacity, aFallible)) {
-      return nullptr;
-    }
-    size_type capacity = Capacity();
-    // SetCapacity doesn't stretch the logical length for us.
-    this->mLength = capacity;
-    *aCapacity = capacity;
-    char_type* ptr = base_string_type::mData;
-    // SetCapacity zero-terminated at intermediate length, not capacity.
-    ptr[capacity] = 0;
-    return ptr;
-  }
-
-  /**
    * Get a const pointer to the string's internal buffer.  The caller
    * MUST NOT modify the characters at the returned address.
    *
    * @returns The length of the buffer in characters.
    */
   inline size_type GetData(const char_type** aData) const
   {
     *aData = base_string_type::mData;
@@ -923,16 +894,54 @@ protected:
 
   /**
    * this function releases mData and does not change the value of
    * any of its member variables.  in other words, this function acts
    * like a destructor.
    */
   void NS_FASTCALL Finalize();
 
+public:
+  /**
+   * Prepares mData to be mutated such that the capacity of the string
+   * (not counting the zero-terminator) is at least aCapacity.
+   * Returns the actual capacity, which may be larger than what was
+   * requested or UINT32_MAX on allocation failure.
+   *
+   * mLength is ignored by this method. If the buffer is reallocated,
+   * aUnitsToPreserve specifies how many code units to copy over to
+   * the new buffer. The old buffer is freed if applicable.
+   *
+   * Unless the return value is UINT32_MAX to signal failure, this
+   * method the string in an invalid state! The caller is reponsible
+   * for zero-terminating the string and setting a valid mLength
+   * by calling FinishBulkWrite(). This method sets the flag to claim
+   * that the string is zero-terminated before it actually is.
+   *
+   * Once this method has been called and before FinishBulkWrite()
+   * has been called, only calls to Data() or this method again
+   * are valid. Do not call any other methods between calling this
+   * method and FinishBulkWrite().
+   */
+  uint32_t NS_FASTCALL StartBulkWrite(size_type aCapacity, size_type aUnitsToPreserve);
+
+  /**
+   * Restores the string to a valid state after a call to BulkWritePrep()
+   * that returned a non-UINT32_MAX value. The argument to this method
+   * must be less than or equal to the non-UINT32_MAX value returned by
+   * the most recent BulkWritePrep() call.
+   */
+  inline void FinishBulkWrite(size_type aLength) {
+    MOZ_ASSERT(aLength != UINT32_MAX, "OOM magic value passed as length.");
+    base_string_type::mData[aLength] = 0;
+    base_string_type::mLength = aLength;
+    AssertValid();
+  }
+
+protected:
   /**
    * this function prepares mData to be mutated.
    *
    * @param aCapacity    specifies the required capacity of mData
    * @param aOldData     returns null or the old value of mData
    * @param aOldFlags    returns 0 or the old value of mDataFlags
    *
    * if mData is already mutable and of sufficient capacity, then this