Bug 1487212 - When hyphenation resources are compressed in omnijar, load them into shared memory and share among all content processes. r=heycam,froydnj
authorJonathan Kew <jkew@mozilla.com>
Thu, 14 Nov 2019 20:05:58 +0000
changeset 502059 d519e5920a23a531269fec72a0ef440674692603
parent 502058 2556a28ab75a074ba8a7ef30e5360cb66a18e04c
child 502060 a21f7f0b362ef9cd81c2f8262d535c2e3396c4b4
push id114172
push userdluca@mozilla.com
push dateTue, 19 Nov 2019 11:31:10 +0000
treeherdermozilla-inbound@b5c5ba07d3db [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersheycam, froydnj
bugs1487212
milestone72.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1487212 - When hyphenation resources are compressed in omnijar, load them into shared memory and share among all content processes. r=heycam,froydnj Differential Revision: https://phabricator.services.mozilla.com/D52835
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PContent.ipdl
intl/hyphenation/glue/moz.build
intl/hyphenation/glue/nsHyphenationManager.cpp
intl/hyphenation/glue/nsHyphenationManager.h
intl/hyphenation/glue/nsHyphenator.cpp
intl/hyphenation/glue/nsHyphenator.h
ipc/ipdl/sync-messages.ini
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -229,16 +229,17 @@
 #include "nsICaptivePortalService.h"
 #include "nsIObjectLoadingContent.h"
 #include "nsIBidiKeyboard.h"
 #include "nsLayoutStylesheetCache.h"
 #include "MMPrinter.h"
 #include "nsStreamUtils.h"
 #include "nsIAsyncInputStream.h"
 #include "xpcpublic.h"
+#include "nsHyphenationManager.h"
 
 #include "mozilla/Sprintf.h"
 
 #ifdef MOZ_WEBRTC
 #  include "signaling/src/peerconnection/WebrtcGlobalParent.h"
 #endif
 
 #if defined(XP_MACOSX)
@@ -5119,16 +5120,28 @@ mozilla::ipc::IPCResult ContentParent::R
 mozilla::ipc::IPCResult ContentParent::RecvSetupFamilyCharMap(
     const uint32_t& aGeneration, const mozilla::fontlist::Pointer& aFamilyPtr) {
   auto fontList = gfxPlatformFontList::PlatformFontList();
   MOZ_RELEASE_ASSERT(fontList, "gfxPlatformFontList not initialized?");
   fontList->SetupFamilyCharMap(aGeneration, aFamilyPtr);
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult ContentParent::RecvGetHyphDict(
+    const mozilla::ipc::URIParams& aURI,
+    mozilla::ipc::SharedMemoryBasic::Handle* aOutHandle, uint32_t* aOutSize) {
+  nsCOMPtr<nsIURI> uri = DeserializeURI(aURI);
+  if (!uri) {
+    return IPC_FAIL_NO_REASON(this);
+  }
+  nsHyphenationManager::Instance()->ShareHyphDictToProcess(
+      uri, Pid(), aOutHandle, aOutSize);
+  return IPC_OK();
+}
+
 mozilla::ipc::IPCResult ContentParent::RecvGraphicsError(
     const nsCString& aError) {
   gfx::LogForwarder* lf = gfx::Factory::GetLogForwarder();
   if (lf) {
     std::stringstream message;
     message << "CP+" << aError.get();
     lf->UpdateStringsVector(message.str());
   }
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -1122,16 +1122,20 @@ class ContentParent final : public PCont
   mozilla::ipc::IPCResult RecvInitOtherFamilyNames(const uint32_t& aGeneration,
                                                    const bool& aDefer,
                                                    bool* aLoaded);
 
   mozilla::ipc::IPCResult RecvSetupFamilyCharMap(
       const uint32_t& aGeneration,
       const mozilla::fontlist::Pointer& aFamilyPtr);
 
+  mozilla::ipc::IPCResult RecvGetHyphDict(
+      const mozilla::ipc::URIParams& aURIParams,
+      mozilla::ipc::SharedMemoryBasic::Handle* aOutHandle, uint32_t* aOutSize);
+
   mozilla::ipc::IPCResult RecvNotifyBenchmarkResult(const nsString& aCodecName,
                                                     const uint32_t& aDecodeFPS);
 
   mozilla::ipc::IPCResult RecvNotifyPushObservers(
       const nsCString& aScope, const IPC::Principal& aPrincipal,
       const nsString& aMessageId);
 
   mozilla::ipc::IPCResult RecvNotifyPushObserversWithData(
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -1319,16 +1319,39 @@ parent:
      *   Returns whether the font name loading process has completed.
      *
      * TODO: This is currently a sync message but can probably be made async,
      * at the cost of an increased chance of some testcases failing because
      * they depend on lazily-loaded font names.
      */
     sync InitOtherFamilyNames(uint32_t aGeneration, bool aDefer) returns (bool aLoaded);
 
+    /**
+     * Ask the parent for a specific hyphenation resource (identified by URI)
+     * as a shared memory block.
+     *
+     * This is a sync method because at the point where a content process finds
+     * that it requires a particular hyphenation dictionary, this is blocking
+     * reflow; making it async would require scheduling another reflow after
+     * the resource is available, and a possible layout "jump" as line-breaks
+     * change. Note that the content process retains a reference to each such
+     * resource it requests, so it will only make this call once per locale for
+     * which hyphenation data exists.
+     *
+     * @param aURI
+     *   The URI (which currently must always point to an omnijar resource)
+     *   for the required hyphenation dictionary.
+     * @param aHandle
+     *   Returns the shmem handle to the resource (or an invalid shmem handle
+     *   in case of failure).
+     * @param aLoaded
+     *   Returns the size in bytes of the resource.
+     */
+    sync GetHyphDict(URIParams aURI) returns (Handle aHandle, uint32_t aSize);
+
     async CreateWindow(nullable PBrowser aThisTab,
                        PBrowser aNewTab,
                        uint32_t aChromeFlags,
                        bool aCalledFromJS,
                        bool aPositionSpecified,
                        bool aSizeSpecified,
                        URIParams? aURIToLoad,
                        nsCString aFeatures,
--- a/intl/hyphenation/glue/moz.build
+++ b/intl/hyphenation/glue/moz.build
@@ -9,16 +9,18 @@ EXPORTS += [
     'nsHyphenator.h',
 ]
 
 UNIFIED_SOURCES += [
     'nsHyphenationManager.cpp',
     'nsHyphenator.cpp',
 ]
 
+include('/ipc/chromium/chromium-config.mozbuild')
+
 FINAL_LIBRARY = 'xul'
 
 if CONFIG['CC_TYPE'] in ('clang', 'gcc'):
     CXXFLAGS += ['-Wno-error=shadow']
 
 if CONFIG['COMPILE_ENVIRONMENT']:
     GENERATED_FILES += [
         'mapped_hyph.h'
--- a/intl/hyphenation/glue/nsHyphenationManager.cpp
+++ b/intl/hyphenation/glue/nsHyphenationManager.cpp
@@ -3,16 +3,17 @@
  * 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/. */
 
 #include "nsHyphenationManager.h"
 #include "nsHyphenator.h"
 #include "nsAtom.h"
 #include "nsIFile.h"
 #include "nsIURI.h"
+#include "nsIJARURI.h"
 #include "nsIProperties.h"
 #include "nsISimpleEnumerator.h"
 #include "nsIDirectoryEnumerator.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsNetUtil.h"
 #include "nsUnicharUtils.h"
 #include "mozilla/CountingAllocatorBase.h"
 #include "mozilla/Preferences.h"
@@ -219,16 +220,35 @@ void nsHyphenationManager::LoadPatternLi
   rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
                               getter_AddRefs(profileDir));
   if (NS_SUCCEEDED(rv)) {
     profileDir->AppendNative(NS_LITERAL_CSTRING("hyphenation"));
     LoadPatternListFromDir(profileDir);
   }
 }
 
+// Extract the locale code we'll use to identify a given hyphenation resource
+// from the path name as found in omnijar or on disk.
+static already_AddRefed<nsAtom> LocaleAtomFromPath(const nsCString& aPath) {
+  MOZ_ASSERT(StringEndsWith(aPath, NS_LITERAL_CSTRING(".hyf")));
+  nsCString locale(aPath);
+  locale.Truncate(locale.Length() - 4);      // strip ".hyf"
+  locale.Cut(0, locale.RFindChar('/') + 1);  // strip directory
+  ToLowerCase(locale);
+  if (StringBeginsWith(locale, NS_LITERAL_CSTRING("hyph_"))) {
+    locale.Cut(0, 5);
+  }
+  for (uint32_t i = 0; i < locale.Length(); ++i) {
+    if (locale[i] == '_') {
+      locale.Replace(i, 1, '-');
+    }
+  }
+  return NS_Atomize(locale);
+}
+
 void nsHyphenationManager::LoadPatternListFromOmnijar(Omnijar::Type aType) {
   nsCString base;
   nsresult rv = Omnijar::GetURIString(aType, base);
   if (NS_FAILED(rv)) {
     return;
   }
 
   RefPtr<nsZipArchive> zip = Omnijar::GetReader(aType);
@@ -252,31 +272,18 @@ void nsHyphenationManager::LoadPatternLi
     if (NS_FAILED(rv)) {
       continue;
     }
     nsCString locale;
     rv = uri->GetPathQueryRef(locale);
     if (NS_FAILED(rv)) {
       continue;
     }
-    ToLowerCase(locale);
-    locale.SetLength(locale.Length() - 4);     // strip ".hyf"
-    locale.Cut(0, locale.RFindChar('/') + 1);  // strip directory
-    if (StringBeginsWith(locale, NS_LITERAL_CSTRING("hyph_"))) {
-      locale.Cut(0, 5);
-    }
-    for (uint32_t i = 0; i < locale.Length(); ++i) {
-      if (locale[i] == '_') {
-        locale.Replace(i, 1, '-');
-      }
-    }
-    RefPtr<nsAtom> localeAtom = NS_Atomize(locale);
-    if (NS_SUCCEEDED(rv)) {
-      mPatternFiles.Put(localeAtom, uri);
-    }
+    RefPtr<nsAtom> localeAtom = LocaleAtomFromPath(locale);
+    mPatternFiles.Put(localeAtom, uri);
   }
 
   delete find;
 }
 
 void nsHyphenationManager::LoadPatternListFromDir(nsIFile* aDir) {
   nsresult rv;
 
@@ -296,38 +303,28 @@ void nsHyphenationManager::LoadPatternLi
   if (NS_FAILED(rv)) {
     return;
   }
 
   nsCOMPtr<nsIFile> file;
   while (NS_SUCCEEDED(files->GetNextFile(getter_AddRefs(file))) && file) {
     nsAutoString dictName;
     file->GetLeafName(dictName);
-    NS_ConvertUTF16toUTF8 locale(dictName);
-    ToLowerCase(locale);
-    if (!StringEndsWith(locale, NS_LITERAL_CSTRING(".hyf"))) {
+    NS_ConvertUTF16toUTF8 path(dictName);
+    if (!StringEndsWith(path, NS_LITERAL_CSTRING(".hyf"))) {
       continue;
     }
-    if (StringBeginsWith(locale, NS_LITERAL_CSTRING("hyph_"))) {
-      locale.Cut(0, 5);
-    }
-    locale.SetLength(locale.Length() - 4);  // strip ".hyf"
-    for (uint32_t i = 0; i < locale.Length(); ++i) {
-      if (locale[i] == '_') {
-        locale.Replace(i, 1, '-');
-      }
-    }
-#ifdef DEBUG_hyph
-    printf("adding hyphenation patterns for %s: %s\n", locale.get(),
-           NS_ConvertUTF16toUTF8(dictName).get());
-#endif
-    RefPtr<nsAtom> localeAtom = NS_Atomize(locale);
+    RefPtr<nsAtom> localeAtom = LocaleAtomFromPath(path);
     nsCOMPtr<nsIURI> uri;
     nsresult rv = NS_NewFileURI(getter_AddRefs(uri), file);
     if (NS_SUCCEEDED(rv)) {
+#ifdef DEBUG_hyph
+      printf("adding hyphenation patterns for %s: %s\n",
+             nsAtomCString(localeAtom).get(), path.get());
+#endif
       mPatternFiles.Put(localeAtom, uri);
     }
   }
 }
 
 void nsHyphenationManager::LoadAliases() {
   nsIPrefBranch* prefRootBranch = Preferences::GetRootBranch();
   if (!prefRootBranch) {
@@ -348,16 +345,43 @@ void nsHyphenationManager::LoadAliases()
         RefPtr<nsAtom> aliasAtom = NS_Atomize(alias);
         RefPtr<nsAtom> valueAtom = NS_Atomize(value);
         mHyphAliases.Put(aliasAtom, valueAtom);
       }
     }
   }
 }
 
+void nsHyphenationManager::ShareHyphDictToProcess(
+    nsIURI* aURI, base::ProcessId aPid,
+    mozilla::ipc::SharedMemoryBasic::Handle* aOutHandle, uint32_t* aOutSize) {
+  MOZ_ASSERT(XRE_IsParentProcess());
+  // aURI will be referring to an omnijar resource (otherwise just bail).
+  *aOutHandle = ipc::SharedMemoryBasic::NULLHandle();
+  *aOutSize = 0;
+  nsCOMPtr<nsIJARURI> jar = do_QueryInterface(aURI);
+  if (!jar) {
+    MOZ_ASSERT_UNREACHABLE("not a JAR resource");
+    return;
+  }
+
+  // Extract the locale code from the URI, and get the corresponding
+  // hyphenator (loading it into shared memory if necessary).
+  nsCString path;
+  jar->GetJAREntry(path);
+  RefPtr<nsAtom> localeAtom = LocaleAtomFromPath(path);
+  RefPtr<nsHyphenator> hyph = GetHyphenator(localeAtom);
+  if (!hyph) {
+    MOZ_ASSERT_UNREACHABLE("failed to find hyphenator");
+    return;
+  }
+
+  hyph->ShareToProcess(aPid, aOutHandle, aOutSize);
+}
+
 size_t nsHyphenationManager::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) {
   size_t result = aMallocSizeOf(this);
 
   result += mHyphAliases.ShallowSizeOfExcludingThis(aMallocSizeOf);
 
   result += mPatternFiles.ShallowSizeOfExcludingThis(aMallocSizeOf);
   // Measurement of the URIs stored in mPatternFiles may be added later if DMD
   // finds it is worthwhile.
--- a/intl/hyphenation/glue/nsHyphenationManager.h
+++ b/intl/hyphenation/glue/nsHyphenationManager.h
@@ -6,30 +6,35 @@
 #ifndef nsHyphenationManager_h__
 #define nsHyphenationManager_h__
 
 #include "nsInterfaceHashtable.h"
 #include "nsRefPtrHashtable.h"
 #include "nsHashKeys.h"
 #include "nsIObserver.h"
 #include "mozilla/Omnijar.h"
+#include "mozilla/ipc/SharedMemoryBasic.h"
 
 class nsHyphenator;
 class nsAtom;
 class nsIURI;
 
 class nsHyphenationManager : public nsIObserver {
  public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
   nsHyphenationManager();
 
   already_AddRefed<nsHyphenator> GetHyphenator(nsAtom* aLocale);
 
+  void ShareHyphDictToProcess(
+      nsIURI* aURI, base::ProcessId aPid,
+      mozilla::ipc::SharedMemoryBasic::Handle* aOutHandle, uint32_t* aOutSize);
+
   static nsHyphenationManager* Instance();
 
   static void Shutdown();
 
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
 
  private:
   virtual ~nsHyphenationManager();
--- a/intl/hyphenation/glue/nsHyphenator.cpp
+++ b/intl/hyphenation/glue/nsHyphenator.cpp
@@ -1,46 +1,53 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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/. */
 
 #include "nsHyphenator.h"
 
+#include "mozilla/dom/ContentChild.h"
 #include "mozilla/Telemetry.h"
 #include "nsContentUtils.h"
 #include "nsIChannel.h"
 #include "nsIFile.h"
 #include "nsIFileURL.h"
 #include "nsIInputStream.h"
 #include "nsIJARURI.h"
 #include "nsIURI.h"
 #include "nsNetUtil.h"
 #include "nsUnicodeProperties.h"
 #include "nsUTF8Utils.h"
 
 #include "mapped_hyph.h"
 
+using namespace mozilla;
+
+void DefaultDelete<const HyphDic>::operator()(const HyphDic* aHyph) const {
+  mapped_hyph_free_dictionary(const_cast<HyphDic*>(aHyph));
+}
+
 static const void* GetItemPtrFromJarURI(nsIJARURI* aJAR, uint32_t* aLength) {
   // Try to get the jarfile's nsZipArchive, find the relevant item, and return
   // a pointer to its data provided it is stored uncompressed.
   nsCOMPtr<nsIURI> jarFile;
   if (NS_FAILED(aJAR->GetJARFile(getter_AddRefs(jarFile)))) {
     return nullptr;
   }
   nsCOMPtr<nsIFileURL> fileUrl = do_QueryInterface(jarFile);
   if (!fileUrl) {
     return nullptr;
   }
   nsCOMPtr<nsIFile> file;
   fileUrl->GetFile(getter_AddRefs(file));
   if (!file) {
     return nullptr;
   }
-  RefPtr<nsZipArchive> archive = mozilla::Omnijar::GetReader(file);
+  RefPtr<nsZipArchive> archive = Omnijar::GetReader(file);
   if (archive) {
     nsCString path;
     aJAR->GetJAREntry(path);
     nsZipItem* item = archive->GetItem(path.get());
     if (item && item->Compression() == 0 && item->Size() > 0) {
       // We do NOT own this data, but it won't go away until the omnijar
       // file is closed during shutdown.
       const uint8_t* data = archive->GetData(item);
@@ -48,17 +55,48 @@ static const void* GetItemPtrFromJarURI(
         *aLength = item->Size();
         return data;
       }
     }
   }
   return nullptr;
 }
 
-static const void* LoadResourceFromURI(nsIURI* aURI, uint32_t* aLength) {
+already_AddRefed<ipc::SharedMemoryBasic> GetHyphDictFromParent(
+    nsIURI* aURI, uint32_t* aLength) {
+  MOZ_ASSERT(!XRE_IsParentProcess());
+  ipc::SharedMemoryBasic::Handle handle = ipc::SharedMemoryBasic::NULLHandle();
+  uint32_t size;
+  ipc::URIParams params;
+  SerializeURI(aURI, params);
+  if (!dom::ContentChild::GetSingleton()->SendGetHyphDict(params, &handle,
+                                                          &size)) {
+    return nullptr;
+  }
+  RefPtr<ipc::SharedMemoryBasic> shm = new ipc::SharedMemoryBasic();
+  if (!shm->IsHandleValid(handle)) {
+    return nullptr;
+  }
+  if (!shm->SetHandle(handle, ipc::SharedMemoryBasic::RightsReadOnly)) {
+    return nullptr;
+  }
+  if (!shm->Map(size)) {
+    return nullptr;
+  }
+  char* addr = static_cast<char*>(shm->memory());
+  if (!addr) {
+    return nullptr;
+  }
+  *aLength = size;
+  return shm.forget();
+}
+
+static already_AddRefed<ipc::SharedMemoryBasic> LoadInShmemFromURI(
+    nsIURI* aURI, uint32_t* aLength) {
+  MOZ_ASSERT(XRE_IsParentProcess());
   nsCOMPtr<nsIChannel> channel;
   if (NS_FAILED(NS_NewChannel(getter_AddRefs(channel), aURI,
                               nsContentUtils::GetSystemPrincipal(),
                               nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
                               nsIContentPolicy::TYPE_OTHER))) {
     return nullptr;
   }
   nsCOMPtr<nsIInputStream> instream;
@@ -68,96 +106,110 @@ static const void* LoadResourceFromURI(n
   // Check size, bail out if it is excessively large (the largest of the
   // hyphenation files currently shipped with Firefox is around 1MB
   // uncompressed).
   uint64_t available;
   if (NS_FAILED(instream->Available(&available)) || !available ||
       available > 16 * 1024 * 1024) {
     return nullptr;
   }
-  char* buffer = static_cast<char*>(malloc(available));
+
+  // The shm-related calls here are not expected to fail, but if they do,
+  // we'll just return null (as if the resource was unavailable) and proceed
+  // without hyphenation.
+  RefPtr<ipc::SharedMemoryBasic> shm = new ipc::SharedMemoryBasic();
+  if (!shm->Create(available)) {
+    return nullptr;
+  }
+  if (!shm->Map(available)) {
+    return nullptr;
+  }
+  char* buffer = static_cast<char*>(shm->memory());
   if (!buffer) {
     return nullptr;
   }
+
   uint32_t bytesRead = 0;
   if (NS_FAILED(instream->Read(buffer, available, &bytesRead)) ||
       bytesRead != available) {
-    free(buffer);
     return nullptr;
   }
   *aLength = bytesRead;
-  return buffer;
+  return shm.forget();
 }
 
 nsHyphenator::nsHyphenator(nsIURI* aURI, bool aHyphenateCapitalized)
-    : mDict(nullptr),
+    : mDict(static_cast<const void*>(nullptr)),
       mDictSize(0),
-      mOwnsDict(false),
       mHyphenateCapitalized(aHyphenateCapitalized) {
   Telemetry::AutoTimer<Telemetry::HYPHENATION_LOAD_TIME> telemetry;
 
   nsCOMPtr<nsIJARURI> jar = do_QueryInterface(aURI);
   if (jar) {
     // This gives us a raw pointer into the omnijar's data (if uncompressed);
     // we do not own it and must not attempt to free it!
-    mDict = GetItemPtrFromJarURI(jar, &mDictSize);
-    if (!mDict) {
-      // Omnijar must be compressed: we need to decompress the item into our
-      // own buffer. (Currently this is the case on Android.)
-      // TODO: Allocate in shared memory for all content processes to use.
-      mDict = LoadResourceFromURI(aURI, &mDictSize);
-      mOwnsDict = true;
-    }
-    if (mDict) {
-      // Reject the resource from omnijar if it fails to validate. (If this
-      // happens, we will hit the MOZ_ASSERT_UNREACHABLE at the end of the
-      // constructor, indicating the build is broken in some way.)
-      if (!mapped_hyph_is_valid_hyphenator(static_cast<const uint8_t*>(mDict),
-                                           mDictSize)) {
-        if (mOwnsDict) {
-          free(const_cast<void*>(mDict));
+    const void* ptr = GetItemPtrFromJarURI(jar, &mDictSize);
+    if (ptr) {
+      if (mapped_hyph_is_valid_hyphenator(static_cast<const uint8_t*>(ptr),
+                                          mDictSize)) {
+        mDict = AsVariant(ptr);
+        return;
+      }
+    } else {
+      // Omnijar must be compressed (currently this is the case on Android).
+      // If we're the parent process, decompress the resource into a shmem
+      // buffer; if we're a child, send a request to the parent for the
+      // shared-memory copy (which it will load if not already available).
+      RefPtr<ipc::SharedMemoryBasic> shm;
+      if (XRE_IsParentProcess()) {
+        shm = LoadInShmemFromURI(aURI, &mDictSize);
+        if (shm && mapped_hyph_is_valid_hyphenator(
+                       static_cast<const uint8_t*>(shm->memory()), mDictSize)) {
+          mDict = AsVariant(shm);
+          return;
         }
-        mDict = nullptr;
-        mDictSize = 0;
+      } else {
+        shm = GetHyphDictFromParent(aURI, &mDictSize);
+        if (shm) {
+          // We don't need to validate mDict because the parent process
+          // will have done so.
+          mDict = AsVariant(shm);
+          return;
+        }
       }
     }
-  } else if (mozilla::net::SchemeIsFile(aURI)) {
+  }
+
+  if (net::SchemeIsFile(aURI)) {
     // Ask the Rust lib to mmap the file. In this case our mDictSize field
     // remains zero; mDict is not a pointer to the raw data but an opaque
     // reference to a Rust object, and can only be freed by passing it to
     // mapped_hyph_free_dictionary().
     nsAutoCString path;
     aURI->GetFilePath(path);
-    mDict = mapped_hyph_load_dictionary(path.get());
+    UniquePtr<const HyphDic> dic(mapped_hyph_load_dictionary(path.get()));
+    if (dic) {
+      mDict = AsVariant(std::move(dic));
+      return;
+    }
   }
 
-  if (!mDict) {
-    // This should never happen, unless someone has included an invalid
-    // hyphenation file that fails to load.
-    MOZ_ASSERT_UNREACHABLE("invalid hyphenation resource?");
-  }
+  MOZ_ASSERT_UNREACHABLE("invalid hyphenation resource?");
 }
 
-nsHyphenator::~nsHyphenator() {
-  if (mDict) {
-    if (mDictSize) {
-      if (mOwnsDict) {
-        free(const_cast<void*>(mDict));
-      }
-    } else {
-      mapped_hyph_free_dictionary((HyphDic*)mDict);
-    }
-  }
+bool nsHyphenator::IsValid() {
+  return mDict.match(
+      [](const void*& ptr) { return ptr != nullptr; },
+      [](RefPtr<ipc::SharedMemoryBasic>& shm) { return shm != nullptr; },
+      [](mozilla::UniquePtr<const HyphDic>& hyph) { return hyph != nullptr; });
 }
 
-bool nsHyphenator::IsValid() { return (mDict != nullptr); }
-
 nsresult nsHyphenator::Hyphenate(const nsAString& aString,
                                  nsTArray<bool>& aHyphens) {
-  if (!aHyphens.SetLength(aString.Length(), mozilla::fallible)) {
+  if (!aHyphens.SetLength(aString.Length(), fallible)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
   memset(aHyphens.Elements(), false, aHyphens.Length() * sizeof(bool));
 
   bool inWord = false;
   uint32_t wordStart = 0, wordLimit = 0;
   uint32_t chLen;
   for (uint32_t i = 0; i < aString.Length(); i += chLen) {
@@ -168,17 +220,17 @@ nsresult nsHyphenator::Hyphenate(const n
       if (i + 1 < aString.Length() && NS_IS_LOW_SURROGATE(aString[i + 1])) {
         ch = SURROGATE_TO_UCS4(ch, aString[i + 1]);
         chLen = 2;
       } else {
         NS_WARNING("unpaired surrogate found during hyphenation");
       }
     }
 
-    nsUGenCategory cat = mozilla::unicode::GetGenCategory(ch);
+    nsUGenCategory cat = unicode::GetGenCategory(ch);
     if (cat == nsUGenCategory::kLetter || cat == nsUGenCategory::kMark) {
       if (!inWord) {
         inWord = true;
         wordStart = i;
       }
       wordLimit = i + chLen;
       if (i + chLen < aString.Length()) {
         continue;
@@ -247,26 +299,33 @@ void nsHyphenator::HyphenateWord(const n
       utf8.Append(0x80 | (0x003F & (ch >> 12)));
       utf8.Append(0x80 | (0x003F & (ch >> 6)));
       utf8.Append(0x80 | (0x003F & ch));
     }
   }
 
   AutoTArray<uint8_t, 200> hyphenValues;
   hyphenValues.SetLength(utf8.Length());
-  int32_t result;
-  if (mDictSize > 0) {
-    result = mapped_hyph_find_hyphen_values_raw(
-        static_cast<const uint8_t*>(mDict), mDictSize, utf8.BeginReading(),
-        utf8.Length(), hyphenValues.Elements(), hyphenValues.Length());
-  } else {
-    result = mapped_hyph_find_hyphen_values_dic(
-        static_cast<const HyphDic*>(mDict), utf8.BeginReading(), utf8.Length(),
-        hyphenValues.Elements(), hyphenValues.Length());
-  }
+  int32_t result = mDict.match(
+      [&](const void*& ptr) {
+        return mapped_hyph_find_hyphen_values_raw(
+            static_cast<const uint8_t*>(ptr), mDictSize, utf8.BeginReading(),
+            utf8.Length(), hyphenValues.Elements(), hyphenValues.Length());
+      },
+      [&](RefPtr<mozilla::ipc::SharedMemoryBasic>& shm) {
+        return mapped_hyph_find_hyphen_values_raw(
+            static_cast<const uint8_t*>(shm->memory()), mDictSize,
+            utf8.BeginReading(), utf8.Length(), hyphenValues.Elements(),
+            hyphenValues.Length());
+      },
+      [&](mozilla::UniquePtr<const HyphDic>& hyph) {
+        return mapped_hyph_find_hyphen_values_dic(
+            hyph.get(), utf8.BeginReading(), utf8.Length(),
+            hyphenValues.Elements(), hyphenValues.Length());
+      });
   if (result > 0) {
     // We need to convert UTF-8 indexing as used by the hyphenation lib into
     // UTF-16 indexing of the aHyphens[] array for Gecko.
     uint32_t utf16index = 0;
     for (uint32_t utf8index = 0; utf8index < utf8.Length();) {
       // We know utf8 is valid, so we only need to look at the first byte of
       // each character to determine its length and the corresponding UTF-16
       // length to add to utf16index.
@@ -285,8 +344,24 @@ void nsHyphenator::HyphenateWord(const n
       // utf-16 character (in the case of a surrogate pair).
       utf16index += leadByte >= 0xF0 ? 2 : 1;
       if (utf16index > 0 && (hyphenValues[utf8index - 1] & 0x01)) {
         aHyphens[aStart + utf16index - 1] = true;
       }
     }
   }
 }
+
+void nsHyphenator::ShareToProcess(base::ProcessId aPid,
+                                  ipc::SharedMemoryBasic::Handle* aOutHandle,
+                                  uint32_t* aOutSize) {
+  // If the resource is invalid, or if we fail to share it to the child
+  // process, we'll just bail out and continue without hyphenation; no need
+  // for this to be a fatal error.
+  if (!mDict.is<RefPtr<mozilla::ipc::SharedMemoryBasic>>()) {
+    return;
+  }
+  if (!mDict.as<RefPtr<mozilla::ipc::SharedMemoryBasic>>()->ShareToProcess(
+          aPid, aOutHandle)) {
+    return;
+  }
+  *aOutSize = mDictSize;
+}
--- a/intl/hyphenation/glue/nsHyphenator.h
+++ b/intl/hyphenation/glue/nsHyphenator.h
@@ -1,42 +1,56 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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/. */
 
 #ifndef nsHyphenator_h__
 #define nsHyphenator_h__
 
+#include "mozilla/ipc/SharedMemoryBasic.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Variant.h"
 #include "nsCOMPtr.h"
 #include "nsString.h"
 #include "nsTArray.h"
 
 class nsIURI;
+struct HyphDic;
+
+namespace mozilla {
+template <>
+class DefaultDelete<const HyphDic> {
+ public:
+  void operator()(const HyphDic* ptr) const;
+};
+}
 
 class nsHyphenator {
  public:
   nsHyphenator(nsIURI* aURI, bool aHyphenateCapitalized);
 
   NS_INLINE_DECL_REFCOUNTING(nsHyphenator)
 
   bool IsValid();
 
   nsresult Hyphenate(const nsAString& aText, nsTArray<bool>& aHyphens);
 
+  void ShareToProcess(base::ProcessId aPid,
+                      mozilla::ipc::SharedMemoryBasic::Handle* aOutHandle,
+                      uint32_t* aOutSize);
+
  private:
-  ~nsHyphenator();
+  ~nsHyphenator() = default;
 
   void HyphenateWord(const nsAString& aString, uint32_t aStart, uint32_t aLimit,
                      nsTArray<bool>& aHyphens);
 
-  const void* mDict;  // If mDictSize > 0, this points to a raw byte buffer
-                      // containing the hyphenation dictionary data (in the
-                      // memory-mapped omnijar, or owned by us if mOwnsDict);
-                      // if mDictSize == 0, it's a HyphDic reference created
-                      // by mapped_hyph_load_dictionary() and must be released
-                      // by calling mapped_hyph_free_dictionary().
-  uint32_t mDictSize;
-  bool mOwnsDict;
+  mozilla::Variant<const void*,  // raw pointer to uncompressed omnijar data
+                   RefPtr<mozilla::ipc::SharedMemoryBasic>,  // shmem block
+                   mozilla::UniquePtr<const HyphDic>  // loaded by mapped_hyph
+                   >
+      mDict;
+  uint32_t mDictSize;  // size of mDict data (not used if type is HyphDic)
   bool mHyphenateCapitalized;
 };
 
 #endif  // nsHyphenator_h__
--- a/ipc/ipdl/sync-messages.ini
+++ b/ipc/ipdl/sync-messages.ini
@@ -1005,16 +1005,18 @@ description =
 [PContent::GetGraphicsDeviceInitData]
 description =
 [PContent::GetFontListShmBlock]
 description = for bug 1514869 - layout code needs synchronous access to font list, but this is used only once per block, after which content directly reads the shared memory
 [PContent::InitializeFamily]
 description = for bug 1514869 - layout is blocked on needing sync access to a specific font family - used once per family, then the data is cached in shared memory
 [PContent::InitOtherFamilyNames]
 description = for bug 1514869 - layout is blocked on font lookup, needs complete family-name information - not used after loading is complete
+[PContent::GetHyphDict]
+description = for bug 1487212 - layout requires hyphenation data from a given omnijar resource - only called once per locale by a given content process
 [PContent::UngrabPointer]
 description =
 [PContent::RemovePermission]
 description =
 [PContent::GetA11yContentId]
 description =
 [PGMP::StartPlugin]
 description =