Bug 1475415 - Add first version of IPDL-JS API r=mrbkap,mccr8
☠☠ backed out by cc77004653fc ☠ ☠
authorTristan Bourvon <tristanbourvon@gmail.com>
Mon, 24 Sep 2018 14:13:20 +0000
changeset 496355 58f0722012cdceecb0955faee00f8bb9f660fa4c
parent 496354 f0d6115c98b5a21b4e7848fe28a6a05a0235d66b
child 496356 a44c98dc966bc48c9e80cd125d05c4a1663b6bc8
push id1864
push userffxbld-merge
push dateMon, 03 Dec 2018 15:51:40 +0000
treeherdermozilla-release@f040763d99ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmrbkap, mccr8
bugs1475415
milestone64.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 1475415 - Add first version of IPDL-JS API r=mrbkap,mccr8 Add the first version of the IPDL-JS API, which allow chrome JS to load IPDL files and use them to communicate accross Content processes. See IPDLProtocol.h for more information regarding how to use the API. Differential Revision: https://phabricator.services.mozilla.com/D2116
Cargo.lock
dom/base/nsGlobalWindowInner.cpp
dom/base/nsGlobalWindowInner.h
dom/base/test/unit/PSubTest.ipdl
dom/base/test/unit/PTest.ipdl
dom/base/test/unit/head_ipdl.js
dom/base/test/unit/test_ipdl_child.js
dom/base/test/unit/test_ipdl_parent.js
dom/base/test/unit/xpcshell.ini
dom/bindings/Bindings.conf
dom/chrome-webidl/IPDL.webidl
dom/chrome-webidl/moz.build
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/IPDL.cpp
dom/ipc/IPDL.h
dom/ipc/PContent.ipdl
dom/ipc/moz.build
dom/webidl/Window.webidl
ipc/ipdl/sync-messages.ini
ipc/ipdl_new/ipdl/Cargo.toml
ipc/ipdl_new/ipdl/README.md
ipc/ipdl_new/ipdl/make_test_command.py
ipc/ipdl_new/ipdl/src/ast.rs
ipc/ipdl_new/ipdl/src/errors.rs
ipc/ipdl_new/ipdl/src/lib.rs
ipc/ipdl_new/ipdl/src/parser.rs
ipc/ipdl_new/ipdl/src/passes/include_resolution.rs
ipc/ipdl_new/ipdl/src/passes/mod.rs
ipc/ipdl_new/ipdl/src/passes/parsetree_to_tu.rs
ipc/ipdl_new/ipdl/src/passes/type_check.rs
ipc/ipdl_new/ipdl/tests/smoke_test.rs
ipc/ipdl_new/ipdl_bindings/Cargo.toml
ipc/ipdl_new/ipdl_bindings/IPCInterface.cpp
ipc/ipdl_new/ipdl_bindings/IPCInterface.h
ipc/ipdl_new/ipdl_bindings/IPDLProtocol.cpp
ipc/ipdl_new/ipdl_bindings/IPDLProtocol.h
ipc/ipdl_new/ipdl_bindings/IPDLProtocolInstance.cpp
ipc/ipdl_new/ipdl_bindings/IPDLProtocolInstance.h
ipc/ipdl_new/ipdl_bindings/PContentChildIPCInterface.cpp
ipc/ipdl_new/ipdl_bindings/PContentChildIPCInterface.h
ipc/ipdl_new/ipdl_bindings/PContentParentIPCInterface.cpp
ipc/ipdl_new/ipdl_bindings/PContentParentIPCInterface.h
ipc/ipdl_new/ipdl_bindings/cbindgen.toml
ipc/ipdl_new/ipdl_bindings/ipdl_ffi_generated.h
ipc/ipdl_new/ipdl_bindings/moz.build
ipc/ipdl_new/ipdl_bindings/src/lib.rs
ipc/ipdl_new/ipdl_bindings/wrapper.cpp
ipc/ipdl_new/ipdl_bindings/wrapper.h
ipc/ipdl_new/moz.build
ipc/ipdl_new/pipdl/.gitignore
ipc/ipdl_new/pipdl/Cargo.toml
ipc/ipdl_new/pipdl/dotest.sh
ipc/ipdl_new/pipdl/examples/try_parse.rs
ipc/ipdl_new/pipdl/src/lib.rs
ipc/ipdl_new/pipdl/src/util.rs
ipc/moz.build
third_party/rust/thin-vec/.cargo-checksum.json
third_party/rust/thin-vec/Cargo.toml
third_party/rust/thin-vec/README.md
third_party/rust/thin-vec/src/heap.rs
third_party/rust/thin-vec/src/lib.rs
third_party/rust/thin-vec/src/range.rs
toolkit/library/rust/shared/Cargo.toml
toolkit/library/rust/shared/lib.rs
xpcom/rust/mfbt-maybe/Cargo.toml
xpcom/rust/mfbt-maybe/src/lib.rs
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -895,16 +895,17 @@ dependencies = [
  "audioipc-server 0.2.3",
  "cose-c 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "cubeb-pulse 0.2.0",
  "cubeb-sys 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "encoding_c 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "encoding_glue 0.1.0",
  "env_logger 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "geckoservo 0.0.1",
+ "ipdl_bindings 0.1.0",
  "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "mozurl 0.0.1",
  "mp4parse_capi 0.10.1",
  "netwerk_helper 0.0.1",
  "nserror 0.1.0",
  "nsstring 0.1.0",
  "prefs_parser 0.0.1",
  "rkv 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1035,16 +1036,33 @@ name = "iovec"
 version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
+name = "ipdl"
+version = "0.1.0"
+dependencies = [
+ "pipdl 0.1.0",
+]
+
+[[package]]
+name = "ipdl_bindings"
+version = "0.1.0"
+dependencies = [
+ "ipdl 0.1.0",
+ "mfbt-maybe 0.1.0",
+ "nsstring 0.1.0",
+ "thin-vec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
 name = "itertools"
 version = "0.7.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "either 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -1301,16 +1319,20 @@ dependencies = [
 ]
 
 [[package]]
 name = "memoffset"
 version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
+name = "mfbt-maybe"
+version = "0.1.0"
+
+[[package]]
 name = "miniz_oxide"
 version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "adler32 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -1651,16 +1673,20 @@ dependencies = [
 name = "phf_shared"
 version = "0.7.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "siphasher 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
+name = "pipdl"
+version = "0.1.0"
+
+[[package]]
 name = "pkg-config"
 version = "0.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "plane-split"
 version = "0.13.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2286,16 +2312,24 @@ dependencies = [
 ]
 
 [[package]]
 name = "thin-slice"
 version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
+name = "thin-vec"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
 name = "thread_local"
 version = "0.3.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -3030,16 +3064,17 @@ dependencies = [
 "checksum synstructure 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "85bb9b7550d063ea184027c9b8c20ac167cd36d3e06b3a40bceb9d746dc1a7b7"
 "checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6"
 "checksum term 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "fa63644f74ce96fbeb9b794f66aff2a52d601cbd5e80f4b97123e3899f4570f1"
 "checksum term_size 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2b6b55df3198cc93372e85dd2ed817f0e38ce8cc0f22eb32391bfad9c4bf209"
 "checksum termcolor 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "adc4587ead41bf016f11af03e55a624c06568b5a19db4e90fde573d805074f83"
 "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
 "checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693"
 "checksum thin-slice 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c"
+"checksum thin-vec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "73fdf4b84c65a85168477b7fb6c498e0716bc9487fba24623389ea7f51708044"
 "checksum thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963"
 "checksum thread_profiler 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf947d192a9be60ef5131cc7a4648886ba89d712f16700ebbf80c8a69d05d48f"
 "checksum time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "d825be0eb33fda1a7e68012d51e9c7f451dc1a69391e7fdc197060bb8c56667b"
 "checksum tokio 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8ee337e5f4e501fc32966fec6fe0ca0cc1c237b0b1b14a335f8bfe3c5f06e286"
 "checksum tokio-codec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "881e9645b81c2ce95fcb799ded2c29ffb9f25ef5bef909089a420e5961dd8ccb"
 "checksum tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "aeeffbbb94209023feaef3c196a41cbcdafa06b4a6f893f68779bb5e53796f71"
 "checksum tokio-executor 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "424f0c87ecd66b863045d84e384cb7ce0ae384d8b065b9f0363d29c0d1b30b2f"
 "checksum tokio-fs 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b5cbe4ca6e71cb0b62a66e4e6f53a8c06a6eefe46cc5f665ad6f274c9906f135"
--- a/dom/base/nsGlobalWindowInner.cpp
+++ b/dom/base/nsGlobalWindowInner.cpp
@@ -7417,16 +7417,26 @@ nsGlobalWindowInner::MessageManager()
   if (!mChromeFields.mMessageManager) {
     RefPtr<ChromeMessageBroadcaster> globalMM =
       nsFrameMessageManager::GetGlobalMessageManager();
     mChromeFields.mMessageManager = new ChromeMessageBroadcaster(globalMM);
   }
   return mChromeFields.mMessageManager;
 }
 
+IPDL*
+nsGlobalWindowInner::IPDL()
+{
+  MOZ_ASSERT(IsChromeWindow());
+  if (!mChromeFields.mIPDL) {
+    mChromeFields.mIPDL = new class IPDL(this);
+  }
+  return mChromeFields.mIPDL;
+}
+
 ChromeMessageBroadcaster*
 nsGlobalWindowInner::GetGroupMessageManager(const nsAString& aGroup)
 {
   MOZ_ASSERT(IsChromeWindow());
 
   RefPtr<ChromeMessageBroadcaster> messageManager =
     mChromeFields.mGroupMessageManagers.LookupForAdd(aGroup).OrInsert(
       [this] () {
--- a/dom/base/nsGlobalWindowInner.h
+++ b/dom/base/nsGlobalWindowInner.h
@@ -32,16 +32,17 @@
 #include "mozilla/EventListenerManager.h"
 #include "nsIPrincipal.h"
 #include "nsSize.h"
 #include "mozilla/FlushType.h"
 #include "prclist.h"
 #include "mozilla/dom/DOMPrefs.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/ChromeMessageBroadcaster.h"
+#include "mozilla/dom/IPDL.h"
 #include "mozilla/dom/NavigatorBinding.h"
 #include "mozilla/dom/StorageEvent.h"
 #include "mozilla/dom/StorageEventBinding.h"
 #include "mozilla/dom/UnionTypes.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/GuardObjects.h"
 #include "mozilla/LinkedList.h"
@@ -933,16 +934,17 @@ public:
                                   mozilla::ErrorResult& aError);
   void SetCursor(const nsAString& aCursor, mozilla::ErrorResult& aError);
   void Maximize();
   void Minimize();
   void Restore();
   void NotifyDefaultButtonLoaded(mozilla::dom::Element& aDefaultButton,
                                  mozilla::ErrorResult& aError);
   mozilla::dom::ChromeMessageBroadcaster* MessageManager();
+  mozilla::dom::IPDL* IPDL();
   mozilla::dom::ChromeMessageBroadcaster* GetGroupMessageManager(const nsAString& aGroup);
   void BeginWindowMove(mozilla::dom::Event& aMouseDownEvent,
                        mozilla::ErrorResult& aError);
 
   already_AddRefed<mozilla::dom::Promise>
   PromiseDocumentFlushed(mozilla::dom::PromiseDocumentFlushedCallback& aCallback,
                          mozilla::ErrorResult& aError);
 
@@ -1484,16 +1486,18 @@ protected:
   struct ChromeFields {
     ChromeFields()
       : mGroupMessageManagers(1)
     {}
 
     RefPtr<mozilla::dom::ChromeMessageBroadcaster> mMessageManager;
     nsRefPtrHashtable<nsStringHashKey,
                       mozilla::dom::ChromeMessageBroadcaster> mGroupMessageManagers;
+
+    RefPtr<mozilla::dom::IPDL> mIPDL;
   } mChromeFields;
 
   // These fields are used by the inner and outer windows to prevent
   // programatically moving the window while the mouse is down.
   static bool sMouseDown;
   static bool sDragServiceDisabled;
 
   friend class nsDOMScriptableHelper;
new file mode 100644
--- /dev/null
+++ b/dom/base/test/unit/PSubTest.ipdl
@@ -0,0 +1,14 @@
+include protocol PTest;
+
+sync protocol PSubTest {
+manager PTest;
+
+both:
+  async AsyncMessage(int inNumber) returns(int outNumber);
+
+parent:
+  sync SyncMessage(int inNumber) returns(int outNumber);
+
+child:
+  async __delete__();
+};
new file mode 100644
--- /dev/null
+++ b/dom/base/test/unit/PTest.ipdl
@@ -0,0 +1,14 @@
+include protocol PSubTest;
+
+sync protocol PTest {
+manages PSubTest;
+
+both:
+  async AsyncMessage(int inNumber) returns(int outNumber);
+
+parent:
+  sync SyncMessage(int inNumber) returns(int outNumber);
+
+child:
+  async PSubTest();
+};
new file mode 100644
--- /dev/null
+++ b/dom/base/test/unit/head_ipdl.js
@@ -0,0 +1,1 @@
+var IPDLGlob = new IPDL();
new file mode 100644
--- /dev/null
+++ b/dom/base/test/unit/test_ipdl_child.js
@@ -0,0 +1,42 @@
+"use strict";
+
+IPDLGlob.registerProtocol("PTest", "resource://test/PTest.ipdl");
+IPDLGlob.registerProtocol("PSubTest", "resource://test/PSubTest.ipdl");
+
+// Child process tests
+async function run_test() {
+  IPDLGlob.registerTopLevelClass(TestChild);
+
+  // Note that we stop the test in the parent process test
+}
+
+class TestChild extends IPDLGlob.PTestChild {
+  allocPSubTest() {
+    return new SubTestChild();
+  }
+
+  async recvPSubTestConstructor(protocol) {
+    test_ipdlChildSendSyncMessage(protocol);
+    await test_ipdlChildSendAsyncMessage(protocol);
+  }
+
+  recvAsyncMessage(input) {
+    return input + 1;
+  }
+}
+
+class SubTestChild extends IPDLGlob.PSubTestChild {
+  recvAsyncMessage(input) {
+    return input + 1;
+  }
+}
+
+function test_ipdlChildSendSyncMessage(protocol) {
+  Assert.equal(protocol.sendSyncMessage(42), 43);
+}
+
+async function test_ipdlChildSendAsyncMessage(protocol) {
+  await protocol.sendAsyncMessage(42).then(res => {
+    Assert.equal(res, 43);
+  });
+}
new file mode 100644
--- /dev/null
+++ b/dom/base/test/unit/test_ipdl_parent.js
@@ -0,0 +1,76 @@
+"use strict";
+
+IPDLGlob.registerProtocol("PTest", "resource://test/PTest.ipdl");
+IPDLGlob.registerProtocol("PSubTest", "resource://test/PSubTest.ipdl");
+
+// Parent process test starting
+function run_test() {
+  do_load_child_test_harness();
+  // Switch test termination to manual
+  do_test_pending();
+
+  do_get_idle();
+
+  IPDLGlob.registerTopLevelClass(TestParent);
+
+  // Load the child process test script
+  sendCommand('load("head_ipdl.js"); load("test_ipdl_child.js");', function() {
+
+    // Setup child protocol and run its tests
+    sendCommand("run_test();", async function() {
+      // Run parent tests
+      var protocol = IPDLGlob.getTopLevelInstances()[0];
+
+      try {
+        await test_ipdlParentSendAsyncMessage(protocol);
+        await test_ipdlParentSendPSubTestConstructor(protocol);
+      } catch (errMsg) {
+        do_test_finished();
+        throw new Error(errMsg);
+      }
+
+      // We're done, bye.
+      do_test_finished();
+    });
+  });
+}
+
+class TestParent extends IPDLGlob.PTestParent {
+  recvSyncMessage(input) {
+    return input + 1;
+  }
+
+  recvAsyncMessage(input) {
+    return input + 1;
+  }
+
+  allocPSubTest() {
+    return new SubTestParent();
+  }
+}
+
+class SubTestParent extends IPDLGlob.PSubTestParent {
+  recvSyncMessage(input) {
+    return input + 1;
+  }
+
+  recvAsyncMessage(input) {
+    return input + 1;
+  }
+}
+
+async function test_ipdlParentSendAsyncMessage(protocol) {
+  await protocol.sendAsyncMessage(42).then(res => {
+    equal(res, 43);
+  });
+}
+
+async function test_ipdlParentSendPSubTestConstructor(protocol) {
+  await protocol.sendPSubTestConstructor().then(async subprotocol => {
+    await subprotocol.sendAsyncMessage(42).then(res => {
+      equal(res, 43);
+    });
+
+    await subprotocol.send__delete__();
+  });
+}
--- a/dom/base/test/unit/xpcshell.ini
+++ b/dom/base/test/unit/xpcshell.ini
@@ -17,16 +17,18 @@ support-files =
   4_result_3.xml
   4_result_4.xml
   4_result_5.xml
   4_result_6.xml
   empty_document.xml
   isequalnode_data.xml
   nodelist_data_1.xml
   nodelist_data_2.xul
+  PTest.ipdl
+  PSubTest.ipdl
   test_delete_range.xml
 
 [test_bug553888.js]
 [test_bug737966.js]
 [test_error_codes.js]
 run-sequentially = Hardcoded 4444 port.
 # Bug 1018414: hardcoded localhost doesn't work properly on some OS X installs
 skip-if = os == 'mac'
@@ -47,14 +49,21 @@ head = head_xml.js
 [test_xhr_origin_attributes.js]
 [test_xml_parser.js]
 head = head_xml.js
 [test_xml_serializer.js]
 head = head_xml.js
 [test_xmlserializer.js]
 [test_cancelPrefetch.js]
 [test_chromeutils_base64.js]
+[test_ipdl_parent.js]
+head = head_ipdl.js
+skip-if = toolkit == 'android'
+firefox-appdir = browser
+[test_ipdl_child.js]
+skip-if = true # Used by test_ipdl_parent only
+head = head_ipdl.js
 [test_generate_xpath.js]
 head = head_xml.js
 [test_js_dev_error_interceptor.js]
 # This feature is implemented only in NIGHTLY.
 run-if = nightly_build
 
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -485,16 +485,20 @@ DOMInterfaces = {
     'nativeType': 'mozilla::dom::DOMIntersectionObserver',
 },
 
 'IntersectionObserverEntry': {
     'nativeType': 'mozilla::dom::DOMIntersectionObserverEntry',
     'headerFile': 'DOMIntersectionObserver.h',
 },
 
+'IPDL': {
+    'implicitJSContext': [ 'registerProtocol']
+},
+
 'KeyEvent': {
     'concrete': False
 },
 
 'LegacyMozTCPSocket': {
     'headerFile': 'TCPSocket.h',
     'wrapperCache': False,
 },
new file mode 100644
--- /dev/null
+++ b/dom/chrome-webidl/IPDL.webidl
@@ -0,0 +1,16 @@
+/* -*- Mode: IDL; 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/.
+ */
+
+
+// UPDATE IPDL::sPropertyNames IF YOU ADD METHODS OR ATTRIBUTES HERE.
+
+[ChromeConstructor, ChromeOnly, Exposed=(Window, System, Worker), NeedResolve]
+interface IPDL {
+  void registerProtocol(DOMString protocolName, DOMString protocolURI, optional boolean eagerLoad = false);
+  void registerTopLevelClass(object classConstructor);
+  object getTopLevelInstance(optional unsigned long id);
+  sequence<object> getTopLevelInstances();
+};
--- a/dom/chrome-webidl/moz.build
+++ b/dom/chrome-webidl/moz.build
@@ -30,16 +30,17 @@ PREPROCESSED_WEBIDL_FILES = [
 ]
 
 WEBIDL_FILES = [
     'BrowsingContext.webidl',
     'ChannelWrapper.webidl',
     'DominatorTree.webidl',
     'HeapSnapshot.webidl',
     'InspectorUtils.webidl',
+    'IPDL.webidl',
     'IteratorResult.webidl',
     'MatchGlob.webidl',
     'MatchPattern.webidl',
     'MessageManager.webidl',
     'MozDocumentObserver.webidl',
     'MozSharedMap.webidl',
     'MozStorageAsyncStatementParams.webidl',
     'MozStorageStatementParams.webidl',
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -57,16 +57,17 @@
 #include "mozilla/ipc/BackgroundChild.h"
 #include "mozilla/ipc/FileDescriptorSetChild.h"
 #include "mozilla/ipc/FileDescriptorUtils.h"
 #include "mozilla/ipc/GeckoChildProcessHost.h"
 #include "mozilla/ipc/ProcessChild.h"
 #include "mozilla/ipc/PChildToParentStreamChild.h"
 #include "mozilla/intl/LocaleService.h"
 #include "mozilla/ipc/TestShellChild.h"
+#include "mozilla/ipdl/ipc/PContentChildIPCInterface.h"
 #include "mozilla/jsipc/CrossProcessObjectWrappers.h"
 #include "mozilla/jsipc/PJavaScript.h"
 #include "mozilla/layers/APZChild.h"
 #include "mozilla/layers/CompositorManagerChild.h"
 #include "mozilla/layers/ContentProcessController.h"
 #include "mozilla/layers/ImageBridgeChild.h"
 #include "mozilla/layout/RenderFrameChild.h"
 #include "mozilla/loader/ScriptCacheActors.h"
@@ -2623,16 +2624,36 @@ ContentChild::RecvUpdateSharedData(const
     mSharedData = new SharedMap(ContentProcessMessageManager::Get()->GetParentObject(),
                                 aMapFile, aMapSize, std::move(blobImpls));
   }
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
+ContentChild::RecvAsyncMessageIPDL(const nsCString& aProtocolName,
+                                   const uint32_t& aChannelId,
+                                    const nsCString& aMessage,
+                                     const ClonedMessageData& aData,
+                                     AsyncMessageIPDLResolver&& aResolve)
+{
+  AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING(
+    "ContentChild::RecvAsyncMessageIPDL", OTHER, aMessage);
+
+  if (mIPDLIPCInterface) {
+    nsTArray<StructuredCloneData> returnData;
+    auto res = mIPDLIPCInterface->RecvMessage(aProtocolName, aChannelId, aMessage, aData, &returnData);
+    aResolve(std::move(returnData));
+    return res;
+  }
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
 ContentChild::RecvGeolocationUpdate(nsIDOMGeoPosition* aPosition)
 {
   RefPtr<nsGeolocationService> gs =
     nsGeolocationService::GetGeolocationService();
   if (!gs) {
     return IPC_OK();
   }
   gs->Update(aPosition);
@@ -3895,16 +3916,23 @@ ContentChild::GetSpecificMessageEventTar
 
       return do_AddRef(SystemGroup::EventTargetFor(TaskCategory::Other));
 
     default:
       return nullptr;
   }
 }
 
+void
+ContentChild::RegisterIPDLIPCInterface(
+  mozilla::ipdl::ipc::PContentChildIPCInterface* aIPDLIPCInterface)
+{
+  mIPDLIPCInterface = aIPDLIPCInterface;
+}
+
 #ifdef NIGHTLY_BUILD
 void
 ContentChild::OnChannelReceivedMessage(const Message& aMsg)
 {
   if (nsContentUtils::IsMessageInputEvent(aMsg)) {
     mPendingInputEvents++;
   }
 }
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -59,16 +59,22 @@ nsresult GetRepoDir(nsIFile **aRepoDir);
 nsresult GetObjDir(nsIFile **aObjDir);
 #endif /* XP_MACOSX */
 
 namespace ipc {
 class OptionalURIParams;
 class URIParams;
 }// namespace ipc
 
+namespace ipdl {
+namespace ipc {
+class PContentChildIPCInterface;
+} // namespace ipc
+} // namespace ipdl
+
 namespace dom {
 
 namespace ipc {
 class SharedMap;
 }
 
 class AlertObserver;
 class ConsoleListener;
@@ -395,16 +401,23 @@ public:
 
   virtual mozilla::ipc::IPCResult RecvLoadProcessScript(const nsString& aURL) override;
 
   virtual mozilla::ipc::IPCResult RecvAsyncMessage(const nsString& aMsg,
                                                    InfallibleTArray<CpowEntry>&& aCpows,
                                                    const IPC::Principal& aPrincipal,
                                                    const ClonedMessageData& aData) override;
 
+  // See PContent.ipdl for doc.
+  virtual mozilla::ipc::IPCResult RecvAsyncMessageIPDL(const nsCString& aProtocol,
+                                                       const uint32_t& aChannelId,
+                                                       const nsCString& aMessage,
+                                                       const ClonedMessageData& aData,
+                                                       AsyncMessageIPDLResolver&& aResolve) override;
+
   mozilla::ipc::IPCResult RecvRegisterStringBundles(nsTArray<StringBundleDescriptor>&& stringBundles) override;
 
   mozilla::ipc::IPCResult RecvUpdateSharedData(const FileDescriptor& aMapFile,
                                                const uint32_t& aMapSize,
                                                nsTArray<IPCBlob>&& aBlobs,
                                                nsTArray<nsCString>&& aChangedKeys) override;
 
   virtual mozilla::ipc::IPCResult RecvGeolocationUpdate(nsIDOMGeoPosition* aPosition) override;
@@ -743,16 +756,19 @@ public:
   // NOTE: This method performs an atomic read, and is safe to call from all threads.
   uint32_t
   GetPendingInputEvents()
   {
     return mPendingInputEvents;
   }
 #endif
 
+  void RegisterIPDLIPCInterface(
+    mozilla::ipdl::ipc::PContentChildIPCInterface* aIPDLIPCInterface);
+
 private:
   static void ForceKillTimerCallback(nsITimer* aTimer, void* aClosure);
   void StartForceKillTimer();
 
   void ShutdownInternal();
 
   mozilla::ipc::IPCResult
   GetResultForRenderingInitFailure(base::ProcessId aOtherPid);
@@ -848,16 +864,18 @@ private:
   // These items are removed when RecvFileCreationResponse is received.
   nsRefPtrHashtable<nsIDHashKey, FileCreatorHelper> mFileCreationPending;
 
 
   nsClassHashtable<nsUint64HashKey, AnonymousTemporaryFileCallback> mPendingAnonymousTemporaryFiles;
 
   mozilla::Atomic<bool> mShuttingDown;
 
+  mozilla::ipdl::ipc::PContentChildIPCInterface* mIPDLIPCInterface;
+
 #ifdef NIGHTLY_BUILD
   // NOTE: This member is atomic because it can be accessed from off-main-thread.
   mozilla::Atomic<uint32_t> mPendingInputEvents;
 #endif
 
   DISALLOW_EVIL_CONSTRUCTORS(ContentChild);
 };
 
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -72,16 +72,17 @@
 #include "mozilla/hal_sandbox/PHalParent.h"
 #include "mozilla/ipc/BackgroundChild.h"
 #include "mozilla/ipc/BackgroundParent.h"
 #include "mozilla/ipc/CrashReporterHost.h"
 #include "mozilla/ipc/FileDescriptorUtils.h"
 #include "mozilla/ipc/PChildToParentStreamParent.h"
 #include "mozilla/ipc/TestShellParent.h"
 #include "mozilla/ipc/IPCStreamUtils.h"
+#include "mozilla/ipdl/ipc/PContentParentIPCInterface.h"
 #include "mozilla/intl/LocaleService.h"
 #include "mozilla/jsipc/CrossProcessObjectWrappers.h"
 #include "mozilla/layers/PAPZParent.h"
 #include "mozilla/layers/CompositorThread.h"
 #include "mozilla/layers/ImageBridgeParent.h"
 #include "mozilla/layers/LayerTreeOwnerTracker.h"
 #include "mozilla/layout/RenderFrameParent.h"
 #include "mozilla/loader/ScriptCacheActors.h"
@@ -2320,16 +2321,17 @@ ContentParent::ContentParent(ContentPare
   , mCalledClose(false)
   , mCalledKillHard(false)
   , mCreatedPairedMinidumps(false)
   , mShutdownPending(false)
   , mIPCOpen(true)
   , mIsRemoteInputEventQueueEnabled(false)
   , mIsInputPriorityEventEnabled(false)
   , mHangMonitorActor(nullptr)
+  , mIPDLIPCInterface(nullptr)
 {
   // Insert ourselves into the global linked list of ContentParent objects.
   if (!sContentParents) {
     sContentParents = new LinkedList<ContentParent>();
   }
   sContentParents->insertBack(this);
 
   // From this point on, NS_WARNING, NS_ASSERTION, etc. should print out the
@@ -4002,16 +4004,33 @@ ContentParent::RecvSyncMessage(const nsS
                                const IPC::Principal& aPrincipal,
                                nsTArray<StructuredCloneData>* aRetvals)
 {
   return nsIContentParent::RecvSyncMessage(aMsg, aData, std::move(aCpows),
                                            aPrincipal, aRetvals);
 }
 
 mozilla::ipc::IPCResult
+ContentParent::RecvSyncMessageIPDL(const nsCString& aProtocolName,
+                                    const uint32_t& aChannelId,
+                                    const nsCString& aMessage,
+                                   const ClonedMessageData& aData,
+                                   nsTArray<StructuredCloneData>* returnData)
+{
+  AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING(
+    "ContentParent::RecvSyncMessageIPDL", OTHER, aMessage);
+
+  if (mIPDLIPCInterface) {
+    return mIPDLIPCInterface->RecvMessage(aProtocolName, aChannelId, aMessage, aData, returnData);
+  }
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
 ContentParent::RecvRpcMessage(const nsString& aMsg,
                               const ClonedMessageData& aData,
                               InfallibleTArray<CpowEntry>&& aCpows,
                               const IPC::Principal& aPrincipal,
                               nsTArray<StructuredCloneData>* aRetvals)
 {
   return nsIContentParent::RecvRpcMessage(aMsg, aData, std::move(aCpows), aPrincipal,
                                           aRetvals);
@@ -4022,16 +4041,36 @@ ContentParent::RecvAsyncMessage(const ns
                                 InfallibleTArray<CpowEntry>&& aCpows,
                                 const IPC::Principal& aPrincipal,
                                 const ClonedMessageData& aData)
 {
   return nsIContentParent::RecvAsyncMessage(aMsg, std::move(aCpows), aPrincipal,
                                             aData);
 }
 
+mozilla::ipc::IPCResult
+ContentParent::RecvAsyncMessageIPDL(const nsCString& aProtocolName,
+                                    const uint32_t& aChannelId,
+                                    const nsCString& aMessage,
+                                      const ClonedMessageData& aData,
+                                      AsyncMessageIPDLResolver&& aResolve)
+{
+  AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING(
+    "ContentParent::RecvAsyncMessageIPDL", OTHER, aMessage);
+
+  if (mIPDLIPCInterface) {
+    nsTArray<StructuredCloneData> returnData;
+    auto res = mIPDLIPCInterface->RecvMessage(aProtocolName, aChannelId, aMessage, aData, &returnData);
+    aResolve(std::move(returnData));
+    return res;
+  }
+
+  return IPC_OK();
+}
+
 static int32_t
 AddGeolocationListener(nsIDOMGeoPositionCallback* watcher,
                        nsIDOMGeoPositionErrorCallback* errorCallBack,
                        bool highAccuracy)
 {
   RefPtr<Geolocation> geo = Geolocation::NonWindowSingleton();
 
   UniquePtr<PositionOptions> options = MakeUnique<PositionOptions>();
@@ -6051,8 +6090,15 @@ ContentParent::RecvDetachBrowsingContext
   if (aMoveToBFCache) {
     context->CacheChildren();
   } else {
     context->Detach();
   }
 
   return IPC_OK();
 }
+
+void
+ContentParent::RegisterIPDLIPCInterface(
+  mozilla::ipdl::ipc::PContentParentIPCInterface* aIPDLIPCInterface)
+{
+  mIPDLIPCInterface = aIPDLIPCInterface;
+}
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -79,16 +79,22 @@ class OptionalURIParams;
 class PFileDescriptorSetParent;
 class URIParams;
 class TestShellParent;
 #ifdef FUZZING
 class ProtocolFuzzerHelper;
 #endif
 } // namespace ipc
 
+namespace ipdl {
+namespace ipc {
+class PContentParentIPCInterface;
+} // namespace ipc
+} // namespace ipdl
+
 namespace jsipc {
 class PJavaScriptParent;
 } // namespace jsipc
 
 namespace layers {
 struct TextureFactoryIdentifier;
 } // namespace layers
 
@@ -1068,27 +1074,41 @@ private:
   virtual mozilla::ipc::IPCResult RecvExtProtocolChannelConnectParent(const uint32_t& registrarId) override;
 
   virtual mozilla::ipc::IPCResult RecvSyncMessage(const nsString& aMsg,
                                                   const ClonedMessageData& aData,
                                                   InfallibleTArray<CpowEntry>&& aCpows,
                                                   const IPC::Principal& aPrincipal,
                                                   nsTArray<StructuredCloneData>* aRetvals) override;
 
+  // See PContent.ipdl for doc.
+  virtual mozilla::ipc::IPCResult RecvSyncMessageIPDL(const nsCString& aProtocol,
+                                                      const uint32_t& aChannelId,
+                                                      const nsCString& aMessage,
+                                                         const ClonedMessageData& aData,
+                                                         nsTArray<StructuredCloneData>* returnData) override;
+
   virtual mozilla::ipc::IPCResult RecvRpcMessage(const nsString& aMsg,
                                                  const ClonedMessageData& aData,
                                                  InfallibleTArray<CpowEntry>&& aCpows,
                                                  const IPC::Principal& aPrincipal,
                                                  nsTArray<StructuredCloneData>* aRetvals) override;
 
   virtual mozilla::ipc::IPCResult RecvAsyncMessage(const nsString& aMsg,
                                                    InfallibleTArray<CpowEntry>&& aCpows,
                                                    const IPC::Principal& aPrincipal,
                                                    const ClonedMessageData& aData) override;
 
+  // See PContent.ipdl for doc.
+  virtual mozilla::ipc::IPCResult RecvAsyncMessageIPDL(const nsCString& aProtocol,
+                                                        const uint32_t& aChannelId,
+                                                        const nsCString& aMessage,
+                                                         const ClonedMessageData& aData,
+                                                         AsyncMessageIPDLResolver&& aResolve) override;
+
   virtual mozilla::ipc::IPCResult RecvAddGeolocationListener(const IPC::Principal& aPrincipal,
                                                              const bool& aHighAccuracy) override;
   virtual mozilla::ipc::IPCResult RecvRemoveGeolocationListener() override;
 
   virtual mozilla::ipc::IPCResult RecvSetGeolocationHigherAccuracy(const bool& aEnable) override;
 
   virtual mozilla::ipc::IPCResult RecvConsoleMessage(const nsString& aMessage) override;
 
@@ -1266,16 +1286,20 @@ public:
 
   bool SendRequestMemoryReport(const uint32_t& aGeneration,
                                const bool& aAnonymize,
                                const bool& aMinimizeMemoryUsage,
                                const MaybeFileDesc& aDMDFile) override;
 
   bool CanCommunicateWith(ContentParentId aOtherProcess);
 
+  void RegisterIPDLIPCInterface(
+    mozilla::ipdl::ipc::PContentParentIPCInterface* aIPDLIPCInterface);
+
+
   nsresult SaveRecording(nsIFile* aFile, bool* aRetval);
 
   bool IsRecordingOrReplaying() const {
     return mRecordReplayState != eNotRecordingOrReplaying;
   }
 
 private:
 
@@ -1378,16 +1402,18 @@ private:
   nsRefPtrHashtable<nsIDHashKey, GetFilesHelper> mGetFilesPendingRequests;
 
   nsTHashtable<nsCStringHashKey> mActivePermissionKeys;
 
   nsTArray<nsCString> mBlobURLs;
 
   UniquePtr<mozilla::ipc::CrashReporterHost> mCrashReporter;
 
+  mozilla::ipdl::ipc::PContentParentIPCInterface* mIPDLIPCInterface;
+
   static uint64_t sNextTabParentId;
   static nsDataHashtable<nsUint64HashKey, TabParent*> sNextTabParents;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 class ParentIdleListener : public nsIObserver
new file mode 100644
--- /dev/null
+++ b/dom/ipc/IPDL.cpp
@@ -0,0 +1,484 @@
+#include "mozilla/dom/IPDL.h"
+
+#include "mozilla/dom/IPDLBinding.h"
+
+#include "mozilla/ipdl/IPDLProtocol.h"
+#include "mozilla/ipdl/IPDLProtocolInstance.h"
+#include "mozilla/ipdl/ipc/PContentChildIPCInterface.h"
+#include "mozilla/ipdl/ipc/PContentParentIPCInterface.h"
+#include "nsReadableUtils.h"
+#include "nsIObserverService.h"
+#include "nsJSUtils.h"
+#include "nsWrapperCacheInlines.h"
+#include "mozilla/Services.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/ContentChild.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(IPDL)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IPDL)
+  tmp->mTopLevelClassConstructor = nullptr;
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mParsedProtocolClassTable)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mTopLevelParentInstances)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mTopLevelChildInstance)
+  mozilla::DropJSObjects(tmp);
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IPDL)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParsedProtocolClassTable)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTopLevelParentInstances)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTopLevelChildInstance)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(IPDL)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mTopLevelClassConstructor)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+NS_IMPL_CYCLE_COLLECTING_ADDREF(IPDL)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(IPDL)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IPDL)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsIObserver)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
+NS_INTERFACE_MAP_END
+
+IPDL::IPDL(nsIGlobalObject* aParentObject)
+  : mParentObject(aParentObject)
+  , mChildInterface(nullptr)
+{
+  mozilla::HoldJSObjects(this);
+
+  // Register to listen to new Content notifications.
+  nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+  if (os) {
+    os->AddObserver(this, "ipc:content-created", /* ownsWeak */ true);
+  }
+}
+
+/* virtual */
+IPDL::~IPDL() {}
+
+void
+IPDL::RegisterTopLevelClass(JSContext* aCx,
+                            JS::Handle<JSObject*> aClassConstructor)
+{
+  mTopLevelClassConstructor = aClassConstructor;
+
+  Sequence<JS::HandleValue> emptyArray;
+
+  // When we register the top level class, we want to trigger a content creation
+  // event for all existing content protocol instances.
+  for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
+    NewTopLevelProtocolParent(aCx, emptyArray, cp);
+  }
+
+  if (ContentChild::GetSingleton()) {
+    NewTopLevelProtocolChild(aCx, emptyArray);
+  }
+}
+
+void
+IPDL::GetTopLevelInstance(JSContext* aCx,
+                          const Optional<uint32_t>& aId,
+                          JS::MutableHandle<JSObject*> aRetval)
+{
+  // If we got an ID, then we return a parent instance, otherwise we return the
+  // child instance.
+  if (aId.WasPassed() && !ContentChild::GetSingleton()) {
+    if (auto instance = mTopLevelParentInstances.GetWeak(aId.Value())) {
+      aRetval.set(instance->GetInstanceObject());
+    } else {
+      JS_ReportErrorUTF8(aCx, "Unknown parent instance ID");
+      return;
+    }
+  } else {
+    if (mTopLevelChildInstance) {
+      aRetval.set(mTopLevelChildInstance->GetInstanceObject());
+    } else {
+      JS_ReportErrorUTF8(aCx, "No top level child instance");
+      return;
+    }
+  }
+}
+
+void
+IPDL::GetTopLevelInstances(JSContext* aCx, nsTArray<JSObject*>& aRetval)
+{
+  // Return all instances depending on the side we're on.
+  if (mTopLevelChildInstance) {
+    aRetval.AppendElement(mTopLevelChildInstance->GetInstanceObject());
+  } else {
+    for (auto i = mTopLevelParentInstances.Iter(); !i.Done(); i.Next()) {
+      aRetval.AppendElement(i.Data()->GetInstanceObject());
+    }
+  }
+}
+
+void
+IPDL::RegisterProtocol(JSContext* aCx,
+                       const nsAString& aProtocolName,
+                       const nsAString& aProtocolURI,
+                       bool aEagerLoad)
+{
+  mProtocolURIs.Put(aProtocolName, nsString(aProtocolURI));
+  if (aEagerLoad) {
+    JS::RootedObject wrapper(aCx, GetWrapper());
+    auto lookup = mParsedProtocolClassTable.LookupForAdd(aProtocolName);
+    if (!lookup) {
+      RefPtr<ipdl::IPDLProtocol> newIPDLProtocol = NewIPDLProtocol(aProtocolName, wrapper, aCx);
+
+      if (!newIPDLProtocol) {
+        lookup.OrRemove();
+        return;
+      }
+
+      lookup.OrInsert([&newIPDLProtocol]() { return std::move(newIPDLProtocol); });
+    }
+  }
+}
+
+bool
+IPDL::DoResolve(JSContext* aCx,
+                JS::Handle<JSObject*> aObj,
+                JS::Handle<jsid> aId,
+                JS::MutableHandle<JS::PropertyDescriptor> aRv)
+{
+  // If it's not an ID, skip this.
+  if (!JSID_IS_STRING(aId)) {
+    return true;
+  }
+
+  // Get the property name.
+  nsAutoJSString propertyName;
+  if (!propertyName.init(aCx, JSID_TO_STRING(aId))) {
+    return true;
+  }
+
+  // Check it's not a real existing property.
+  if (IsRealProperty(propertyName)) {
+    return true;
+  }
+
+  // Otherwise, resolve the protocol property.
+  return ResolveProtocolProperty(propertyName, aObj, aCx, aRv);
+}
+
+void
+IPDL::GetOwnPropertyNames(JSContext* aCx,
+                          JS::AutoIdVector& aNames,
+                          bool aEnumerableOnly,
+                          mozilla::ErrorResult& aRv)
+{
+  if (aEnumerableOnly) {
+    return;
+  }
+
+  // We want to return the real properties + the existing protocol names we
+  // already parsed.
+  if (!aNames.initCapacity(mParsedProtocolIDs.Capacity() +
+                           sPropertyNamesLength)) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
+  // We add the protocol names.
+  JS::RootedId id(aCx);
+  for (auto& name : mParsedProtocolIDs) {
+    if (!JS_CharsToId(aCx, JS::TwoByteChars(name.get(), name.Length()), &id)) {
+      aRv.Throw(NS_ERROR_FAILURE);
+      return;
+    }
+    if (!aNames.append(id)) {
+      aRv.Throw(NS_ERROR_FAILURE);
+      return;
+    }
+  }
+
+  // We add the real properties.
+  for (auto& propertyName : sPropertyNames) {
+    if (!JS_CharsToId(
+          aCx,
+          JS::TwoByteChars(propertyName.get(), propertyName.Length()),
+          &id)) {
+      aRv.Throw(NS_ERROR_FAILURE);
+      return;
+    }
+    if (!aNames.append(id)) {
+      aRv.Throw(NS_ERROR_FAILURE);
+      return;
+    }
+  }
+}
+
+nsISupports*
+IPDL::GetParentObject()
+{
+  return mParentObject;
+}
+
+/* static */ bool
+IPDL::MayResolve(jsid aId)
+{
+  return true;
+}
+
+/* virtual */ JSObject*
+IPDL::WrapObject(JSContext* aCx, JS::HandleObject aGivenProto)
+{
+  return IPDL_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+/* static */ already_AddRefed<IPDL>
+IPDL::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+  RefPtr<IPDL> ipdl = new IPDL(global);
+  return ipdl.forget();
+}
+
+bool
+IPDL::IsRealProperty(const nsAString& aPropertyName)
+{
+  // Simple loop through the real property names.
+  for (auto& propertyName : sPropertyNames) {
+    if (aPropertyName.Equals(propertyName)) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+bool
+IPDL::ResolveProtocolProperty(const nsString& aProtocolPropertyName,
+                              JS::Handle<JSObject*> aObj,
+                              JSContext* aCx,
+                              JS::MutableHandle<JS::PropertyDescriptor> aRv)
+{
+  // If we haven't parsed this protocol yet, do it and create a new IPDLProtocol
+  // object.
+  auto ipdlProtocol = mParsedProtocolClassTable.LookupForAdd(aProtocolPropertyName);
+  if (!ipdlProtocol) {
+    RefPtr<ipdl::IPDLProtocol> newIPDLProtocol = NewIPDLProtocol(aProtocolPropertyName, aObj, aCx);
+
+    if (!newIPDLProtocol) {
+      ipdlProtocol.OrRemove();
+      return false;
+    }
+
+    ipdlProtocol.OrInsert([&newIPDLProtocol]() { return std::move(newIPDLProtocol); });
+  }
+
+  aRv.object().set(aObj);
+  aRv.value().setObject(*ipdlProtocol.Data()->GetProtocolClassConstructor());
+  aRv.setAttributes(0);
+  aRv.setGetter(nullptr);
+  aRv.setSetter(nullptr);
+
+  return true;
+}
+
+Maybe<ipdl::IPDLSide>
+IPDL::GetProtocolSide(const nsAString& aProtocolName)
+{
+  NS_NAMED_LITERAL_STRING(parentLiteral, "Parent");
+  NS_NAMED_LITERAL_STRING(childLiteral, "Child");
+
+  // Find the parent suffix.
+  if (StringEndsWith(aProtocolName, parentLiteral)) {
+    return Some(ipdl::IPDLSide::Parent);
+  }
+
+  // Find the child suffix.
+  if (StringEndsWith(aProtocolName, childLiteral)) {
+    return Some(ipdl::IPDLSide::Child);
+  }
+
+  return Nothing();
+}
+
+void
+IPDL::StripParentSuffix(const nsAString& aProtocolName, nsAString& aRetVal)
+{
+  NS_NAMED_LITERAL_STRING(parentLiteral, "Parent");
+  aRetVal = Substring(
+    aProtocolName, 0, aProtocolName.Length() - parentLiteral.Length());
+}
+
+void
+IPDL::StripChildSuffix(const nsAString& aProtocolName, nsAString& aRetVal)
+{
+  NS_NAMED_LITERAL_STRING(childLiteral, "Child");
+  aRetVal =
+    Substring(aProtocolName, 0, aProtocolName.Length() - childLiteral.Length());
+}
+
+ipdl::ipc::PContentChildIPCInterface*
+IPDL::GetOrInitChildInterface()
+{
+  if (!mChildInterface && ContentChild::GetSingleton()) {
+    mChildInterface.reset(new ipdl::ipc::PContentChildIPCInterface(
+      this, ContentChild::GetSingleton()));
+  }
+
+  return mChildInterface.get();
+}
+
+already_AddRefed<ipdl::IPDLProtocol>
+IPDL::NewIPDLProtocol(const nsAString& aProtocolName,
+                      JS::Handle<JSObject*> aObj,
+                      JSContext* aCx)
+{
+  auto ipdlSide = GetProtocolSide(aProtocolName);
+
+  nsAutoString unsidedProtocolName;
+  // Assign a name depending on the protocol side.
+  if (!ipdlSide) {
+    return nullptr;
+  }
+
+  switch (*ipdlSide) {
+    case ipdl::IPDLSide::Parent:
+      StripParentSuffix(aProtocolName, unsidedProtocolName);
+      break;
+
+    case ipdl::IPDLSide::Child:
+      GetOrInitChildInterface();
+      StripChildSuffix(aProtocolName, unsidedProtocolName);
+      break;
+
+    default:
+      return nullptr;
+  }
+
+  if (!mParsedProtocolIDs.AppendElement(aProtocolName)) {
+    return nullptr;
+  }
+
+  if (!mProtocolURIs.Contains(unsidedProtocolName)) {
+    return nullptr;
+  }
+
+  // Create our new IPDLProtocol object and return it.
+  auto newIPDLProtocol = MakeRefPtr<ipdl::IPDLProtocol>(
+    this,
+    *ipdlSide,
+    NS_ConvertUTF16toUTF8(mProtocolURIs.Get(unsidedProtocolName)),
+    xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx)),
+    aObj,
+    aCx);
+
+  // Some error during parsing or construction.
+  if (!newIPDLProtocol->GetProtocolClassConstructor()) {
+    return nullptr;
+  }
+
+  return newIPDLProtocol.forget();
+}
+
+JSObject*
+IPDL::NewTopLevelProtocolParent(JSContext* aCx,
+                                const Sequence<JS::Handle<JS::Value>>& aArgs,
+                                ContentParent* aCp)
+{
+  // If we don't already have an IPCInterface for this content parent, create
+  // it.
+  auto parentInterface = mParentInterfaces.LookupForAdd(aCp->ChildID()).OrInsert([this, aCp]() {
+    return new ipdl::ipc::PContentParentIPCInterface(this, aCp);
+  });
+
+  JS::RootedObject constructor(aCx, mTopLevelClassConstructor.get());
+
+  // Prepare the arguments into a vector.
+  JS::AutoValueVector argVector(aCx);
+  if (!argVector.initCapacity(aArgs.Length())) {
+    JS_ReportErrorUTF8(aCx, "Couldn't initialize argument vector");
+    return nullptr;
+  }
+
+  for (auto& arg : aArgs) {
+    if (!argVector.append(arg.get())) {
+      JS_ReportErrorUTF8(aCx, "Couldn't add argument to arg vector");
+      return nullptr;
+    }
+  }
+
+  // Create the protocol instance, add the IPCInterface to the
+  // IPDLProtocolInstance...
+  JS::RootedObject topLevelProtocol(aCx, JS_New(aCx, constructor, argVector));
+  auto* instance =
+    static_cast<ipdl::IPDLProtocolInstance*>(JS_GetPrivate(topLevelProtocol.get()));
+
+  if (!instance) {
+    JS_ReportErrorUTF8(aCx, "Couldn't get protocol instance object from private date field");
+    return nullptr;
+  }
+
+  instance->SetIPCInterface(parentInterface);
+
+  mTopLevelParentInstances.Put(aCp->ChildID(), instance);
+
+  return topLevelProtocol;
+}
+
+JSObject*
+IPDL::NewTopLevelProtocolChild(JSContext* aCx,
+                               const Sequence<JS::Handle<JS::Value>>& aArgs)
+{
+  JS::RootedObject constructor(aCx, mTopLevelClassConstructor.get());
+
+  // Prepare the arguments into a vector.
+  JS::AutoValueVector argVector(aCx);
+  if (!argVector.initCapacity(aArgs.Length())) {
+    JS_ReportErrorUTF8(aCx, "Couldn't initialize argument vector");
+    return nullptr;
+  }
+
+  for (auto& arg : aArgs) {
+    if (!argVector.append(arg.get())) {
+      JS_ReportErrorUTF8(aCx, "Couldn't add argument to arg vector");
+      return nullptr;
+    }
+  }
+
+  // Create the protocol instance, add the IPCInterface to the
+  // IPDLProtocolInstance...
+  JS::RootedObject topLevelProtocol(aCx, JS_New(aCx, constructor, argVector));
+  auto* instance =
+    static_cast<ipdl::IPDLProtocolInstance*>(JS_GetPrivate(topLevelProtocol.get()));
+
+  if (!instance) {
+    JS_ReportErrorUTF8(aCx, "Couldn't get protocol instance object from private date field");
+    return nullptr;
+  }
+
+  instance->SetIPCInterface(GetOrInitChildInterface());
+
+  mTopLevelChildInstance = instance;
+
+  return topLevelProtocol;
+}
+
+NS_IMETHODIMP
+IPDL::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData)
+{
+  nsDependentCString topic(aTopic);
+  if (topic.EqualsLiteral("ipc:content-created")) {
+    nsCOMPtr<nsIContentParent> cp = do_QueryInterface(aSubject);
+    AutoEntryScript aes(mParentObject, "New Content Parent");
+    NewTopLevelProtocolParent(
+      aes.cx(), Sequence<JS::HandleValue>(), cp->AsContentParent());
+  }
+
+  return NS_OK;
+}
+
+constexpr nsLiteralString IPDL::sPropertyNames[];
+constexpr size_t IPDL::sPropertyNamesLength;
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/ipc/IPDL.h
@@ -0,0 +1,154 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_IPDL__
+#define mozilla_dom_IPDL__
+
+#include "nsClassHashtable.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIObserver.h"
+#include "nsRefPtrHashtable.h"
+#include "nsDataHashtable.h"
+#include "nsWrapperCache.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/HoldDropJSObjects.h"
+
+class nsIGlobalObject;
+
+namespace mozilla {
+
+namespace ipdl {
+class IPDLProtocol;
+class IPDLProtocolInstance;
+enum class IPDLSide : bool;
+
+namespace ipc {
+class PContentParentIPCInterface;
+class PContentChildIPCInterface;
+} // ipc
+} // ipdl
+
+namespace dom {
+
+class ContentParent;
+
+class IPDL
+  : public nsIObserver
+  , public nsWrapperCache
+{
+protected:
+  virtual ~IPDL();
+
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(IPDL)
+  NS_DECL_NSIOBSERVER
+
+  explicit IPDL(nsIGlobalObject* aParentObject);
+
+  static already_AddRefed<IPDL> Constructor(const GlobalObject& aGlobal,
+                                            ErrorResult& aRv);
+
+  void SetupContentParent();
+
+  void RegisterProtocol(JSContext* aCx, const nsAString& aProtocolName, const nsAString& aProtocolURI, bool aEagerLoad);
+
+  void RegisterTopLevelClass(JSContext* aCx,
+                             JS::Handle<JSObject*> aClassConstructor);
+  void CreateTopLevelInstance(JSContext* aCx,
+                              const Sequence<JS::Handle<JS::Value>>& aArgs);
+  void GetTopLevelInstance(JSContext* aCx,
+                           const Optional<uint32_t>& aId,
+                           JS::MutableHandle<JSObject*> aRetval);
+  void GetTopLevelInstances(JSContext* aCx,
+                            nsTArray<JSObject*>& aRetval);
+
+  bool DoResolve(JSContext* aCx,
+                 JS::Handle<JSObject*> aObj,
+                 JS::Handle<jsid> aId,
+                 JS::MutableHandle<JS::PropertyDescriptor> aRv);
+
+  void GetOwnPropertyNames(JSContext* aCx,
+                           JS::AutoIdVector& aNames,
+                           bool aEnumerableOnly,
+                           ErrorResult& aRv);
+
+  nsISupports* GetParentObject();
+
+  nsIGlobalObject* GetGlobalObject() { return mParentObject; }
+
+  static bool MayResolve(jsid aId);
+
+  virtual JSObject* WrapObject(JSContext* aCx,
+                               JS::HandleObject aGivenProto) override;
+
+  ipdl::ipc::PContentParentIPCInterface* GetParentInterface(uint32_t aId)
+  {
+    return mParentInterfaces.Get(aId);
+  }
+
+  ipdl::ipc::PContentChildIPCInterface* GetChildInterface()
+  {
+    return mChildInterface.get();
+  }
+
+  JSObject* NewTopLevelProtocolParent(
+    JSContext* aCx,
+    const Sequence<JS::Handle<JS::Value>>& aArgs,
+    ContentParent* aCp);
+  JSObject* NewTopLevelProtocolChild(
+    JSContext* aCx,
+    const Sequence<JS::Handle<JS::Value>>& aArgs);
+
+protected:
+  bool IsRealProperty(const nsAString& aPropertyName);
+
+  bool ResolveProtocolProperty(const nsString& aProtocolPropertyName,
+                               JS::Handle<JSObject*> aObj,
+                               JSContext* aCx,
+                               JS::MutableHandle<JS::PropertyDescriptor> aRv);
+
+  Maybe<ipdl::IPDLSide> GetProtocolSide(const nsAString& aProtocolName);
+
+  void StripParentSuffix(const nsAString& aProtocolName, nsAString& aRetVal);
+  void StripChildSuffix(const nsAString& aProtocolName, nsAString& aRetVal);
+
+  ipdl::ipc::PContentChildIPCInterface* GetOrInitChildInterface();
+
+  already_AddRefed<ipdl::IPDLProtocol> NewIPDLProtocol(
+    const nsAString& aProtocolName,
+    JS::Handle<JSObject*> aObj,
+    JSContext* aCx);
+
+  typedef nsRefPtrHashtable<nsStringHashKey, ipdl::IPDLProtocol>
+    ProtocolClassTable;
+
+  ProtocolClassTable mParsedProtocolClassTable;
+  nsTArray<nsString> mParsedProtocolIDs;
+  nsIGlobalObject* MOZ_NON_OWNING_REF mParentObject;
+
+  nsClassHashtable<nsUint32HashKey, ipdl::ipc::PContentParentIPCInterface>
+    mParentInterfaces;
+  UniquePtr<ipdl::ipc::PContentChildIPCInterface> mChildInterface;
+
+  nsRefPtrHashtable<nsUint32HashKey, ipdl::IPDLProtocolInstance>
+    mTopLevelParentInstances;
+  RefPtr<ipdl::IPDLProtocolInstance> mTopLevelChildInstance;
+
+  JS::Heap<JSObject*> mTopLevelClassConstructor;
+
+  nsDataHashtable<nsStringHashKey, nsString> mProtocolURIs;
+
+  static constexpr const nsLiteralString sPropertyNames[] = {NS_LITERAL_STRING("registerTopLevelClass"), NS_LITERAL_STRING("getTopLevelInstance"), NS_LITERAL_STRING("getTopLevelInstances"), NS_LITERAL_STRING("registerProtocol")};
+  static constexpr size_t sPropertyNamesLength = ArrayLength(sPropertyNames);
+
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_IPDL__
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -821,16 +821,24 @@ parent:
     sync GetGfxVars() returns (GfxVarUpdate[] vars);
 
     sync ReadFontList() returns (FontListEntry[] retValue);
 
     sync SyncMessage(nsString aMessage, ClonedMessageData aData,
                      CpowEntry[] aCpows, Principal aPrincipal)
       returns (StructuredCloneData[] retval);
 
+    /**
+     * Send an IPDL sync message with args packed as a JS array in the input
+     * and the output ClonedMessageData.
+     */
+    sync SyncMessageIPDL(nsCString aProtocolName, uint32_t aChannelId,
+                         nsCString aMessage, ClonedMessageData aData)
+      returns (StructuredCloneData[] retval);
+
     nested(inside_sync) sync RpcMessage(nsString aMessage, ClonedMessageData aData,
                                         CpowEntry[] aCpows, Principal aPrincipal)
       returns (StructuredCloneData[] retval);
 
     async ShowAlert(nsIAlertNotification alert);
 
     async CloseAlert(nsString name, Principal principal);
 
@@ -1193,20 +1201,28 @@ parent:
      * belonging to an already detached subtree. The 'aMoveToBFCache'
      * paramater controls if detaching a BrowsingContext should move
      * it to the bfcache allowing it to be re-attached if navigated
      * to.
      */
     async DetachBrowsingContext(BrowsingContextId aContextId,
                                 bool aMoveToBFCache);
 both:
-     async AsyncMessage(nsString aMessage, CpowEntry[] aCpows,
+    async AsyncMessage(nsString aMessage, CpowEntry[] aCpows,
                         Principal aPrincipal, ClonedMessageData aData);
 
     /**
+     * Send an IPDL async message with args packed as a JS array in the input
+     * and the output ClonedMessageData.
+     */
+    async AsyncMessageIPDL(nsCString aProtocol, uint32_t aChannelId,
+                           nsCString aMessage, ClonedMessageData aData)
+      returns (StructuredCloneData[] retval);
+
+    /**
      * Notify `push-subscription-modified` observers in the parent and child.
      */
     async NotifyPushSubscriptionModifiedObservers(nsCString scope,
                                                   Principal principal);
 };
 
 }
 }
--- a/dom/ipc/moz.build
+++ b/dom/ipc/moz.build
@@ -35,16 +35,17 @@ EXPORTS.mozilla.dom += [
     'ContentBridgeParent.h',
     'ContentChild.h',
     'ContentParent.h',
     'ContentProcess.h',
     'ContentProcessHost.h',
     'ContentProcessManager.h',
     'CPOWManagerGetter.h',
     'FilePickerParent.h',
+    'IPDL.h',
     'MemoryReportRequest.h',
     'nsIContentChild.h',
     'nsIContentParent.h',
     'PermissionMessageUtils.h',
     'TabChild.h',
     'TabContext.h',
     'TabMessageUtils.h',
     'TabParent.h',
@@ -85,16 +86,17 @@ UNIFIED_SOURCES += [
     'TabMessageUtils.cpp',
     'TabParent.cpp',
     'URLClassifierParent.cpp',
 ]
 
 # ContentChild.cpp cannot be compiled in unified mode on  linux due to Time conflict
 SOURCES += [
     'ContentChild.cpp',
+    'IPDL.cpp',
     'ProcessHangMonitor.cpp',
 ]
 
 IPDL_SOURCES += [
     'DOMTypes.ipdlh',
     'MemoryReportTypes.ipdlh',
     'PBrowser.ipdl',
     'PBrowserOrId.ipdlh',
--- a/dom/webidl/Window.webidl
+++ b/dom/webidl/Window.webidl
@@ -435,16 +435,19 @@ partial interface Window {
    * defaultButton is the default button.
    */
   [Throws, Func="nsGlobalWindowInner::IsPrivilegedChromeWindow"]
   void notifyDefaultButtonLoaded(Element defaultButton);
 
   [Func="nsGlobalWindowInner::IsPrivilegedChromeWindow"]
   readonly attribute ChromeMessageBroadcaster messageManager;
 
+  [Func="nsGlobalWindowInner::IsPrivilegedChromeWindow"]
+  readonly attribute IPDL IPDL;
+
   /**
    * Returns the message manager identified by the given group name that
    * manages all frame loaders belonging to that group.
    */
   [Func="nsGlobalWindowInner::IsPrivilegedChromeWindow"]
   ChromeMessageBroadcaster getGroupMessageManager(DOMString aGroup);
 
   /**
--- a/ipc/ipdl/sync-messages.ini
+++ b/ipc/ipdl/sync-messages.ini
@@ -843,16 +843,18 @@ description = test only
 [PBrowser::GetSystemFont]
 description = test only
 [PBrowser::SetPrefersReducedMotionOverrideForTest]
 description = test only
 [PBrowser::ResetPrefersReducedMotionOverrideForTest]
 description = test only
 [PContent::SyncMessage]
 description =
+[PContent::SyncMessageIPDL]
+description =
 [PContent::CreateChildProcess]
 description =
 [PContent::BridgeToChildProcess]
 description =
 [PContent::OpenRecordReplayChannel]
 description = bug 1475898 this could be async
 [PContent::LoadPlugin]
 description =
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl_new/ipdl/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "ipdl"
+version = "0.1.0"
+authors = ["Tristan Bourvon <tristanbourvon@gmail.com>", "Andrew McCreight <continuation@gmail.com>"]
+license = "MPL-2.0"
+
+[dependencies]
+pipdl = { path = "../pipdl" }
+
+[lib]
+name = "ipdl"
+path = "src/lib.rs"
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl_new/ipdl/README.md
@@ -0,0 +1,5 @@
+# ipdl-rs
+
+Code taken from https://github.com/mystor/pipdl-rs has been written by Nika Layzell
+
+Code taken from https://github.com/amccreight/ipdl_parser has been written by Andrew McCreight
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl_new/ipdl/make_test_command.py
@@ -0,0 +1,58 @@
+#!/usr/bin/python3
+
+# 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/.
+
+# Test command generator for the IPDL parser.
+
+# 1. Add the following code to ipc/ipdl/ipdl.py, do a build, and copy
+# the output from the build, from INCLUDES to DONE, somewhere:
+#
+# print "INCLUDES"
+# for i in includedirs:
+#     print i
+# print "FILES"
+# for f in files:
+#     print f
+# print "DONE"
+
+# 2. Adjust leading_text_example as necessary, if the log timestamp
+# stuff has changed.
+
+# 3. Run this script on the output from step 1. This should produce a
+# command to run cargo with all of the files from step 1. You can run
+# it with bash or whatever.
+
+import sys
+
+# Used to decide how many characters to chop off the start.
+leading_text_example = " 0:02.39 "
+
+in_include = False
+in_files = False
+
+start_trim = len(leading_text_example)
+
+print("cargo run --"),
+
+for line in sys.stdin:
+    line = line [start_trim:-1]
+    if line.endswith("INCLUDES"):
+        in_include = True
+        continue
+    if line.endswith("FILES"):
+        assert in_include
+        in_include = False
+        in_files = True
+        continue
+    if line.endswith("DONE"):
+        assert in_files
+        exit(0)
+
+    if in_include:
+        print("-I", line),
+    elif in_files:
+        print(line),
+    else:
+        assert False
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl_new/ipdl/src/ast.rs
@@ -0,0 +1,285 @@
+/* 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/. */
+
+use std::fmt;
+use std::collections::HashMap;
+
+#[derive(Debug, Clone)]
+pub struct QualifiedId {
+    pub base_id: Identifier,
+    pub quals: Vec<String>,
+}
+
+impl QualifiedId {
+    pub fn new(base: Identifier) -> QualifiedId {
+        QualifiedId { base_id: base, quals: Vec::new() }
+    }
+
+    pub fn qualify(mut self, id: Identifier) -> QualifiedId {
+        self.quals.push(self.base_id.id);
+        self.base_id = id;
+        self
+    }
+
+    pub fn new_from_iter<'a, I> (mut ids: I) -> QualifiedId
+        where I: Iterator<Item=&'a str>
+    {
+        let loc = Location { file_name: String::from("<builtin>"), lineno: 0, colno: 0 };
+        let mut qual_id = QualifiedId::new(Identifier::new(String::from(ids.next().expect("Empty iterator when creating QID")), loc.clone()));
+        for i in ids {
+            qual_id = qual_id.qualify(Identifier::new(String::from(i), loc.clone()));
+        }
+        qual_id
+    }
+
+    pub fn short_name(&self) -> String {
+        self.base_id.to_string()
+    }
+
+    pub fn full_name(&self) -> Option<String> {
+        if self.quals.is_empty() {
+            None
+        } else {
+            Some(self.to_string())
+        }
+    }
+
+    pub fn loc(&self) -> &Location {
+        &self.base_id.loc
+    }
+}
+
+impl fmt::Display for QualifiedId {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        for q in &self.quals {
+            try!(write!(f, "{}::", q));
+        }
+        write!(f, "{}", self.base_id)
+    }
+}
+
+#[derive(Debug)]
+pub struct TypeSpec {
+    pub spec: QualifiedId,
+    pub array: bool,
+    pub nullable: bool,
+}
+
+impl TypeSpec {
+    pub fn new(spec: QualifiedId) -> TypeSpec {
+        TypeSpec { spec: spec, array: false, nullable: false }
+    }
+
+    pub fn loc(&self) -> &Location {
+        self.spec.loc()
+    }
+}
+
+#[derive(Debug)]
+pub struct Param {
+    pub name: Identifier,
+    pub type_spec: TypeSpec,
+}
+
+#[derive(Debug)]
+pub struct StructField {
+    pub type_spec: TypeSpec,
+    pub name: Identifier,
+}
+
+#[derive(Clone, Debug)]
+pub struct Namespace {
+    pub name: Identifier,
+    pub namespaces: Vec<String>,
+}
+
+impl Namespace {
+    pub fn qname(&self) -> QualifiedId {
+        QualifiedId { base_id: self.name.clone(), quals: self.namespaces.clone() }
+    }
+}
+
+#[derive(Debug, PartialEq, Clone, Copy)]
+pub enum Compress {
+    None,
+    Enabled,
+    All,
+}
+
+#[derive(Debug, PartialEq, Clone, Copy)]
+pub enum SendSemantics {
+    Async,
+    Sync,
+    Intr,
+}
+
+impl SendSemantics {
+    pub fn is_async(&self) -> bool {
+        self == &SendSemantics::Async
+    }
+
+    pub fn is_sync(&self) -> bool {
+        self == &SendSemantics::Sync
+    }
+
+    pub fn is_intr(&self) -> bool {
+        self == &SendSemantics::Intr
+    }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
+pub enum Nesting {
+    None,
+    InsideSync,
+    InsideCpow,
+}
+
+impl Nesting {
+    pub fn is_none(&self) -> bool {
+        self == &Nesting::None
+    }
+
+    pub fn inside_sync(&self) -> bool {
+        self == &Nesting::InsideSync
+    }
+
+    pub fn inside_cpow(&self) -> bool {
+        self == &Nesting::InsideCpow
+    }
+}
+
+#[derive(Debug, Clone, Copy)]
+pub enum Priority {
+    Normal,
+    High,
+    Input,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum Direction {
+    ToParent,
+    ToChild,
+    ToParentOrChild,
+}
+
+impl Direction {
+    pub fn is_to_child(&self) -> bool {
+        self == &Direction::ToChild
+    }
+
+    pub fn is_both(&self) -> bool {
+        self == &Direction::ToParentOrChild
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct Location {
+    pub file_name: String,
+    pub lineno: usize,
+    pub colno: usize,
+}
+
+impl fmt::Display for Location {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{}:{}:{}", self.file_name, self.lineno, self.colno)
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct Identifier {
+    pub id: String,
+    pub loc: Location,
+}
+
+impl Identifier {
+    pub fn new(name: String, loc: Location) -> Identifier {
+        Identifier {
+            id: name,
+            loc: loc,
+        }
+    }
+}
+
+impl fmt::Display for Identifier {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{}", self.id)
+    }
+}
+
+#[derive(Debug)]
+pub struct MessageDecl {
+    pub name: Identifier,
+    pub send_semantics: SendSemantics,
+    pub nested: Nesting,
+    pub prio: Priority,
+    pub direction: Direction,
+    pub in_params: Vec<Param>,
+    pub out_params: Vec<Param>,
+    pub compress: Compress,
+    pub verify: bool,
+}
+
+#[derive(Debug)]
+pub struct Protocol {
+    pub send_semantics: SendSemantics,
+    pub nested: Nesting,
+    pub managers: Vec<Identifier>,
+    pub manages: Vec<Identifier>,
+    pub messages: Vec<MessageDecl>,
+}
+
+#[derive(Debug)]
+pub enum CxxTypeKind {
+  Struct,
+  Class,
+}
+
+#[derive(Debug)]
+pub struct UsingStmt {
+    pub cxx_type: TypeSpec,
+    pub header: String,
+    pub kind: Option<CxxTypeKind>,
+    pub refcounted: bool,
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub enum FileType {
+    Protocol,
+    Header,
+}
+
+impl FileType {
+    pub fn from_file_path(file_path: &str) -> Option<FileType> {
+        if let Some(e) = file_path.rsplit('.').next() {
+            if e == "ipdlh" {
+                Some(FileType::Header)
+            } else {
+                Some(FileType::Protocol)
+            }
+        } else {
+            None
+        }
+    }
+}
+
+// Translation unit identifier.
+pub type TUId = i32;
+
+#[derive(Debug)]
+pub struct TranslationUnit {
+    pub namespace: Namespace,
+    pub file_type: FileType,
+    pub file_name: String,
+    pub cxx_includes: Vec<String>,
+    pub includes: Vec<TUId>,
+    pub using: Vec<UsingStmt>,
+    pub structs: Vec<(Namespace, Vec<StructField>)>,
+    pub unions: Vec<(Namespace, Vec<TypeSpec>)>,
+    pub protocol: Option<(Namespace, Protocol)>,
+}
+
+pub struct AST {
+    pub main_tuid: TUId,
+    pub translation_units: HashMap<TUId, TranslationUnit>,
+}
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl_new/ipdl/src/errors.rs
@@ -0,0 +1,68 @@
+/* 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/. */
+
+use ast::Location;
+use pipdl;
+use std::error::Error;
+use std::fmt;
+
+fn error_msg(loc: &Location, err: &str) -> String {
+    format!("{}: error: {}", loc, err)
+}
+
+#[must_use]
+pub struct Errors {
+    errors: Vec<String>,
+}
+
+impl From<pipdl::Error> for Errors {
+    fn from(error: pipdl::Error) -> Self {
+        Errors::one(
+            &Location {
+                file_name: error.span().start.file,
+                lineno: error.span().start.line,
+                colno: error.span().start.col,
+            },
+            error.description(),
+        )
+    }
+}
+
+impl fmt::Display for Errors {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        f.write_str(&self.errors.join("\n"))
+    }
+}
+
+impl Errors {
+    pub fn none() -> Errors {
+        Errors { errors: Vec::new() }
+    }
+
+    pub fn one(loc: &Location, err: &str) -> Errors {
+        Errors {
+            errors: vec![error_msg(&loc, &err)],
+        }
+    }
+
+    pub fn append(&mut self, mut other: Errors) {
+        self.errors.append(&mut other.errors);
+    }
+
+    pub fn append_one(&mut self, loc: &Location, other: &str) {
+        self.errors.push(error_msg(&loc, &other));
+    }
+
+    pub fn to_result(&self) -> Result<(), String> {
+        if self.errors.is_empty() {
+            Ok(())
+        } else {
+            Err(self.errors.join("\n"))
+        }
+    }
+
+    pub fn is_empty(&self) -> bool {
+        self.errors.is_empty()
+    }
+}
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl_new/ipdl/src/lib.rs
@@ -0,0 +1,12 @@
+/* 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/. */
+
+#![allow(unknown_lints)]
+
+extern crate pipdl;
+
+pub mod ast;
+mod errors;
+pub mod parser;
+mod passes;
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl_new/ipdl/src/parser.rs
@@ -0,0 +1,114 @@
+/* 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/. */
+
+use pipdl;
+
+use passes::include_resolution::IncludeResolver;
+use passes::parsetree_to_tu::ParseTreeToTU;
+use passes::type_check;
+
+use errors;
+
+use std::collections::HashMap;
+use std::hash::Hash;
+use std::str;
+
+use std::path::PathBuf;
+
+use ast::{Location, AST};
+
+pub trait OwnedSourceBuffer: Drop + AsRef<[u8]> {
+    fn to_utf8(&self) -> &str;
+}
+impl<T> OwnedSourceBuffer for T
+where
+    T: Drop + AsRef<[u8]>,
+{
+    fn to_utf8(&self) -> &str {
+        str::from_utf8(self.as_ref()).unwrap()
+    }
+}
+
+pub trait FileURI: Clone + Eq + Hash {
+    fn resolve_relative_path(&self, relative_path: &str) -> Result<Self, ()>;
+    fn to_utf8(&self) -> &str;
+}
+
+impl FileURI for PathBuf {
+    fn resolve_relative_path(&self, relative_path: &str) -> Result<Self, ()> {
+        let mut new_path = self.clone();
+        new_path.push(relative_path);
+        new_path.canonicalize().map_err(|_| ())
+    }
+
+    fn to_utf8(&self) -> &str {
+        self.to_str().unwrap()
+    }
+}
+
+#[derive(Clone)]
+pub struct ParseTree<T>
+where
+    T: FileURI,
+{
+    pub translation_unit: pipdl::Spanned<pipdl::TranslationUnit>,
+    pub file_path: T,
+}
+
+pub fn parse_file<F: Fn(&str) -> Result<Box<OwnedSourceBuffer>, ()>, T: FileURI>(
+    file_path: &T,
+    source_string_loader: &mut F,
+) -> Result<ParseTree<T>, errors::Errors> {
+    let file_path_str = file_path.to_utf8();
+    let file_text = source_string_loader(file_path_str).map_err(|()| {
+        errors::Errors::one(
+            &Location {
+                file_name: file_path_str.to_owned(),
+                lineno: 0,
+                colno: 0,
+            },
+            "Error loading source string",
+        )
+    })?;
+
+    let parse_tree = ParseTree {
+        translation_unit: pipdl::parse(file_text.to_utf8(), file_path_str)?,
+        file_path: file_path.clone(),
+    };
+
+    Ok(parse_tree)
+}
+
+pub fn parse<F: Fn(&str) -> Result<Box<OwnedSourceBuffer>, ()>, T: FileURI>(
+    file_path: &T,
+    include_dirs: &[T],
+    mut source_string_loader: F,
+) -> Result<AST, errors::Errors> {
+    let parse_tree = parse_file(file_path, &mut source_string_loader)?;
+
+    let mut include_resolver = IncludeResolver::new(include_dirs);
+
+    let (main_tuid, result) = include_resolver.resolve_includes(parse_tree, source_string_loader)?;
+
+    let parsetree_to_translation_unit = ParseTreeToTU::new(&include_resolver);
+
+    let ast = AST {
+        main_tuid,
+        translation_units: {
+            result
+                .into_iter()
+                .map(|(tuid, parse_tree)| {
+                    Ok((
+                        tuid,
+                        parsetree_to_translation_unit.parsetree_to_translation_unit(parse_tree)?,
+                    ))
+                })
+                .collect::<Result<HashMap<_, _>, errors::Errors>>()?
+        },
+    };
+
+    type_check::check(&ast.translation_units)?;
+
+    Ok(ast)
+}
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl_new/ipdl/src/passes/include_resolution.rs
@@ -0,0 +1,205 @@
+/* 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/. */
+
+use ast::{Location, TUId};
+use errors::Errors;
+use parser::{parse_file, FileURI, OwnedSourceBuffer, ParseTree};
+use std::collections::{HashMap, HashSet};
+
+pub struct IncludeResolver<'a, T: 'a>
+where
+    T: FileURI,
+{
+    include_dirs: &'a [T],
+    include_files: HashMap<String, T>,
+    id_file_map: TUIdFileMap<T>,
+}
+
+impl<'a, T> IncludeResolver<'a, T>
+where
+    T: FileURI,
+{
+    pub fn new(include_dirs: &'a [T]) -> IncludeResolver<'a, T> {
+        IncludeResolver {
+            include_dirs,
+            include_files: HashMap::new(),
+            id_file_map: TUIdFileMap::new(),
+        }
+    }
+
+    pub fn get_include(&self, include_name: &str) -> Option<TUId> {
+        match self.include_files.get(include_name) {
+            Some(ref path) => self.id_file_map.get_tuid(path),
+            None => None,
+        }
+    }
+
+    pub fn resolve_include<'b>(&'b mut self, include_name: &str) -> Option<TUId> {
+        if let Some(ref include_file_path) = self.include_files.get(include_name) {
+            return Some(self.id_file_map.resolve_file_name(include_file_path));
+        }
+
+        // XXX The Python parser also checks '' for some reason.
+        for include_dir in self.include_dirs {
+            let mut new_include_path = include_dir.clone();
+            new_include_path = match new_include_path.resolve_relative_path(include_name) {
+                Ok(inc_path) => inc_path,
+                Err(_) => continue,
+            };
+
+            let new_id = self.id_file_map.resolve_file_name(&new_include_path);
+            self.include_files
+                .insert(String::from(include_name), new_include_path);
+            return Some(new_id);
+        }
+
+        None
+    }
+
+    fn print_include_context(include_context: &[T]) {
+        for path in include_context {
+            println!("  in file included from `{}':", path.to_utf8());
+        }
+    }
+
+    #[allow(needless_pass_by_value)]
+    pub fn resolve_includes<F: Fn(&str) -> Result<Box<OwnedSourceBuffer>, ()>>(
+        &mut self,
+        parse_tree: ParseTree<T>,
+        mut source_string_loader: F,
+    ) -> Result<(TUId, HashMap<TUId, ParseTree<T>>), Errors> {
+        let mut work_list: Vec<(T, Vec<T>)> = Vec::new();
+        let mut parsed_files = HashMap::new();
+        let mut visited_files = HashSet::new();
+
+        let resolved_path = parse_tree
+            .file_path
+            .resolve_relative_path("")
+            .map_err(|()| {
+                Errors::one(
+                    &Location {
+                        file_name: parse_tree.file_path.to_utf8().to_owned(),
+                        lineno: 0,
+                        colno: 0,
+                    },
+                    "Could not resolve file path",
+                )
+            })?;
+
+        let file_id = self.id_file_map.resolve_file_name(&resolved_path);
+        visited_files.insert(file_id);
+        work_list.push((resolved_path.clone(), Vec::new()));
+
+        while !work_list.is_empty() {
+            let mut new_work_list = Vec::new();
+            for (curr_file, include_context) in work_list {
+                let curr_parse_tree = if curr_file == resolved_path {
+                    parse_tree.clone()
+                } else {
+                    match parse_file(&curr_file, &mut source_string_loader) {
+                        Ok(tu) => tu,
+                        Err(err) => {
+                            Self::print_include_context(&include_context);
+                            return Err(Errors::from(err));
+                        }
+                    }
+                };
+
+                let mut include_errors = Errors::none();
+
+                for include in &curr_parse_tree.translation_unit.data.includes {
+                    let include_filename = format!(
+                        "{}{}{}",
+                        include.data.id.data,
+                        ".ipdl",
+                        if include.data.protocol.is_some() {
+                            ""
+                        } else {
+                            "h"
+                        }
+                    );
+                    let include_id = match self.resolve_include(&include_filename) {
+                        Some(tuid) => tuid,
+                        None => {
+                            include_errors.append_one(
+                                &Location {
+                                    file_name: include_filename.clone(),
+                                    lineno: 0,
+                                    colno: 0,
+                                },
+                                &format!("Cannot resolve include {}", include_filename),
+                            );
+                            continue;
+                        }
+                    };
+
+                    if visited_files.contains(&include_id) {
+                        continue;
+                    }
+
+                    let mut new_include_context = include_context.clone();
+                    new_include_context.push(curr_file.clone());
+
+                    visited_files.insert(include_id);
+                    new_work_list.push((
+                        self.include_files
+                            .get(&include_filename)
+                            .expect("Resolve include is broken")
+                            .clone(),
+                        new_include_context,
+                    ));
+                }
+
+                if !include_errors.is_empty() {
+                    return Err(include_errors);
+                }
+
+                let curr_id = self.id_file_map.resolve_file_name(&curr_file);
+                parsed_files.insert(curr_id, curr_parse_tree);
+            }
+
+            work_list = new_work_list;
+        }
+
+        Ok((file_id, parsed_files))
+    }
+}
+
+pub struct TUIdFileMap<T>
+where
+    T: FileURI,
+{
+    next_id: TUId,
+    file_ids: HashMap<T, TUId>,
+    id_files: HashMap<TUId, T>,
+}
+
+impl<T> TUIdFileMap<T>
+where
+    T: FileURI,
+{
+    fn new() -> TUIdFileMap<T> {
+        TUIdFileMap {
+            next_id: 0,
+            file_ids: HashMap::new(),
+            id_files: HashMap::new(),
+        }
+    }
+
+    fn get_tuid(&self, path: &T) -> Option<TUId> {
+        self.file_ids.get(path).cloned()
+    }
+
+    fn resolve_file_name(&mut self, path: &T) -> TUId {
+        if let Some(&id) = self.file_ids.get(path) {
+            return id;
+        }
+
+        let id = self.next_id;
+        self.next_id += 1;
+        self.id_files.insert(id, path.clone());
+        self.file_ids.insert(path.clone(), id);
+        id
+    }
+}
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl_new/ipdl/src/passes/mod.rs
@@ -0,0 +1,7 @@
+/* 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/. */
+
+pub mod include_resolution;
+pub mod parsetree_to_tu;
+pub mod type_check;
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl_new/ipdl/src/passes/parsetree_to_tu.rs
@@ -0,0 +1,387 @@
+/* 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/. */
+
+use ast;
+use errors::Errors;
+use parser;
+use passes::include_resolution::IncludeResolver;
+use pipdl;
+
+pub struct ParseTreeToTU<'includedirs, T: 'includedirs> where T: parser::FileURI {
+    include_resolver: &'includedirs IncludeResolver<'includedirs, T>,
+}
+
+impl<'includedirs, T> ParseTreeToTU<'includedirs, T> where T: parser::FileURI {
+    pub fn new(include_resolver: &'includedirs IncludeResolver<'includedirs, T>) -> Self {
+        ParseTreeToTU { include_resolver }
+    }
+
+    pub fn parsetree_to_translation_unit(
+        &self,
+        parse_tree: parser::ParseTree<T>,
+    ) -> Result<ast::TranslationUnit, Errors> {
+        self.convert_translation_unit(parse_tree.translation_unit.data, parse_tree.file_path.to_utf8().to_owned())
+    }
+
+    fn convert_translation_unit(
+        &self,
+        pt_translation_unit: pipdl::TranslationUnit,
+        pt_filename: String,
+    ) -> Result<ast::TranslationUnit, Errors> {
+        let mut structs = Vec::new();
+        let mut unions = Vec::new();
+        let mut protocol = None;
+        let mut last_is_struct = false;
+
+        for item in pt_translation_unit.items {
+            match item {
+                pipdl::Item::Struct(struct_item) => {
+                    structs.push(self.convert_struct_item(struct_item.data));
+                    last_is_struct = true;
+                }
+                pipdl::Item::Union(union_item) => {
+                    unions.push(self.convert_union_item(union_item.data));
+                    last_is_struct = false;
+                }
+                pipdl::Item::Protocol(protocol_item) => match protocol {
+                    Some(_) => {
+                        return Err(Errors::one(
+                            &ast::Location {
+                                file_name: pt_filename,
+                                lineno: protocol_item.span.start.line,
+                                colno: protocol_item.span.start.col,
+                            },
+                            "only one protocol definition per file",
+                        ))
+                    }
+                    None => protocol = Some(self.convert_protocol_item(protocol_item.data)),
+                },
+            }
+        }
+
+        Ok(ast::TranslationUnit {
+            cxx_includes: pt_translation_unit
+                .cxx_includes
+                .into_iter()
+                .map(|x| x.data.file.data)
+                .collect(),
+
+            includes: pt_translation_unit
+                .includes
+                .into_iter()
+                .map(|x| {
+                    let filename = format!(
+                        "{}{}{}",
+                        x.data.id.data,
+                        ".ipdl",
+                        if x.data.protocol.is_some() { "" } else { "h" }
+                    );
+                    self.include_resolver
+                        .get_include(&filename)
+                        .expect("Cannot find include TUId when converting ParseTree to AST")
+                })
+                .collect(),
+
+            using: pt_translation_unit
+                .usings
+                .into_iter()
+                .map(|x| self.convert_using_stmt(x.data))
+                .collect(),
+
+            namespace: match &protocol {
+                Some(p) => p.0.clone(),
+                None => {
+                    // There's not really a canonical "thing" in headers. So
+                    // somewhat arbitrarily use the namespace of the last
+                    // interesting thing that was declared.
+                    if last_is_struct {
+                        structs.last().expect("last_is_struct is broken").0.clone()
+                    } else {
+                        match unions.last() {
+                            Some(u) => u.0.clone(),
+                            None => {
+                                return Err(Errors::one(
+                                    &ast::Location {
+                                        file_name: pt_filename,
+                                        lineno: 0,
+                                        colno: 0,
+                                    },
+                                    "file is empty",
+                                ))
+                            }
+                        }
+                    }
+                }
+            },
+            structs,
+            unions,
+            protocol,
+            file_type: ast::FileType::from_file_path(&pt_filename)
+                .expect("Cannot determine file type when converting parse tree to AST"),
+            file_name: pt_filename,
+        })
+    }
+
+    fn convert_using_stmt(&self, pt_using_stmt: pipdl::Using) -> ast::UsingStmt {
+        ast::UsingStmt {
+            cxx_type: self.convert_cxx_type(pt_using_stmt.ty.data),
+            header: pt_using_stmt.file.data,
+            kind: self.convert_cxx_type_kind(&pt_using_stmt.kind.data),
+            refcounted: pt_using_stmt.refcounted.is_some(),
+        }
+    }
+
+    fn convert_cxx_type_kind(&self, pt_type_kind: &pipdl::CxxTypeKind) -> Option<ast::CxxTypeKind> {
+        match pt_type_kind {
+            pipdl::CxxTypeKind::Class => Some(ast::CxxTypeKind::Class),
+            pipdl::CxxTypeKind::Struct => Some(ast::CxxTypeKind::Struct),
+            pipdl::CxxTypeKind::None => None,
+        }
+    }
+
+    fn convert_cxx_type(&self, pt_cxx_type: pipdl::CxxPath) -> ast::TypeSpec {
+        ast::TypeSpec {
+            spec: self.convert_cxx_path(pt_cxx_type),
+            array: false,
+            nullable: false,
+        }
+    }
+
+    fn convert_cxx_path(&self, mut pt_cxx_path: pipdl::CxxPath) -> ast::QualifiedId {
+        ast::QualifiedId {
+            base_id: self.convert_cxx_path_seg(
+                pt_cxx_path
+                    .segs
+                    .pop()
+                    .expect("Empty path when converting into QualifiedId")
+                    .data,
+            ),
+            quals: pt_cxx_path
+                .segs
+                .into_iter()
+                .map(|x| self.convert_cxx_path_seg(x.data).id)
+                .collect(),
+        }
+    }
+
+    fn convert_cxx_path_seg(&self, pt_cxx_path_seg: pipdl::CxxPathSeg) -> ast::Identifier {
+        ast::Identifier {
+            id: match pt_cxx_path_seg.args {
+                Some(args) => format!(
+                    "{}<{}>",
+                    pt_cxx_path_seg.id.data,
+                    args.data
+                        .into_iter()
+                        .map(|x| x.data)
+                        .collect::<Vec<String>>()
+                        .concat()
+                ),
+                None => pt_cxx_path_seg.id.data,
+            },
+            loc: self.convert_location(pt_cxx_path_seg.id.span.start),
+        }
+    }
+
+    fn convert_location(&self, pt_location: pipdl::Location) -> ast::Location {
+        ast::Location {
+            file_name: pt_location.file,
+            lineno: pt_location.line,
+            colno: pt_location.col,
+        }
+    }
+
+    fn convert_struct_item(
+        &self,
+        pt_struct_item: pipdl::StructItem,
+    ) -> (ast::Namespace, Vec<ast::StructField>) {
+        (
+            self.convert_path(pt_struct_item.path),
+            pt_struct_item
+                .fields
+                .into_iter()
+                .map(|x| self.convert_struct_field(x.data))
+                .collect(),
+        )
+    }
+
+    fn convert_path(&self, mut pt_path: Vec<pipdl::Spanned<String>>) -> ast::Namespace {
+        ast::Namespace {
+            name: self.convert_name(
+                pt_path
+                    .pop()
+                    .expect("Empty path when converting into Namespace"),
+            ),
+            namespaces: pt_path.into_iter().map(|x| x.data).collect(),
+        }
+    }
+
+    fn convert_struct_field(&self, pt_struct_field: pipdl::Field) -> ast::StructField {
+        ast::StructField {
+            type_spec: self.convert_type(pt_struct_field.ty.data),
+            name: self.convert_name(pt_struct_field.name),
+        }
+    }
+
+    fn convert_name(&self, pt_name: pipdl::Spanned<String>) -> ast::Identifier {
+        ast::Identifier {
+            id: pt_name.data,
+            loc: self.convert_location(pt_name.span.start),
+        }
+    }
+
+    fn convert_type(&self, pt_type: pipdl::Type) -> ast::TypeSpec {
+        ast::TypeSpec {
+            spec: ast::QualifiedId::new(self.convert_cxx_path_seg(pt_type.name.data)),
+            array: pt_type.is_array.is_some(),
+            nullable: pt_type.is_nullable.is_some(),
+        }
+    }
+
+    fn convert_union_item(
+        &self,
+        pt_union_item: pipdl::UnionItem,
+    ) -> (ast::Namespace, Vec<ast::TypeSpec>) {
+        (
+            self.convert_path(pt_union_item.path),
+            pt_union_item
+                .components
+                .into_iter()
+                .map(|x| self.convert_type(x.data))
+                .collect(),
+        )
+    }
+
+    fn convert_protocol_item(
+        &self,
+        pt_protocol_item: pipdl::ProtocolItem,
+    ) -> (ast::Namespace, ast::Protocol) {
+        (
+            self.convert_path(pt_protocol_item.path),
+            ast::Protocol {
+                send_semantics: self.convert_send_semantics(&pt_protocol_item.send_semantics.data),
+                nested: self.convert_nesting(pt_protocol_item.nested),
+                managers: match pt_protocol_item.managers {
+                    Some(managers) => managers
+                        .data
+                        .into_iter()
+                        .map(|x| self.convert_name(x))
+                        .collect(),
+                    None => Vec::new(),
+                },
+                manages: pt_protocol_item
+                    .manages
+                    .into_iter()
+                    .map(|x| self.convert_name(x.data))
+                    .collect(),
+                messages: pt_protocol_item
+                    .groups
+                    .into_iter()
+                    .flat_map(|group| {
+                        let (decls, direction) = (group.data.decls, group.data.direction.data);
+                        decls
+                            .into_iter()
+                            .map(move |x| (x.data, direction.clone()))
+                            .map(|(message_decl, direction)| {
+                                self.convert_message_decl(message_decl, &direction)
+                            })
+                    })
+                    .collect(),
+            },
+        )
+    }
+
+    fn convert_message_decl(
+        &self,
+        pt_message_decl: pipdl::MessageDecl,
+        pt_direction: &pipdl::Direction,
+    ) -> ast::MessageDecl {
+        let mut compress = ast::Compress::None;
+        let mut verify = false;
+
+        for modifier in pt_message_decl.modifiers {
+            match modifier.data {
+                pipdl::MessageModifier::Compress => compress = ast::Compress::Enabled,
+                pipdl::MessageModifier::CompressAll => compress = ast::Compress::All,
+                pipdl::MessageModifier::Verify => verify = true,
+            }
+        }
+
+        ast::MessageDecl {
+            name: self.convert_name(pt_message_decl.name),
+            send_semantics: self.convert_send_semantics(&pt_message_decl.send_semantics.data),
+            nested: self.convert_nesting(pt_message_decl.nested),
+            prio: self.convert_priority(pt_message_decl.priority),
+            direction: self.convert_direction(&pt_direction),
+            in_params: pt_message_decl
+                .params
+                .into_iter()
+                .map(|x| self.convert_param(x.data))
+                .collect(),
+            out_params: match pt_message_decl.returns {
+                Some(returns) => returns
+                    .data
+                    .into_iter()
+                    .map(|x| self.convert_param(x.data))
+                    .collect(),
+                None => Vec::new(),
+            },
+            compress,
+            verify,
+        }
+    }
+
+    fn convert_param(&self, pt_param: pipdl::Param) -> ast::Param {
+        ast::Param {
+            name: self.convert_name(pt_param.name),
+            type_spec: self.convert_type(pt_param.ty.data),
+        }
+    }
+
+    fn convert_send_semantics(
+        &self,
+        pt_send_semantics: &pipdl::SendSemantics,
+    ) -> ast::SendSemantics {
+        match pt_send_semantics {
+            pipdl::SendSemantics::Async => ast::SendSemantics::Async,
+            pipdl::SendSemantics::Sync => ast::SendSemantics::Sync,
+            pipdl::SendSemantics::Intr => ast::SendSemantics::Intr,
+        }
+    }
+
+    fn convert_nesting(
+        &self,
+        pt_nesting: Option<pipdl::Spanned<pipdl::Spanned<pipdl::Nesting>>>,
+    ) -> ast::Nesting {
+        match pt_nesting {
+            Some(x) => match x.data.data {
+                pipdl::Nesting::None => ast::Nesting::None,
+                pipdl::Nesting::InsideCpow => ast::Nesting::InsideCpow,
+                pipdl::Nesting::InsideSync => ast::Nesting::InsideSync,
+            },
+            None => ast::Nesting::None,
+        }
+    }
+
+    fn convert_priority(
+        &self,
+        pt_priority: Option<pipdl::Spanned<pipdl::Spanned<pipdl::Priority>>>,
+    ) -> ast::Priority {
+        match pt_priority {
+            Some(x) => match x.data.data {
+                pipdl::Priority::Normal => ast::Priority::Normal,
+                pipdl::Priority::High => ast::Priority::High,
+                pipdl::Priority::Input => ast::Priority::Input,
+            },
+            None => ast::Priority::Normal,
+        }
+    }
+
+    fn convert_direction(&self, pt_direction: &pipdl::Direction) -> ast::Direction {
+        match pt_direction {
+            pipdl::Direction::ToChild => ast::Direction::ToChild,
+            pipdl::Direction::ToParent => ast::Direction::ToParent,
+            pipdl::Direction::Both => ast::Direction::ToParentOrChild,
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl_new/ipdl/src/passes/type_check.rs
@@ -0,0 +1,1207 @@
+/* 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/. */
+
+use std::collections::{HashMap, HashSet};
+use ast::*;
+use errors::Errors;
+
+
+const BUILTIN_TYPES: &[&str] = &[
+    // C types
+    "bool",
+    "char",
+    "short",
+    "int",
+    "long",
+    "float",
+    "double",
+
+    // stdint types
+    "int8_t",
+    "uint8_t",
+    "int16_t",
+    "uint16_t",
+    "int32_t",
+    "uint32_t",
+    "int64_t",
+    "uint64_t",
+    "intptr_t",
+    "uintptr_t",
+
+    // stddef types
+    "size_t",
+    "ssize_t",
+
+    // Mozilla types: "less" standard things we know how serialize/deserialize
+    "nsresult",
+    "nsString",
+    "nsCString",
+    "nsDependentSubstring",
+    "nsDependentCSubstring",
+    "mozilla::ipc::Shmem",
+    "mozilla::ipc::ByteBuf",
+    "mozilla::ipc::FileDescriptor",
+];
+
+fn builtin_from_string(tname: &str) -> TypeSpec {
+    TypeSpec::new(QualifiedId::new_from_iter(tname.split("::")))
+}
+
+const DELETE_MESSAGE_NAME: &str = "__delete__";
+const CONSTRUCTOR_SUFFIX: &str = "Constructor";
+
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+struct TypeRef {
+    tu: TUId,
+    index: usize,
+}
+
+impl TypeRef {
+    fn new(tu: TUId, index: usize) -> TypeRef {
+        TypeRef { tu, index }
+    }
+
+    fn lookup_struct<'a>(&self,
+                         tuts: &'a HashMap<TUId, TranslationUnitType>) -> &'a StructDef {
+        &tuts.get(&self.tu).unwrap().structs[self.index]
+    }
+
+    fn lookup_union<'a>(&self,
+                         tuts: &'a HashMap<TUId, TranslationUnitType>) -> &'a UnionDef {
+        &tuts.get(&self.tu).unwrap().unions[self.index]
+    }
+}
+
+// XXX The Python compiler has "Type" and a subclass "IPDLType". I
+// don't know how useful it is to split them. Plus my notion of type
+// may be different.
+#[derive(Debug, Clone)]
+enum IPDLType {
+    ImportedCxx(QualifiedId, bool /* refcounted */),
+    Message(TypeRef),
+    Protocol(TUId),
+    Actor(TUId, bool /* nullable */),
+    Struct(TypeRef),
+    Union(TypeRef),
+    Array(Box<IPDLType>),
+    Shmem(QualifiedId),
+    ByteBuf(QualifiedId),
+    FD(QualifiedId),
+    Endpoint(QualifiedId),
+}
+
+
+impl IPDLType {
+    fn type_name(&self) -> &'static str {
+        match self {
+            IPDLType::ImportedCxx(_, _) => "imported C++ type",
+            IPDLType::Message(_) => "message type",
+            IPDLType::Protocol(_) => "protocol type",
+            IPDLType::Actor(_, _) => "actor type",
+            IPDLType::Struct(_) => "struct type",
+            IPDLType::Union(_) => "union type",
+            IPDLType::Array(_) => "array type",
+            IPDLType::Shmem(_) => "shmem type",
+            IPDLType::ByteBuf(_) => "bytebuf type",
+            IPDLType::FD(_) => "fd type",
+            IPDLType::Endpoint(_) => "endpoint type",
+        }
+    }
+
+    fn canonicalize(&self, type_spec: &TypeSpec) -> (Errors, IPDLType) {
+        let mut errors = Errors::none();
+        let mut itype = self.clone();
+
+        if let IPDLType::Protocol(p) = *self {
+            itype = IPDLType::Actor(p, type_spec.nullable)
+        }
+
+        match itype {
+            IPDLType::Actor(_, _) => (),
+            _ => {
+                if type_spec.nullable {
+                    errors.append_one(type_spec.loc(),
+                                      &format!("`nullable' qualifier for {} makes no sense", itype.type_name()));
+                }
+            }
+        }
+
+        if type_spec.array {
+            itype = IPDLType::Array(Box::new(itype))
+        }
+
+        (errors, itype)
+    }
+
+    fn is_refcounted(&self) -> bool {
+        match *self {
+            IPDLType::ImportedCxx(_, refcounted) => refcounted,
+            _ => false,
+        }
+    }
+}
+
+#[derive(Debug, Clone)]
+struct StructDef {
+    qname: QualifiedId,
+    fields: Vec<IPDLType>,
+}
+
+impl StructDef {
+    fn new(ns: &Namespace) -> StructDef {
+        StructDef { qname: ns.qname(), fields: Vec::new() }
+    }
+
+    fn append_field(&mut self, field_type: IPDLType) {
+        self.fields.push(field_type)
+    }
+}
+
+#[derive(Debug, Clone)]
+struct UnionDef {
+    qname: QualifiedId,
+    components: Vec<IPDLType>,
+}
+
+impl UnionDef {
+    fn new(ns: &Namespace) -> UnionDef {
+        UnionDef { qname: ns.qname(), components: Vec::new() }
+    }
+
+    fn append_component(&mut self, component_type: IPDLType) {
+        self.components.push(component_type)
+    }
+}
+
+#[derive(Debug, Clone)]
+enum Message {
+    Ctor(TUId),
+    Dtor(TUId),
+    Other,
+}
+
+impl Message {
+    fn is_ctor(&self) -> bool {
+        match self {
+            Message::Ctor(_) => true,
+            _ => false,
+        }
+    }
+
+    fn constructed_type(&self) -> TUId {
+        match *self {
+            Message::Ctor(tuid) => tuid,
+            _ => panic!("Tried to get constructed type on non-Ctor"),
+        }
+    }
+
+    fn is_dtor(&self) -> bool {
+        match self {
+            Message::Dtor(_) => true,
+            _ => false,
+        }
+    }
+}
+
+
+struct MessageStrength {
+    send_semantics: SendSemantics,
+    nested_min: Nesting,
+    nested_max: Nesting,
+}
+
+impl MessageStrength {
+    fn converts_to(&self, other: &MessageStrength) -> bool {
+        if self.nested_min < other.nested_min {
+            return false;
+        }
+
+        if self.nested_max > other.nested_max {
+            return false;
+        }
+
+        // Protocols that use intr semantics are not allowed to use
+        // message nesting.
+        if other.send_semantics.is_intr() {
+            return self.nested_min.is_none() && self.nested_max.is_none();
+        }
+
+        match self.send_semantics {
+            SendSemantics::Async => true,
+            SendSemantics::Sync => !other.send_semantics.is_async(),
+            SendSemantics::Intr => false,
+        }
+    }
+}
+
+#[derive(Debug, Clone)]
+struct ParamTypeDef {
+    name: Identifier,
+    param_type: IPDLType,
+}
+
+#[derive(Debug, Clone)]
+struct MessageDef {
+    name: Identifier,
+    send_semantics: SendSemantics,
+    nested: Nesting,
+    prio: Priority,
+    direction: Direction,
+    params: Vec<ParamTypeDef>,
+    returns: Vec<ParamTypeDef>,
+    mtype: Message,
+    compress: Compress,
+    verify: bool,
+}
+
+impl MessageDef {
+    fn new(md: &MessageDecl, name: &str, mtype: Message) -> MessageDef {
+        assert!(!mtype.is_ctor() || name.ends_with(CONSTRUCTOR_SUFFIX));
+        MessageDef {
+            name: Identifier::new(String::from(name), md.name.loc.clone()),
+            send_semantics: md.send_semantics, nested: md.nested, prio: md.prio,
+            direction: md.direction, params: Vec::new(), returns: Vec::new(),
+            mtype, compress: md.compress, verify: md.verify
+        }
+    }
+
+    fn is_ctor(&self) -> bool {
+        self.mtype.is_ctor()
+    }
+
+    fn constructed_type(&self) -> TUId {
+        self.mtype.constructed_type()
+    }
+
+    fn is_dtor(&self) -> bool {
+        self.mtype.is_dtor()
+    }
+
+    fn message_strength(&self) -> MessageStrength {
+        MessageStrength {
+            send_semantics: self.send_semantics,
+            nested_min: self.nested,
+            nested_max: self.nested,
+        }
+    }
+
+    fn converts_to(&self, protocol: &ProtocolDef) -> bool {
+        self.message_strength().converts_to(&protocol.message_strength())
+    }
+
+    pub fn is_async(&self) -> bool {
+        self.send_semantics.is_async()
+    }
+
+    pub fn is_sync(&self) -> bool {
+        self.send_semantics.is_sync()
+    }
+
+    pub fn is_intr(&self) -> bool {
+        self.send_semantics.is_intr()
+    }
+}
+
+#[derive(Debug, Clone)]
+struct ProtocolDef {
+    qname: QualifiedId,
+    send_semantics: SendSemantics,
+    nested: Nesting,
+    managers: Vec<TUId>,
+    manages: Vec<TUId>,
+    messages: Vec<MessageDef>,
+    has_delete: bool,
+    has_reentrant_delete: bool,
+}
+
+impl ProtocolDef {
+    fn new(&(ref ns, ref p): &(Namespace, Protocol)) -> ProtocolDef {
+        ProtocolDef {
+            qname: ns.qname(), send_semantics: p.send_semantics, nested: p.nested,
+            managers: Vec::new(), manages: Vec::new(), messages: Vec::new(),
+            has_delete: false, has_reentrant_delete: false,
+        }
+    }
+
+    fn is_top_level(&self) -> bool {
+        self.managers.is_empty()
+    }
+
+    fn message_strength(&self) -> MessageStrength {
+        MessageStrength {
+            send_semantics: self.send_semantics,
+            nested_min: Nesting::None,
+            nested_max: self.nested,
+        }
+    }
+
+    fn converts_to(&self, other: &ProtocolDef) -> bool {
+        self.message_strength().converts_to(&other.message_strength())
+    }
+}
+
+#[derive(Debug, Clone)]
+struct Decl {
+    loc: Location,
+    decl_type: IPDLType,
+    short_name: String,
+    full_name: Option<String>,
+}
+
+// The Python version also has a "progname" field, but I don't see any
+// reason to keep that separate from the short_name field.
+
+impl Decl {
+    fn new(loc: &Location, decl_type: IPDLType, short_name: String) -> Decl {
+        Decl {
+            loc: loc.clone(), decl_type,
+            short_name, full_name: None
+        }
+    }
+
+    fn new_from_qid(qid: &QualifiedId, decl_type: IPDLType) -> Decl {
+        Decl {
+            loc: qid.loc().clone(), decl_type,
+            short_name: qid.short_name(), full_name: qid.full_name()
+        }
+    }
+}
+
+struct SymbolTable {
+    scopes: Vec<HashMap<String, Decl>>
+}
+
+impl SymbolTable {
+    fn new() -> SymbolTable {
+        SymbolTable { scopes: vec![HashMap::new()] }
+    }
+
+    fn enter_scope(&mut self) {
+        self.scopes.push(HashMap::new())
+    }
+
+    fn exit_scope(&mut self) {
+        self.scopes.pop().unwrap();
+        ()
+    }
+
+    // XXX Should/can this return a reference?
+    fn lookup(&self, sym: &str) -> Option<Decl> {
+        for s in &self.scopes {
+            if let Some(e) = s.get(sym) {
+                return Some(e.clone())
+            }
+        }
+        None
+    }
+
+    fn declare_inner(&mut self, name: &str, decl: Decl) -> Errors {
+        if let Some(old_decl) = self.lookup(name) {
+            return Errors::one(&decl.loc,
+                               &format!("redeclaration of symbol `{}', first declared at {}",
+                                        name, old_decl.loc))
+        }
+
+        let old_binding = self.scopes.last_mut().unwrap().insert(String::from(name), decl);
+        assert!(old_binding.is_none());
+        Errors::none()
+    }
+
+    fn declare(&mut self, decl: &Decl) -> Errors {
+        let mut errors = self.declare_inner(&decl.short_name, decl.clone());
+        if let Some(ref full_name) = decl.full_name {
+            errors.append(self.declare_inner(full_name, decl.clone()));
+        }
+        errors
+    }
+}
+
+fn declare_cxx_type(sym_tab: &mut SymbolTable, cxx_type: &TypeSpec, refcounted: bool) -> Errors {
+    let ipdl_type = match cxx_type.spec.full_name() {
+        Some(ref n) if n == "mozilla::ipc::Shmem" =>
+            IPDLType::Shmem(cxx_type.spec.clone()),
+        Some(ref n) if n == "mozilla::ipc::ByteBuf" =>
+            IPDLType::ByteBuf(cxx_type.spec.clone()),
+        Some(ref n) if n == "mozilla::ipc::FileDescriptor" =>
+            IPDLType::FD(cxx_type.spec.clone()),
+        _ => {
+            let ipdl_type = IPDLType::ImportedCxx(cxx_type.spec.clone(), refcounted);
+            let full_name = format!("{}", cxx_type.spec);
+            if let Some(decl) = sym_tab.lookup(&full_name) {
+                if let Some(existing_type) = decl.full_name {
+                    if existing_type == full_name {
+                        if refcounted != decl.decl_type.is_refcounted() {
+                            return Errors::one(&cxx_type.loc(),
+                                               &format!("inconsistent refcounted status of type `{}', first declared at {}",
+                                                        full_name, decl.loc))
+                        }
+                        // This type has already been added, so don't do anything.
+                        return Errors::none()
+                    }
+                };
+            };
+            ipdl_type
+        },
+    };
+    sym_tab.declare(&Decl::new_from_qid(&cxx_type.spec, ipdl_type))
+}
+
+struct TranslationUnitType {
+    pub structs: Vec<StructDef>,
+    pub unions: Vec<UnionDef>,
+    pub protocol: Option<ProtocolDef>,
+}
+
+impl TranslationUnitType {
+    fn new(maybe_protocol: &Option<(Namespace, Protocol)>) -> TranslationUnitType {
+        let protocol = maybe_protocol.as_ref().map(|ref p| ProtocolDef::new(&p));
+        TranslationUnitType { structs: Vec::new(), unions: Vec::new(), protocol }
+    }
+}
+
+
+fn declare_protocol(sym_tab: &mut SymbolTable,
+                    tuid: TUId,
+                    ns: &Namespace) -> Errors {
+    let mut errors = Errors::none();
+
+    let p_type = IPDLType::Protocol(tuid);
+    errors.append(sym_tab.declare(&Decl::new_from_qid(&ns.qname(), p_type)));
+
+    let loc = &ns.name.loc;
+    let mut declare_endpoint = |side: String| {
+        let full_id = Identifier::new(format!("Endpoint<{}{}>", ns.qname(), side), loc.clone());
+        let namespaces = vec!["mozilla".to_string(), "ipc".to_string()];
+        let full_qid = QualifiedId { base_id: full_id, quals: namespaces };
+        let short_name = format!("Endpoint<{}{}>", ns.name.id, side);
+        sym_tab.declare(&Decl::new(loc, IPDLType::Endpoint(full_qid), short_name))
+    };
+    errors.append(declare_endpoint("Parent".to_string()));
+    errors.append(declare_endpoint("Child".to_string()));
+
+    errors
+}
+
+
+fn declare_usings(mut sym_tab: &mut SymbolTable,
+                  tu: &TranslationUnit) -> Errors {
+    let mut errors = Errors::none();
+    for u in &tu.using {
+        errors.append(declare_cxx_type(&mut sym_tab, &u.cxx_type, u.refcounted));
+    }
+    errors
+}
+
+
+fn declare_structs_and_unions(sym_tab: &mut SymbolTable,
+                              tuid: TUId,
+                              tu: &TranslationUnit) -> Errors {
+    let mut errors = Errors::none();
+    let mut index = 0;
+
+    for s in &tu.structs {
+        let s_type = IPDLType::Struct(TypeRef::new(tuid, index));
+        errors.append(sym_tab.declare(&Decl::new_from_qid(&s.0.qname(), s_type)));
+        index += 1;
+    }
+
+    index = 0;
+    for u in &tu.unions {
+        let u_type = IPDLType::Union(TypeRef::new(tuid, index));
+        errors.append(sym_tab.declare(&Decl::new_from_qid(&u.0.qname(), u_type)));
+        index += 1;
+    }
+
+    errors
+}
+
+
+fn gather_decls_struct(sym_tab: &mut SymbolTable,
+                       &(ref ns, ref sd): &(Namespace, Vec<StructField>),
+                       sdef: &mut StructDef) -> Errors {
+    let mut errors = Errors::none();
+
+    sym_tab.enter_scope();
+
+    for f in sd {
+        let fty_string = f.type_spec.spec.to_string();
+        let fty_decl = sym_tab.lookup(&fty_string);
+        if fty_decl.is_none() {
+            errors.append_one(&f.name.loc,
+                              &format!("field `{}' of struct `{}' has unknown type `{}'",
+                                       f.name, ns.qname().short_name(), fty_string));
+            continue;
+        }
+        let (errors2, f_type) = fty_decl.unwrap().decl_type.canonicalize(&f.type_spec);
+        errors.append(errors2);
+
+        errors.append(sym_tab.declare(&Decl::new(&f.name.loc, f_type.clone(), f.name.id.clone())));
+        sdef.append_field(f_type);
+    }
+
+    sym_tab.exit_scope();
+
+    errors
+}
+
+
+fn gather_decls_union(sym_tab: &mut SymbolTable,
+                      &(ref ns, ref ud): &(Namespace, Vec<TypeSpec>),
+                      udef: &mut UnionDef) -> Errors {
+    let mut errors = Errors::none();
+
+    for c in ud {
+        let c_string = c.spec.to_string();
+        let c_decl = sym_tab.lookup(&c_string);
+        if c_decl.is_none() {
+            errors.append_one(c.loc(),
+                              &format!("unknown component type `{}' of union `{}'",
+                                       c_string, ns.qname().short_name()));
+            continue;
+        }
+        let (errors2, c_ty) = c_decl.unwrap().decl_type.canonicalize(&c);
+        errors.append(errors2);
+        udef.append_component(c_ty);
+    }
+
+    errors
+}
+
+
+fn gather_decls_manager(sym_tab: &mut SymbolTable,
+                      managee: &(Namespace, Protocol),
+                      managee_type: &mut ProtocolDef,
+                      manager: &Identifier) -> Errors {
+    let manager_decl = match sym_tab.lookup(&manager.id) {
+        Some(decl) => decl,
+        None => return Errors::one(&manager.loc,
+                                   &format!("protocol `{}' referenced as |manager| of `{}' has not been declared",
+                                            manager.id, managee.0.qname().short_name())),
+    };
+
+    if let IPDLType::Protocol(ref pt) = &manager_decl.decl_type {
+        managee_type.managers.push(pt.clone());
+        return Errors::none();
+    }
+
+    Errors::one(&manager.loc,
+                &format!("entity `{}' referenced as |manager| of `{}' is not of `protocol' type; instead it is a {}",
+                        manager.id, managee.0.qname().short_name(),
+                        manager_decl.decl_type.type_name()))
+}
+
+
+fn gather_decls_manages(sym_tab: &mut SymbolTable,
+                        manager: &(Namespace, Protocol),
+                        manager_type: &mut ProtocolDef,
+                        managee: &Identifier) -> Errors {
+
+    let managee_decl = match sym_tab.lookup(&managee.id) {
+        Some(decl) => decl,
+        None => return Errors::one(&managee.loc,
+                                   &format!("protocol `{}', managed by `{}', has not been declared",
+                                            managee.id, manager.0.qname().short_name())),
+    };
+
+    if let IPDLType::Protocol(ref pt) = &managee_decl.decl_type {
+        manager_type.manages.push(pt.clone());
+        return Errors::none();
+    }
+
+    Errors::one(&managee.loc,
+                &format!("{} declares itself managing a non-`protocol' entity `{}' that is a {}",
+                        manager.0.qname().short_name(), managee.id, managee_decl.decl_type.type_name()))
+}
+
+
+fn gather_decls_message(sym_tab: &mut SymbolTable,
+                        tuid: TUId,
+                        protocol_type: &mut ProtocolDef,
+                        md: &MessageDecl) -> Errors {
+    let mut errors = Errors::none();
+    let mut message_name = md.name.id.clone();
+    let mut mtype = Message::Other;
+
+    if let Some(ref decl) = sym_tab.lookup(&message_name) {
+        if let IPDLType::Protocol(pt) = decl.decl_type {
+            // Probably a ctor. We'll check validity later.
+            message_name += CONSTRUCTOR_SUFFIX;
+            mtype = Message::Ctor(pt);
+        } else {
+            errors.append_one(&md.name.loc,
+                              &format!("message name `{}' already declared as `{}'",
+                                       md.name, decl.decl_type.type_name()));
+            // If we error here, no big deal; move on to find more.
+        }
+    }
+
+    if DELETE_MESSAGE_NAME == message_name {
+        mtype = Message::Dtor(tuid);
+    }
+
+    sym_tab.enter_scope();
+
+    let mut msg_type = MessageDef::new(&md, &message_name, mtype);
+
+    {
+        // The Python version adds the parameter, just with a dummy
+        // type. Here I choose to be consistent with how we handle struct
+        // fields with invalid types and simply omit the parameter.
+        let mut param_to_decl = |param: &Param| {
+            let pt_name = param.type_spec.spec.to_string();
+            match sym_tab.lookup(&pt_name) {
+                Some(p_type) => {
+                    let (errors2, t) = p_type.decl_type.canonicalize(&param.type_spec);
+                    errors.append(errors2);
+                    let decl = Decl::new(param.type_spec.loc(), t.clone(), param.name.id.clone());
+                    errors.append(sym_tab.declare(&decl));
+                    Some(ParamTypeDef { name: param.name.clone(), param_type: t })
+                }
+                None => {
+                    errors.append_one(param.type_spec.loc(),
+                                      &format!("argument typename `{}' of message `{}' has not been declared",
+                                               &pt_name, message_name));
+                    None
+                }
+            }
+        };
+
+        for in_param in &md.in_params {
+            if let Some(t) = param_to_decl(&in_param) {
+                msg_type.params.push(t);
+            }
+        }
+
+        for out_param in &md.out_params {
+            if let Some(t) = param_to_decl(&out_param) {
+                msg_type.returns.push(t);
+            }
+        }
+    }
+
+    sym_tab.exit_scope();
+
+    let index = protocol_type.messages.len();
+    protocol_type.messages.push(msg_type);
+
+    let mt = IPDLType::Message(TypeRef::new(tuid, index));
+    errors.append(sym_tab.declare(&Decl::new(&md.name.loc, mt, message_name)));
+
+    errors
+}
+
+fn gather_decls_protocol(mut sym_tab: &mut SymbolTable,
+                         tuid: TUId,
+                         p: &(Namespace, Protocol),
+                         mut p_type: &mut ProtocolDef) -> Errors {
+    let mut errors = Errors::none();
+
+    sym_tab.enter_scope();
+
+    {
+        let mut seen_managers = HashSet::new();
+        for manager in &p.1.managers {
+            if seen_managers.contains(&manager.id) {
+                errors.append_one(&manager.loc,
+                                  &format!("manager `{}' appears multiple times",
+                                           manager.id));
+                continue;
+            }
+
+            seen_managers.insert(manager.id.clone());
+
+            errors.append(gather_decls_manager(&mut sym_tab, &p, &mut p_type, &manager));
+        }
+    }
+
+    for managee in &p.1.manages {
+        errors.append(gather_decls_manages(&mut sym_tab, &p, &mut p_type, &managee));
+    }
+
+    if p.1.managers.is_empty() && p.1.messages.is_empty() {
+        errors.append_one(&p.0.name.loc,
+                          &format!("top-level protocol `{}' cannot be empty",
+                                   p.0.qname().short_name()));
+    }
+
+    for md in &p.1.messages {
+        errors.append(gather_decls_message(&mut sym_tab, tuid, &mut p_type, &md));
+    }
+
+    let delete_type = sym_tab.lookup(DELETE_MESSAGE_NAME);
+    p_type.has_delete = delete_type.is_some();
+    if !(p_type.has_delete || p_type.is_top_level()) {
+        errors.append_one(&p.0.name.loc,
+                          &format!("destructor declaration `{}(...)' required for managed protocol `{}'",
+                                   DELETE_MESSAGE_NAME , p.0.qname().short_name()));
+    }
+
+    p_type.has_reentrant_delete = match delete_type {
+        Some(decl) => match decl.decl_type {
+            IPDLType::Message(tr) =>
+                p_type.messages[tr.index].is_intr(),
+            _ => panic!("Invalid message type for delete message"),
+        },
+        None => false
+    };
+
+    for managed in &p.1.manages {
+        let ctor_name = managed.id.clone() + CONSTRUCTOR_SUFFIX;
+        if let Some(Decl { decl_type: IPDLType::Message(tr), .. }) = sym_tab.lookup(&ctor_name) {
+            if p_type.messages[tr.index].is_ctor() {
+                continue;
+            }
+        }
+        errors.append_one(&managed.loc,
+                          &format!("constructor declaration required for managed protocol `{}' (managed by protocol `{}')",
+                                   managed.id, p.0.qname().short_name()));
+    }
+
+    // FIXME/cjones Declare all the little C++ thingies that will
+    // be generated. They're not relevant to IPDL itself, but
+    // those ("invisible") symbols can clash with others in the
+    // IPDL spec, and we'd like to catch those before C++ compilers
+    // are allowed to obfuscate the error.
+
+    sym_tab.exit_scope();
+
+    errors
+}
+
+
+fn gather_decls_tu(tus: &HashMap<TUId, TranslationUnit>,
+                   tuts: &mut HashMap<TUId, TranslationUnitType>,
+                   tuid: TUId,
+                   tu: &TranslationUnit) -> Result<(), Errors> {
+    let mut errors = Errors::none();
+    let mut sym_tab = SymbolTable::new();
+    let tut = &mut tuts.get_mut(&tuid).unwrap();
+
+    if let Some(ref p) = &tu.protocol {
+        errors.append(declare_protocol(&mut sym_tab, tuid, &p.0));
+    }
+
+    // Add the declarations from all the IPDL files we include.
+    for &include_tuid in &tu.includes {
+        let include_tu = tus.get(&include_tuid).unwrap();
+        match include_tu.protocol {
+            Some(ref p) =>
+                errors.append(declare_protocol(&mut sym_tab, include_tuid, &p.0)),
+            None => {
+                // This is a header.  Import its "exported" globals into our scope.
+                errors.append(declare_usings(&mut sym_tab, &include_tu));
+                errors.append(declare_structs_and_unions(&mut sym_tab, include_tuid, &include_tu));
+            },
+        }
+    }
+
+    // Declare builtin C++ types.
+    for t in BUILTIN_TYPES {
+        let cxx_type = builtin_from_string(t);
+        errors.append(declare_cxx_type(&mut sym_tab, &cxx_type, false /* refcounted */));
+    }
+
+    // Declare imported C++ types.
+    errors.append(declare_usings(&mut sym_tab, &tu));
+
+    // Create stubs for top level struct and union decls.
+    for s in &tu.structs {
+        tut.structs.push(StructDef::new(&s.0));
+    }
+    for s in &tu.unions {
+        tut.unions.push(UnionDef::new(&s.0));
+    }
+
+    // Forward declare all structs and unions in order to support
+    // recursive definitions.
+    errors.append(declare_structs_and_unions(&mut sym_tab, tuid, &tu));
+
+    // Check definitions of structs and unions.
+    // XXX It might be cleaner to do a zip iteration over {tu,tut}.structs
+    let mut index = 0;
+    for su in &tu.structs {
+        errors.append(gather_decls_struct(&mut sym_tab, &su, &mut tut.structs[index]));
+        index += 1;
+    }
+    index = 0;
+    for u in &tu.unions {
+        errors.append(gather_decls_union(&mut sym_tab, &u, &mut tut.unions[index]));
+        index += 1;
+    }
+
+    // The Python version type checks every struct and union included
+    // from an ipdlh file here, but I don't think that makes any
+    // sense.
+
+    if let Some(ref p) = &tu.protocol {
+        errors.append(gather_decls_protocol(&mut sym_tab, tuid, &p, &mut tut.protocol.as_mut().unwrap()));
+    }
+
+    if errors.is_empty() { Ok(()) } else { Err(errors) }
+}
+
+
+enum FullyDefinedState {
+    Visiting,
+    Defined(bool),
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+enum CompoundType {
+    Struct,
+    Union,
+}
+
+
+/* The rules for "full definition" of a type are
+     defined(atom)             := true
+     defined(array basetype)   := defined(basetype)
+     defined(struct f1 f2...)  := defined(f1) and defined(f2) and ...
+     defined(union c1 c2 ...)  := defined(c1) or defined(c2) or ...
+ */
+fn fully_defined(tuts: &HashMap<TUId, TranslationUnitType>,
+                 mut defined: &mut HashMap<(CompoundType, TypeRef), FullyDefinedState>,
+                 t: &IPDLType) -> bool {
+
+    let key = match t {
+        IPDLType::Struct(ref tr) => (CompoundType::Struct, tr.clone()),
+        IPDLType::Union(ref tr) => (CompoundType::Union, tr.clone()),
+        IPDLType::Array(ref t_inner) => return fully_defined(&tuts, &mut defined, &t_inner),
+        _ => return true,
+    };
+
+    // The Python version would repeatedly visit a type that was found
+    // to be not defined. I think that's unnecessary. Not doing it
+    // might save some time in the case of an error.
+
+    if let Some(state) = defined.get(&key) {
+        return match *state {
+            FullyDefinedState::Visiting => false,
+            FullyDefinedState::Defined(is_defined) => is_defined,
+        }
+    }
+
+    defined.insert(key.clone(), FullyDefinedState::Visiting);
+
+    let mut is_defined;
+    match key.0 {
+        CompoundType::Struct => {
+            is_defined = true;
+            for f in &key.1.lookup_struct(&tuts).fields {
+                if !fully_defined(&tuts, &mut defined, f) {
+                    is_defined = false;
+                    break
+                }
+            }
+        },
+        CompoundType::Union => {
+            is_defined = false;
+            for f in &key.1.lookup_union(&tuts).components {
+                if fully_defined(&tuts, &mut defined, f) {
+                    is_defined = true;
+                    break
+                }
+            }
+        },
+    }
+
+    // XXX Don't need to insert here. get_mut should work.
+    defined.insert(key, FullyDefinedState::Defined(is_defined));
+
+    is_defined
+}
+
+
+enum ManagerCycleState {
+    Visiting,
+    Acyclic,
+}
+
+
+fn get_protocol_type<'a>(tuts: &'a HashMap<TUId, TranslationUnitType>,
+                         tuid: &TUId) -> &'a ProtocolDef
+{
+    tuts.get(tuid).unwrap().protocol.as_ref().unwrap()
+}
+
+fn manager_cycle_error(tuts: &HashMap<TUId, TranslationUnitType>,
+                       v: &[TUId],
+                       tuid: &TUId) -> Errors {
+    let mut errors = Errors::none();
+    let mut found = false;
+    for p in v {
+        if !found {
+            if p != tuid {
+                continue;
+            }
+            errors.append_one(get_protocol_type(&tuts, &tuid).qname.loc(),
+                              "cycle detected in manager/manages hierarchy:");
+            found = true;
+        }
+        let pt = get_protocol_type(&tuts, &p);
+        errors.append_one(pt.qname.loc(),
+                          &format!("\t{}", pt.qname));
+    }
+    assert!(found);
+    errors
+}
+
+fn protocol_managers_acyclic(tuts: &HashMap<TUId, TranslationUnitType>,
+                             mut visited: &mut HashMap<TUId, ManagerCycleState>,
+                             mut stack: &mut Vec<TUId>,
+                             tuid: &TUId) -> Errors {
+    if let Some(state) = visited.get(tuid) {
+        return match state {
+            ManagerCycleState::Visiting => manager_cycle_error(&tuts, &stack, tuid),
+            ManagerCycleState::Acyclic => Errors::none(),
+        }
+    }
+
+    let mut errors = Errors::none();
+    visited.insert(tuid.clone(), ManagerCycleState::Visiting);
+
+    stack.push(tuid.clone());
+
+    let pt = get_protocol_type(&tuts, &tuid);
+    for managee in &pt.manages {
+        // Self-managed protocols are allowed, except at the top level.
+        // The top level case is checked in protocol_managers_acyclic.
+        if managee == tuid {
+            continue;
+        }
+
+        errors.append(protocol_managers_acyclic(&tuts, &mut visited, &mut stack, &managee));
+    }
+
+    stack.pop();
+
+    *visited.get_mut(tuid).unwrap() = ManagerCycleState::Acyclic;
+
+    errors
+}
+
+fn protocols_managers_acyclic(tuts: &HashMap<TUId, TranslationUnitType>) -> Errors {
+    let mut errors = Errors::none();
+    let mut visited = HashMap::new();
+    let mut stack = Vec::new();
+
+    for (tuid, tut) in tuts {
+        if tut.protocol.is_none() {
+            continue;
+        }
+
+        errors.append(protocol_managers_acyclic(&tuts, &mut visited, &mut stack, &tuid));
+
+        let pt = get_protocol_type(&tuts, &tuid);
+        if pt.managers.len() == 1 && &pt.managers[0] == tuid {
+            errors.append_one(pt.qname.loc(),
+                              &format!("top-level protocol `{}' cannot manage itself",
+                                      pt.qname.short_name()));
+        }
+    }
+    errors
+}
+
+fn check_types_message(ptype: &ProtocolDef,
+                       mtype: &MessageDef) -> Errors {
+    let mut errors = Errors::none();
+    let mname = &mtype.name.id;
+
+    if mtype.nested.inside_sync() && !mtype.is_sync() {
+        errors.append_one(&mtype.name.loc,
+                          &format!("inside_sync nested messages must be sync (here, message `{}' in protocol `{}')",
+                                   mname, ptype.qname.short_name()));
+    }
+
+    let is_to_child = mtype.direction.is_to_child() || mtype.direction.is_both();
+
+    if mtype.nested.inside_cpow() && is_to_child {
+        errors.append_one(&mtype.name.loc,
+                          &format!("inside_cpow nested parent-to-child messages are verboten (here, message `{}' in protocol `{}')",
+                                   mname, ptype.qname.short_name()));
+    }
+
+    // We allow inside_sync messages that are themselves sync to be sent from the
+    // parent. Normal and inside_cpow nested messages that are sync can only come from
+    // the child.
+    if mtype.is_sync() && mtype.nested.is_none() && is_to_child {
+        errors.append_one(&mtype.name.loc,
+                          &format!("sync parent-to-child messages are verboten (here, message `{}' in protocol `{}')",
+                                   mname, ptype.qname.short_name()));
+    }
+
+    if !mtype.converts_to(&ptype) {
+        errors.append_one(&mtype.name.loc,
+                          &format!("message `{}' requires more powerful send semantics than its protocol `{}' provides",
+                                   mname, ptype.qname.short_name()));
+    }
+
+    if (mtype.is_ctor() || mtype.is_dtor()) && mtype.is_async() && !mtype.returns.is_empty() {
+        errors.append_one(&mtype.name.loc,
+                          &format!("asynchronous ctor/dtor message `{}' in protocol `{}' declares return values",
+                                   mname, ptype.qname.short_name()));
+    }
+
+    if mtype.compress != Compress::None && (!mtype.is_async() || mtype.is_ctor() || mtype.is_dtor()) {
+        let pname = ptype.qname.short_name();
+        let message = if mtype.is_ctor() || mtype.is_dtor() {
+            let message_type = if mtype.is_ctor() { "constructor" } else { "destructor" };
+            format!("{} messages can't use compression (here, in protocol `{}')",
+                              message_type, pname)
+        } else {
+            format!("message `{}' in protocol `{}' requests compression but is not async",
+                              mname, pname)
+        };
+
+        errors.append_one(&mtype.name.loc, &message);
+    }
+
+    if mtype.is_ctor() && !ptype.manages.contains(&mtype.constructed_type()) {
+        let ctor_protocol_len = mname.len() - CONSTRUCTOR_SUFFIX.len();
+        errors.append_one(&mtype.name.loc,
+                          &format!("ctor for protocol `{}', which is not managed by protocol `{}'",
+                                   &mname[0..ctor_protocol_len],
+                                   ptype.qname.short_name()));
+    }
+
+    errors
+}
+
+fn check_types_protocol(tuts: &HashMap<TUId, TranslationUnitType>,
+                        tuid: &TUId,
+                        ptype: &ProtocolDef) -> Errors {
+    let mut errors = protocols_managers_acyclic(&tuts);
+
+    for manager in &ptype.managers {
+        let manager_type = get_protocol_type(&tuts, &manager);
+        if !ptype.converts_to(&manager_type) {
+            errors.append_one(&ptype.qname.loc(),
+                              &format!("protocol `{}' requires more powerful send semantics than its manager `{}' provides",
+                                       ptype.qname.short_name(), manager_type.qname.short_name()));
+        }
+
+        if !manager_type.manages.contains(&tuid) {
+            errors.append_one(&manager_type.qname.loc(),
+                              &format!("|manager| declaration in protocol `{}' does not match any |manages| declaration in protocol `{}'",
+                                       ptype.qname.short_name(), manager_type.qname.short_name()));
+        }
+    }
+
+    for managee in &ptype.manages {
+        let managee_type = get_protocol_type(&tuts, &managee);
+
+        if !managee_type.managers.contains(&tuid) {
+            errors.append_one(&managee_type.qname.loc(),
+                              &format!("|manages| declaration in protocol `{}' does not match any |manager| declaration in protocol `{}'",
+                                       ptype.qname.short_name(), managee_type.qname.short_name()));
+        }
+    }
+
+    for mtype in &ptype.messages {
+        errors.append(check_types_message(&ptype, &mtype));
+    }
+
+    errors
+}
+
+
+fn check_types_tu(tus: &HashMap<TUId, TranslationUnit>,
+                  tuts: &HashMap<TUId, TranslationUnitType>,
+                  mut defined: &mut HashMap<(CompoundType, TypeRef), FullyDefinedState>,
+                  tuid: TUId,
+                  tut: &TranslationUnitType) -> Result<(), Errors> {
+    let mut errors = Errors::none();
+
+    let tu = tus.get(&tuid).unwrap();
+
+    for i in 0..tut.structs.len() {
+        if !fully_defined(&tuts, &mut defined,
+                          &IPDLType::Struct(TypeRef::new(tuid, i))) {
+            errors.append_one(&tu.structs[i].0.name.loc,
+                              &format!("struct `{}' is only partially defined",
+                                       &tu.structs[i].0.name.id));
+        }
+    }
+
+    for i in 0..tut.unions.len() {
+        if !fully_defined(&tuts, &mut defined,
+                          &IPDLType::Union(TypeRef::new(tuid, i))) {
+            errors.append_one(&tu.unions[i].0.name.loc,
+                              &format!("union `{}' is only partially defined",
+                                       &tu.unions[i].0.name.id));
+        }
+    }
+
+    if let Some(ref pt) = &tut.protocol {
+        errors.append(check_types_protocol(&tuts, &tuid, &pt));
+    }
+
+    // XXX We don't need to track visited because we will visited all
+    // translation units at the top level.
+
+    // XXX What is "ptype"? In Python, it is set to None at the top of this method.
+
+    // XXX The Python checker calls visitIncludes on tu.includes,
+    // which checks any included protocols. I don't know why that
+    // would be useful.
+
+
+    if errors.is_empty() { Ok(()) } else { Err(errors) }
+}
+
+
+// Basic checking that doesn't relate to types specifically.
+pub fn check_translation_unit(tu: &TranslationUnit) -> Result<(), Errors> {
+    if let Some((ref ns, _)) = &tu.protocol {
+
+        // For a protocol file, the filename should match the
+        // protocol. (In the Python IPDL compiler, translation units have
+        // a separate "name" field that is checked here, but for protocol
+        // files the name is just the name of the protocol, and for
+        // non-protocols the name is derived from the file name, so this
+        // checking should be equivalent.)
+        let base_file_name = match tu.file_name.rsplit('/').next() { // FIXME: adhoc
+            Some(fs) => fs,
+            None => return Err(Errors::one(&Location { file_name: tu.file_name.clone(), lineno: 0, colno: 0 }, "File path has no file")),
+        };
+        let expected_file_name = ns.name.id.clone() + ".ipdl";
+        if base_file_name != expected_file_name {
+            return Err(Errors::one(&Location { file_name: tu.file_name.clone(), lineno: 0, colno: 0 }, &format!("expected file for translation unit `{}' to be named `{}'; instead it's named `{}'.",
+                               tu.namespace.name.id, expected_file_name, base_file_name)))
+        }
+    }
+
+    Ok(())
+}
+
+
+pub fn check(tus: &HashMap<TUId, TranslationUnit>) -> Result<(), Errors> {
+    let mut tuts = HashMap::new();
+
+    // XXX This ordering should be deterministic. I could sort by the
+    // TUId.
+
+    let tus_vec = tus.iter().collect::<Vec<_>>();
+
+    for (&tuid, tu) in &tus_vec {
+        try!(check_translation_unit(&tu));
+
+        // Create top-level type decl for all protocols.
+        let old_entry = tuts.insert(tuid, TranslationUnitType::new(&tu.protocol));
+        assert!(old_entry.is_none());
+    }
+
+    for (&tuid, tu) in &tus_vec {
+        try!(gather_decls_tu(&tus, &mut tuts, tuid, &tu));
+    }
+
+    let tuts_vec = tuts.iter().collect::<Vec<_>>();
+    let mut defined = HashMap::new();
+    for (&tuid, tut) in tuts_vec {
+        try!(check_types_tu(&tus, &tuts, &mut defined, tuid, &tut));
+    }
+
+    Ok(())
+}
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl_new/ipdl/tests/smoke_test.rs
@@ -0,0 +1,74 @@
+extern crate ipdl;
+
+use std::collections::HashSet;
+use std::ffi::OsStr;
+use std::fs;
+use std::fs::File;
+use std::io::Read;
+use std::path::PathBuf;
+
+const BASE_PATH: [&str; 5] = ["..", "..", "ipdl", "test", "ipdl"];
+const OK_PATH: &str = "ok";
+const ERROR_PATH: &str = "error";
+
+// Tests in error/ are disabled because the given checking is not
+// enabled yet.
+
+const DISABLED_TESTS: &[&str] = &["unknownSyncMessage.ipdl", "unknownIntrMessage.ipdl", "asyncMessageListed.ipdl"];
+
+// XXX This does not run efficiently. If A includes B, then we end up
+// testing A and B two times each. At least for the non-error case we
+// should be able to do them all together.
+
+fn test_files(test_file_path: &str, should_pass: bool) {
+    let mut path: PathBuf = BASE_PATH.iter().collect();
+    path.push(test_file_path);
+
+    let include_dirs = vec![path.clone()];
+
+    let mut disabled_tests = HashSet::new();
+    for f in DISABLED_TESTS {
+        disabled_tests.insert(OsStr::new(f));
+    }
+
+    let entries = fs::read_dir(&path).expect("Should have the test file directory");
+    for entry in entries {
+        if let Ok(entry) = entry {
+            let expected_result = if !should_pass
+                && disabled_tests.contains(
+                    entry
+                        .path()
+                        .file_name()
+                        .expect("No filename for path in test directory"),
+                ) {
+                println!(
+                    "Expecting test to pass when it should fail {:?}",
+                    entry.file_name()
+                );
+                true
+            } else {
+                println!("Testing {:?}", entry.file_name());
+                should_pass
+            };
+
+            let file_name = entry.path();
+            let ok = ipdl::parser::parse(&file_name, &include_dirs, |src| {
+                let mut buffer = Vec::new();
+                let mut file = File::open(src).map_err(|_| ())?;
+                file.read_to_end(&mut buffer).map_err(|_| ())?;
+                Ok(Box::new(buffer))
+            }).is_ok();
+            assert!(expected_result == ok);
+        }
+    }
+}
+
+#[test]
+fn ok_tests() {
+    test_files(OK_PATH, true);
+}
+
+#[test]
+fn error_tests() {
+    test_files(ERROR_PATH, false);
+}
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl_new/ipdl_bindings/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "ipdl_bindings"
+version = "0.1.0"
+authors = ["Tristan Bourvon <tristanbourvon@gmail.com>"]
+license = "MPL-2.0"
+
+[dependencies]
+ipdl = { path = "../ipdl" }
+thin-vec = { version = "*", features = ["gecko-ffi"] }
+nsstring = { path = "../../../servo/support/gecko/nsstring" }
+mfbt-maybe = { path = "../../../xpcom/rust/mfbt-maybe" }
+
+[lib]
+name = "ipdl_bindings"
+path = "src/lib.rs"
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl_new/ipdl_bindings/IPCInterface.cpp
@@ -0,0 +1,121 @@
+#include "IPCInterface.h"
+
+#include "mozilla/ipdl/IPDLProtocolInstance.h"
+#include "mozilla/dom/IPDL.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/ipdl/IPDLProtocol.h"
+
+namespace mozilla {
+namespace ipdl {
+namespace ipc {
+
+/* virtual */ mozilla::ipc::IPCResult
+IPCInterface::RecvMessageCommon(
+  mozilla::ipc::IProtocol* aActor,
+  IPDLSide aSide,
+  const nsCString& aProtocolName,
+  const uint32_t& aChannelId,
+  const nsCString& aMessage,
+  const dom::ClonedMessageData& aData,
+  nsTArray<dom::ipc::StructuredCloneData>* aReturnData)
+{
+  // Borrow from the CloneMessageData to the StructuredCloneData.
+  dom::ipc::StructuredCloneData data;
+  switch (aSide) {
+    case IPDLSide::Child:
+      data.BorrowFromClonedMessageDataForChild(aData);
+      break;
+    case IPDLSide::Parent:
+      data.BorrowFromClonedMessageDataForParent(aData);
+      break;
+  }
+
+  // Get the destination instance object to initiate a new context.
+  JS::RootingContext* rcx = CycleCollectedJSContext::Get()->RootingCx();
+  JS::RootedObject object(rcx);
+
+  auto sidedProtocolName = IPDLProtocol::GetSidedProtocolName(
+    aProtocolName, aSide);
+
+  object = GetDestinationObject(sidedProtocolName, aChannelId);
+  dom::AutoEntryScript aes(object, "RecvSyncMessageIPDL");
+
+  JSContext* cx = aes.cx();
+
+  // Read the arguments from the StructuredCloneData.
+  JS::RootedValue argsVal(cx);
+
+  ErrorResult errRes;
+  data.Read(cx, &argsVal, errRes);
+
+  if (NS_WARN_IF(errRes.Failed())) {
+    return IPC_FAIL(aActor,
+                    "Could not read arguments from StructuredCloneData");
+  }
+
+  JS::RootedObject argsObj(cx, &argsVal.toObject());
+
+  uint32_t arrayLength = 0;
+  if (!JS_GetArrayLength(cx, argsObj, &arrayLength)) {
+    return IPC_FAIL(aActor, "Could not get argument array length");
+  }
+
+  // Extract all the arguments.
+  JS::AutoValueVector args(cx);
+  for (uint32_t i = 0; i < arrayLength; i++) {
+    JS::RootedValue val(cx);
+
+    if (!JS_GetElement(cx, argsObj, i, &val)) {
+      return IPC_FAIL(aActor,
+                      "Could not get argument value from argument array");
+    }
+
+    if (!args.append(val)) {
+      return IPC_FAIL(aActor,
+                      "Could not append argument value to argument vector");
+    }
+  }
+
+  JS::HandleValueArray argsHandle(args);
+
+  JS::RootedValue retValue(cx);
+
+  // Call the receive handler in the protocol instance.
+  if (!mProtocolInstances.GetOrInsert(sidedProtocolName)
+         .Get(aChannelId)
+         ->RecvMessage(
+           cx, aMessage, argsHandle, &retValue)) {
+    NS_WARNING("Error in the RecvMessage handler");
+    aReturnData->Clear();
+    return IPC_OK();
+  }
+
+  // Write back the return value.
+  dom::ipc::StructuredCloneData retData;
+  retData.Write(cx, retValue, errRes);
+
+  if (NS_WARN_IF(errRes.Failed())) {
+    return IPC_FAIL(aActor,
+                    "Could not write return value to StructuredCloneData");
+  }
+
+  aReturnData->AppendElement(std::move(retData));
+
+  return IPC_OK();
+}
+
+/* virtual */ JSObject*
+IPCInterface::GetDestinationObject(const nsCString& aProtocolName,
+                                   const uint32_t& aChannelId)
+{
+  // Return the instance object corresponding to the protocol name and channel
+  // ID.
+  return mProtocolInstances.GetOrInsert(aProtocolName)
+    .Get(aChannelId)
+    ->GetInstanceObject();
+}
+
+} // ipc
+} // ipdl
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl_new/ipdl_bindings/IPCInterface.h
@@ -0,0 +1,117 @@
+
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef dom_base_ipdl_bindings_ipcinterface_h
+#define dom_base_ipdl_bindings_ipcinterface_h
+
+#include <functional>
+
+#include "jsapi.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+
+namespace mozilla {
+namespace dom {
+class IPDL;
+}
+
+namespace ipdl {
+class IPDLProtocolInstance;
+enum class IPDLSide : bool;
+
+namespace ipc {
+
+class IPCInterface
+{
+public:
+  // Message call return type.
+  typedef JS::MutableHandleValue OutObject;
+  // Message call argument list type.
+  typedef JS::HandleValueArray InArgs;
+  // Async message call promise type.
+  typedef MozPromise<JS::Value, nsCString, true> AsyncMessagePromise;
+
+  explicit IPCInterface(dom::IPDL* aIPDL)
+    : mIPDL(aIPDL)
+  {
+  }
+
+  virtual ~IPCInterface() = default;
+
+  // Send an async message through IPC.
+  virtual RefPtr<AsyncMessagePromise> SendAsyncMessage(
+    JSContext* aCx,
+    const nsCString& aProtocolName,
+    const uint32_t& aChannelId,
+    const nsCString& aMessageName,
+    const InArgs& aArgs) = 0;
+
+  // Send a sync message through IPC.
+  virtual bool SendSyncMessage(JSContext* aCx,
+                               const nsCString& aProtocolName,
+                               const uint32_t& aChannelId,
+                               const nsCString& aMessageName,
+                               const InArgs& aArgs,
+                               OutObject aRet) = 0;
+
+  // Send an intr message through IPC.
+  virtual bool SendIntrMessage(JSContext* aCx,
+                               const nsCString& aProtocolName,
+                               const uint32_t& aChannelId,
+                               const nsCString& aMessageName,
+                               const InArgs& aArgs,
+                               OutObject aRet) = 0;
+
+  // Set the recv handler to call when we receive a message.
+  virtual void SetIPDLInstance(const uint32_t& aChannelId,
+                               const nsCString& aProtocolName,
+                               IPDLProtocolInstance* aInstance)
+  {
+    mProtocolInstances.GetOrInsert(aProtocolName).GetOrInsert(aChannelId) =
+      aInstance;
+  }
+
+  virtual void RemoveIPDLInstance(const uint32_t& aChannelId,
+                                  const nsCString& aProtocolName)
+  {
+    mProtocolInstances.GetOrInsert(aProtocolName).Remove(aChannelId);
+  }
+
+  // Receive a message from IPC. Empty return array means error.
+  virtual mozilla::ipc::IPCResult RecvMessage(
+    const nsCString& aProtocolName,
+    const uint32_t& aChannelId,
+    const nsCString& aMessage,
+    const dom::ClonedMessageData& aData,
+    nsTArray<dom::ipc::StructuredCloneData>* aReturnData) = 0;
+
+protected:
+  // Inner common function for receiving a message from either the parent or the
+  // child. Empty return array means error.
+  virtual mozilla::ipc::IPCResult RecvMessageCommon(
+    mozilla::ipc::IProtocol* aActor,
+    IPDLSide aSide,
+    const nsCString& aProtocolName,
+    const uint32_t& aChannelId,
+    const nsCString& aMessage,
+    const dom::ClonedMessageData& aData,
+    nsTArray<dom::ipc::StructuredCloneData>* aReturnData);
+
+  // Retrieves the destination instance object for a protocol/channel pair.
+  virtual JSObject* GetDestinationObject(const nsCString& aProtocolName,
+                                         const uint32_t& aChannelId);
+
+  dom::IPDL* MOZ_NON_OWNING_REF mIPDL;
+  nsDataHashtable<nsCStringHashKey,
+                  nsDataHashtable<nsUint32HashKey, IPDLProtocolInstance*>>
+    mProtocolInstances;
+};
+
+} // ipc
+} // ipdl
+} // mozilla
+
+#endif // dom_base_ipdl_bindings_ipcinterface_h
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl_new/ipdl_bindings/IPDLProtocol.cpp
@@ -0,0 +1,778 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "IPDLProtocol.h"
+
+#include <cfloat>
+
+#include "ipdl_ffi_generated.h"
+#include "mozilla/dom/DOMMozPromiseRequestHolder.h"
+#include "mozilla/dom/Promise.h"
+#include "nsJSUtils.h"
+#include "wrapper.h"
+#include "mozilla/dom/IPDL.h"
+#include "IPCInterface.h"
+#include "IPDLProtocolInstance.h"
+
+namespace mozilla {
+namespace ipdl {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(IPDLProtocol)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IPDLProtocol)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mInstances)
+  tmp->mProtoObj = nullptr;
+  tmp->mConstructorObj = nullptr;
+  mozilla::DropJSObjects(tmp);
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IPDLProtocol)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInstances)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(IPDLProtocol)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mProtoObj)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mConstructorObj)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+NS_IMPL_CYCLE_COLLECTING_ADDREF(IPDLProtocol)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(IPDLProtocol)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IPDLProtocol)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+void
+IPDLProtocol::ASTDeletePolicy::operator()(const mozilla::ipdl::ffi::AST* aASTPtr)
+{
+  if (!aASTPtr) {
+    return;
+  }
+  wrapper::FreeAST(aASTPtr);
+}
+
+IPDLProtocol::IPDLProtocol(dom::IPDL* aIPDL,
+                           IPDLSide aSide,
+                           const nsACString& aIPDLFile,
+                           nsIGlobalObject* aGlobal,
+                           JS::HandleObject aParent,
+                           JSContext* aCx)
+  : mSide(aSide)
+  , mGlobal(aGlobal)
+  , mIPDL(aIPDL)
+  , mNextProtocolInstanceChannelId(0)
+{
+  mozilla::HoldJSObjects(this);
+
+  nsCString errorString;
+  mAST.reset(wrapper::Parse(aIPDLFile, errorString));
+
+  if (!mAST) {
+    JS_ReportErrorUTF8(aCx, "IPDL: %s", errorString.get());
+    return;
+  }
+
+  // Get the main translation unit.
+  auto mainTU = GetMainTU();
+
+  MOZ_ASSERT(mainTU->protocol, "should have a protocol in protocol file");
+
+  mProtocolName = JoinNamespace(mainTU->protocol->ns);
+
+  auto& manages = mainTU->protocol->protocol.manages;
+  auto& messages = mainTU->protocol->protocol.messages;
+
+  NS_NAMED_LITERAL_CSTRING(sendPrefix, "send");
+  NS_NAMED_LITERAL_CSTRING(recvPrefix, "recv");
+  NS_NAMED_LITERAL_CSTRING(allocPrefix, "alloc");
+  NS_NAMED_LITERAL_CSTRING(constructorSuffix, "Constructor");
+
+  // Store the managed protocols in a set for fast retrieval and access.
+  nsTHashtable<nsCStringHashKey> managedProtocols;
+  for (auto& managedProtocol : manages) {
+    managedProtocols.PutEntry(managedProtocol.id);
+  }
+
+  nsTArray<JSFunctionSpec> funcs;
+  nsTArray<nsCString> functionNames;
+  // We loop through every message of the protocol and add them to our message
+  // table, taking into account our protocol side and the message direction.
+  for (auto& message : messages) {
+    // The message is gonna be sendXXX.
+    if ((message.direction == ffi::Direction::ToChild &&
+         mSide == IPDLSide::Parent) ||
+        (message.direction == ffi::Direction::ToParent &&
+         mSide == IPDLSide::Child) ||
+        message.direction == ffi::Direction::ToParentOrChild) {
+
+      // If this is a managed protocol name...
+      if (managedProtocols.Contains(message.name.id)) {
+        // Append the sendXXXConstructor message.
+        const nsAutoCString& constructorName =
+          sendPrefix + message.name.id + constructorSuffix;
+
+        functionNames.AppendElement(constructorName);
+        funcs.AppendElement<JSFunctionSpec>(JS_FN(
+          functionNames.LastElement().get(),
+          SendConstructorDispatch,
+          static_cast<uint16_t>(message.in_params.Length()),
+          0));
+        mMessageTable.Put(constructorName, &message);
+
+        // And the allocXXX message.
+        const nsAutoCString& allocName = allocPrefix + message.name.id;
+        functionNames.AppendElement(allocName);
+        funcs.AppendElement<JSFunctionSpec>(JS_FN(
+          functionNames.LastElement().get(),
+          AbstractAlloc,
+          static_cast<uint16_t>(message.in_params.Length()),
+          0));
+        mMessageTable.Put(allocName, &message);
+      } else {
+        // Otherwise for the regular sendXXX messages, we define the associated JS function.
+        const nsAutoCString& funcName = sendPrefix + message.name.id;
+        functionNames.AppendElement(funcName);
+        funcs.AppendElement<JSFunctionSpec>(JS_FN(
+          functionNames.LastElement().get(),
+          message.name.id.Equals("__delete__") ? SendDeleteDispatch
+                                               : SendMessageDispatch,
+          static_cast<uint16_t>(message.in_params.Length()),
+          0));
+        mMessageTable.Put(funcName, &message);
+      }
+    }
+
+    // The message is gonna be recvXXX;
+    if ((message.direction == ffi::Direction::ToChild &&
+         mSide == IPDLSide::Child) ||
+        (message.direction == ffi::Direction::ToParent &&
+         mSide == IPDLSide::Parent) ||
+        message.direction == ffi::Direction::ToParentOrChild) {
+
+      // If this is a managed protocol...
+      if (managedProtocols.Contains(message.name.id)) {
+        // Append the recvXXXConstructor message.
+        const nsAutoCString& constructorName =
+          recvPrefix + message.name.id + constructorSuffix;
+        functionNames.AppendElement(constructorName);
+        funcs.AppendElement<JSFunctionSpec>(JS_FN(
+          functionNames.LastElement().get(),
+          RecvConstructor,
+          static_cast<uint16_t>(message.in_params.Length()),
+          0));
+        mMessageTable.Put(constructorName, &message);
+
+        // And the allocXXX message.
+        const nsAutoCString& allocName = allocPrefix + message.name.id;
+        functionNames.AppendElement(allocName);
+        funcs.AppendElement<JSFunctionSpec>(JS_FN(
+          functionNames.LastElement().get(),
+          AbstractAlloc,
+          static_cast<uint16_t>(message.in_params.Length()),
+          0));
+        mMessageTable.Put(allocName, &message);
+      } else {
+        // For the regular recvXXX callbacks, we define the associated "abstract"
+        // functions or the delete default function.
+        const nsAutoCString& funcName = recvPrefix + message.name.id;
+        functionNames.AppendElement(funcName);
+        funcs.AppendElement<JSFunctionSpec>(JS_FN(
+          functionNames.LastElement().get(),
+          message.name.id.Equals("__delete__") ? RecvDelete
+                                               : AbstractRecvMessage,
+          static_cast<uint16_t>(message.in_params.Length()),
+          0));
+
+        mMessageTable.Put(funcName, &message);
+      }
+    }
+  }
+
+  funcs.AppendElement<JSFunctionSpec>(JS_FS_END);
+
+  // Create our new protocol JSClass.
+  mSidedProtocolName = GetSidedProtocolName(mProtocolName, mSide);
+  mProtocolClass =
+    { mSidedProtocolName.get(),
+               JSCLASS_HAS_PRIVATE,
+               &sIPDLJSClassOps };
+
+  JS::RootedObject parentProto(aCx);
+  JS_GetClassPrototype(aCx, JSProto_Object, &parentProto);
+
+  // Initialize it with the constructor and the functions.
+  mProtoObj = JS_InitClass(aCx,
+                           aParent,
+                           parentProto,
+                           &mProtocolClass,
+                           Constructor,
+                           0,
+                           nullptr,
+                           funcs.Elements(),
+                           nullptr,
+                           nullptr);
+
+  JS::RootedObject protoObj(aCx, mProtoObj);
+
+  // Add this object to the private field.
+  JS_SetPrivate(protoObj, this);
+  mConstructorObj = JS_GetConstructor(aCx, protoObj);
+
+  // We build the name->AST lookup tables for later typechecking.
+  BuildNameLookupTables();
+}
+
+nsCString
+IPDLProtocol::GetProtocolName()
+{
+  return mProtocolName;
+}
+
+JSObject*
+IPDLProtocol::GetProtocolClassConstructor()
+{
+  return mConstructorObj.get();
+}
+
+uint32_t
+IPDLProtocol::RegisterExternalInstance()
+{
+  auto childID = mNextProtocolInstanceChannelId++;
+  return childID;
+}
+
+JSClass&
+IPDLProtocol::GetProtocolClass()
+{
+  return mProtocolClass;
+}
+
+const ffi::TranslationUnit*
+IPDLProtocol::GetMainTU()
+{
+  return wrapper::GetTU(mAST.get(), wrapper::GetMainTUId(mAST.get()));
+}
+
+void
+IPDLProtocol::BuildNameLookupTables()
+{
+  // We don't have an Int32 hashkey, but it's ok since we can just perform
+  // conversions in and out.
+  nsTHashtable<nsUint32HashKey> visitedTUId;
+  nsTArray<ffi::TUId> workList;
+
+  // Our current loop element.
+  const ffi::TranslationUnit* currentTU = nullptr;
+  // Our current loop index.
+  ffi::TUId currentTUId = -1;
+
+  // We start with only the main tu in the worklist, and then unroll all the
+  // includes.
+  ffi::TUId mainTUId = wrapper::GetMainTUId(mAST.get());
+  workList.AppendElement(mainTUId);
+  while (!workList.IsEmpty()) {
+    // Get an index and anelement from the work list.
+    currentTUId = workList.PopLastElement();
+    currentTU = wrapper::GetTU(mAST.get(), currentTUId);
+
+    // If we are in a protocol file, just add the protocol and stop here (we)
+    // shouldn't keep going through the includes recursively, and we should
+    // ignore the local structs and unions.
+    if (currentTU->file_type == ffi::FileType::Protocol) {
+      // If there is a protocol, add it.
+      if (currentTU->protocol) {
+        mProtocolTable.Put(JoinNamespace(currentTU->protocol->ns),
+                           currentTU->protocol.ptr());
+      }
+    }
+
+    // If we are in a header file, add the structs and the unions it contains,
+    // and then recurse through all of its includes.
+    if (currentTU->file_type == ffi::FileType::Header ||
+        currentTUId == mainTUId) {
+      for (size_t i = 0; i < currentTU->structs.Length(); i++) {
+        // Dirty, but this is the only way to have a pointer to an element
+        const auto* s = currentTU->structs.Elements() + i;
+        mStructTable.Put(JoinNamespace(s->ns), s);
+      }
+
+      for (size_t i = 0; i < currentTU->unions.Length(); i++) {
+        // Dirty, but this is the only way to have a pointer to an element
+        const auto* u = currentTU->unions.Elements() + i;
+        mUnionTable.Put(JoinNamespace(u->ns), u);
+      }
+
+      workList.AppendElements(currentTU->includes);
+    }
+  }
+}
+
+/* static */ bool
+IPDLProtocol::SendMessageDispatch(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
+{
+  // Unwrap the JS args.
+  auto args = JS::CallArgsFromVp(aArgc, aVp);
+
+  // Get the IPDLProtocol object from the private field of the method `this`.
+  JS::RootedObject thisObj(aCx);
+  args.computeThis(aCx, &thisObj);
+
+  auto* instance = static_cast<IPDLProtocolInstance*>(JS_GetPrivate(thisObj));
+
+  if (!instance) {
+    JS_ReportErrorUTF8(aCx, "Cannot use deleted protocol");
+    return false;
+  }
+
+  // Get the method name that we are calling.
+  auto function = JS_GetObjectFunction(&args.callee());
+  nsAutoJSString functionName;
+  if (!functionName.init(aCx, JS_GetFunctionId(function))) {
+    return false;
+  }
+
+  // Call our internal SendMessage function.
+  return instance->SendMessage(aCx, NS_ConvertUTF16toUTF8(functionName), args);
+}
+
+/* static */ bool
+IPDLProtocol::SendConstructorDispatch(JSContext* aCx,
+                                      unsigned aArgc,
+                                      JS::Value* aVp)
+{
+  // Unwrap the JS args.
+  auto args = JS::CallArgsFromVp(aArgc, aVp);
+
+  // Get the IPDLProtocol object from the private field of the method `this`.
+  JS::RootedObject thisObj(aCx);
+  args.computeThis(aCx, &thisObj);
+
+  auto* instance = static_cast<IPDLProtocolInstance*>(JS_GetPrivate(thisObj));
+
+  if (!instance) {
+    JS_ReportErrorUTF8(aCx, "Cannot use deleted protocol");
+    return false;
+  }
+
+  // Get the method name that we are calling.
+  auto function = JS_GetObjectFunction(&args.callee());
+  nsAutoJSString functionName;
+  if (!functionName.init(aCx, JS_GetFunctionId(function))) {
+    return false;
+  }
+
+  // Call our internal SendConstructor function.
+  return instance->SendConstructor(aCx, thisObj, NS_ConvertUTF16toUTF8(functionName), args);
+}
+
+/* static */ bool
+IPDLProtocol::SendDeleteDispatch(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
+{
+  // Unwrap the JS args.
+  auto args = JS::CallArgsFromVp(aArgc, aVp);
+
+  // Get the IPDLProtocol object from the private field of the method `this`.
+  JS::RootedObject thisObj(aCx);
+  args.computeThis(aCx, &thisObj);
+
+  auto* instance = static_cast<IPDLProtocolInstance*>(JS_GetPrivate(thisObj));
+
+  if (!instance) {
+    JS_ReportErrorUTF8(aCx, "Cannot use deleted protocol");
+    return false;
+  }
+
+  // Get the method name that we are calling.
+  auto function = JS_GetObjectFunction(&args.callee());
+  nsAutoJSString functionName;
+  if (!functionName.init(aCx, JS_GetFunctionId(function))) {
+    return false;
+  }
+
+  // Call our internal SendConstructor function.
+  return instance->SendDelete(aCx, NS_ConvertUTF16toUTF8(functionName), args);
+}
+
+void
+IPDLProtocol::RemoveInstance(IPDLProtocolInstance *instance)
+{
+  mInstances.RemoveEntry(instance);
+}
+
+bool
+IPDLProtocol::CheckParamTypeSpec(JSContext* aCx,
+                                 JS::HandleValue aJSVal,
+                                 ffi::Param aParam)
+{
+  // Just unwrap the type spec.
+  return CheckTypeSpec(aCx, aJSVal, aParam.type_spec);
+}
+
+bool
+IPDLProtocol::CheckTypeSpec(JSContext* aCx,
+                            JS::HandleValue aJSVal,
+                            ffi::TypeSpec aTypeSpec)
+{
+  // If the type is nullable and we get a null, we return now
+  if (aTypeSpec.nullable && aJSVal.isNull()) {
+    return true;
+  }
+
+  // If the type is an array, we check the type for all the array elements
+  if (aTypeSpec.array) {
+    // Check if jsVal is an array.
+    bool isArray = false;
+    JS_IsArrayObject(aCx, aJSVal, &isArray);
+    if (!isArray) {
+      return false;
+    }
+
+    // Get the JS array.
+    JS::RootedObject jsArray(aCx, &aJSVal.toObject());
+
+    uint32_t arrayLength = 0;
+    JS_GetArrayLength(aCx, jsArray, &arrayLength);
+
+    // For each element of the array, we check its type.
+    for (size_t i = 0; i < arrayLength; i++) {
+      JS::RootedValue arrayVal(aCx);
+      JS_GetElement(aCx, jsArray, i, &arrayVal);
+
+      if (!CheckType(aCx, arrayVal, aTypeSpec.spec)) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+  // We check the actual type if not an array.
+  return CheckType(aCx, aJSVal, aTypeSpec.spec);
+}
+
+bool
+IPDLProtocol::CheckType(JSContext* aCx,
+                        JS::HandleValue aJSVal,
+                        ffi::QualifiedId aType)
+{
+  // First check the builtin types
+  if (CheckBuiltinType(aCx, aJSVal, aType)) {
+    return true;
+  }
+
+  // Then the protocol types
+  if (CheckProtocolType(aCx, aJSVal, aType)) {
+    return true;
+  }
+
+  // Then the struct types
+  if (CheckStructType(aCx, aJSVal, aType)) {
+    return true;
+  }
+
+  // Then the union types.
+  if (CheckUnionType(aCx, aJSVal, aType)) {
+    return true;
+  }
+
+  // Otherwise we have a type mismatch.
+  return false;
+}
+
+bool
+IPDLProtocol::CheckProtocolType(JSContext* aCx,
+                                JS::HandleValue aJSVal,
+                                ffi::QualifiedId aType)
+{
+  // If we don't know that protocol name, return false.
+  auto typeString = JoinQualifiedId(aType);
+  if (!mProtocolTable.Contains(typeString)) {
+    return false;
+  }
+
+  // If we don't have an object, return false.
+  if (!aJSVal.isObject()) {
+    return false;
+  }
+
+  // Get our JS object.
+  JS::RootedObject jsObj(aCx, &aJSVal.toObject());
+
+  // If the object is not an IPDL protocol class instance, return false.
+  if (typeString.Equals(JS_GetClass(jsObj)->name) != 0) { // not equal
+    return false;
+  }
+
+  auto* protocolObj = static_cast<IPDLProtocol*>(JS_GetPrivate(jsObj));
+
+  if (!protocolObj) {
+    JS_ReportErrorUTF8(aCx, "Couldn't get protocol object from private date field");
+    return false;
+  }
+
+  // Check the protocol name against the one we require.
+  return protocolObj->GetProtocolName().Equals(typeString);
+}
+
+bool
+IPDLProtocol::CheckStructType(JSContext* aCx,
+                              JS::HandleValue aJSVal,
+                              ffi::QualifiedId aType)
+{
+  // If we don't know that struct name, return false.
+  auto* ipdlStruct = mStructTable.Get(JoinQualifiedId(aType));
+  if (!ipdlStruct) {
+    return false;
+  }
+
+  // If we don't have an object, return false.
+  if (!aJSVal.isObject()) {
+    return false;
+  }
+
+  // Get our JS object.
+  JS::RootedObject jsObj(aCx, &aJSVal.toObject());
+
+  // Go through every field of the struct.
+  for (auto& field : ipdlStruct->fields) {
+    JS::RootedValue propertyValue(aCx);
+
+    // Check that the field exists in the object we got.
+    if (!JS_GetProperty(aCx, jsObj, field.name.id.get(), &propertyValue)) {
+      return false;
+    }
+    if (propertyValue.isUndefined()) {
+      return false;
+    }
+
+    // Check that the field type matches the element we got.
+    if (!CheckTypeSpec(aCx, propertyValue, field.type_spec)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool
+IPDLProtocol::CheckUnionType(JSContext* aCx,
+                             JS::HandleValue aJSVal,
+                             ffi::QualifiedId aType)
+{
+  auto* ipdlUnion = mUnionTable.Get(JoinQualifiedId(aType));
+  if (!ipdlUnion) {
+    return false;
+  }
+
+  // Go through every type of the union
+  for (auto& type : ipdlUnion->types) {
+    // If our input value matches any of these types, return true.
+    if (CheckTypeSpec(aCx, aJSVal, type)) {
+      return true;
+    }
+  }
+
+  // Otherwise return false.
+  return false;
+}
+
+/* static */ bool
+IPDLProtocol::CheckBuiltinType(JSContext* aCx, JS::HandleValue aJSVal, ffi::QualifiedId type)
+{
+  // Check all the builtin types using their type name, and custom constraints
+  // such as bounds checking and length.
+  auto typeString = JoinQualifiedId(type);
+
+  if (typeString.Equals("bool")) {
+    return aJSVal.isBoolean();
+  }
+  if (typeString.Equals("char")) {
+    JS::AutoCheckCannotGC nogc;
+    size_t length;
+    if (!aJSVal.isString()) {
+      return false;
+    }
+
+    JS_GetLatin1StringCharsAndLength(aCx, nogc, aJSVal.toString(), &length);
+    return length == 1;
+  }
+  if (typeString.Equals("nsString") || typeString.Equals("nsCString")) {
+    return aJSVal.isString();
+  }
+  if (typeString.Equals("short")) {
+    return aJSVal.isNumber() && (trunc(aJSVal.toNumber()) == aJSVal.toNumber()) &&
+           (aJSVal.toNumber() <= SHRT_MAX) && (aJSVal.toNumber() >= SHRT_MIN);
+  }
+  if (typeString.Equals("int")) {
+    return aJSVal.isNumber() && (trunc(aJSVal.toNumber()) == aJSVal.toNumber()) &&
+           (aJSVal.toNumber() <= INT_MAX) && (aJSVal.toNumber() >= INT_MIN);
+  }
+  if (typeString.Equals("long")) {
+    return aJSVal.isNumber() && (trunc(aJSVal.toNumber()) == aJSVal.toNumber()) &&
+           (aJSVal.toNumber() <= LONG_MAX) && (aJSVal.toNumber() >= LONG_MIN);
+  }
+  if (typeString.Equals("float")) {
+    return aJSVal.isNumber() && (aJSVal.toNumber() <= FLT_MAX) &&
+           (aJSVal.toNumber() >= FLT_MIN);
+  }
+  if (typeString.Equals("double")) {
+    return aJSVal.isNumber();
+  }
+  if (typeString.Equals("int8_t")) {
+    return aJSVal.isNumber() && (trunc(aJSVal.toNumber()) == aJSVal.toNumber()) &&
+           (aJSVal.toNumber() <= INT8_MAX) && (aJSVal.toNumber() >= INT8_MIN);
+  }
+  if (typeString.Equals("uint8_t")) {
+    return aJSVal.isNumber() && (trunc(aJSVal.toNumber()) == aJSVal.toNumber()) &&
+           (aJSVal.toNumber() <= UINT8_MAX) && (aJSVal.toNumber() >= 0);
+  }
+  if (typeString.Equals("int16_t")) {
+    return aJSVal.isNumber() && (trunc(aJSVal.toNumber()) == aJSVal.toNumber()) &&
+           (aJSVal.toNumber() <= INT16_MAX) && (aJSVal.toNumber() >= INT16_MIN);
+  }
+  if (typeString.Equals("uint16_t")) {
+    return aJSVal.isNumber() && (trunc(aJSVal.toNumber()) == aJSVal.toNumber()) &&
+           (aJSVal.toNumber() <= UINT16_MAX) && (aJSVal.toNumber() >= 0);
+  }
+  if (typeString.Equals("int32_t")) {
+    return aJSVal.isNumber() && (trunc(aJSVal.toNumber()) == aJSVal.toNumber()) &&
+           (aJSVal.toNumber() <= INT32_MAX) && (aJSVal.toNumber() >= INT32_MIN);
+  }
+  if (typeString.Equals("uint32_t")) {
+    return aJSVal.isNumber() && (trunc(aJSVal.toNumber()) == aJSVal.toNumber()) &&
+           (aJSVal.toNumber() <= UINT32_MAX) && (aJSVal.toNumber() >= 0);
+  }
+
+  // These IPDL builtin types are not allowed from the JS API because they are
+  // not easily representable.
+  if (typeString.Equals("int64_t") || typeString.Equals("uint64_t") ||
+      typeString.Equals("size_t") || typeString.Equals("ssize_t") ||
+      typeString.Equals("nsresult") ||
+      typeString.Equals("mozilla::ipc::Shmem") ||
+      typeString.Equals("mozilla::ipc::ByteBuf") ||
+      typeString.Equals("mozilla::ipc::FileDescriptor")) {
+    JS_ReportErrorUTF8(
+      aCx, "IPDL: cannot use type `%s` from JS", typeString.get());
+    return false;
+  }
+
+  return false;
+}
+
+IPDLProtocol::~IPDLProtocol()
+{
+  mProtoObj = nullptr;
+  mConstructorObj = nullptr;
+  mozilla::DropJSObjects(this);
+}
+
+/* static */ bool
+IPDLProtocol::Constructor(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
+{
+  JS::CallArgs args = JS::CallArgsFromVp(aArgc, aVp);
+
+  // This function is a bit tricky. Basically we need to construct an object with
+  // the protocol's C++ JSClass, but with the prototype of the inherited JS class.
+
+  // First we get the prototype of the inherited class.
+  JS::RootedValue newTargetObjProtov(aCx);
+  JS::RootedObject newTargetObj(aCx, &args.newTarget().toObject());
+  if (!JS_GetProperty(aCx, newTargetObj, "prototype", &newTargetObjProtov)) {
+    return false;
+  }
+  JS::RootedObject newTargetObjProto(aCx, &newTargetObjProtov.toObject());
+
+  // Now we get the prototype of abstract protocol class, and get the IPDLProtocol
+  // object from it.
+  JS::RootedObject callee(aCx, &args.callee());
+  JS::RootedValue prototypev(aCx);
+  if (!JS_GetProperty(aCx, callee, "prototype", &prototypev)) {
+    return false;
+  }
+  JS::RootedObject prototype(aCx, &prototypev.toObject());
+  auto protocol = static_cast<IPDLProtocol*>(JS_GetPrivate(prototype));
+
+  if (!protocol) {
+    JS_ReportErrorUTF8(aCx, "Couldn't get protocol object from private date field");
+    return false;
+  }
+
+  // Now we construct our new object.
+  JS::RootedObject newClassObject(
+    aCx,
+    JS_NewObjectWithGivenProto(
+      aCx, &protocol->GetProtocolClass(), newTargetObjProto));
+
+  // We add it to our list of protocol instances and set its private field.
+
+  auto newInstance = MakeRefPtr<IPDLProtocolInstance>(
+    nullptr, protocol->RegisterExternalInstance(), protocol, newClassObject);
+
+  protocol->mInstances.PutEntry(newInstance);
+  JS_SetPrivate(newClassObject, newInstance);
+
+  args.rval().setObject(*newClassObject);
+  return true;
+}
+
+/* static */ bool
+IPDLProtocol::RecvDelete(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
+{
+  return true;
+}
+
+/* static */ bool
+IPDLProtocol::RecvConstructor(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
+{
+  return true;
+}
+
+/* static */ bool
+IPDLProtocol::AbstractRecvMessage(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
+{
+  JS_ReportErrorUTF8(
+    aCx, "Received message but recv method not overriden!");
+  return false;
+}
+
+/* static */ bool
+IPDLProtocol::AbstractAlloc(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
+{
+  JS_ReportErrorUTF8(
+    aCx, "Cannot alloc from abstract IPDL protocol class!");
+  return false;
+}
+
+/* static */ nsCString
+IPDLProtocol::JoinQualifiedId(const ffi::QualifiedId aQid)
+{
+  nsAutoCString ret;
+
+  for (auto& qual : aQid.quals) {
+    ret.Append(qual);
+    ret.Append("::");
+  }
+  ret.Append(aQid.base_id.id);
+
+  return std::move(ret);
+}
+
+/* static */ nsCString
+IPDLProtocol::JoinNamespace(const ffi::Namespace aNs)
+{
+  nsAutoCString ret;
+
+  for (auto& name : aNs.namespaces) {
+    ret.Append(name);
+    ret.Append("::");
+  }
+  ret.Append(aNs.name.id);
+
+  return std::move(ret);
+}
+
+constexpr JSClassOps IPDLProtocol::sIPDLJSClassOps;
+
+} // ipdl
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl_new/ipdl_bindings/IPDLProtocol.h
@@ -0,0 +1,269 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef dom_base_ipdl_bindings_IPDLProtocol_h
+#define dom_base_ipdl_bindings_IPDLProtocol_h
+
+#include "jsapi.h"
+#include "nsDataHashtable.h"
+#include "nsStringFwd.h"
+#include "nsCycleCollectionParticipant.h"
+
+class nsIGlobalObject;
+
+namespace mozilla {
+namespace dom {
+class IPDL;
+} // dom
+
+namespace ipdl {
+class IPDLProtocolInstance;
+
+namespace ipc {
+class IPCInterface;
+} // ipc
+
+namespace ffi {
+struct AST;
+struct Union;
+struct Struct;
+struct NamedProtocol;
+struct MessageDecl;
+struct TranslationUnit;
+struct Param;
+struct TypeSpec;
+struct QualifiedId;
+struct Namespace;
+} // ffi
+
+// Represents the side of the protocol.
+// This is un-nested to be forward-declarable.
+enum class IPDLSide : bool
+{
+  Parent,
+  Child
+};
+
+/**
+ * Represents an IPDLProtocol to be used under the cover from JS.
+ * This is owned by the IPDL class, and represents an abstract IPDL protocol
+ * class which can be extended from JS.
+ *
+ * Example usage from JS:
+ *
+ *     IPDL.registerTopLevelClass(TestParent);
+ *
+ *     class TestParent extends IPDL.PTestParent {
+ *       // We add a listener for the message `SomeMessageFromChild` defined in
+ *       // the IPDL protocol
+ *       recvSomeMessageFromChild(arg1, arg2) {
+ *           // In IPDL, out params are named. This is how we return them.
+ *           return {out1: "test", out2: "test2" };
+ *       };
+ *
+ *       // We define the allocation function
+ *       allocPSubTest() {
+ *         return new SubTestParent();
+ *       }
+ *
+ *       // We need to pass the content parent ID to the super constructor
+ *       constructor(id) {
+ *         super(id);
+ *         // We send the sync message `SomeSyncMessageToChild` to the child
+ *         var syncResult = this.sendSomeSyncMessageToChild(123, [4, 5, 6]);
+ *         console.log(syncResult);
+ *
+ *         // We send the async message `SomeAsyncMessageToChild` to the child
+ *         // Async calls return a promise
+ *         protocol.sendSomeAsyncMessageToChild("hi").then(function(asyncResult)
+ * { console.log(asyncResult);
+ *         });
+ *       }
+ *     }
+ */
+class IPDLProtocol : public nsISupports
+{
+
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(IPDLProtocol)
+
+  // Custom deleter for our AST smart pointer.
+  struct ASTDeletePolicy
+  {
+    void operator()(const mozilla::ipdl::ffi::AST* aASTPtr);
+  };
+
+  IPDLProtocol(dom::IPDL* aIPDL,
+               IPDLSide aSide,
+               const nsACString& aIPDLFile,
+               nsIGlobalObject* aGlobal,
+               JS::HandleObject aParent,
+               JSContext* aCx);
+
+  // Get the JSObject corresponding to the constructor of the JS class
+  // of our protocol.
+  JSObject* GetProtocolClassConstructor();
+
+  // Returns a new channel ID and updates the internal channel ID counter.
+  uint32_t RegisterExternalInstance();
+
+  // Returns the protocol name.
+  nsCString GetProtocolName();
+
+  // Returns the protocol side.
+  IPDLSide GetSide() { return mSide; }
+
+  // Returns a message decl for a given name.
+  const ffi::MessageDecl* GetMessageDecl(const nsACString& aName)
+  {
+    return mMessageTable.Get(aName);
+  }
+
+  // Type checks a parameter against a JS Value argument.
+  // This is just a slim wrapper around CheckTypeSpec.
+  bool CheckParamTypeSpec(JSContext* aCx,
+                          JS::HandleValue aJSVal,
+                          ffi::Param aParam);
+
+  // Returns the global object associated to that protocol.
+  nsIGlobalObject* GetGlobal() { return mGlobal; }
+
+  void RemoveInstance(IPDLProtocolInstance* instance);
+
+  // Returns the protocol name suffixed with the protocol side.
+  static nsCString GetSidedProtocolName(const nsCString& aProtocolName,
+                                        IPDLSide aSide)
+  {
+    return aProtocolName + (aSide == IPDLSide::Parent
+                             ? NS_LITERAL_CSTRING("Parent")
+                             : NS_LITERAL_CSTRING("Child"));
+  }
+
+  // Small helper function to join a qualified id into a string.
+  static nsCString JoinQualifiedId(const ffi::QualifiedId qid);
+  // Small helper function to join a namespace into a string.
+  static nsCString JoinNamespace(const ffi::Namespace ns);
+
+protected:
+  // The IPC interface object that we use for the actual IPC stuff.
+  // Which side of the protocol we're on.
+  IPDLSide mSide;
+  // Our Rust-owned AST.
+  UniquePtr<const ffi::AST, ASTDeletePolicy> mAST;
+  // The global JS object interface needed for promises.
+  nsCOMPtr<nsIGlobalObject> mGlobal;
+  // The JS constructor we expose to the API user.
+  JS::Heap<JSObject*> mConstructorObj;
+  // The JS prototype we expose to the API user.
+  JS::Heap<JSObject*> mProtoObj;
+  // The C++ JSClass corresponding to the protocol.
+  JSClass mProtocolClass;
+  // The unsided protocol name.
+  nsCString mProtocolName;
+  // The sided protocol name.
+  nsCString mSidedProtocolName;
+  // The IPDL global object managing this protocol.
+  dom::IPDL* MOZ_NON_OWNING_REF mIPDL;
+
+  // Typedefs for the member variables defined below.
+  typedef nsDataHashtable<nsCStringHashKey, const ffi::MessageDecl*>
+    MessageTable;
+
+  typedef nsDataHashtable<nsCStringHashKey, const ffi::Struct*> StructTable;
+
+  typedef nsDataHashtable<nsCStringHashKey, const ffi::Union*> UnionTable;
+
+  typedef nsDataHashtable<nsCStringHashKey, const ffi::NamedProtocol*>
+    ProtocolTable;
+
+  typedef nsTHashtable<nsRefPtrHashKey<IPDLProtocolInstance>> InstanceList;
+
+  // List of current IPDL protocol instances living in JS.
+  InstanceList mInstances;
+  uint32_t mNextProtocolInstanceChannelId;
+
+  // Maps message names to the AST struct describing them.
+  MessageTable mMessageTable;
+  // Maps struct names to the AST struct describing them.
+  StructTable mStructTable;
+  // Maps union names to the AST struct describing them.
+  UnionTable mUnionTable;
+  // Maps protocol names to the AST struct describing them.
+  ProtocolTable mProtocolTable;
+
+  virtual ~IPDLProtocol();
+
+  // Builds the struct, union and protocol name->AST lookup tables so that we
+  // can easily access name from the TypeSpecs given by message parameters.
+  void BuildNameLookupTables();
+
+  // Used internally to get the protocol JSClass.
+  JSClass& GetProtocolClass();
+
+  // Returns the main translation unit.
+  const ffi::TranslationUnit* GetMainTU();
+
+  // Type checks a TypeSpec against a JS Value.
+  // This takes care of checking the nullable and array extras, and then calls
+  // CheckType, possibly on all elements if dealing with an array.
+  bool CheckTypeSpec(JSContext* cx,
+                     JS::HandleValue jsVal,
+                     ffi::TypeSpec typeSpec);
+
+  // Type checks a type name against a JS Value.
+  bool CheckType(JSContext* cx, JS::HandleValue jsVal, ffi::QualifiedId type);
+
+  // Type checks a protocol type name against a JS Value.
+  bool CheckProtocolType(JSContext* cx,
+                         JS::HandleValue jsVal,
+                         ffi::QualifiedId type);
+
+  // Type checks a struct type name against a JS Value.
+  bool CheckStructType(JSContext* cx,
+                       JS::HandleValue jsVal,
+                       ffi::QualifiedId type);
+
+  // Type checks a union type name against a JS Value.
+  bool CheckUnionType(JSContext* cx,
+                      JS::HandleValue jsVal,
+                      ffi::QualifiedId type);
+
+  // Type checks a builtin type name against a JS Value.
+  // Note that some usual IPDL C++-inspired types are not available to use
+  // with this JS API (for instance, int64_t).
+  static bool CheckBuiltinType(JSContext* cx,
+                               JS::HandleValue jsVal,
+                               ffi::QualifiedId type);
+
+  // These are wrappers unpacking the IPDLProtocolInstance stored in the private
+  // data field of the callee, then dispatching the call to that instance.
+  static bool SendMessageDispatch(JSContext* cx, unsigned argc, JS::Value* vp);
+
+  static bool SendConstructorDispatch(JSContext* cx,
+                                      unsigned argc,
+                                      JS::Value* vp);
+  static bool SendDeleteDispatch(JSContext* cx, unsigned argc, JS::Value* vp);
+
+  // Constructor for the protocol class, which is called from the inherited
+  // classes.
+  static bool Constructor(JSContext* cx, unsigned argc, JS::Value* vp);
+
+  // Default implementations for various IPDL stuff. Abstract methods throw on
+  // call because they MUST be reimplemented.
+  static bool RecvDelete(JSContext* cx, unsigned argc, JS::Value* vp);
+  static bool RecvConstructor(JSContext* cx, unsigned argc, JS::Value* vp);
+  static bool AbstractRecvMessage(JSContext* cx, unsigned argc, JS::Value* vp);
+  static bool AbstractAlloc(JSContext* cx, unsigned argc, JS::Value* vp);
+
+  // Our IPDLProtocol JS class custom operations.
+  static constexpr JSClassOps sIPDLJSClassOps = {};
+};
+
+} // ipdl
+} // mozilla
+
+#endif // dom_base_ipdl_bindings_IPDLProtocol_h
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl_new/ipdl_bindings/IPDLProtocolInstance.cpp
@@ -0,0 +1,460 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "IPDLProtocolInstance.h"
+
+#include <cfloat>
+
+#include "IPCInterface.h"
+#include "ipdl_ffi_generated.h"
+#include "mozilla/dom/DOMMozPromiseRequestHolder.h"
+#include "mozilla/dom/Promise.h"
+#include "nsJSUtils.h"
+#include "wrapper.h"
+#include "mozilla/dom/IPDL.h"
+
+namespace mozilla {
+namespace ipdl {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(IPDLProtocolInstance)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IPDLProtocolInstance)
+  tmp->mInstanceObject = nullptr;
+  tmp->mAsyncReturnOverride = nullptr;
+  mozilla::DropJSObjects(tmp);
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IPDLProtocolInstance)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(IPDLProtocolInstance)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mInstanceObject)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mAsyncReturnOverride)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+NS_IMPL_CYCLE_COLLECTING_ADDREF(IPDLProtocolInstance)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(IPDLProtocolInstance)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IPDLProtocolInstance)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+IPDLProtocolInstance::IPDLProtocolInstance(ipc::IPCInterface* aIPCInterface,
+                                           uint32_t aChannelId,
+                                           IPDLProtocol* aIPDLProtocol,
+                                           JS::HandleObject aInstanceObject)
+  : mChannelId(aChannelId)
+  , mIPDLProtocol(aIPDLProtocol)
+  , mInstanceObject(aInstanceObject)
+  , mAsyncReturnOverride(nullptr)
+  , mConstructing(true)
+{
+  mozilla::HoldJSObjects(this);
+
+  SetIPCInterface(aIPCInterface);
+}
+
+IPDLProtocolInstance::~IPDLProtocolInstance()
+{
+  mInstanceObject = nullptr;
+  mAsyncReturnOverride = nullptr;
+  mozilla::DropJSObjects(this);
+}
+
+void
+IPDLProtocolInstance::SetIPCInterface(ipc::IPCInterface* aIPCInterface)
+{
+  mIPCInterface = aIPCInterface;
+
+  if (mIPCInterface) {
+    // If we're setting the interface, it means that we're done with the
+    // constructor.
+    mConstructing = false;
+
+    NS_NAMED_LITERAL_CSTRING(parentSuffix, "Parent");
+    NS_NAMED_LITERAL_CSTRING(childSuffix, "Child");
+
+    // Hook up this instance to the IPC interface for receiving messages.
+    mIPCInterface->SetIPDLInstance(
+      mChannelId,
+      mIPDLProtocol->GetProtocolName() +
+        (mIPDLProtocol->GetSide() == IPDLSide::Child ? childSuffix
+                                                     : parentSuffix),
+      this);
+  }
+}
+
+bool
+IPDLProtocolInstance::SendConstructor(JSContext* aCx,
+                                      JS::HandleObject aThisObj,
+                                      const nsCString& aFuncName,
+                                      JS::CallArgs aArgs)
+{
+  // Make sure the instance can send stuff.
+  if (!CheckIsAvailable(aCx)) {
+    return false;
+  }
+
+  NS_NAMED_LITERAL_CSTRING(sendPrefix, "send");
+  NS_NAMED_LITERAL_CSTRING(constructorSuffix, "Constructor");
+
+  nsAutoCString allocName(NS_LITERAL_CSTRING("alloc"));
+  allocName.Append(Substring(aFuncName,
+                             sendPrefix.Length(),
+                             aFuncName.Length() - sendPrefix.Length() -
+                               constructorSuffix.Length()));
+
+  JS::HandleValueArray argsArray(aArgs);
+
+  // Construct the sub protocol by calling our alloc function.
+  JS::RootedValue constructedValue(aCx);
+  if (!JS_CallFunctionName(
+        aCx, aThisObj, allocName.get(), argsArray, &constructedValue)) {
+    aArgs.rval().setUndefined();
+    return false;
+  }
+
+  JS::RootedObject constructedObject(aCx, &constructedValue.toObject());
+
+  // Add the IPC interface to the newly constructed object.
+  auto instance = static_cast<IPDLProtocolInstance*>(JS_GetPrivate(constructedObject.get()));
+
+  if (!instance) {
+    JS_ReportErrorUTF8(aCx, "Couldn't get protocol instance object from private date field");
+    return false;
+  }
+
+  instance->SetIPCInterface(mIPCInterface);
+
+  // Create new call arguments from the current call arguments to be passed to
+  // SendMessage.
+  JS::AutoValueVector newArgsVec(aCx);
+  if (!newArgsVec.initCapacity(aArgs.length() +
+                               3)) { // this + callee + channelID
+    JS_ReportErrorUTF8(
+      aCx, "Could not initialize new argument vector for constructor");
+    aArgs.rval().setUndefined();
+    return false;
+  }
+
+  JS::CallArgs newArgs =
+    JS::CallArgsFromVp(aArgs.length() + 1, newArgsVec.begin());
+  newArgs.setThis(aArgs.thisv().get());
+  newArgs.setCallee(aArgs.calleev().get());
+
+  // We add the channel ID as the first argument.
+  JS::RootedValue channelID(aCx, JS::NumberValue(instance->ChannelId()));
+  newArgs[0].set(channelID);
+  // We copy all the other arguments.
+  for (unsigned int i = 0; i < aArgs.length(); i++) {
+    newArgs[i + 1].set(aArgs[i]);
+  }
+
+  // We send the construction message, overriding the return result
+  // with the constructed object.
+  bool res = SendMessage(aCx, aFuncName, newArgs, constructedObject);
+
+  aArgs.rval().set(newArgs.rval());
+
+  return res;
+}
+
+bool
+IPDLProtocolInstance::SendDelete(JSContext* aCx,
+                                 const nsCString& aFuncName,
+                                 JS::CallArgs aArgs)
+{
+  // Make sure the instance can send stuff.
+  if (!CheckIsAvailable(aCx)) {
+    return false;
+  }
+
+  bool res = SendMessage(aCx, aFuncName, aArgs);
+  return res;
+}
+
+bool
+IPDLProtocolInstance::SendMessage(JSContext* aCx,
+                                  const nsCString& aFuncName,
+                                  JS::CallArgs aArgs,
+                                  JS::HandleObject aReturnOverride)
+{
+  // Make sure the instance can send stuff.
+  if (!CheckIsAvailable(aCx)) {
+    return false;
+  }
+
+  // Get the corresponding message AST struct from our lookup table.
+  auto message = mIPDLProtocol->GetMessageDecl(aFuncName);
+  // Sanity check. Usually the JS engine will trigger a type error saying it
+  // doesn't find the function before we reach this stage. If this assertion
+  // is triggered, it means there is a flaw in the way we add messages.
+  MOZ_ASSERT(message);
+
+  auto& inParams = message->in_params;
+
+  auto expectedArgCount = inParams.Length();
+  // Since our arg count reflects a well defined protocol, should we
+  // require strict arg count?
+  if (!aArgs.requireAtLeast(aCx, aFuncName.get(), expectedArgCount)) {
+    return false;
+  }
+
+
+  // Check that every param and args have matching types.
+  for (size_t i = 0; i < inParams.Length(); i++) {
+    if (!mIPDLProtocol->CheckParamTypeSpec(
+          aCx, aArgs.get(i), inParams.ElementAt(i))) {
+      JS_ReportErrorUTF8(
+        aCx,
+        "Type mismatch on %zuth argument of `%s`: expected %s, got %s",
+        i,
+        aFuncName.get(),
+        IPDLProtocol::JoinQualifiedId(inParams.ElementAt(i).type_spec.spec)
+          .get(),
+        InformalValueTypeName(aArgs.get(i)));
+      aArgs.rval().setUndefined();
+      return false;
+    }
+  }
+
+  NS_NAMED_LITERAL_CSTRING(sendPrefix, "send");
+
+  nsAutoCString messageName(
+    Substring(aFuncName,
+              sendPrefix.Length(),
+              aFuncName.Length() - sendPrefix.Length())); // trim leading `send`
+
+  // Prepare the argument array for sending to the IPC interface.
+  auto argArray = JS::HandleValueArray(aArgs);
+
+  // Send the message to the underlying IPC interface depending on the send
+  // semantics.
+  JS::RootedValue retVal(aCx);
+  bool ret;
+  switch (message->send_semantics) {
+    case ffi::SendSemantics::Async:
+      retVal = SendAsyncMessage(aCx, messageName, argArray, aReturnOverride);
+      ret = true;
+      break;
+
+    case ffi::SendSemantics::Sync:
+      ret = SendSyncMessage(aCx, messageName, argArray, &retVal);
+      if (aReturnOverride.get()) {
+        retVal.setObject(*aReturnOverride);
+      }
+      break;
+
+    case ffi::SendSemantics::Intr:
+      ret = SendIntrMessage(aCx, messageName, argArray, &retVal);
+      if (aReturnOverride.get()) {
+        retVal.setObject(*aReturnOverride);
+      }
+      break;
+  }
+
+  aArgs.rval().set(retVal);
+
+  return ret;
+}
+
+JS::Value
+IPDLProtocolInstance::SendAsyncMessage(JSContext* aCx,
+                                       const nsCString& aFuncName,
+                                       const JS::HandleValueArray& aArgArray,
+                                       JS::HandleObject aReturnOverride)
+{
+  // Create a JS/DOM promise to return.
+  // It will resolve to the result of the message call.
+  ErrorResult aRv;
+  RefPtr<dom::Promise> outer =
+    dom::Promise::Create(mIPDLProtocol->GetGlobal(), aRv);
+  if (aRv.Failed()) {
+    return JS::UndefinedValue();
+  }
+
+  // This wraps the AsyncMessagePromise that we use for the actual IPC/C++
+  // message so that if the global object gets disconnected, that promise also
+  // gets disconnected and is not left dangling.
+  auto promiseHolder = MakeRefPtr<
+    dom::DOMMozPromiseRequestHolder<ipc::IPCInterface::AsyncMessagePromise>>(
+    mIPDLProtocol->GetGlobal());
+
+  bool hasReturnOverride = (aReturnOverride.get());
+  mAsyncReturnOverride = aReturnOverride;
+
+  // Actually send the message to the IPC interface.
+  // If the C++ promise succeeds, then we resolve the JS promise.
+  // If it fails, we reject the JS promise.
+  // We also make sure that the promise holder tracks our promise.
+  mIPCInterface
+    ->SendAsyncMessage(
+      aCx, mIPDLProtocol->GetProtocolName(), mChannelId, aFuncName, aArgArray)
+    ->Then(mIPDLProtocol->GetGlobal()->EventTargetFor(TaskCategory::Other),
+           __func__,
+           [&asyncReturnOverride = mAsyncReturnOverride, promiseHolder, outer, hasReturnOverride](
+             const JS::Value& aResult) {
+             // Tell the promise holder that our promise is resolved.
+             promiseHolder->Complete();
+
+             // Note, you can access the holder's bound global in
+             // your reaction handler.  Its mostly likely set if
+             // the handler fires, but you still must check for
+             // its existence since something could disconnect
+             // the global between when the MozPromise reaction
+             // runnable is queued and when it actually runs.
+             nsIGlobalObject* global = promiseHolder->GetParentObject();
+             NS_ENSURE_TRUE_VOID(global);
+
+             // Resolve the JS promise with our result.
+             // If we have an override return value, return it instead.
+             if (hasReturnOverride) {
+               outer->MaybeResolve(JS::ObjectValue(*asyncReturnOverride));
+               asyncReturnOverride = nullptr;
+             } else {
+               outer->MaybeResolve(aResult);
+             }
+           },
+           [promiseHolder, outer, aCx](nsCString aRv) {
+             // Tell the promise holder that our promise is resolved.
+             promiseHolder->Complete();
+
+             // Reject the JS promise with our error message.
+             JS::RootedValue retVal(
+               aCx, JS::StringValue(JS_NewStringCopyZ(aCx, aRv.get())));
+             outer->MaybeReject(aCx, retVal);
+           })
+    ->Track(*promiseHolder);
+
+  // Get the JSObject corresponding to the JS promise.
+  JS::RootedObject retObj(aCx, outer->PromiseObj());
+
+  return JS::ObjectValue(*retObj);
+}
+
+bool
+IPDLProtocolInstance::SendSyncMessage(JSContext* aCx,
+                                      const nsCString& aFuncName,
+                                      const JS::HandleValueArray& aArgArray,
+                                      JS::MutableHandleValue aRet)
+{
+  // Simply call the underlying IPC interface directly.
+  return mIPCInterface->SendSyncMessage(aCx,
+                                        mIPDLProtocol->GetProtocolName(),
+                                        mChannelId,
+                                        aFuncName,
+                                        aArgArray,
+                                        aRet);
+}
+
+bool
+IPDLProtocolInstance::SendIntrMessage(JSContext* aCx,
+                                      const nsCString& aFuncName,
+                                      const JS::HandleValueArray& aArgArray,
+                                      JS::MutableHandleValue aRet)
+{
+  // Simply call the underlying IPC interface directly.
+  return mIPCInterface->SendIntrMessage(aCx,
+                                        mIPDLProtocol->GetProtocolName(),
+                                        mChannelId,
+                                        aFuncName,
+                                        aArgArray,
+                                        aRet);
+}
+
+bool
+IPDLProtocolInstance::RecvMessage(JSContext* aCx,
+                                  const nsCString& aMessageName,
+                                  const JS::HandleValueArray& aArgArray,
+                                  JS::MutableHandleValue aRet)
+{
+  // Make sure the instance can receive stuff.
+  if (!CheckIsAvailable(aCx)) {
+    return false;
+  }
+
+  const nsAutoCString& funcName = NS_LITERAL_CSTRING("recv") + aMessageName;
+  JS::RootedValue prop(aCx);
+
+  NS_NAMED_LITERAL_CSTRING(constructorSuffix, "Constructor");
+
+  // Check if the message name ends with the constructor suffix.
+  if (aMessageName.RFind(
+        constructorSuffix.get(), false, -1, constructorSuffix.Length()) != kNotFound) {
+    nsAutoCString protocolName(Substring(
+      aMessageName, 0, aMessageName.Length() - constructorSuffix.Length()));
+    nsAutoCString allocName(protocolName);
+    allocName.InsertLiteral("alloc", 0);
+
+    // Allocate the new sub protocol instance by calling the alloc method.
+    JS::RootedValue allocatedProtocolValue(aCx);
+    JS::RootedObject objectInstance(aCx, mInstanceObject);
+    JS_CallFunctionName(
+      aCx,
+      objectInstance,
+      allocName.get(),
+      // Ignore the leading channel ID.
+      JS::HandleValueArray::subarray(aArgArray, 1, aArgArray.length() - 1),
+      &allocatedProtocolValue);
+
+    JS::RootedObject allocatedProtocol(aCx, &allocatedProtocolValue.toObject());
+
+    auto instance = static_cast<IPDLProtocolInstance*>(JS_GetPrivate(allocatedProtocol.get()));
+
+    if (!instance) {
+      JS_ReportErrorUTF8(aCx, "Couldn't get protocol instance object from private date field");
+      return false;
+    }
+
+    // Set the IPC interface of the constructed protocol object.
+    instance->SetIPCInterface(mIPCInterface);
+
+    nsAutoCString constructorName(protocolName);
+    constructorName.InsertLiteral("recv", 0);
+    constructorName.AppendLiteral("Constructor");
+
+    // Call the recvConstructor function
+    JS::RootedValue unusedResult(aCx);
+    JS_CallFunctionName(aCx,
+                        objectInstance,
+                        constructorName.get(),
+                        JS::HandleValueArray(allocatedProtocolValue),
+                        &unusedResult);
+
+    return true;
+  }
+
+  // Otherwise if we have a regular message, just call it.
+  JS::RootedObject objInstance(aCx, mInstanceObject);
+  if (!JS_GetProperty(aCx, objInstance, funcName.get(), &prop)) {
+    aRet.setUndefined();
+    return false;
+  }
+  if (!JS_CallFunctionValue(aCx, objInstance, prop, aArgArray, aRet)) {
+    aRet.setUndefined();
+    return false;
+  }
+
+  // If we receive a delete, mark our protocol instance as deleted.
+  if (aMessageName.Equals("__delete__")) {
+    aRet.setUndefined();
+
+    JS_SetPrivate(mInstanceObject, nullptr);
+    mIPCInterface->RemoveIPDLInstance(mChannelId,
+                                      mIPDLProtocol->GetProtocolName());
+    // ALWAYS PUT THIS LAST, DO NOT ACCESS `this` AFTERWARDS
+    mIPDLProtocol->RemoveInstance(this);
+  }
+
+  return true;
+}
+
+JSObject*
+IPDLProtocolInstance::GetInstanceObject()
+{
+  return mInstanceObject;
+}
+
+} // ipdl
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl_new/ipdl_bindings/IPDLProtocolInstance.h
@@ -0,0 +1,134 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef dom_base_ipdl_bindings_IPDLProtocolInstance_h
+#define dom_base_ipdl_bindings_IPDLProtocolInstance_h
+
+#include "jsapi.h"
+#include "nsStringFwd.h"
+#include "nsCycleCollectionParticipant.h"
+
+namespace mozilla {
+namespace dom {
+class IPDL;
+} // dom
+
+namespace ipdl {
+class IPDLProtocol;
+
+namespace ipc {
+class IPCInterface;
+} // ipc
+
+namespace ffi {
+struct AST;
+struct Union;
+struct Struct;
+struct NamedProtocol;
+struct MessageDecl;
+struct TranslationUnit;
+struct Param;
+struct TypeSpec;
+struct QualifiedId;
+struct Namespace;
+} // ffi
+
+/**
+ * Represents an IPDL protocol object instance, either created automatically if the protocol is top level,
+ * or allocated from the parent protocol.
+ */
+class IPDLProtocolInstance : public nsISupports
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(IPDLProtocolInstance)
+
+  IPDLProtocolInstance(ipc::IPCInterface* aIPCInterface,
+                       uint32_t aChannelId,
+                       IPDLProtocol* aIPDLProtocol,
+                       JS::HandleObject aInstanceObject);
+
+  // Returns the JSObject corresponding to this instance.
+  JSObject* GetInstanceObject();
+
+  // Returns the channel ID of this instance.
+  uint32_t ChannelId() { return mChannelId; };
+
+  // Represents the IPDL sendSubProtocolConstructor functions.
+  bool SendConstructor(JSContext* aCx,
+                       JS::HandleObject aThisObj,
+                       const nsCString& aFuncName,
+                       JS::CallArgs aArgs);
+  // Represents the IPDL send__delete__ function.
+  bool SendDelete(JSContext* aCx, const nsCString& aFuncName, JS::CallArgs aArgs);
+
+  // Performs param/arg type checking and calls a sub-function depending on
+  // the message send semantics.
+  bool SendMessage(JSContext* aCx,
+                   const nsCString& aFuncName,
+                   JS::CallArgs aArgs,
+                   JS::HandleObject aReturnOverride = nullptr);
+
+  // Sends an async message by calling the underlying IPC interface, returning
+  // a promise to the return value object.
+  JS::Value SendAsyncMessage(JSContext* aCx,
+                             const nsCString& aFuncName,
+                             const JS::HandleValueArray& aArgArray,
+                             JS::HandleObject aReturnOverride = nullptr);
+
+  // Sends a sync message by calling the underlying IPC interface, returning
+  // the return value object.
+  bool SendSyncMessage(JSContext* aCx,
+                       const nsCString& aFuncName,
+                       const JS::HandleValueArray& aArgArray,
+                       JS::MutableHandleValue aRet);
+
+  // Sends an intr message by calling the underlying IPC interface, returning
+  // the return value object.
+  bool SendIntrMessage(JSContext* aCx,
+                       const nsCString& aFuncName,
+                       const JS::HandleValueArray& aArgArray,
+                       JS::MutableHandleValue aRet);
+
+  // Receives a message from the underlying IPC interface, returning the result
+  // if any.
+  bool RecvMessage(JSContext* aCx,
+                   const nsCString& aMessageName,
+                   const JS::HandleValueArray& aArgArray,
+                   JS::MutableHandleValue aRet);
+
+  // Sets the IPC interface of the IPDL instance.
+  void SetIPCInterface(ipc::IPCInterface* aIPCInterface);
+
+protected:
+  virtual ~IPDLProtocolInstance();
+
+  // Whether this protocol is being constructed (we're in the constructor of the instance).
+  bool IsConstructing() { return mConstructing; }
+
+  bool CheckIsAvailable(JSContext* aCx)
+  {
+    if (IsConstructing()) {
+      JS_ReportErrorUTF8(aCx,
+                        "Protocol is constructing, cannot be used yet.");
+      return false;
+    }
+
+    return true;
+  }
+
+  ipc::IPCInterface* MOZ_NON_OWNING_REF mIPCInterface;
+  uint32_t mChannelId;
+  IPDLProtocol* MOZ_NON_OWNING_REF mIPDLProtocol;
+  JS::Heap<JSObject*> mInstanceObject;
+  JS::Heap<JSObject*> mAsyncReturnOverride;
+  bool mConstructing;
+};
+
+} // ipdl
+} // mozilla
+
+#endif // dom_base_ipdl_bindings_IPDLProtocolInstance_h
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl_new/ipdl_bindings/PContentChildIPCInterface.cpp
@@ -0,0 +1,198 @@
+#include "PContentChildIPCInterface.h"
+
+#include "mozilla/dom/ContentChild.h"
+
+namespace mozilla {
+namespace ipdl {
+namespace ipc {
+
+PContentChildIPCInterface::PContentChildIPCInterface(dom::IPDL* aIPDL,
+                                                     dom::ContentChild* aCc)
+  : IPCInterface(aIPDL)
+  , mCc(aCc)
+{
+  mCc->RegisterIPDLIPCInterface(this);
+}
+
+/* virtual */ mozilla::ipc::IPCResult
+PContentChildIPCInterface::RecvMessage(
+  const nsCString& aProtocolName,
+  const uint32_t& aChannelId,
+  const nsCString& aMessage,
+  const dom::ClonedMessageData& aData,
+  nsTArray<dom::ipc::StructuredCloneData>* aReturnData)
+{
+  return RecvMessageCommon(mCc,
+                           IPDLSide::Child,
+                           aProtocolName,
+                           aChannelId,
+                           aMessage,
+                           aData,
+                           aReturnData);
+}
+
+/* virtual */ RefPtr<IPCInterface::AsyncMessagePromise>
+PContentChildIPCInterface::SendAsyncMessage(JSContext* aCx,
+                                            const nsCString& aProtocolName,
+                                            const uint32_t& aChannelId,
+                                            const nsCString& aMessageName,
+                                            const InArgs& aArgs)
+{
+  // Create a new Promise to resolve the async call.
+  auto promise = MakeRefPtr<AsyncMessagePromise::Private>(__func__);
+
+  auto promiseHolder = MakeRefPtr<
+    dom::DOMMozPromiseRequestHolder<dom::ContentChild::AsyncMessageIPDLPromise>>(
+    xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx)));
+
+  // Write arguments to the StructuredCloneData.
+  dom::ipc::StructuredCloneData data;
+  ErrorResult errRes;
+
+  auto* argsObjPtr = JS_NewArrayObject(aCx, aArgs);
+
+  if (!argsObjPtr) {
+    promise->Reject(
+      NS_LITERAL_CSTRING("Failed to allocate new array object"),
+      __func__);
+    return promise;
+  }
+
+  JS::RootedObject argsObj(aCx, argsObjPtr);
+  JS::RootedValue argsVal(aCx, JS::ObjectValue(*argsObj));
+
+  data.Write(aCx, argsVal, errRes);
+
+  if (NS_WARN_IF(errRes.Failed())) {
+    promise->Reject(
+      NS_LITERAL_CSTRING("Failed to write arguments to StructuredCloneData"),
+      __func__);
+    return promise;
+  }
+
+  // Wrap the StructuredCloneData in a ClonedMessageData.
+  dom::ClonedMessageData cmd;
+  if (!data.BuildClonedMessageDataForChild((dom::nsIContentChild*)mCc, cmd)) {
+    promise->Reject(
+      NS_LITERAL_CSTRING("Failed to write arguments to StructuredCloneData"),
+      __func__);
+    return promise;
+  }
+
+  // Send the actual message through the ContentChild
+  mCc->SendAsyncMessageIPDL(aProtocolName, aChannelId, aMessageName, cmd)
+    ->Then(promiseHolder->GetParentObject()->EventTargetFor(TaskCategory::Other),
+           __func__,
+           [promiseHolder, promise](nsTArray<dom::ipc::StructuredCloneData> aRes) {
+             promiseHolder->Complete();
+
+             // If the array is empty, it means we got an error.
+             if (aRes.IsEmpty()) {
+               promise->Reject(NS_LITERAL_CSTRING(
+                                 "Error in RecvAsyncMessage in parent process"),
+                               __func__);
+               return;
+             }
+
+             nsIGlobalObject* global = promiseHolder->GetParentObject();
+             NS_ENSURE_TRUE_VOID(global);
+
+             dom::AutoEntryScript aes(global, "SendAsyncMessageIPDL");
+
+             JSContext* cx = aes.cx();
+
+             JS::RootedValue ret(cx);
+             ErrorResult rv;
+             aRes.ElementAt(0).Read(cx, &ret, rv);
+
+             if (NS_WARN_IF(rv.Failed())) {
+               promise->Reject(
+                 NS_LITERAL_CSTRING(
+                   "Failed to read return value from StructuredCloneData"),
+                 __func__);
+             } else {
+               promise->Resolve(ret, __func__);
+             }
+           },
+           [promiseHolder, promise](::mozilla::ipc::ResponseRejectReason aReason) {
+             promiseHolder->Complete();
+             promise->Reject(nsPrintfCString(
+                               "IPC error when calling SendAsyncMessageIPDL, "
+                               "see error message above. Reason: %d",
+                               (unsigned int)aReason),
+                             __func__);
+           })->Track(*promiseHolder);
+
+  return promise;
+}
+
+/* virtual */ bool
+PContentChildIPCInterface::SendSyncMessage(JSContext* aCx,
+                                           const nsCString& aProtocolName,
+                                           const uint32_t& aChannelId,
+                                           const nsCString& aMessageName,
+                                           const InArgs& aArgs,
+                                           OutObject aRet)
+{
+  // Write arguments to the StructuredCloneData.
+  dom::ipc::StructuredCloneData data;
+  ErrorResult errRes;
+
+  auto* argsObjPtr = JS_NewArrayObject(aCx, aArgs);
+
+  if (!argsObjPtr) {
+    aRet.setUndefined();
+    return false;
+  }
+
+  JS::RootedObject argsObj(aCx, argsObjPtr);
+  JS::RootedValue argsVal(aCx, JS::ObjectValue(*argsObj));
+
+  data.Write(aCx, argsVal, errRes);
+
+  if (NS_WARN_IF(errRes.Failed())) {
+    JS_ReportErrorUTF8(aCx, "Failed to write arguments to StructuredCloneData");
+      aRet.setUndefined();
+      return false;
+  }
+
+  // Wrap the StructuredCloneData in a ClonedMessageData.
+  dom::ClonedMessageData cmd;
+  if (!data.BuildClonedMessageDataForChild((dom::nsIContentChild*)mCc, cmd)) {
+    JS_ReportErrorUTF8(aCx, "Failed to build cloned message data for arguments");
+    aRet.setUndefined();
+    return false;
+  }
+
+  nsTArray<dom::ipc::StructuredCloneData> retData;
+
+  // Send the actual message through the ContentChild
+  if (!mCc->SendSyncMessageIPDL(
+        aProtocolName, aChannelId, aMessageName, cmd, &retData)) {
+    JS_ReportErrorUTF8(aCx, "IPC error when calling SendSyncMessageIPDL");
+    aRet.setUndefined();
+    return false;
+  }
+
+  // If the array is empty, it means we got an error.
+  if (retData.IsEmpty()) {
+    JS_ReportErrorUTF8(aCx, "Error in RecvSyncMessageIPDL in parent process");
+    aRet.setUndefined();
+    return false;
+  }
+
+  retData.ElementAt(0).Read(aCx, aRet, errRes);
+
+  if (NS_WARN_IF(errRes.Failed())) {
+    JS_ReportErrorUTF8(aCx,
+                       "Failed to read return value from StructuredCloneData");
+    aRet.setUndefined();
+    return false;
+  }
+
+  return true;
+}
+
+} // ipc
+} // ipdl
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl_new/ipdl_bindings/PContentChildIPCInterface.h
@@ -0,0 +1,77 @@
+
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef dom_base_ipdl_bindings_pcontentchildipcinterface_h
+#define dom_base_ipdl_bindings_pcontentchildipcinterface_h
+
+#include "IPCInterface.h"
+
+namespace mozilla {
+namespace dom {
+class IPDL;
+class ContentChild;
+namespace ipc {
+class StructuredCloneData;
+}
+}
+
+namespace ipdl {
+namespace ipc {
+
+class PContentChildIPCInterface : public IPCInterface
+{
+public:
+  PContentChildIPCInterface(dom::IPDL* aIPDL, dom::ContentChild* aCc);
+
+  virtual ~PContentChildIPCInterface() = default;
+
+  // Receive a message from IPC.
+  virtual mozilla::ipc::IPCResult RecvMessage(
+    const nsCString& aProtocolName,
+    const uint32_t& aChannelId,
+    const nsCString& aMessage,
+    const dom::ClonedMessageData& aData,
+    nsTArray<dom::ipc::StructuredCloneData>* aReturnData) override;
+
+  // Send an async message through IPC.
+  virtual RefPtr<AsyncMessagePromise> SendAsyncMessage(
+    JSContext* aCx,
+    const nsCString& aProtocolName,
+    const uint32_t& aChannelId,
+    const nsCString& aMessageName,
+    const InArgs& aArgs) override;
+
+  // Send a sync message through IPC.
+  virtual bool SendSyncMessage(JSContext* aCx,
+                               const nsCString& aProtocolName,
+                               const uint32_t& aChannelId,
+                               const nsCString& aMessageName,
+                               const InArgs& aArgs,
+                               OutObject aRet) override;
+
+  // Send an intr message through IPC.
+  virtual bool SendIntrMessage(JSContext* aCx,
+                               const nsCString& aProtocolName,
+                               const uint32_t& aChannelId,
+                               const nsCString& aMessageName,
+                               const InArgs& aArgs,
+                               OutObject aRet) override
+  {
+    MOZ_CRASH("Unimplemented");
+    aRet.setUndefined();
+    return false;
+  }
+
+protected:
+  dom::ContentChild* mCc;
+};
+
+} // ipc
+} // ipdl
+} // mozilla
+
+#endif // dom_base_ipdl_bindings_pcontentchildipcinterface_h
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl_new/ipdl_bindings/PContentParentIPCInterface.cpp
@@ -0,0 +1,131 @@
+#include "PContentParentIPCInterface.h"
+
+#include "mozilla/dom/ContentParent.h"
+
+namespace mozilla {
+namespace ipdl {
+namespace ipc {
+
+PContentParentIPCInterface::PContentParentIPCInterface(dom::IPDL* aIPDL,
+                                                       dom::ContentParent* aCp)
+  : IPCInterface(aIPDL)
+  , mCp(aCp)
+{
+  mCp->RegisterIPDLIPCInterface(this);
+}
+
+/* virtual */ mozilla::ipc::IPCResult
+PContentParentIPCInterface::RecvMessage(
+  const nsCString& aProtocolName,
+  const uint32_t& aChannelId,
+  const nsCString& aMessage,
+  const dom::ClonedMessageData& aData,
+  nsTArray<dom::ipc::StructuredCloneData>* aReturnData)
+{
+  return RecvMessageCommon(mCp,
+                           IPDLSide::Parent,
+                           aProtocolName,
+                           aChannelId,
+                           aMessage,
+                           aData,
+                           aReturnData);
+}
+
+/* virtual */ RefPtr<IPCInterface::AsyncMessagePromise>
+PContentParentIPCInterface::SendAsyncMessage(JSContext* aCx,
+                                             const nsCString& aProtocolName,
+                                             const uint32_t& aChannelId,
+                                             const nsCString& aMessageName,
+                                             const InArgs& aArgs)
+{
+  auto promise = MakeRefPtr<AsyncMessagePromise::Private>(__func__);
+
+  auto promiseHolder = MakeRefPtr<
+    dom::DOMMozPromiseRequestHolder<dom::ContentParent::AsyncMessageIPDLPromise>>(
+    xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx)));
+
+  // Write arguments to the StructuredCloneData.
+  dom::ipc::StructuredCloneData data;
+  ErrorResult errRes;
+
+  auto* argsObjPtr = JS_NewArrayObject(aCx, aArgs);
+
+  if (!argsObjPtr) {
+    promise->Reject(
+      NS_LITERAL_CSTRING("Failed to allocate new array object"),
+      __func__);
+    return promise;
+  }
+
+  JS::RootedObject argsObj(aCx, argsObjPtr);
+  JS::RootedValue argsVal(aCx, JS::ObjectValue(*argsObj));
+
+  data.Write(aCx, argsVal, errRes);
+
+  if (NS_WARN_IF(errRes.Failed())) {
+    promise->Reject(
+      NS_LITERAL_CSTRING("Failed to write arguments to StructuredCloneData"),
+      __func__);
+    return promise;
+  }
+
+  // Wrap the StructuredCloneData in a ClonedMessageData.
+  dom::ClonedMessageData cmd;
+  if (!data.BuildClonedMessageDataForParent((dom::nsIContentParent*)mCp, cmd)) {
+    promise->Reject(
+      NS_LITERAL_CSTRING("Failed to write arguments to StructuredCloneData"),
+      __func__);
+    return promise;
+  }
+
+  // Send the actual message through the ContentParent
+  mCp->SendAsyncMessageIPDL(aProtocolName, aChannelId, aMessageName, cmd)
+    ->Then(promiseHolder->GetParentObject()->EventTargetFor(TaskCategory::Other),
+           __func__,
+           [promiseHolder, promise](nsTArray<dom::ipc::StructuredCloneData> aRes) {
+             promiseHolder->Complete();
+
+             // If the array is empty, it means we got an error.
+             if (aRes.IsEmpty()) {
+               promise->Reject(NS_LITERAL_CSTRING(
+                                 "Error in RecvAsyncMessage in parent process"),
+                               __func__);
+               return;
+             }
+
+             nsIGlobalObject* global = promiseHolder->GetParentObject();
+             NS_ENSURE_TRUE_VOID(global);
+
+             dom::AutoEntryScript aes(global, "SendAsyncMessageIPDL");
+
+             JSContext* cx = aes.cx();
+
+             JS::RootedValue ret(cx);
+             ErrorResult rv;
+             aRes.ElementAt(0).Read(cx, &ret, rv);
+
+             if (NS_WARN_IF(rv.Failed())) {
+               promise->Reject(
+                 NS_LITERAL_CSTRING(
+                   "Failed to read return value from StructuredCloneData"),
+                 __func__);
+             } else {
+               promise->Resolve(ret, __func__);
+             }
+           },
+           [promiseHolder, promise](::mozilla::ipc::ResponseRejectReason aReason) {
+             promiseHolder->Complete();
+
+             promise->Reject(nsPrintfCString(
+                               "IPC error when calling SendAsyncMessageIPDL, "
+                               "see error message above. Reason: %d",
+                               (unsigned int)aReason),
+                             __func__);
+           })->Track(*promiseHolder);
+
+  return promise;
+}
+
+} // ipc
+} // ipdl
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl_new/ipdl_bindings/PContentParentIPCInterface.h
@@ -0,0 +1,81 @@
+
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef dom_base_ipdl_bindings_pcontentparentipcinterface_h
+#define dom_base_ipdl_bindings_pcontentparentipcinterface_h
+
+#include "IPCInterface.h"
+
+namespace mozilla {
+namespace dom {
+class IPDL;
+class ContentParent;
+namespace ipc {
+class StructuredCloneData;
+}
+}
+
+namespace ipdl {
+namespace ipc {
+
+class PContentParentIPCInterface : public IPCInterface
+{
+public:
+  explicit PContentParentIPCInterface(dom::IPDL* aIPDL, dom::ContentParent* aCp);
+  virtual ~PContentParentIPCInterface() = default;
+
+  // Receive a message from IPC.
+  virtual mozilla::ipc::IPCResult RecvMessage(
+    const nsCString& aProtocolName,
+    const uint32_t& aChannelId,
+    const nsCString& aMessage,
+    const dom::ClonedMessageData& aData,
+    nsTArray<dom::ipc::StructuredCloneData>* aReturnData) override;
+
+  // Send an async message through IPC.
+  virtual RefPtr<AsyncMessagePromise> SendAsyncMessage(
+    JSContext* aCx,
+    const nsCString& aProtocolName,
+    const uint32_t& aChannelId,
+    const nsCString& aMessageName,
+    const InArgs& aArgs) override;
+
+  // Send a sync message through IPC.
+  virtual bool SendSyncMessage(JSContext* aCx,
+                               const nsCString& aProtocolName,
+                               const uint32_t& aChannelId,
+                               const nsCString& aMessageName,
+                               const InArgs& aArgs,
+                               OutObject aRet) override
+  {
+    MOZ_CRASH("Unimplemented");
+    aRet.setUndefined();
+    return false;
+  }
+
+  // Send an intr message through IPC.
+  virtual bool SendIntrMessage(JSContext* aCx,
+                               const nsCString& aProtocolName,
+                               const uint32_t& aChannelId,
+                               const nsCString& aMessageName,
+                               const InArgs& aArgs,
+                               OutObject aRet) override
+  {
+    MOZ_CRASH("Unimplemented");
+    aRet.setUndefined();
+    return false;
+  }
+
+protected:
+  dom::ContentParent* mCp;
+};
+
+} // ipc
+} // ipdl
+} // mozilla
+
+#endif // dom_base_ipdl_bindings_pcontentparentipcinterface_h
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl_new/ipdl_bindings/cbindgen.toml
@@ -0,0 +1,24 @@
+# rustup run nightly cbindgen toolkit/library/rust/ --crate ipdl --lockfile Cargo.lock -o dom/base/ipdl_bindings/ipdl_ffi_generated.h
+
+header="""
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef ipdl_ffi_generated_h
+#define ipdl_ffi_generated_h
+
+#include <nsTArray.h>
+#include <nsString.h>"""
+
+trailer="""
+#endif // ipdl_ffi_generated_h"""
+
+autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */"
+namespaces = ["mozilla", "ipdl", "ffi"]
+language="C++"
+
+[export.rename]
+"ThinVec" = "nsTArray"
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl_new/ipdl_bindings/ipdl_ffi_generated.h
@@ -0,0 +1,181 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef ipdl_ffi_generated_h
+#define ipdl_ffi_generated_h
+
+#include <nsTArray.h>
+#include <nsString.h>
+
+/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */
+
+#include <cstdint>
+#include <cstdlib>
+
+namespace mozilla {
+namespace ipdl {
+namespace ffi {
+
+enum class Compress {
+  None,
+  Enabled,
+  All,
+};
+
+enum class CxxTypeKind {
+  Struct,
+  Class,
+};
+
+enum class Direction {
+  ToParent,
+  ToChild,
+  ToParentOrChild,
+};
+
+enum class FileType {
+  Protocol,
+  Header,
+};
+
+enum class Nesting {
+  None,
+  InsideSync,
+  InsideCpow,
+};
+
+enum class Priority {
+  Normal,
+  High,
+  Input,
+};
+
+enum class SendSemantics {
+  Async,
+  Sync,
+  Intr,
+};
+
+struct AST;
+
+struct Location {
+  nsCString file_name;
+  uintptr_t lineno;
+  uintptr_t colno;
+};
+
+struct Identifier {
+  nsCString id;
+  Location loc;
+};
+
+struct Namespace {
+  Identifier name;
+  nsTArray<nsCString> namespaces;
+};
+
+using TUId = int32_t;
+
+struct QualifiedId {
+  Identifier base_id;
+  nsTArray<nsCString> quals;
+};
+
+struct TypeSpec {
+  QualifiedId spec;
+  bool array;
+  bool nullable;
+};
+
+struct UsingStmt {
+  TypeSpec cxx_type;
+  nsCString header;
+  Maybe<CxxTypeKind> kind;
+  bool refcounted;
+};
+
+struct StructField {
+  TypeSpec type_spec;
+  Identifier name;
+};
+
+struct Struct {
+  Namespace ns;
+  nsTArray<StructField> fields;
+};
+
+struct Union {
+  Namespace ns;
+  nsTArray<TypeSpec> types;
+};
+
+struct Param {
+  Identifier name;
+  TypeSpec type_spec;
+};
+
+struct MessageDecl {
+  Identifier name;
+  SendSemantics send_semantics;
+  Nesting nested;
+  Priority prio;
+  Direction direction;
+  nsTArray<Param> in_params;
+  nsTArray<Param> out_params;
+  Compress compress;
+  bool verify;
+};
+
+struct Protocol {
+  SendSemantics send_semantics;
+  Nesting nested;
+  nsTArray<Identifier> managers;
+  nsTArray<Identifier> manages;
+  nsTArray<MessageDecl> messages;
+};
+
+struct NamedProtocol {
+  Namespace ns;
+  Protocol protocol;
+};
+
+struct TranslationUnit {
+  Namespace ns;
+  FileType file_type;
+  nsCString file_name;
+  nsTArray<nsCString> cxx_includes;
+  nsTArray<TUId> includes;
+  nsTArray<UsingStmt> using_stmt;
+  nsTArray<Struct> structs;
+  nsTArray<Union> unions;
+  Maybe<NamedProtocol> protocol;
+};
+
+extern "C" {
+
+/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */
+
+const TranslationUnit *ipdl_ast_get_tu(const AST *ast, TUId tuid);
+
+TUId ipdl_ast_main_tuid(const AST *ast);
+
+void ipdl_free_ast(const AST *ast);
+
+const AST *ipdl_parse_file(const nsACString *ipdl_file,
+                           nsACString *error_string,
+                           uint8_t (*source_string_loader)(const nsACString*, nsACString*),
+                           uint8_t (*resolve_relative_path)(const nsACString*, const nsACString*, nsACString*),
+                           uint8_t (*equals)(const nsACString*, const nsACString*));
+
+} // extern "C"
+
+} // namespace ffi
+} // namespace ipdl
+} // namespace mozilla
+
+/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */
+
+#endif // ipdl_ffi_generated_h
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl_new/ipdl_bindings/moz.build
@@ -0,0 +1,29 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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('/ipc/chromium/chromium-config.mozbuild')
+
+EXPORTS.mozilla.ipdl = [
+    'IPDLProtocol.h',
+    'IPDLProtocolInstance.h'
+]
+
+EXPORTS.mozilla.ipdl.ipc = [
+    'IPCInterface.h',
+    'PContentChildIPCInterface.h',
+    'PContentParentIPCInterface.h'
+]
+
+UNIFIED_SOURCES += [
+    'IPCInterface.cpp',
+    'IPDLProtocol.cpp',
+    'IPDLProtocolInstance.cpp',
+    'PContentChildIPCInterface.cpp',
+    'PContentParentIPCInterface.cpp',
+    'wrapper.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl_new/ipdl_bindings/src/lib.rs
@@ -0,0 +1,529 @@
+/* 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 ipdl;
+extern crate nsstring;
+extern crate thin_vec;
+#[macro_use]
+extern crate mfbt_maybe;
+
+use ipdl::ast;
+use ipdl::parser;
+use ipdl::parser::FileURI;
+use mfbt_maybe::{Maybe, MaybeTrait};
+use nsstring::{nsACString, nsCStr, nsCString};
+use std::collections::HashMap;
+use std::iter::FromIterator;
+use std::str;
+use thin_vec::ThinVec;
+use std::hash::Hash;
+use std::hash::Hasher;
+
+maybe!(CxxTypeKind);
+maybe!(NamedProtocol);
+
+fn vec_to_thinvec<T, F: Into<T>>(vec: Vec<F>) -> ThinVec<T> {
+    ThinVec::from_iter(vec.into_iter().map(|x| x.into()))
+}
+
+#[repr(C)]
+pub struct QualifiedId {
+    pub base_id: Identifier,
+    pub quals: ThinVec<nsCString>,
+}
+
+impl From<ast::QualifiedId> for QualifiedId {
+    fn from(qi: ast::QualifiedId) -> Self {
+        QualifiedId {
+            base_id: qi.base_id.into(),
+            quals: ThinVec::from_iter(qi.quals.into_iter().map(|x| x.into())),
+        }
+    }
+}
+
+#[repr(C)]
+pub struct TypeSpec {
+    pub spec: QualifiedId,
+    pub array: bool,
+    pub nullable: bool,
+}
+
+impl From<ast::TypeSpec> for TypeSpec {
+    fn from(ts: ast::TypeSpec) -> Self {
+        TypeSpec {
+            spec: ts.spec.into(),
+            array: ts.array,
+            nullable: ts.nullable,
+        }
+    }
+}
+
+#[repr(C)]
+pub struct Param {
+    pub name: Identifier,
+    pub type_spec: TypeSpec,
+}
+
+impl From<ast::Param> for Param {
+    fn from(p: ast::Param) -> Self {
+        Param {
+            name: p.name.into(),
+            type_spec: p.type_spec.into(),
+        }
+    }
+}
+
+#[repr(C)]
+pub struct StructField {
+    pub type_spec: TypeSpec,
+    pub name: Identifier,
+}
+
+impl From<ast::StructField> for StructField {
+    fn from(sf: ast::StructField) -> Self {
+        StructField {
+            type_spec: sf.type_spec.into(),
+            name: sf.name.into(),
+        }
+    }
+}
+
+#[repr(C)]
+pub struct Namespace {
+    pub name: Identifier,
+    pub namespaces: ThinVec<nsCString>,
+}
+
+impl From<ast::Namespace> for Namespace {
+    fn from(ns: ast::Namespace) -> Self {
+        Namespace {
+            name: ns.name.into(),
+            namespaces: vec_to_thinvec(ns.namespaces),
+        }
+    }
+}
+
+#[repr(C)]
+pub enum Compress {
+    None,
+    Enabled,
+    All,
+}
+
+impl From<ast::Compress> for Compress {
+    fn from(c: ast::Compress) -> Self {
+        match c {
+            ast::Compress::None => Compress::None,
+            ast::Compress::Enabled => Compress::Enabled,
+            ast::Compress::All => Compress::All,
+        }
+    }
+}
+
+#[repr(C)]
+pub enum SendSemantics {
+    Async,
+    Sync,
+    Intr,
+}
+
+impl From<ast::SendSemantics> for SendSemantics {
+    fn from(ss: ast::SendSemantics) -> Self {
+        match ss {
+            ast::SendSemantics::Async => SendSemantics::Async,
+            ast::SendSemantics::Sync => SendSemantics::Sync,
+            ast::SendSemantics::Intr => SendSemantics::Intr,
+        }
+    }
+}
+
+#[repr(C)]
+pub enum Nesting {
+    None,
+    InsideSync,
+    InsideCpow,
+}
+
+impl From<ast::Nesting> for Nesting {
+    fn from(n: ast::Nesting) -> Self {
+        match n {
+            ast::Nesting::InsideCpow => Nesting::InsideCpow,
+            ast::Nesting::InsideSync => Nesting::InsideSync,
+            ast::Nesting::None => Nesting::None,
+        }
+    }
+}
+
+#[repr(C)]
+pub enum Priority {
+    Normal,
+    High,
+    Input,
+}
+
+impl From<ast::Priority> for Priority {
+    fn from(p: ast::Priority) -> Self {
+        match p {
+            ast::Priority::High => Priority::High,
+            ast::Priority::Input => Priority::Input,
+            ast::Priority::Normal => Priority::Normal,
+        }
+    }
+}
+
+#[repr(C)]
+pub enum Direction {
+    ToParent,
+    ToChild,
+    ToParentOrChild,
+}
+
+impl From<ast::Direction> for Direction {
+    fn from(d: ast::Direction) -> Self {
+        match d {
+            ast::Direction::ToChild => Direction::ToChild,
+            ast::Direction::ToParent => Direction::ToParent,
+            ast::Direction::ToParentOrChild => Direction::ToParentOrChild,
+        }
+    }
+}
+
+#[repr(C)]
+pub struct Location {
+    pub file_name: nsCString,
+    pub lineno: usize,
+    pub colno: usize,
+}
+
+impl From<ast::Location> for Location {
+    fn from(l: ast::Location) -> Self {
+        Location {
+            file_name: l.file_name.into(),
+            lineno: l.lineno,
+            colno: l.colno,
+        }
+    }
+}
+
+#[repr(C)]
+pub struct Identifier {
+    pub id: nsCString,
+    pub loc: Location,
+}
+
+impl From<ast::Identifier> for Identifier {
+    fn from(id: ast::Identifier) -> Self {
+        Identifier {
+            id: id.id.into(),
+            loc: id.loc.into(),
+        }
+    }
+}
+
+#[repr(C)]
+pub struct MessageDecl {
+    pub name: Identifier,
+    pub send_semantics: SendSemantics,
+    pub nested: Nesting,
+    pub prio: Priority,
+    pub direction: Direction,
+    pub in_params: ThinVec<Param>,
+    pub out_params: ThinVec<Param>,
+    pub compress: Compress,
+    pub verify: bool,
+}
+
+impl From<ast::MessageDecl> for MessageDecl {
+    fn from(md: ast::MessageDecl) -> Self {
+        MessageDecl {
+            name: md.name.into(),
+            send_semantics: md.send_semantics.into(),
+            nested: md.nested.into(),
+            prio: md.prio.into(),
+            direction: md.direction.into(),
+            in_params: vec_to_thinvec(md.in_params),
+            out_params: vec_to_thinvec(md.out_params),
+            compress: md.compress.into(),
+            verify: md.verify,
+        }
+    }
+}
+
+#[repr(C)]
+pub struct Protocol {
+    pub send_semantics: SendSemantics,
+    pub nested: Nesting,
+    pub managers: ThinVec<Identifier>,
+    pub manages: ThinVec<Identifier>,
+    pub messages: ThinVec<MessageDecl>,
+}
+
+impl From<ast::Protocol> for Protocol {
+    fn from(p: ast::Protocol) -> Self {
+        Protocol {
+            send_semantics: p.send_semantics.into(),
+            nested: p.nested.into(),
+            managers: vec_to_thinvec(p.managers),
+            manages: vec_to_thinvec(p.manages),
+            messages: vec_to_thinvec(p.messages),
+        }
+    }
+}
+
+#[repr(C)]
+pub enum CxxTypeKind {
+    Struct,
+    Class,
+}
+
+impl From<ast::CxxTypeKind> for CxxTypeKind {
+    fn from(cxxtk: ast::CxxTypeKind) -> Self {
+        match cxxtk {
+            ast::CxxTypeKind::Class => CxxTypeKind::Class,
+            ast::CxxTypeKind::Struct => CxxTypeKind::Struct,
+        }
+    }
+}
+
+#[repr(C)]
+pub struct UsingStmt {
+    pub cxx_type: TypeSpec,
+    pub header: nsCString,
+    pub kind: Maybe<CxxTypeKind>,
+    pub refcounted: bool,
+}
+
+impl From<ast::UsingStmt> for UsingStmt {
+    fn from(using: ast::UsingStmt) -> Self {
+        UsingStmt {
+            cxx_type: using.cxx_type.into(),
+            header: using.header.into(),
+            kind: using.kind.into(),
+            refcounted: using.refcounted,
+        }
+    }
+}
+
+#[repr(C)]
+pub enum FileType {
+    Protocol,
+    Header,
+}
+
+impl From<ast::FileType> for FileType {
+    fn from(ft: ast::FileType) -> Self {
+        match ft {
+            ast::FileType::Protocol => FileType::Protocol,
+            ast::FileType::Header => FileType::Header,
+        }
+    }
+}
+
+// Translation unit identifier.
+pub type TUId = i32;
+
+#[repr(C)]
+pub struct Struct {
+    pub ns: Namespace,
+    pub fields: ThinVec<StructField>,
+}
+
+impl From<(ast::Namespace, Vec<ast::StructField>)> for Struct {
+    fn from(s: (ast::Namespace, Vec<ast::StructField>)) -> Self {
+        Struct {
+            ns: s.0.into(),
+            fields: vec_to_thinvec(s.1),
+        }
+    }
+}
+
+#[repr(C)]
+pub struct Union {
+    pub ns: Namespace,
+    pub types: ThinVec<TypeSpec>,
+}
+
+impl From<(ast::Namespace, Vec<ast::TypeSpec>)> for Union {
+    fn from(u: (ast::Namespace, Vec<ast::TypeSpec>)) -> Self {
+        Union {
+            ns: u.0.into(),
+            types: vec_to_thinvec(u.1),
+        }
+    }
+}
+
+#[repr(C)]
+pub struct NamedProtocol {
+    pub ns: Namespace,
+    pub protocol: Protocol,
+}
+
+impl From<(ast::Namespace, ast::Protocol)> for NamedProtocol {
+    fn from(p: (ast::Namespace, ast::Protocol)) -> Self {
+        NamedProtocol {
+            ns: p.0.into(),
+            protocol: p.1.into(),
+        }
+    }
+}
+
+#[repr(C)]
+pub struct TranslationUnit {
+    pub ns: Namespace,
+    pub file_type: FileType,
+    pub file_name: nsCString,
+    pub cxx_includes: ThinVec<nsCString>,
+    pub includes: ThinVec<TUId>,
+    pub using_stmt: ThinVec<UsingStmt>,
+    pub structs: ThinVec<Struct>,
+    pub unions: ThinVec<Union>,
+    pub protocol: Maybe<NamedProtocol>,
+}
+
+impl From<ast::TranslationUnit> for TranslationUnit {
+    fn from(tu: ast::TranslationUnit) -> Self {
+        TranslationUnit {
+            ns: tu.namespace.into(),
+            file_type: tu.file_type.into(),
+            file_name: tu.file_name.into(),
+            cxx_includes: vec_to_thinvec(tu.cxx_includes),
+            includes: vec_to_thinvec(tu.includes),
+            using_stmt: vec_to_thinvec(tu.using),
+            structs: vec_to_thinvec(tu.structs),
+            unions: vec_to_thinvec(tu.unions),
+            protocol: tu.protocol.into(),
+        }
+    }
+}
+
+pub struct AST {
+    pub main_tuid: TUId,
+    pub translation_units: HashMap<TUId, TranslationUnit>,
+}
+
+impl From<ast::AST> for AST {
+    fn from(ast: ast::AST) -> Self {
+        AST {
+            main_tuid: ast.main_tuid,
+            translation_units: ast
+                .translation_units
+                .into_iter()
+                .map(|(k, v)| (k, v.into()))
+                .collect(),
+        }
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn ipdl_ast_get_tu(ast: &AST, tuid: TUId) -> *const TranslationUnit {
+    ast.translation_units
+        .get(&tuid)
+        .map(|x| x as *const TranslationUnit)
+        .unwrap_or(::std::ptr::null())
+}
+
+#[no_mangle]
+pub extern "C" fn ipdl_ast_main_tuid(ast: &AST) -> TUId {
+    ast.main_tuid
+}
+
+struct FFIURI {
+    data: nsCString,
+    resolve_relative_path_fn: fn(&nsACString, &nsACString, &mut nsACString) -> u8,
+    equals_fn: fn(&nsACString, &nsACString) -> u8,
+}
+
+impl parser::FileURI for FFIURI {
+    fn resolve_relative_path(&self, relative_path: &str) -> Result<Self, ()> {
+        let mut result = nsCString::new();
+        let ok = (self.resolve_relative_path_fn)(&self.data, &nsCStr::from(relative_path), &mut result);
+        if ok != 0 {
+            Ok(FFIURI {
+                data: result,
+                resolve_relative_path_fn: self.resolve_relative_path_fn,
+                equals_fn: self.equals_fn,
+            })
+        } else {
+            Err(())
+        }
+    }
+
+    fn to_utf8(&self) -> &str {
+        str::from_utf8(&self.data).unwrap()
+    }
+}
+
+impl PartialEq for FFIURI {
+    fn eq(&self, other: &FFIURI) -> bool {
+        (self.equals_fn)(&self.data, &other.data) != 0
+    }
+}
+
+impl Eq for FFIURI {}
+
+impl Hash for FFIURI {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        self.data.hash(state);
+    }
+}
+
+impl Clone for FFIURI {
+    fn clone(&self) -> FFIURI {
+        let new_data = nsCString::from(self.data.as_ref());
+        FFIURI {
+            data: new_data,
+            resolve_relative_path_fn: self.resolve_relative_path_fn,
+            equals_fn: self.equals_fn,
+        }
+    }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn ipdl_parse_file(
+    ipdl_file: &nsACString,
+    error_string: &mut nsACString,
+    source_string_loader: fn(&nsACString, &mut nsACString) -> u8,
+    resolve_relative_path: fn(&nsACString, &nsACString, &mut nsACString) -> u8,
+    equals: fn(&nsACString, &nsACString) -> u8,
+) -> *const AST {
+    let uri = FFIURI {
+        data: nsCString::from(ipdl_file),
+        resolve_relative_path_fn: resolve_relative_path,
+        equals_fn: equals,
+    };
+
+    let parent_uri = match uri.resolve_relative_path("./") {
+        Ok(pu) => pu,
+        Err(()) => {
+            error_string.assign("Could not get IPDL file parent directory");
+            return ::std::ptr::null()
+        },
+    };
+
+    Box::into_raw(Box::new(match parser::parse(
+        &uri,
+        &[parent_uri],
+        |uri: &str| -> Result<Box<parser::OwnedSourceBuffer>, ()> {
+            let ns_uri = nsCStr::from(uri);
+            let mut result = nsCString::new();
+            let ok = source_string_loader(&ns_uri, &mut result);
+            if ok != 0 {
+                Ok(Box::new(result))
+            } else {
+                Err(())
+            }
+        },
+    ) {
+        Ok(ast) => ast.into(),
+        Err(error) => {
+            error_string.assign(&format!("{}", error));
+            return ::std::ptr::null()
+        },
+    }))
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn ipdl_free_ast(ast: *const AST) {
+    drop(Box::from_raw(ast as *mut AST))
+}
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl_new/ipdl_bindings/wrapper.cpp
@@ -0,0 +1,155 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "wrapper.h"
+#include "nsAString.h"
+#include "nsString.h"
+#include "nsNetUtil.h"
+
+namespace mozilla {
+namespace ipdl {
+namespace wrapper {
+
+static uint8_t
+source_string_loader(const nsACString* aFileName, nsACString* aRet)
+{
+  nsCOMPtr<nsIChannel> chan;
+  nsCOMPtr<nsIInputStream> instream;
+  nsCOMPtr<nsIURI> uri;
+  nsCOMPtr<nsIIOService> serv = do_GetService(NS_IOSERVICE_CONTRACTID);
+  nsresult rv;
+
+  rv = NS_NewURI(getter_AddRefs(uri), *aFileName);
+
+  if (NS_FAILED(rv)) {
+    NS_WARNING("FAILED TO GET URI");
+    return 0;
+  }
+
+  rv = NS_NewChannel(getter_AddRefs(chan),
+                    uri,
+                    nsContentUtils::GetSystemPrincipal(),
+                    nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+                    nsIContentPolicy::TYPE_OTHER,
+                    nullptr,  // PerformanceStorage
+                    nullptr,  // aLoadGroup
+                    nullptr,  // aCallbacks
+                    nsIRequest::LOAD_NORMAL,
+                    serv);
+
+  if (NS_SUCCEEDED(rv)) {
+    chan->SetContentType(NS_LITERAL_CSTRING("application/x-ipdl"));
+    rv = chan->Open2(getter_AddRefs(instream));
+  }
+
+  if (NS_FAILED(rv)) {
+    NS_WARNING("LOAD_ERROR_NOSTREAM");
+    return 0;
+  }
+
+  int64_t len = -1;
+
+  rv = chan->GetContentLength(&len);
+  if (NS_FAILED(rv) || len == -1) {
+    NS_WARNING("LOAD_ERROR_NOCONTENT");
+    return 0;
+  }
+
+  if (len > INT32_MAX) {
+    NS_WARNING("LOAD_ERROR_CONTENTTOOBIG");
+    return 0;
+  }
+
+  rv = NS_ReadInputStreamToString(instream, *aRet, len);
+
+  if (NS_FAILED(rv)) {
+    NS_WARNING("FAILED TO READ");
+    return 0;
+  }
+
+  return 1;
+}
+
+static uint8_t
+resolve_relative_path(const nsACString* aFileName, const nsACString* aRelativePath, nsACString* aRet)
+{
+  nsCOMPtr<nsIURI> uri;
+  nsresult rv;
+
+  rv = NS_NewURI(getter_AddRefs(uri), *aFileName);
+
+  if (NS_FAILED(rv)) {
+    NS_WARNING("FAILED TO GET URI");
+    return 0;
+  }
+
+  rv = uri->Resolve(*aRelativePath, *aRet);
+
+  if (NS_FAILED(rv)) {
+    NS_WARNING("FAILED TO RESOLVE URI");
+    return 0;
+  }
+
+  return 1;
+}
+
+static uint8_t
+uri_equals(const nsACString* aFileName1, const nsACString* aFileName2)
+{
+  nsCOMPtr<nsIURI> uri1;
+  nsCOMPtr<nsIURI> uri2;
+  nsresult rv;
+
+  rv = NS_NewURI(getter_AddRefs(uri1), *aFileName1);
+
+  if (NS_FAILED(rv)) {
+    NS_WARNING("FAILED TO GET URI 1");
+  }
+
+  rv = NS_NewURI(getter_AddRefs(uri2), *aFileName2);
+
+  if (NS_FAILED(rv)) {
+    NS_WARNING("FAILED TO GET URI 2");
+  }
+
+  bool retval;
+
+  rv = uri1->Equals(uri2, &retval);
+
+  if (NS_FAILED(rv)) {
+    NS_WARNING("FAILED TO COMPARE URIs");
+  }
+
+  return retval ? 1 : 0;
+}
+
+const ffi::AST*
+Parse(const nsACString& aIPDLFile, nsACString& errorString)
+{
+  return ffi::ipdl_parse_file(&aIPDLFile, &errorString, source_string_loader, resolve_relative_path, uri_equals);
+}
+
+void
+FreeAST(const ffi::AST* aAST)
+{
+  ffi::ipdl_free_ast(aAST);
+}
+
+const ffi::TranslationUnit*
+GetTU(const ffi::AST* aAST, ffi::TUId aTUID)
+{
+  return ffi::ipdl_ast_get_tu(aAST, aTUID);
+}
+
+ffi::TUId
+GetMainTUId(const ffi::AST* aAST)
+{
+  return ffi::ipdl_ast_main_tuid(aAST);
+}
+
+} // wrapper
+} // ipdl
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl_new/ipdl_bindings/wrapper.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef dom_base_ipdl_bindings_wrapper_h
+#define dom_base_ipdl_bindings_wrapper_h
+
+#include "ipdl_ffi_generated.h"
+#include "nsStringFwd.h"
+
+namespace mozilla {
+namespace ipdl {
+namespace wrapper {
+
+// Parse the given ipdl file and return a Rust-owned AST.
+const ffi::AST*
+Parse(const nsACString& aIPDLFile, nsACString& errorString);
+
+// Free the AST from Rust code.
+void
+FreeAST(const ffi::AST* aAST);
+
+// Get the translation unit with the given tuid from the given AST.
+const ffi::TranslationUnit*
+GetTU(const ffi::AST* aAST, ffi::TUId aTUID);
+
+// Get the tuid of the main translation unit of the AST.
+ffi::TUId
+GetMainTUId(const ffi::AST* aAST);
+
+} // wrapper
+} // ipdl
+} // mozilla
+
+#endif // dom_base_ipdl_bindings_wrapper_h
copy from ipc/moz.build
copy to ipc/ipdl_new/moz.build
--- a/ipc/moz.build
+++ b/ipc/ipdl_new/moz.build
@@ -1,23 +1,9 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 DIRS += [
-    'chromium',
-    'glue',
-    'ipdl',
-    'testshell',
+    'ipdl_bindings',
 ]
-
-if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'android':
-    DIRS += ['contentproc']
-
-if CONFIG['OS_ARCH'] == 'WINNT':
-    DIRS += ['mscom']
-
-DIRS += ['app']
-
-with Files("**"):
-    BUG_COMPONENT = ("Core", "IPC")
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl_new/pipdl/.gitignore
@@ -0,0 +1,2 @@
+/target/
+**/*.rs.bk
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl_new/pipdl/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "pipdl"
+version = "0.1.0"
+authors = ["Nika Layzell <nika@thelayzells.com>"]
+license = "MPL-2.0"
+
+[dependencies]
new file mode 100755
--- /dev/null
+++ b/ipc/ipdl_new/pipdl/dotest.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+# Run the parser on every error ipdl file in the passed-in directory.
+find $1 -name "*.ipdl" -print0 | sort -z | xargs -0 -n 1 cargo run --release --example try_parse --
+echo status $? >&2
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl_new/pipdl/examples/try_parse.rs
@@ -0,0 +1,30 @@
+/* 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 pipdl;
+
+use std::env::args;
+use std::fs::File;
+use std::io::Read;
+
+fn run() -> Result<(), Box<std::error::Error>> {
+    let filename = args().nth(1).ok_or("Filename expected")?;
+    let mut f = File::open(&filename)?;
+    let mut s = String::new();
+    f.read_to_string(&mut s)?;
+    if let Err(e) = pipdl::parse(&s, &filename) {
+        return Err(Box::new(e));
+    }
+    Ok(())
+}
+
+fn main() {
+    ::std::process::exit(match run() {
+        Ok(_) => 0,
+        Err(e) => {
+            eprintln!("{}", e);
+            1
+        }
+    })
+}
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl_new/pipdl/src/lib.rs
@@ -0,0 +1,720 @@
+/* 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/. */
+
+//! This is a basic recursive-descent parser for ipdl. It intentionally doesn't
+//! have some featres which most languages would have, such as unicode support
+//! in identifiers, as it is unnecessary for our usecase.
+
+// Our code is Clippy aware, so we disable warnings for unknown lints
+// which are triggered when we silence a clippy lint
+#![allow(unknown_lints)]
+
+use std::error;
+use std::fmt;
+
+#[macro_use]
+pub mod util;
+pub use util::*;
+
+/// Public error type for messages with resolved type information.
+pub struct Error(Box<ParserError>);
+
+impl Error {
+    pub fn span(&self) -> Span {
+        self.0.span()
+    }
+}
+
+impl error::Error for Error {
+    fn description(&self) -> &str {
+        &self.0.message()
+    }
+}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        f.write_str(&format!(
+            "{}:{}:{}: error: {}",
+            self.0.span().start.file,
+            self.0.span().start.line,
+            self.0.span().start.col,
+            self.0.message()
+        ))
+    }
+}
+
+impl fmt::Debug for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        self.0.fmt(f)
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct CxxInclude {
+    pub file: Spanned<String>,
+}
+
+fn cxx_include(i: In) -> PResult<Spanned<CxxInclude>> {
+    let start = i.loc();
+    let (i, _) = kw(i, "include")?;
+    let (i, file) = string(i)?;
+    commit! {
+        let (i, _) = punct(i, ";")?;
+        let end = i.loc();
+        Ok((i.clone(), Spanned::new(Span { start, end }, CxxInclude { file })))
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct Include {
+    pub protocol: Option<Spanned<()>>,
+    pub id: Spanned<String>,
+}
+
+fn include(i: In) -> PResult<Spanned<Include>> {
+    let start = i.loc();
+    let (i, _) = kw(i, "include")?;
+    let (i, pcol) = maybe(i.clone(), kw(i, "protocol"))?;
+
+    let pcol_is_some = pcol.is_some();
+
+    let common_end_code = |i, id| {
+        let (i, _) = punct(i, ";")?;
+        let end = i.loc();
+        Ok((
+            i,
+            Spanned::new(Span { start, end }, Include { protocol: pcol, id }),
+        ))
+    };
+
+    if pcol_is_some {
+        // If we have the protocol keyword, then it can't be a C++ include anymore, and we start committing
+        commit! {
+            let (i, id) = ident(i)?;
+            common_end_code(i, id)
+        }
+    } else {
+        // Otherwise we first parse the ident
+        let (i, id) = ident(i)?;
+        commit! {
+            common_end_code(i, id)
+        }
+    }
+}
+
+#[derive(Debug, Clone)]
+pub enum CxxTypeKind {
+    Class,
+    Struct,
+    None,
+}
+
+#[derive(Debug, Clone)]
+pub struct CxxPathSeg {
+    pub id: Spanned<String>,
+    pub args: Option<Spanned<Vec<Spanned<String>>>>,
+}
+
+fn template_args(i: In) -> PResult<Spanned<Vec<Spanned<String>>>> {
+    let start = i.loc();
+    let (i, _) = punct(i, "<")?;
+    commit! {
+        let (i, args) = sep(i, ident, ",")?;
+        let (i, _) = punct(i, ">")?;
+
+        let end = i.loc();
+        Ok((i, Spanned::new(Span { start, end }, args)))
+    }
+}
+
+fn cxx_path_seg(i: In) -> PResult<Spanned<CxxPathSeg>> {
+    let start = i.loc();
+    let (i, id) = ident(i)?;
+    let (i, args) = maybe(i.clone(), template_args(i))?;
+    let end = i.loc();
+    Ok((
+        i,
+        Spanned::new(Span { start, end }, CxxPathSeg { id, args }),
+    ))
+}
+
+#[derive(Debug, Clone)]
+pub struct CxxPath {
+    pub segs: Vec<Spanned<CxxPathSeg>>,
+}
+
+fn cxx_path(i: In) -> PResult<Spanned<CxxPath>> {
+    let start = i.loc();
+    let (i, segs) = sep(i, cxx_path_seg, "::")?;
+    let end = i.loc();
+    Ok((i, Spanned::new(Span { start, end }, CxxPath { segs })))
+}
+
+#[derive(Debug, Clone)]
+pub struct Using {
+    pub refcounted: Option<Spanned<()>>,
+    pub kind: Spanned<CxxTypeKind>,
+    pub ty: Spanned<CxxPath>,
+    pub file: Spanned<String>,
+}
+
+fn using(i: In) -> PResult<Spanned<Using>> {
+    let start = i.loc();
+    let (i, _) = kw(i, "using")?;
+    commit! {
+        let (i, refcounted) = maybe(i.clone(), kw(i, "refcounted"))?;
+        let kw_start = i.loc();
+        let (i, kind) = any!(
+            i, "struct or class keyword",
+            kw(i.clone(), "struct") => CxxTypeKind::Struct,
+            kw(i.clone(), "class") => CxxTypeKind::Class,
+            Ok((i.clone(), ())) => CxxTypeKind::None,
+        )?;
+        let kw_end = i.loc();
+
+        let (i, ty) = cxx_path(i)?;
+        let (i, _) = kw(i, "from")?;
+        let (i, file) = string(i)?;
+        let (i, _) = punct(i, ";")?;
+
+        let end = i.loc();
+
+        Ok((i, Spanned::new(Span { start, end }, Using { refcounted, kind: Spanned::new(Span { start: kw_start, end: kw_end }, kind), ty, file })))
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct Type {
+    pub is_nullable: Option<Spanned<()>>,
+    pub name: Spanned<CxxPathSeg>,
+    pub is_array: Option<Spanned<()>>,
+}
+
+fn ty(i: In) -> PResult<Spanned<Type>> {
+    fn array_brackets(i: In) -> PResult<Spanned<()>> {
+        let (i, _) = punct(i, "[")?;
+        commit! { punct(i, "]") }
+    }
+
+    let start = i.loc();
+    let (i, nullable) = maybe(i.clone(), kw(i, "nullable"))?;
+    let (i, name) = cxx_path_seg(i)?;
+    let (i, array) = maybe(i.clone(), array_brackets(i))?;
+
+    let end = i.loc();
+
+    Ok((
+        i,
+        Spanned::new(
+            Span { start, end },
+            Type {
+                is_nullable: nullable,
+                name,
+                is_array: array,
+            },
+        ),
+    ))
+}
+
+fn component(i: In) -> PResult<Spanned<Type>> {
+    let (i, ty) = ty(i)?;
+    commit! {
+        let (i, _) = punct(i, ";")?;
+        Ok((i, ty))
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct UnionItem {
+    pub path: Vec<Spanned<String>>,
+    pub components: Vec<Spanned<Type>>,
+}
+
+fn union_item(i: In) -> PResult<Spanned<UnionItem>> {
+    let start = i.loc();
+    let (i, _) = kw(i, "union")?;
+    commit! {
+        let (i, name) = ident(i)?;
+        let (i, _) = punct(i, "{")?;
+        let (i, components) = many(i, component)?;
+        let (i, _) = punct(i, "}")?;
+        let (i, _) = punct(i, ";")?;
+
+        let end = i.loc();
+
+        Ok((i, Spanned::new(Span { start, end }, UnionItem {
+            path: vec![name],
+            components,
+        })))
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct Field {
+    pub ty: Spanned<Type>,
+    pub name: Spanned<String>,
+}
+
+fn field(i: In) -> PResult<Spanned<Field>> {
+    let start = i.loc();
+    let (i, ty) = ty(i)?;
+    commit! {
+        let (i, name) = ident(i)?;
+        let (i, _) = punct(i, ";")?;
+        let end = i.loc();
+        Ok((i, Spanned::new(Span {start, end}, Field { ty, name })))
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct StructItem {
+    pub path: Vec<Spanned<String>>,
+    pub fields: Vec<Spanned<Field>>,
+}
+
+fn struct_item(i: In) -> PResult<Spanned<StructItem>> {
+    let start = i.loc();
+    let (i, _) = kw(i, "struct")?;
+    commit! {
+        let (i, name) = ident(i)?;
+        let (i, _) = punct(i, "{")?;
+        let (i, fields) = many(i, field)?;
+        let (i, _) = punct(i, "}")?;
+        let (i, _) = punct(i, ";")?;
+
+        let end = i.loc();
+
+        Ok((i, Spanned::new(Span { start, end }, StructItem {
+            path: vec![name],
+            fields,
+        })))
+    }
+}
+
+#[derive(Debug, Clone)]
+pub enum Nesting {
+    None,
+    InsideSync,
+    InsideCpow,
+}
+
+#[allow(needless_pass_by_value)]
+fn nesting(i: In) -> PResult<Spanned<Nesting>> {
+    let start = i.loc();
+    let (i, nesting) = any!(
+        i, "nesting specifier (not, inside_sync, or inside_cpow)",
+        kw(i.clone(), "not") => Nesting::None,
+        kw(i.clone(), "inside_sync") => Nesting::InsideSync,
+        kw(i.clone(), "inside_cpow") => Nesting::InsideCpow,
+    )?;
+    let end = i.loc();
+    Ok((i, Spanned::new(Span { start, end }, nesting)))
+}
+
+#[derive(Debug, Clone)]
+pub enum Priority {
+    Normal,
+    High,
+    Input,
+}
+
+#[allow(needless_pass_by_value)]
+fn priority(i: In) -> PResult<Spanned<Priority>> {
+    let start = i.loc();
+    let (i, priority) = any!(
+        i, "priority specifier (normal, high, or input)",
+        kw(i.clone(), "normal") => Priority::Normal,
+        kw(i.clone(), "high") => Priority::High,
+        kw(i.clone(), "input") => Priority::Input,
+    )?;
+    let end = i.loc();
+    Ok((i, Spanned::new(Span { start, end }, priority)))
+}
+
+#[derive(Debug, Clone)]
+pub enum SendSemantics {
+    Async,
+    Sync,
+    Intr,
+}
+
+#[derive(Debug, Clone)]
+pub enum MessageModifier {
+    Verify,
+    Compress,
+    CompressAll,
+}
+
+#[allow(needless_pass_by_value)]
+fn message_modifier(i: In) -> PResult<Spanned<MessageModifier>> {
+    let start = i.loc();
+    let (i, message_modifier) = any!(
+        i, "message modifier (verify, compress, or compressall)",
+        kw(i.clone(), "verify") => MessageModifier::Verify,
+        kw(i.clone(), "compress") => MessageModifier::Compress,
+        kw(i.clone(), "compressall") => MessageModifier::CompressAll,
+    )?;
+    let end = i.loc();
+    Ok((i, Spanned::new(Span { start, end }, message_modifier)))
+}
+
+#[derive(Debug, Clone)]
+pub struct Param {
+    pub ty: Spanned<Type>,
+    pub name: Spanned<String>,
+}
+
+fn param(i: In) -> PResult<Spanned<Param>> {
+    let start = i.loc();
+    let (i, ty) = ty(i)?;
+    commit! {
+        let (i, name) = ident(i)?;
+        let end = i.loc();
+        Ok((i, Spanned::new(Span { start, end }, Param { ty, name })))
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct MessageDecl {
+    pub nested: Option<Spanned<Spanned<Nesting>>>,
+    pub priority: Option<Spanned<Spanned<Priority>>>,
+    pub send_semantics: Spanned<SendSemantics>,
+    pub name: Spanned<String>,
+    pub params: Vec<Spanned<Param>>,
+    pub returns: Option<Spanned<Vec<Spanned<Param>>>>,
+    pub modifiers: Vec<Spanned<MessageModifier>>,
+}
+
+fn returns(i: In) -> PResult<Spanned<Vec<Spanned<Param>>>> {
+    let start = i.loc();
+    let (i, _) = kw(i, "returns")?;
+    commit! {
+        let (i, _) = punct(i, "(")?;
+        let (i, p) = sep(i, param, ",")?;
+        let (i, _) = punct(i, ")")?;
+        let end = i.loc();
+        Ok((i, Spanned::new(Span { start, end }, p)))
+    }
+}
+
+fn message_nested(i: In) -> PResult<Spanned<Spanned<Nesting>>> {
+    let start = i.loc();
+    let (i, _) = kw(i, "nested")?;
+    commit! {
+        let (i, _) = punct(i, "(")?;
+        let (i, nested) = nesting(i)?;
+        let (i, _) = punct(i, ")")?;
+
+        let end = i.loc();
+
+        Ok((i, Spanned::new(Span { start, end }, nested)))
+    }
+}
+
+fn message_prio(i: In) -> PResult<Spanned<Spanned<Priority>>> {
+    let start = i.loc();
+    let (i, _) = kw(i, "prio")?;
+    commit! {
+        let (i, _) = punct(i, "(")?;
+        let (i, prio) = priority(i)?;
+        let (i, _) = punct(i, ")")?;
+
+        let end = i.loc();
+
+        Ok((i, Spanned::new(Span { start, end }, prio)))
+    }
+}
+
+fn message_decl(i: In) -> PResult<Spanned<MessageDecl>> {
+    let start = i.loc();
+
+    // XXX(nika): This is really gross, maybe clean it up?
+    let mut nested = None;
+    let mut priority = None;
+    drive!(
+        i,
+        any!(
+        i, "message prefix",
+        message_prio(i.clone()) => |p| priority = Some(p),
+        message_nested(i.clone()) => |n| nested = Some(n),
+    )
+    );
+
+    let send_semantics_start = i.loc();
+    let (i, send_semantics) = any!(
+        i, "send semantics (async, sync, or intr)",
+        kw(i.clone(), "async") => SendSemantics::Async,
+        kw(i.clone(), "sync") => SendSemantics::Sync,
+        kw(i.clone(), "intr") => SendSemantics::Intr,
+    )?;
+    let send_semantics_end = i.loc();
+
+    commit! {
+        let (i, name) = ident(i)?;
+        let (i, _) = punct(i, "(")?;
+        let (i, params) = sep(i, param, ",")?;
+        let (i, _) = punct(i, ")")?;
+        let (i, returns) = maybe(i.clone(), returns(i))?;
+        let (i, modifiers) = many(i, message_modifier)?;
+        let (i, _) = punct(i, ";")?;
+
+        let end = i.loc();
+
+        Ok((i, Spanned::new(Span { start, end }, MessageDecl {
+            nested,
+            priority,
+            send_semantics: Spanned::new(Span { start: send_semantics_start, end: send_semantics_end }, send_semantics),
+            name,
+            params,
+            returns,
+            modifiers,
+        })))
+    }
+}
+
+#[derive(Debug, Clone)]
+pub enum Direction {
+    ToChild,
+    ToParent,
+    Both,
+}
+
+#[allow(needless_pass_by_value)]
+fn direction(i: In) -> PResult<Spanned<Direction>> {
+    let start = i.loc();
+    let (i, direction) = any!(
+        i, "direction (child, parent, or both)",
+        kw(i.clone(), "child") => Direction::ToChild,
+        kw(i.clone(), "parent") => Direction::ToParent,
+        kw(i.clone(), "both") => Direction::Both,
+    )?;
+    let end = i.loc();
+    Ok((i, Spanned::new(Span { start, end }, direction)))
+}
+
+#[derive(Debug, Clone)]
+pub struct MessageGroup {
+    pub direction: Spanned<Direction>,
+    pub decls: Vec<Spanned<MessageDecl>>,
+}
+
+fn message_group(i: In) -> PResult<Spanned<MessageGroup>> {
+    let start = i.loc();
+    let (i, direction) = direction(i)?;
+    commit! {
+        let (i, _) = punct(i, ":")?;
+        let (i, decls) = many(i, message_decl)?;
+
+        let end = i.loc();
+
+        Ok((i, Spanned::new(Span { start, end }, MessageGroup {
+            direction,
+            decls,
+        })))
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct ProtocolItem {
+    pub path: Vec<Spanned<String>>,
+    pub nested: Option<Spanned<Spanned<Nesting>>>,
+    pub send_semantics: Spanned<SendSemantics>,
+    pub managers: Option<Spanned<Vec<Spanned<String>>>>,
+    pub manages: Vec<Spanned<Spanned<String>>>,
+    pub groups: Vec<Spanned<MessageGroup>>,
+}
+
+fn protocol_nested(i: In) -> PResult<(Spanned<Spanned<Nesting>>, Spanned<SendSemantics>)> {
+    let nested_start = i.loc();
+    let (i, _) = kw(i, "nested")?;
+    commit! {
+        let (i, _) = punct(i, "(")?;
+        let (i, _) = kw(i, "upto")?;
+        let (i, n) = nesting(i)?;
+        let (i, _) = punct(i, ")")?;
+        let nested_end = i.loc();
+        let ss_start = i.loc();
+        let (i, ss) = any!(
+            i, "send semantics (async or sync)",
+            kw(i.clone(), "async") => SendSemantics::Async,
+            kw(i.clone(), "sync") => SendSemantics::Sync,
+        )?;
+        let ss_end = i.loc();
+        Ok((i, (Spanned::new(Span { start: nested_start, end: nested_end }, n), Spanned::new(Span { start: ss_start, end: ss_end}, ss))))
+    }
+}
+
+fn managers(i: In) -> PResult<Spanned<Vec<Spanned<String>>>> {
+    let start = i.loc();
+    let (i, _) = kw(i, "manager")?;
+    commit! {
+        let (i, managers) = sep(i, ident, "or")?;
+        let (i, _) = punct(i, ";")?;
+        let end = i.loc();
+        Ok((i, Spanned::new(Span {start, end}, managers)))
+    }
+}
+
+fn manages(i: In) -> PResult<Spanned<Spanned<String>>> {
+    let start = i.loc();
+    let (i, _) = kw(i, "manages")?;
+    commit! {
+        let (i, name) = ident(i)?;
+        let (i, _) = punct(i, ";")?;
+        let end = i.loc();
+        Ok((i, Spanned::new(Span { start, end }, name)))
+    }
+}
+
+#[allow(needless_pass_by_value)]
+fn protocol_item(i: In) -> PResult<Spanned<ProtocolItem>> {
+    let start = i.loc();
+    let mut ss_span = Span::new(i.loc().file);
+
+    let ss_start = i.loc();
+    let (i, (nested, send_semantics)) = any!(
+        i, "protocol item prefixes",
+        kw(i.clone(), "async") => |_x| (None, SendSemantics::Async),
+        kw(i.clone(), "sync") => |_x| (None, SendSemantics::Sync),
+        kw(i.clone(), "intr") => |_x| (None, SendSemantics::Intr),
+        protocol_nested(i.clone()) => |x| {ss_span = x.1.span; (Some(x.0), x.1.data)},
+        Ok((i.clone(), ())) => |_x| (None, SendSemantics::Async),
+    )?;
+    let ss_end = i.loc();
+
+    if ss_span.is_null() {
+        ss_span = Span {
+            start: ss_start,
+            end: ss_end,
+        };
+    }
+
+    let (i, _) = kw(i, "protocol")?;
+    commit! {
+        let (i, name) = ident(i)?;
+        let (i, _) = punct(i, "{")?;
+        let (i, managers) = maybe(i.clone(), managers(i))?;
+        let (i, manages) = many(i, manages)?;
+        let (i, groups) = many(i, message_group)?;
+        let (i, _) = punct(i, "}")?;
+        let (i, _) = punct(i, ";")?;
+
+        let end = i.loc();
+        Ok((i, Spanned::new(Span { start, end }, ProtocolItem {
+            send_semantics: Spanned::new(ss_span, send_semantics),
+            nested,
+            path: vec![name],
+            managers,
+            manages,
+            groups,
+        })))
+    }
+}
+
+#[derive(Debug, Clone)]
+#[allow(large_enum_variant)]
+pub enum Item {
+    Struct(Spanned<StructItem>),
+    Union(Spanned<UnionItem>),
+    Protocol(Spanned<ProtocolItem>),
+}
+
+fn namespace(i: In) -> PResult<Vec<Item>> {
+    let (i, _) = kw(i, "namespace")?;
+
+    commit! {
+        let (i, name) = ident(i)?;
+        let (i, _) = punct(i, "{")?;
+        let (i, mut items) = items(i)?;
+        let (i, _) = punct(i, "}")?;
+        for it in &mut items {
+            match *it  {
+                Item::Struct(ref mut i) =>
+                    i.data.path.insert(0, name.clone()),
+                Item::Union(ref mut i) =>
+                    i.data.path.insert(0, name.clone()),
+                Item::Protocol(ref mut i) =>
+                    i.data.path.insert(0, name.clone()),
+            }
+        }
+        Ok((i, items))
+    }
+}
+
+fn items(i: In) -> PResult<Vec<Item>> {
+    let mut v = Vec::new();
+    drive!(
+        i,
+        any!(
+        i, "item (struct, union, protocol, or namespace)",
+        struct_item(i.clone()) => |x| v.push(Item::Struct(x)),
+        union_item(i.clone()) => |x| v.push(Item::Union(x)),
+        protocol_item(i.clone()) => |x| v.push(Item::Protocol(x)),
+        namespace(i.clone()) => |x| v.extend(x),
+    )
+    );
+    Ok((i, v))
+}
+
+#[derive(Debug, Clone)]
+pub struct TranslationUnit {
+    pub cxx_includes: Vec<Spanned<CxxInclude>>,
+    pub includes: Vec<Spanned<Include>>,
+    pub usings: Vec<Spanned<Using>>,
+    pub items: Vec<Item>,
+}
+
+fn translation_unit(i: In) -> PResult<Spanned<TranslationUnit>> {
+    // Prelude.
+    let mut usings = Vec::new();
+    let mut includes = Vec::new();
+    let mut cxx_includes = Vec::new();
+
+    let start = i.loc();
+
+    drive!(
+        i,
+        any!(
+        i, "include or using declaration",
+        using(i.clone()) => |u| usings.push(u),
+        include(i.clone()) => |u| includes.push(u),
+        cxx_include(i.clone()) => |u| cxx_includes.push(u),
+    )
+    );
+
+    // Body.
+    let (i, items) = items(i)?;
+
+    // Make sure we're at EOF
+    let i = skip_ws(i)?;
+    if !i.rest().is_empty() {
+        return i.expected("item (struct, union, protocol, or namespace)");
+    }
+
+    let end = i.loc();
+
+    Ok((
+        i,
+        Spanned::new(
+            Span { start, end },
+            TranslationUnit {
+                cxx_includes,
+                includes,
+                usings,
+                items,
+            },
+        ),
+    ))
+}
+
+/// Entry point - parses a whole translation unit.
+pub fn parse<'filepath>(
+    src: &str,
+    file: &'filepath str,
+) -> Result<Spanned<TranslationUnit>, Error> {
+    match translation_unit(In::new(src, file.to_owned())) {
+        Ok((_, v)) => Ok(v),
+        Err(err) => Err(Error(Box::new(err))),
+    }
+}
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl_new/pipdl/src/util.rs
@@ -0,0 +1,450 @@
+/* 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/. */
+
+//! Fairly minimal recursive-descent parser helper functions and types.
+
+use std::fmt;
+
+#[derive(Clone, Debug)]
+pub struct Location {
+    pub line: usize,
+    pub col: usize,
+    pub file: String,
+}
+
+impl Location {
+    fn new(file: String) -> Self {
+        Location {
+            line: 0,
+            col: 0,
+            file,
+        }
+    }
+
+    pub fn is_null(&self) -> bool {
+        self.line == 0
+    }
+}
+
+#[derive(Clone, Debug)]
+pub struct Span {
+    pub start: Location,
+    pub end: Location,
+}
+
+impl Span {
+    pub fn new(file: String) -> Self {
+        Span {
+            start: Location::new(file.clone()),
+            end: Location::new(file),
+        }
+    }
+
+    pub fn is_null(&self) -> bool {
+        self.start.is_null() && self.end.is_null()
+    }
+}
+
+#[derive(Clone, Debug)]
+pub struct Spanned<T> {
+    pub data: T,
+    pub span: Span,
+}
+
+impl<T> Spanned<T> {
+    pub fn new(span: Span, data: T) -> Self {
+        Spanned { data, span }
+    }
+}
+
+/// Every bit set but the high bit in usize.
+const OFF_MASK: usize = <usize>::max_value() / 2;
+
+/// Only the high bit in usize.
+const FATAL_MASK: usize = !OFF_MASK;
+
+/// An error produced by pipdl
+pub struct ParserError {
+    message: String,
+    fatal: bool,
+    span: Span,
+}
+
+impl ParserError {
+    pub(crate) fn is_fatal(&self) -> bool {
+        self.fatal
+    }
+
+    pub(crate) fn make_fatal(mut self) -> Self {
+        self.fatal = true;
+        self
+    }
+
+    pub(crate) fn span(&self) -> Span {
+        self.span.clone()
+    }
+
+    pub(crate) fn message(&self) -> &str {
+        &self.message
+    }
+}
+
+impl fmt::Debug for ParserError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        f.debug_struct("Error")
+            .field("file", &self.span.start.file)
+            .field("start_line", &self.span.start.line)
+            .field("start_column", &self.span.start.col)
+            .field("end_line", &self.span.end.line)
+            .field("end_column", &self.span.end.col)
+            .finish()
+    }
+}
+
+/// Attempts to run each expression in order, recovering from any non-fatal
+/// errors and attempting the next option.
+macro_rules! any {
+    ($i:ident, $expected:expr, $($e:expr => |$x:ident| $f:expr),+ $(,)*) => {
+        // NOTE: We have to do this sorcery for early returns. Using a loop with breaks makes clippy
+        // mad because the loop never loops and we can't desable the lint because we're not in a function.
+        // Also, we don't directly call the closure otherwise clippy would also complain about that. Yeah.
+        {
+            let mut my_closure = || {
+                $(match $e {
+                    Ok((i, $x)) => return Ok((i, $f)),
+                    Err(e) => {
+                        // This assignment is used to help out with type inference.
+                        let e: $crate::util::ParserError = e;
+                        if e.is_fatal() {
+                            return Err(e);
+                        }
+                    }
+                })+
+                return $i.expected($expected);
+            };
+
+            my_closure()
+        }
+    };
+
+    ($i:ident, $expected:expr, $($e:expr => $f:expr),+ $(,)*) => {
+        any!($i, $expected, $($e => |_x| $f),+);
+    }
+}
+
+/// Attempts to repeatedly run the expression, stopping on a non-fatal error,
+/// and directly returning any fatal error.
+macro_rules! drive {
+    ($i:ident, $e:expr) => {
+        let mut $i = $i;
+        loop {
+            match $e {
+                Ok((j, _)) => $i = j,
+                Err(e) => if e.is_fatal() {
+                    return Err(e);
+                } else {
+                    break;
+                },
+            }
+        }
+    };
+}
+
+/// The type of error used by internal parsers
+pub(crate) type PResult<'src, T> = Result<(In<'src>, T), ParserError>;
+
+/// Specify that after this point, errors produced while parsing which are not
+/// handled should instead be treated as fatal parsing errors.
+macro_rules! commit {
+    ($($e:tt)*) => {
+        // Evaluate the inner expression, transforming errors into fatal errors.
+        let eval = || { $($e)* };
+        eval().map_err($crate::util::ParserError::make_fatal)
+    }
+}
+
+/// This datastructure is used as the cursor type into the input source data. It
+/// holds the full source string, and the current offset.
+#[derive(Clone, Debug)]
+pub(crate) struct In<'src> {
+    src: &'src str,
+    byte_offset: usize,
+    loc: Location,
+}
+impl<'src> In<'src> {
+    pub(crate) fn new(s: &'src str, file: String) -> Self {
+        In {
+            src: s,
+            byte_offset: 0,
+            loc: Location {
+                line: 1,
+                col: 0,
+                file,
+            },
+        }
+    }
+
+    /// The remaining string in the source file.
+    pub(crate) fn rest(&self) -> &'src str {
+        &self.src[self.byte_offset..]
+    }
+
+    /// Move the cursor forward by `n` characters.
+    pub(crate) fn advance(&self, n_chars: usize) -> Self {
+        let mut loc = self.loc.clone();
+
+        let (n_bytes, last_c) = self
+            .rest()
+            .char_indices()
+            .take(n_chars)
+            .inspect(|&(_, character)| {
+                if character == '\n' {
+                    loc.line += 1;
+                    loc.col = 0;
+                } else {
+                    loc.col += 1;
+                }
+            })
+            .last()
+            .expect("No characters remaining in advance");
+
+        let byte_offset = self
+            .byte_offset
+            .checked_add(n_bytes + last_c.len_utf8())
+            .expect("Failed checked add");
+
+        assert!(byte_offset <= self.src.len());
+
+        In {
+            src: self.src,
+            byte_offset,
+            loc,
+        }
+    }
+
+    /// Produce a new non-fatal error result with the given expected value.
+    pub(crate) fn expected<T>(&self, expected: &'static str) -> Result<T, ParserError> {
+        assert!((self.byte_offset & FATAL_MASK) == 0, "Offset is too large!");
+
+        // Get the line where the error occurred.
+        let text = self.src.lines().nth(self.loc.line - 1).unwrap_or(""); // Usually happens when the error occurs on the last, empty line
+
+        // Format the final error message.
+        let message = format!(
+            "{}\n\
+             | {}\n{:~>off$}^\n",
+            format!("Expected {}", expected),
+            text,
+            "",
+            off = self.loc.col + 2
+        );
+
+        Err(ParserError {
+            message,
+            fatal: false,
+            span: Span {
+                start: self.loc.clone(),
+                end: self.loc.clone(),
+            },
+        })
+    }
+
+    pub(crate) fn loc(&self) -> Location {
+        self.loc.clone()
+    }
+}
+
+/// Repeatedly run f, collecting results into a vec. Returns an error if a fatal
+/// error is produced while parsing.
+pub(crate) fn many<'src, F, R>(i: In<'src>, mut f: F) -> PResult<'src, Vec<R>>
+where
+    F: FnMut(In<'src>) -> PResult<'src, R>,
+{
+    let mut v = Vec::new();
+    drive!(
+        i,
+        match f(i.clone()) {
+            Ok((i, x)) => {
+                v.push(x);
+                Ok((i, ()))
+            }
+            Err(e) => Err(e),
+        }
+    );
+    Ok((i, v))
+}
+
+/// Repeatedly run f, followed by parsing the seperator sep. Returns an error if
+/// a fatal error is produced while parsing.
+pub(crate) fn sep<'src, Parser, Ret>(
+    i: In<'src>,
+    mut parser: Parser,
+    sep: &'static str,
+) -> PResult<'src, Vec<Ret>>
+where
+    Parser: FnMut(In<'src>) -> PResult<'src, Ret>,
+{
+    let mut return_vector = Vec::new();
+    drive!(
+        i,
+        match parser(i.clone()) {
+            Ok((i, result)) => {
+                return_vector.push(result);
+                match punct(i.clone(), sep) {
+                    Ok(o) => Ok(o),
+                    Err(_) => return Ok((i, return_vector)),
+                }
+            }
+            Err(e) => Err(e),
+        }
+    );
+    Ok((i, return_vector))
+}
+
+/// Skip any leading whitespace, including comments
+pub(crate) fn skip_ws(mut i: In) -> Result<In, ParserError> {
+    loop {
+        if i.rest().is_empty() {
+            break;
+        }
+
+        let c = i
+            .rest()
+            .chars()
+            .next()
+            .expect("No characters remaining when skipping ws");
+        if c.is_whitespace() {
+            i = i.advance(1);
+            continue;
+        }
+
+        // Line comments
+        if i.rest().starts_with("//") {
+            while !i.rest().starts_with('\n') {
+                i = i.advance(1);
+                if i.rest().is_empty() {
+                    break;
+                }
+            }
+            continue;
+        }
+
+        // Block comments
+        if i.rest().starts_with("/*") {
+            while !i.rest().starts_with("*/") {
+                i = i.advance(1);
+                if i.rest().is_empty() {
+                    return i.expected("end of block comment (`*/`)");
+                }
+            }
+
+            i = i.advance(2);
+            continue;
+        }
+        break;
+    }
+
+    Ok(i)
+}
+
+/// Read an identifier as a string.
+pub(crate) fn ident(i: In) -> PResult<Spanned<String>> {
+    let i = skip_ws(i)?;
+    let start = i.loc();
+    let (end_char, end_byte) = i
+        .rest()
+        .char_indices()
+        .enumerate()
+        .skip_while(|&(_, (b_idx, c))| match c {
+            '_' | 'a'...'z' | 'A'...'Z' => true,
+            '0'...'9' if b_idx != 0 => true,
+            _ => false,
+        })
+        .next()
+        .map(|(c_idx, (b_idx, _))| (c_idx, b_idx))
+        .unwrap_or((i.rest().chars().count(), i.rest().len()));
+
+    if end_byte == 0 {
+        return i.expected("identifier");
+    }
+
+    let j = i.advance(end_char);
+    let end = j.loc();
+
+    Ok((
+        j,
+        Spanned::new(Span { start, end }, i.rest()[..end_byte].to_owned()),
+    ))
+}
+
+/// Parse a specific keyword.
+pub(crate) fn kw<'src>(i: In<'src>, kw: &'static str) -> PResult<'src, Spanned<()>> {
+    let error_message = i.expected(kw);
+
+    let (i, id) = ident(i)?;
+    if id.data == kw {
+        Ok((i, Spanned::new(id.span, ())))
+    } else {
+        error_message
+    }
+}
+
+/// Parse punctuation.
+pub(crate) fn punct<'src>(i: In<'src>, p: &'static str) -> PResult<'src, Spanned<()>> {
+    let i = skip_ws(i)?;
+    let start = i.loc();
+    if i.rest().starts_with(p) {
+        let i = i.advance(p.chars().count());
+        let end = i.loc();
+        Ok((i, Spanned::new(Span { start, end }, ())))
+    } else {
+        i.expected(p)
+    }
+}
+
+/// Try to parse the inner value, and return Some() if it succeeded, None if it
+/// failed non-fatally, and an error if it failed fatally.
+pub(crate) fn maybe<'src, T>(i: In<'src>, r: PResult<'src, T>) -> PResult<'src, Option<T>> {
+    match r {
+        Ok((i, x)) => Ok((i, Some(x))),
+        Err(e) => if e.is_fatal() {
+            Err(e)
+        } else {
+            Ok((i, None))
+        },
+    }
+}
+
+/// Parse a string literal.
+pub(crate) fn string(i: In) -> PResult<Spanned<String>> {
+    let mut s = String::new();
+    let start = i.loc();
+    let (i, _) = punct(i, "\"")?;
+    let mut chars = i.rest().chars().enumerate().peekable();
+    while let Some((char_offset, ch)) = chars.next() {
+        match ch {
+            '"' => {
+                let i = i.advance(char_offset + 1);
+                let end = i.loc();
+                return Ok((i, Spanned::new(Span { start, end }, s)));
+            }
+            '\\' => match chars.next() {
+                Some((_, 'n')) => s.push('\n'),
+                Some((_, 'r')) => s.push('\r'),
+                Some((_, 't')) => s.push('\t'),
+                Some((_, '\\')) => s.push('\\'),
+                Some((_, '\'')) => s.push('\''),
+                Some((_, '"')) => s.push('"'),
+                Some((_, '0')) => s.push('\0'),
+                _ => {
+                    return i
+                        .advance(char_offset)
+                        .expected("valid escape (\\n, \\r, \\t, \\\\, \\', \\\", or \\0)")
+                }
+            },
+            x => s.push(x),
+        }
+    }
+    i.expected("end of string literal (\")")
+}
--- a/ipc/moz.build
+++ b/ipc/moz.build
@@ -3,16 +3,17 @@
 # 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/.
 
 DIRS += [
     'chromium',
     'glue',
     'ipdl',
+    'ipdl_new',
     'testshell',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'android':
     DIRS += ['contentproc']
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     DIRS += ['mscom']
new file mode 100644
--- /dev/null
+++ b/third_party/rust/thin-vec/.cargo-checksum.json
@@ -0,0 +1,1 @@
+{"files":{"Cargo.toml":"fb96cad605ae48215811808c1cc1b9a50248f2b14542058094b23983e2f8d8a0","README.md":"c26d7101e3031e7dd8890ce938e50cad7a1e6adf7fc2f2b0d3c36b03afe68c0b","src/heap.rs":"fe84a4ff433568d5713685456d87597ac5dcdb9d5190061a3da8074240ba1bc3","src/lib.rs":"ce36db8e3464dddade7c1ddbe3ee1f5e525af5be492ea51a0d8a0776c1adfc28","src/range.rs":"bac59bcb6230367a39c7e28ac15263e4526f966cd8c72015873017f17c115aaa"},"package":"73fdf4b84c65a85168477b7fb6c498e0716bc9487fba24623389ea7f51708044"}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/third_party/rust/thin-vec/Cargo.toml
@@ -0,0 +1,28 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g. crates.io) dependencies
+#
+# If you believe there's an error in this file please file an
+# issue against the rust-lang/cargo repository. If you're
+# editing this file be aware that the upstream Cargo.toml
+# will likely look very different (and much more reasonable)
+
+[package]
+name = "thin-vec"
+version = "0.1.0"
+authors = ["Alexis Beingessner <a.beingessner@gmail.com>"]
+description = "a vec that takes up less space on the stack"
+homepage = "https://github.com/gankro/thin-vec"
+readme = "README.md"
+license = "MIT/Apache-2.0"
+repository = "https://github.com/gankro/thin-vec"
+[dependencies.libc]
+version = "0.2"
+
+[features]
+default = []
+gecko-ffi = []
+unstable = []
new file mode 100644
--- /dev/null
+++ b/third_party/rust/thin-vec/README.md
@@ -0,0 +1,4 @@
+ThinVec is a Vec that stores its length and capacity inline, making it take up
+less space. Currently this crate mostly exists to facilitate gecko ffi. The
+crate isn't quite ready for use elsewhere, as it currently unconditionally
+uses the libc allocator.
new file mode 100644
--- /dev/null
+++ b/third_party/rust/thin-vec/src/heap.rs
@@ -0,0 +1,15 @@
+extern crate libc;
+
+pub unsafe fn allocate(size: usize, align: usize) -> *mut u8 {
+    assert!(align <= 16);
+    libc::malloc(size) as *mut _
+}
+
+pub unsafe fn deallocate(ptr: *mut u8, _size: usize, _align: usize) {
+    libc::free(ptr as *mut _);
+}
+
+pub unsafe fn reallocate(ptr: *mut u8, _old_size: usize, size: usize, align: usize) -> *mut u8 {
+    assert!(align <= 16);
+    libc::realloc(ptr as *mut _, size) as *mut _
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/thin-vec/src/lib.rs
@@ -0,0 +1,2366 @@
+mod range;
+
+use std::{fmt, io, ptr, mem, slice};
+use std::collections::Bound;
+use std::iter::FromIterator;
+use std::slice::IterMut;
+use std::ops::{Deref, DerefMut};
+use std::marker::PhantomData;
+use std::cmp::*;
+use std::hash::*;
+use std::borrow::*;
+use range::RangeArgument;
+use std::ptr::NonNull;
+
+// Heap shimming because reasons. This doesn't unfortunately match the heap api
+// right now because reasons.
+mod heap;
+
+#[cfg(not(feature = "gecko-ffi"))]
+type SizeType = usize;
+#[cfg(feature = "gecko-ffi")]
+type SizeType = u32;
+
+#[cfg(feature = "gecko-ffi")]
+const AUTO_MASK: u32 = 1 << 31;
+#[cfg(feature = "gecko-ffi")]
+const CAP_MASK: u32 = !AUTO_MASK;
+
+#[cfg(not(feature = "gecko-ffi"))]
+const MAX_CAP: usize = !0;
+#[cfg(feature = "gecko-ffi")]
+const MAX_CAP: usize = i32::max_value() as usize;
+
+#[cfg(not(feature = "gecko-ffi"))]
+#[inline(always)]
+fn assert_size(x: usize) -> SizeType { x }
+
+#[cfg(feature = "gecko-ffi")]
+#[inline]
+fn assert_size(x: usize) -> SizeType {
+    if x > MAX_CAP as usize {
+        panic!("nsTArray size may not exceed the capacity of a 32-bit sized int");
+    }
+    x as SizeType
+}
+
+/// The header of a ThinVec
+#[repr(C)]
+struct Header {
+    _len: SizeType,
+    _cap: SizeType,
+}
+
+impl Header {
+    fn len(&self) -> usize {
+        self._len as usize
+    }
+
+    #[cfg(feature = "gecko-ffi")]
+    fn cap(&self) -> usize {
+        (self._cap & CAP_MASK) as usize
+    }
+
+    #[cfg(not(feature = "gecko-ffi"))]
+    fn cap(&self) -> usize {
+        self._cap as usize
+    }
+
+    fn set_len(&mut self, len: usize) {
+        self._len = assert_size(len);
+    }
+
+    #[cfg(feature = "gecko-ffi")]
+    fn set_cap(&mut self, cap: usize) {
+        debug_assert!(cap & (CAP_MASK as usize) == cap);
+        // FIXME: this is busted because it reads uninit memory
+        // debug_assert!(!self.uses_stack_allocated_buffer());
+        self._cap = assert_size(cap) & CAP_MASK;
+    }
+
+    #[cfg(feature = "gecko-ffi")]
+    fn uses_stack_allocated_buffer(&self) -> bool {
+        self._cap & AUTO_MASK != 0
+    }
+
+    #[cfg(not(feature = "gecko-ffi"))]
+    fn set_cap(&mut self, cap: usize) {
+        self._cap = assert_size(cap);
+    }
+
+    fn data<T>(&self) -> *mut T {
+        let header_size = mem::size_of::<Header>();
+        let padding = padding::<T>();
+
+        let ptr = self as *const Header as *mut Header as *mut u8;
+
+        unsafe {
+            if padding > 0 && self.len() == 0 {
+                // The empty header isn't well-aligned, just make an aligned one up
+                NonNull::dangling().as_ptr()
+            } else {
+                ptr.offset(header_size as isize) as *mut T
+            }
+        }
+    }
+}
+
+/// Singleton that all empty collections share.
+/// Note: can't store non-zero ZSTs, we allocate in that case. We could
+/// optimize everything to not do that (basically, make ptr == len and branch
+/// on size == 0 in every method), but it's a bunch of work for something that
+/// doesn't matter much.
+#[cfg(any(not(feature = "gecko-ffi"), test))]
+static EMPTY_HEADER: Header = Header { _len: 0, _cap: 0 };
+
+#[cfg(all(feature = "gecko-ffi", not(test)))]
+extern {
+    #[link_name = "sEmptyTArrayHeader"]
+    static EMPTY_HEADER: Header;
+}
+
+// TODO: overflow checks everywhere
+
+// Utils
+
+fn oom() -> ! { std::process::abort() }
+
+fn alloc_size<T>(cap: usize) -> usize {
+    // Compute "real" header size with pointer math
+    let header_size = mem::size_of::<Header>();
+    let elem_size = mem::size_of::<T>();
+    let padding = padding::<T>();
+
+    // TODO: care about isize::MAX overflow?
+    let data_size = elem_size.checked_mul(cap).expect("capacity overflow");
+
+    data_size.checked_add(header_size + padding).expect("capacity overflow")
+}
+
+fn padding<T>() -> usize {
+    let alloc_align = alloc_align::<T>();
+    let header_size = mem::size_of::<Header>();
+
+    if alloc_align > header_size {
+        if cfg!(feature = "gecko-ffi") {
+            panic!("nsTArray does not handle alignment above > {} correctly",
+                   header_size);
+        }
+        alloc_align - header_size
+    } else {
+        0
+    }
+}
+
+fn alloc_align<T>() -> usize {
+    max(mem::align_of::<T>(), mem::align_of::<Header>())
+}
+
+fn header_with_capacity<T>(cap: usize) -> NonNull<Header> {
+    debug_assert!(cap > 0);
+    unsafe {
+        let header = heap::allocate(
+            alloc_size::<T>(cap),
+            alloc_align::<T>(),
+        ) as *mut Header;
+
+        if header.is_null() { oom() }
+
+        // "Infinite" capacity for zero-sized types:
+        (*header).set_cap(if mem::size_of::<T>() == 0 { MAX_CAP } else { cap });
+        (*header).set_len(0);
+
+        NonNull::new_unchecked(header)
+    }
+}
+
+
+
+/// ThinVec is exactly the same as Vec, except that it stores its `len` and `capacity` in the buffer
+/// it allocates.
+///
+/// This makes the memory footprint of ThinVecs lower; notably in cases where space is reserved for
+/// a non-existence ThinVec<T>. So `Vec<ThinVec<T>>` and `Option<ThinVec<T>>::None` will waste less
+/// space. Being pointer-sized also means it can be passed/stored in registers.
+///
+/// Of course, any actually constructed ThinVec will theoretically have a bigger allocation, but
+/// the fuzzy nature of allocators means that might not actually be the case.
+///
+/// Properties of Vec that are preserved:
+/// * `ThinVec::new()` doesn't allocate (it points to a statically allocated singleton)
+/// * reallocation can be done in place
+/// * `size_of::<ThinVec<T>>()` == `size_of::<Option<ThinVec<T>>>()`
+///   * NOTE: This is only possible when the `unstable` feature is used.
+///
+/// Properties of Vec that aren't preserved:
+/// * `ThinVec<T>` can't ever be zero-cost roundtripped to a `Box<[T]>`, `String`, or `*mut T`
+/// * `from_raw_parts` doesn't exist
+/// * ThinVec currently doesn't bother to not-allocate for Zero Sized Types (e.g. `ThinVec<()>`),
+///   but it could be done if someone cared enough to implement it.
+#[cfg_attr(feature = "gecko-ffi", repr(C))]
+pub struct ThinVec<T> {
+    ptr: NonNull<Header>,
+    boo: PhantomData<T>,
+}
+
+
+/// Creates a `ThinVec` containing the arguments.
+///
+/// ```
+/// #[macro_use] extern crate thin_vec;
+///
+/// fn main() {
+///     let v = thin_vec![1, 2, 3];
+///     assert_eq!(v.len(), 3);
+///     assert_eq!(v[0], 1);
+///     assert_eq!(v[1], 2);
+///     assert_eq!(v[2], 3);
+///
+///     let v = thin_vec![1; 3];
+///     assert_eq!(v, [1, 1, 1]);
+/// }
+/// ```
+#[macro_export]
+macro_rules! thin_vec {
+    (@UNIT $($t:tt)*) => (());
+
+    ($elem:expr; $n:expr) => ({
+        let mut vec = $crate::ThinVec::new();
+        vec.resize($n, $elem);
+        vec
+    });
+    () => {$crate::ThinVec::new()};
+    ($($x:expr),*) => ({
+        let len = [$(thin_vec!(@UNIT $x)),*].len();
+        let mut vec = $crate::ThinVec::with_capacity(len);
+        $(vec.push($x);)*
+        vec
+    });
+    ($($x:expr,)*) => (thin_vec![$($x),*]);
+}
+
+impl<T> ThinVec<T> {
+    pub fn new() -> ThinVec<T> {
+        unsafe {
+            ThinVec {
+                ptr: NonNull::new_unchecked(&EMPTY_HEADER
+                                           as *const Header
+                                           as *mut Header),
+                boo: PhantomData,
+            }
+        }
+    }
+
+    pub fn with_capacity(cap: usize) -> ThinVec<T> {
+        if cap == 0 {
+            ThinVec::new()
+        } else {
+            ThinVec {
+                ptr: header_with_capacity::<T>(cap),
+                boo: PhantomData,
+            }
+        }
+    }
+
+    // Accessor conveniences
+
+    fn ptr(&self) -> *mut Header { self.ptr.as_ptr() }
+    fn header(&self) -> &Header { unsafe { self.ptr.as_ref() } }
+    fn data_raw(&self) -> *mut T { self.header().data() }
+
+    // This is unsafe when the header is EMPTY_HEADER.
+    unsafe fn header_mut(&mut self) -> &mut Header { &mut *self.ptr() }
+
+    pub fn len(&self) -> usize { self.header().len() }
+    pub fn is_empty(&self) -> bool { self.len() == 0 }
+    pub fn capacity(&self) -> usize { self.header().cap() }
+    pub unsafe fn set_len(&mut self, len: usize) { self.header_mut().set_len(len) }
+
+    pub fn push(&mut self, val: T) {
+        let old_len = self.len();
+        if old_len == self.capacity() {
+            self.reserve(1);
+        }
+        unsafe {
+            ptr::write(self.data_raw().offset(old_len as isize), val);
+            self.set_len(old_len + 1);
+        }
+    }
+
+    pub fn pop(&mut self) -> Option<T> {
+        let old_len = self.len();
+        if old_len == 0 { return None }
+
+        unsafe {
+            self.set_len(old_len - 1);
+            Some(ptr::read(self.data_raw().offset(old_len as isize - 1)))
+        }
+    }
+
+    pub fn insert(&mut self, idx: usize, elem: T) {
+        let old_len = self.len();
+
+        assert!(idx <= old_len, "Index out of bounds");
+        if old_len == self.capacity() {
+            self.reserve(1);
+        }
+        unsafe {
+            let ptr = self.data_raw();
+            ptr::copy(ptr.offset(idx as isize), ptr.offset(idx as isize + 1), old_len - idx);
+            ptr::write(ptr.offset(idx as isize), elem);
+            self.set_len(old_len + 1);
+        }
+    }
+
+    pub fn remove(&mut self, idx: usize) -> T {
+        let old_len = self.len();
+
+        assert!(idx < old_len, "Index out of bounds");
+
+        unsafe {
+            self.set_len(old_len - 1);
+            let ptr = self.data_raw();
+            let val = ptr::read(self.data_raw().offset(idx as isize));
+            ptr::copy(ptr.offset(idx as isize + 1), ptr.offset(idx as isize),
+                      old_len - idx - 1);
+            val
+        }
+    }
+
+    pub fn swap_remove(&mut self, idx: usize) -> T {
+        let old_len = self.len();
+
+        assert!(idx < old_len, "Index out of bounds");
+
+        unsafe {
+            let ptr = self.data_raw();
+            ptr::swap(ptr.offset(idx as isize), ptr.offset(old_len as isize - 1));
+            self.set_len(old_len - 1);
+            ptr::read(ptr.offset(old_len as isize - 1))
+        }
+    }
+
+    pub fn truncate(&mut self, len: usize) {
+        unsafe {
+            // drop any extra elements
+            while len < self.len() {
+                // decrement len before the drop_in_place(), so a panic on Drop
+                // doesn't re-drop the just-failed value.
+                let new_len = self.len() - 1;
+                self.set_len(new_len);
+                ptr::drop_in_place(self.get_unchecked_mut(new_len));
+            }
+        }
+    }
+
+    pub fn clear(&mut self) {
+        unsafe {
+            ptr::drop_in_place(&mut self[..]);
+
+            // Don't mutate the empty singleton!
+            if self.len() != 0 {
+                self.set_len(0);
+            }
+        }
+    }
+
+    pub fn as_slice(&self) -> &[T] {
+        unsafe {
+            slice::from_raw_parts(self.data_raw(), self.len())
+        }
+    }
+
+    pub fn as_mut_slice(&mut self) -> &mut [T] {
+        unsafe {
+            slice::from_raw_parts_mut(self.data_raw(), self.len())
+        }
+    }
+
+    /// Reserve capacity for at least `additional` more elements to be inserted.
+    ///
+    /// May reserve more space than requested, to avoid frequent reallocations.
+    ///
+    /// Panics if the new capacity overflows `usize`.
+    ///
+    /// Re-allocates only if `self.capacity() < self.len() + additional`.
+    #[cfg(not(feature = "gecko-ffi"))]
+    pub fn reserve(&mut self, additional: usize) {
+        let len = self.len();
+        let old_cap = self.capacity();
+        let min_cap = len.checked_add(additional).expect("capacity overflow");
+        if min_cap <= old_cap {
+            return
+        }
+        // Ensure the new capacity is at least double, to guarantee exponential growth.
+        let double_cap = if old_cap == 0 {
+            // skip to 4 because tiny ThinVecs are dumb; but not if that would cause overflow
+            if mem::size_of::<T>() > (!0) / 8 { 1 } else { 4 }
+        } else {
+            old_cap.saturating_mul(2)
+        };
+        let new_cap = max(min_cap, double_cap);
+        unsafe {
+            self.reallocate(new_cap);
+        }
+    }
+
+    /// Reserve capacity for at least `additional` more elements to be inserted.
+    ///
+    /// This method mimics the growth algorithm used by the C++ implementation
+    /// of nsTArray.
+    #[cfg(feature = "gecko-ffi")]
+    pub fn reserve(&mut self, additional: usize) {
+        let elem_size = mem::size_of::<T>();
+
+        let len = self.len();
+        let old_cap = self.capacity();
+        let min_cap = len.checked_add(additional).expect("capacity overflow");
+        if min_cap <= old_cap {
+            return
+        }
+
+        // The growth logic can't handle zero-sized types, so we have to exit
+        // early here.
+        if elem_size == 0 {
+            unsafe {
+                self.reallocate(min_cap);
+            }
+            return;
+        }
+
+        let min_cap_bytes = assert_size(min_cap)
+            .checked_mul(assert_size(elem_size))
+            .and_then(|x| x.checked_add(assert_size(mem::size_of::<Header>())))
+            .unwrap();
+
+        // Perform some checked arithmetic to ensure all of the numbers we
+        // compute will end up in range.
+        let will_fit = min_cap_bytes.checked_mul(2).is_some();
+        if !will_fit {
+            panic!("Exceeded maximum nsTArray size");
+        }
+
+        const SLOW_GROWTH_THRESHOLD: usize = 8 * 1024 * 1024;
+
+        let bytes = if min_cap > SLOW_GROWTH_THRESHOLD {
+            // Grow by a minimum of 1.125x
+            let old_cap_bytes = old_cap * elem_size + mem::size_of::<Header>();
+            let min_growth = old_cap_bytes + (old_cap_bytes >> 3);
+            let growth = max(min_growth, min_cap_bytes as usize);
+
+            // Round up to the next megabyte.
+            const MB: usize = 1 << 20;
+            MB * ((growth + MB - 1) / MB)
+        } else {
+            // Try to allocate backing buffers in powers of two.
+            min_cap_bytes.next_power_of_two() as usize
+        };
+
+        let cap = (bytes - std::mem::size_of::<Header>()) / elem_size;
+        unsafe {
+            self.reallocate(cap);
+        }
+    }
+
+    /// Reserves the minimum capacity for `additional` more elements to be inserted.
+    ///
+    /// Panics if the new capacity overflows `usize`.
+    ///
+    /// Re-allocates only if `self.capacity() < self.len() + additional`.
+    pub fn reserve_exact(&mut self, additional: usize) {
+        let new_cap = self.len().checked_add(additional).expect("capacity overflow");
+        let old_cap = self.capacity();
+        if new_cap > old_cap {
+            unsafe {
+                self.reallocate(new_cap);
+            }
+        }
+    }
+
+    pub fn shrink_to_fit(&mut self) {
+        let old_cap = self.capacity();
+        let new_cap = self.len();
+        if new_cap < old_cap {
+            if new_cap == 0 {
+                *self = ThinVec::new();
+            } else {
+                unsafe {
+                    self.reallocate(new_cap);
+                }
+            }
+        }
+    }
+
+    /// Retains only the elements specified by the predicate.
+    ///
+    /// In other words, remove all elements `e` such that `f(&e)` returns `false`.
+    /// This method operates in place and preserves the order of the retained
+    /// elements.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # #[macro_use] extern crate thin_vec;
+    /// # fn main() {
+    /// let mut vec = thin_vec![1, 2, 3, 4];
+    /// vec.retain(|&x| x%2 == 0);
+    /// assert_eq!(vec, [2, 4]);
+    /// # }
+    /// ```
+    pub fn retain<F>(&mut self, mut f: F) where F: FnMut(&T) -> bool {
+        let len = self.len();
+        let mut del = 0;
+        {
+            let v = &mut self[..];
+
+            for i in 0..len {
+                if !f(&v[i]) {
+                    del += 1;
+                } else if del > 0 {
+                    v.swap(i - del, i);
+                }
+            }
+        }
+        if del > 0 {
+            self.truncate(len - del);
+        }
+    }
+
+    /// Removes consecutive elements in the vector that resolve to the same key.
+    ///
+    /// If the vector is sorted, this removes all duplicates.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # #[macro_use] extern crate thin_vec;
+    /// # fn main() {
+    /// let mut vec = thin_vec![10, 20, 21, 30, 20];
+    ///
+    /// vec.dedup_by_key(|i| *i / 10);
+    ///
+    /// assert_eq!(vec, [10, 20, 30, 20]);
+    /// # }
+    /// ```
+    pub fn dedup_by_key<F, K>(&mut self, mut key: F) where F: FnMut(&mut T) -> K, K: PartialEq<K> {
+        self.dedup_by(|a, b| key(a) == key(b))
+    }
+
+    /// Removes consecutive elements in the vector according to a predicate.
+    ///
+    /// The `same_bucket` function is passed references to two elements from the vector, and
+    /// returns `true` if the elements compare equal, or `false` if they do not. Only the first
+    /// of adjacent equal items is kept.
+    ///
+    /// If the vector is sorted, this removes all duplicates.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # #[macro_use] extern crate thin_vec;
+    /// # fn main() {
+    /// let mut vec = thin_vec!["foo", "bar", "Bar", "baz", "bar"];
+    ///
+    /// vec.dedup_by(|a, b| a.eq_ignore_ascii_case(b));
+    ///
+    /// assert_eq!(vec, ["foo", "bar", "baz", "bar"]);
+    /// # }
+    /// ```
+    pub fn dedup_by<F>(&mut self, mut same_bucket: F) where F: FnMut(&mut T, &mut T) -> bool {
+        // See the comments in `Vec::dedup` for a detailed explanation of this code.
+        unsafe {
+            let ln = self.len();
+            if ln <= 1 {
+                return;
+            }
+
+            // Avoid bounds checks by using raw pointers.
+            let p = self.as_mut_ptr();
+            let mut r: usize = 1;
+            let mut w: usize = 1;
+
+            while r < ln {
+                let p_r = p.offset(r as isize);
+                let p_wm1 = p.offset((w - 1) as isize);
+                if !same_bucket(&mut *p_r, &mut *p_wm1) {
+                    if r != w {
+                        let p_w = p_wm1.offset(1);
+                        mem::swap(&mut *p_r, &mut *p_w);
+                    }
+                    w += 1;
+                }
+                r += 1;
+            }
+
+            self.truncate(w);
+        }
+    }
+
+    pub fn split_off(&mut self, at: usize) -> ThinVec<T> {
+        let old_len = self.len();
+        let new_vec_len = old_len - at;
+
+        assert!(at <= old_len, "Index out of bounds");
+
+        unsafe {
+            let mut new_vec = ThinVec::with_capacity(new_vec_len);
+
+            ptr::copy_nonoverlapping(self.data_raw().offset(at as isize),
+                                     new_vec.data_raw(),
+                                     new_vec_len);
+
+            // Don't mutate the empty singleton!
+            if new_vec_len != 0 {
+                new_vec.set_len(new_vec_len);
+            }
+
+            if old_len != 0 {
+                self.set_len(at);
+            }
+
+            new_vec
+        }
+    }
+
+    pub fn append(&mut self, other: &mut ThinVec<T>) {
+        self.extend(other.drain(..))
+    }
+
+    pub fn drain<R>(&mut self, range: R) -> Drain<T>
+        where R: RangeArgument<usize>
+    {
+        let len = self.len();
+        let start = match range.start() {
+            Bound::Included(&n) => n,
+            Bound::Excluded(&n) => n + 1,
+            Bound::Unbounded => 0,
+        };
+        let end = match range.end() {
+            Bound::Included(&n) => n + 1,
+            Bound::Excluded(&n) => n,
+            Bound::Unbounded => len,
+        };
+        assert!(start <= end);
+        assert!(end <= len);
+
+        unsafe {
+            // Set our length to the start bound
+            // Don't mutate the empty singleton!
+            if len != 0 {
+                self.set_len(start);
+            }
+
+            let iter = slice::from_raw_parts_mut(
+                self.data_raw().offset(start as isize),
+                end - start,
+            ).iter_mut();
+
+            Drain {
+                iter: iter,
+                vec: self,
+                end: end,
+                tail: len - end,
+            }
+        }
+    }
+
+    unsafe fn deallocate(&mut self) {
+        if self.has_allocation() {
+            heap::deallocate(self.ptr() as *mut u8,
+                alloc_size::<T>(self.capacity()),
+                alloc_align::<T>());
+        }
+    }
+
+    /// Resize the buffer and update its capacity, without changing the length.
+    /// Unsafe because it can cause length to be greater than capacity.
+    unsafe fn reallocate(&mut self, new_cap: usize) {
+        debug_assert!(new_cap > 0);
+        if self.has_allocation() {
+            let old_cap = self.capacity();
+            let ptr = heap::reallocate(self.ptr() as *mut u8,
+                                       alloc_size::<T>(old_cap),
+                                       alloc_size::<T>(new_cap),
+                                       alloc_align::<T>()) as *mut Header;
+            if ptr.is_null() { oom() }
+            (*ptr).set_cap(new_cap);
+            self.ptr = NonNull::new_unchecked(ptr);
+        } else {
+            self.ptr = header_with_capacity::<T>(new_cap);
+        }
+    }
+
+    #[cfg(feature = "gecko-ffi")]
+    #[inline]
+    fn has_allocation(&self) -> bool {
+        unsafe {
+            self.ptr.as_ptr() as *const Header != &EMPTY_HEADER &&
+                !self.ptr.as_ref().uses_stack_allocated_buffer()
+        }
+    }
+
+    #[cfg(not(feature = "gecko-ffi"))]
+    #[inline]
+    fn has_allocation(&self) -> bool {
+        self.ptr.as_ptr() as *const Header != &EMPTY_HEADER
+    }
+}
+
+impl<T: Clone> ThinVec<T> {
+    /// Resizes the `Vec` in-place so that `len()` is equal to `new_len`.
+    ///
+    /// If `new_len` is greater than `len()`, the `Vec` is extended by the
+    /// difference, with each additional slot filled with `value`.
+    /// If `new_len` is less than `len()`, the `Vec` is simply truncated.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # #[macro_use] extern crate thin_vec;
+    /// # fn main() {
+    /// let mut vec = thin_vec!["hello"];
+    /// vec.resize(3, "world");
+    /// assert_eq!(vec, ["hello", "world", "world"]);
+    ///
+    /// let mut vec = thin_vec![1, 2, 3, 4];
+    /// vec.resize(2, 0);
+    /// assert_eq!(vec, [1, 2]);
+    /// # }
+    /// ```
+    pub fn resize(&mut self, new_len: usize, value: T) {
+        let old_len = self.len();
+
+        if new_len > old_len {
+            let additional = new_len - old_len;
+            self.reserve(additional);