Bug 1293362 - Part 4: Generate runtime bindings for calling xpcom methods from rust, r=froydnj
authorNika Layzell <nika@thelayzells.com>
Thu, 04 Jan 2018 17:32:15 -0500
changeset 453043 da3b2cacc7b399730e6d50dac3059929780dcf9d
parent 453042 95d9c9dd9909d44b1c8aeb2110e51f9669f34652
child 453044 7727478c82174343b6f6c8844523f1006f6bca16
push id8799
push usermtabara@mozilla.com
push dateThu, 01 Mar 2018 16:46:23 +0000
treeherdermozilla-beta@15334014dc67 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfroydnj
bugs1293362
milestone60.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 1293362 - Part 4: Generate runtime bindings for calling xpcom methods from rust, r=froydnj MozReview-Commit-ID: K37KyHkKsSl
config/makefiles/xpidl/Makefile.in
docshell/base/nsIScrollObserver.h
dom/base/nsIContent.h
dom/base/nsIDocument.h
dom/base/nsIGlobalObject.h
dom/base/nsINode.h
dom/base/nsIScriptContext.h
dom/base/nsIScriptGlobalObject.h
dom/base/nsPIDOMWindow.h
dom/console/nsIConsoleReportCollector.h
dom/script/nsIScriptElement.h
python/mozbuild/mozbuild/action/xpidl-process.py
python/mozbuild/mozbuild/backend/common.py
widget/nsIWidget.h
xpcom/idl-parser/xpidl/rust.py
xpcom/rust/xpcom/src/base.rs
xpcom/rust/xpcom/src/interfaces/idl.rs
xpcom/rust/xpcom/src/interfaces/mod.rs
xpcom/rust/xpcom/src/interfaces/nonidl.rs
xpcom/rust/xpcom/src/lib.rs
xpcom/rust/xpcom/src/refptr.rs
--- a/config/makefiles/xpidl/Makefile.in
+++ b/config/makefiles/xpidl/Makefile.in
@@ -23,25 +23,26 @@ include $(topsrcdir)/config/rules.mk
 # belonging to a module with a single command invocation. This prevents
 # redundant parsing of .idl files and significantly reduces CPU cycles.
 
 # For dependency files.
 idl_deps_dir := .deps
 
 dist_idl_dir := $(DIST)/idl
 dist_include_dir := $(DIST)/include
+dist_xpcrs_dir := $(DIST)/xpcrs
 process_py := $(topsrcdir)/python/mozbuild/mozbuild/action/xpidl-process.py
 
 # TODO we should use py_action, but that would require extra directories to be
 # in the virtualenv.
 %.xpt:
 	$(REPORT_BUILD)
 	$(PYTHON_PATH) $(PLY_INCLUDE) -I$(topsrcdir)/xpcom/idl-parser -I$(DEPTH)/xpcom/idl-parser/xpidl \
 		$(process_py) --cache-dir $(DEPTH)/xpcom/idl-parser/xpidl --depsdir $(idl_deps_dir) \
-		$(dist_idl_dir) $(dist_include_dir) $(@D) $(libxul_sdk_includes) \
+		$(dist_idl_dir) $(dist_include_dir) $(dist_xpcrs_dir) $(@D) $(libxul_sdk_includes) \
 		$(basename $(notdir $@)) $($(basename $(notdir $@))_deps)
 # When some IDL is added or removed, if the actual IDL file was already, or
 # still is, in the tree, simple dependencies can't detect that the XPT needs
 # to be rebuilt.
 # Add the current value of $($(xpidl_module)_deps) in the depend file, such that
 # we can later check if the value has changed since last build, which will
 # indicate whether IDLs were added or removed.
 # Note that removing previously built files is not covered.
@@ -71,17 +72,17 @@ xpt_files := $(registered_xpt_files) @xp
 depends_files := $(foreach root,$(xpidl_modules),$(idl_deps_dir)/$(root).pp)
 
 GARBAGE += $(xpt_files) $(depends_files)
 
 ifdef COMPILE_ENVIRONMENT
 xpidl:: $(xpt_files) $(chrome_manifests) $(interfaces_manifests)
 endif
 
-$(xpt_files): $(process_py) $(call mkdir_deps,$(idl_deps_dir) $(dist_include_dir))
+$(xpt_files): $(process_py) $(call mkdir_deps,$(idl_deps_dir) $(dist_include_dir) $(dist_xpcrs_dir))
 
 -include $(depends_files)
 
 define xpt_deps
 $(1): $(call mkdir_deps,$(dir $(1)))
 $(1): $(addsuffix .idl,$(addprefix $(dist_idl_dir)/,$($(basename $(notdir $(1)))_deps)))
 ifneq ($($(basename $(notdir $(1)))_deps),$($(basename $(notdir $(1)))_deps_built))
 $(1): FORCE
--- a/docshell/base/nsIScrollObserver.h
+++ b/docshell/base/nsIScrollObserver.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsIScrollObserver_h___
 #define nsIScrollObserver_h___
 
 #include "nsISupports.h"
 #include "Units.h"
 
+// Must be kept in sync with xpcom/rust/xpcom/src/interfaces/nonidl.rs
 #define NS_ISCROLLOBSERVER_IID \
   { 0xaa5026eb, 0x2f88, 0x4026, \
     { 0xa4, 0x6b, 0xf4, 0x59, 0x6b, 0x4e, 0xdf, 0x00 } }
 
 class nsIScrollObserver : public nsISupports
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISCROLLOBSERVER_IID)
--- a/dom/base/nsIContent.h
+++ b/dom/base/nsIContent.h
@@ -36,16 +36,17 @@ struct IMEState;
 
 enum nsLinkState {
   eLinkState_Unvisited  = 1,
   eLinkState_Visited    = 2,
   eLinkState_NotLink    = 3
 };
 
 // IID for the nsIContent interface
+// Must be kept in sync with xpcom/rust/xpcom/src/interfaces/nonidl.rs
 #define NS_ICONTENT_IID \
 { 0x8e1bab9d, 0x8815, 0x4d2c, \
   { 0xa2, 0x4d, 0x7a, 0xba, 0x52, 0x39, 0xdc, 0x22 } }
 
 /**
  * A node of content in a document's content model. This interface
  * is supported by all content objects.
  */
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -177,16 +177,17 @@ template<typename> class Sequence;
 template<typename, typename> class CallbackObjectHolder;
 typedef CallbackObjectHolder<NodeFilter, nsIDOMNodeFilter> NodeFilterHolder;
 
 enum class CallerType : uint32_t;
 
 } // namespace dom
 } // namespace mozilla
 
+// Must be kept in sync with xpcom/rust/xpcom/src/interfaces/nonidl.rs
 #define NS_IDOCUMENT_IID \
 { 0xce1f7627, 0x7109, 0x4977, \
   { 0xba, 0x77, 0x49, 0x0f, 0xfd, 0xe0, 0x7a, 0xaa } }
 
 // Enum for requesting a particular type of document when creating a doc
 enum DocumentFlavor {
   DocumentFlavorLegacyGuess, // compat with old code until made HTML5-compliant
   DocumentFlavorHTML, // HTMLDocument with HTMLness bit set to true
--- a/dom/base/nsIGlobalObject.h
+++ b/dom/base/nsIGlobalObject.h
@@ -11,16 +11,17 @@
 #include "mozilla/dom/ClientInfo.h"
 #include "mozilla/dom/DispatcherTrait.h"
 #include "mozilla/dom/ServiceWorkerDescriptor.h"
 #include "nsISupports.h"
 #include "nsStringFwd.h"
 #include "nsTArray.h"
 #include "js/TypeDecls.h"
 
+// Must be kept in sync with xpcom/rust/xpcom/src/interfaces/nonidl.rs
 #define NS_IGLOBALOBJECT_IID \
 { 0x11afa8be, 0xd997, 0x4e07, \
 { 0xa6, 0xa3, 0x6f, 0x87, 0x2e, 0xc3, 0xee, 0x7f } }
 
 class nsCycleCollectionTraversalCallback;
 class nsIPrincipal;
 
 class nsIGlobalObject : public nsISupports,
--- a/dom/base/nsINode.h
+++ b/dom/base/nsINode.h
@@ -274,16 +274,17 @@ private:
   virtual void AddSizeOfExcludingThis(nsWindowSizes& aSizes, \
                                       size_t* aNodeSize) const override;
 
 // Categories of node properties
 // 0 is global.
 #define DOM_USER_DATA         1
 
 // IID for the nsINode interface
+// Must be kept in sync with xpcom/rust/xpcom/src/interfaces/nonidl.rs
 #define NS_INODE_IID \
 { 0x70ba4547, 0x7699, 0x44fc, \
   { 0xb3, 0x20, 0x52, 0xdb, 0xe3, 0xd1, 0xf9, 0x0a } }
 
 /**
  * An internal interface that abstracts some DOMNode-related parts that both
  * nsIContent and nsIDocument share.  An instance of this interface has a list
  * of nsIContent children and provides access to them.
--- a/dom/base/nsIScriptContext.h
+++ b/dom/base/nsIScriptContext.h
@@ -10,16 +10,17 @@
 #include "nscore.h"
 #include "nsString.h"
 #include "nsISupports.h"
 #include "nsCOMPtr.h"
 #include "jspubtd.h"
 
 class nsIScriptGlobalObject;
 
+// Must be kept in sync with xpcom/rust/xpcom/src/interfaces/nonidl.rs
 #define NS_ISCRIPTCONTEXT_IID \
 { 0x54cbe9cf, 0x7282, 0x421a, \
  { 0x91, 0x6f, 0xd0, 0x70, 0x73, 0xde, 0xb8, 0xc0 } }
 
 class nsIOffThreadScriptReceiver;
 
 /**
  * It is used by the application to initialize a runtime and run scripts.
--- a/dom/base/nsIScriptGlobalObject.h
+++ b/dom/base/nsIScriptGlobalObject.h
@@ -27,16 +27,17 @@ struct ErrorEventInit;
 // Returns true if HandleDOMEvent was actually called, in which case
 // aStatus will be filled in with the status.
 bool
 NS_HandleScriptError(nsIScriptGlobalObject *aScriptGlobal,
                      const mozilla::dom::ErrorEventInit &aErrorEvent,
                      nsEventStatus *aStatus);
 
 
+// Must be kept in sync with xpcom/rust/xpcom/src/interfaces/nonidl.rs
 #define NS_ISCRIPTGLOBALOBJECT_IID \
 { 0x876f83bd, 0x6314, 0x460a, \
   { 0xa0, 0x45, 0x1c, 0x8f, 0x46, 0x2f, 0xb8, 0xe1 } }
 
 /**
  * The global object which keeps a script context for each supported script
  * language. This often used to store per-window global state.
  * This is a heavyweight interface implemented only by DOM globals, and
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -118,20 +118,22 @@ enum class LargeAllocStatus : uint8_t
   NON_GET,
   NON_E10S,
   NOT_ONLY_TOPLEVEL_IN_TABGROUP,
   NON_WIN32
 };
 } // namespace dom
 } // namespace mozilla
 
+// Must be kept in sync with xpcom/rust/xpcom/src/interfaces/nonidl.rs
 #define NS_PIDOMWINDOWINNER_IID \
 { 0x775dabc9, 0x8f43, 0x4277, \
   { 0x9a, 0xdb, 0xf1, 0x99, 0x0d, 0x77, 0xcf, 0xfb } }
 
+// Must be kept in sync with xpcom/rust/xpcom/src/interfaces/nonidl.rs
 #define NS_PIDOMWINDOWOUTER_IID \
   { 0x769693d4, 0xb009, 0x4fe2, \
   { 0xaf, 0x18, 0x7d, 0xc8, 0xdf, 0x74, 0x96, 0xdf } }
 
 class nsPIDOMWindowInner : public mozIDOMWindow
 {
 protected:
   friend nsGlobalWindowInner;
--- a/dom/console/nsIConsoleReportCollector.h
+++ b/dom/console/nsIConsoleReportCollector.h
@@ -9,16 +9,17 @@
 
 #include "nsContentUtils.h"
 #include "nsISupports.h"
 #include "nsStringFwd.h"
 #include "nsTArrayForwardDeclare.h"
 
 class nsIDocument;
 
+// Must be kept in sync with xpcom/rust/xpcom/src/interfaces/nonidl.rs
 #define NS_NSICONSOLEREPORTCOLLECTOR_IID \
   {0xdd98a481, 0xd2c4, 0x4203, {0x8d, 0xfa, 0x85, 0xbf, 0xd7, 0xdc, 0xd7, 0x05}}
 
 // An interface for saving reports until we can flush them to the correct
 // window at a later time.
 class NS_NO_VTABLE nsIConsoleReportCollector : public nsISupports
 {
 public:
--- a/dom/script/nsIScriptElement.h
+++ b/dom/script/nsIScriptElement.h
@@ -12,16 +12,17 @@
 #include "nsCOMPtr.h"
 #include "nsIScriptLoaderObserver.h"
 #include "nsWeakPtr.h"
 #include "nsIParser.h"
 #include "nsIContent.h"
 #include "nsContentCreatorFunctions.h"
 #include "mozilla/CORSMode.h"
 
+// Must be kept in sync with xpcom/rust/xpcom/src/interfaces/nonidl.rs
 #define NS_ISCRIPTELEMENT_IID \
 { 0xe60fca9b, 0x1b96, 0x4e4e, \
  { 0xa9, 0xb4, 0xdc, 0x98, 0x4f, 0x88, 0x3f, 0x9c } }
 
 /**
  * Internal interface implemented by script elements
  */
 class nsIScriptElement : public nsIScriptLoaderObserver
--- a/python/mozbuild/mozbuild/action/xpidl-process.py
+++ b/python/mozbuild/mozbuild/action/xpidl-process.py
@@ -12,26 +12,28 @@ from __future__ import absolute_import
 import argparse
 import os
 import sys
 
 from io import BytesIO
 
 from buildconfig import topsrcdir
 from xpidl.header import print_header
+from xpidl.rust import print_rust_bindings
 from xpidl.typelib import write_typelib
 from xpidl.xpidl import IDLParser
 from xpt import xpt_link
 
 from mozbuild.makeutil import Makefile
 from mozbuild.pythonutil import iter_modules_in_path
 from mozbuild.util import FileAvoidWrite
 
 
-def process(input_dir, inc_paths, cache_dir, header_dir, xpt_dir, deps_dir, module, stems):
+def process(input_dir, inc_paths, cache_dir, header_dir, xpcrs_dir,
+            xpt_dir, deps_dir, module, stems):
     p = IDLParser(outputdir=cache_dir)
 
     xpts = {}
     mk = Makefile()
     rule = mk.create_rule()
 
     # Write out dependencies for Python modules we import. If this list isn't
     # up to date, we will not re-process XPIDL files if the processor changes.
@@ -40,27 +42,31 @@ def process(input_dir, inc_paths, cache_
     for stem in stems:
         path = os.path.join(input_dir, '%s.idl' % stem)
         idl_data = open(path).read()
 
         idl = p.parse(idl_data, filename=path)
         idl.resolve([input_dir] + inc_paths, p)
 
         header_path = os.path.join(header_dir, '%s.h' % stem)
+        rs_rt_path = os.path.join(xpcrs_dir, 'rt', '%s.rs' % stem)
 
         xpt = BytesIO()
         write_typelib(idl, xpt, path)
         xpt.seek(0)
         xpts[stem] = xpt
 
         rule.add_dependencies(idl.deps)
 
         with FileAvoidWrite(header_path) as fh:
             print_header(idl, fh, path)
 
+        with FileAvoidWrite(rs_rt_path) as fh:
+            print_rust_bindings(idl, fh, path)
+
     # TODO use FileAvoidWrite once it supports binary mode.
     xpt_path = os.path.join(xpt_dir, '%s.xpt' % module)
     xpt_link(xpts.values()).write(xpt_path)
 
     rule.add_targets([xpt_path])
     if deps_dir:
         deps_path = os.path.join(deps_dir, '%s.pp' % module)
         with FileAvoidWrite(deps_path) as fh:
@@ -72,23 +78,25 @@ def main(argv):
     parser.add_argument('--cache-dir',
         help='Directory in which to find or write cached lexer data.')
     parser.add_argument('--depsdir',
         help='Directory in which to write dependency files.')
     parser.add_argument('inputdir',
         help='Directory in which to find source .idl files.')
     parser.add_argument('headerdir',
         help='Directory in which to write header files.')
+    parser.add_argument('xpcrsdir',
+        help='Directory in which to write rust xpcom binding files.')
     parser.add_argument('xptdir',
         help='Directory in which to write xpt file.')
     parser.add_argument('module',
         help='Final module name to use for linked output xpt file.')
     parser.add_argument('idls', nargs='+',
         help='Source .idl file(s). Specified as stems only.')
     parser.add_argument('-I', dest='incpath', action='append', default=[],
         help='Extra directories where to look for included .idl files.')
 
     args = parser.parse_args(argv)
     process(args.inputdir, args.incpath, args.cache_dir, args.headerdir,
-        args.xptdir, args.depsdir, args.module, args.idls)
+        args.xpcrsdir, args.xptdir, args.depsdir, args.module, args.idls)
 
 if __name__ == '__main__':
     main(sys.argv[1:])
--- a/python/mozbuild/mozbuild/backend/common.py
+++ b/python/mozbuild/mozbuild/backend/common.py
@@ -322,16 +322,17 @@ class CommonBackend(BuildBackend):
 
         else:
             return False
 
         return True
 
     def consume_finished(self):
         if len(self._idl_manager.idls):
+            self._write_rust_xpidl_summary(self._idl_manager)
             self._handle_idl_manager(self._idl_manager)
             self._handle_generated_sources(mozpath.join(self.environment.topobjdir, 'dist/include/%s.h' % idl['root']) for idl in self._idl_manager.idls.values())
 
         self._handle_webidl_collection(self._webidls)
 
         sorted_ipdl_sources = list(sorted(self._ipdls.all_sources()))
         sorted_nonstatic_ipdl_sources = list(sorted(self._ipdls.all_preprocessed_sources()))
         sorted_static_ipdl_sources = list(sorted(self._ipdls.all_regular_sources()))
@@ -551,8 +552,20 @@ class CommonBackend(BuildBackend):
                     FinalTargetPreprocessedFiles(jar_context, files_pp))
 
             for m in jarinfo.chrome_manifests:
                 entry = parse_manifest_line(
                     mozpath.dirname(jarinfo.name),
                     m.replace('%', mozpath.basename(jarinfo.name) + '/'))
                 self.consume_object(ChromeManifestEntry(
                     jar_context, '%s.manifest' % jarinfo.name, entry))
+
+    def _write_rust_xpidl_summary(self, manager):
+        """Write out a rust file which includes the generated xpcom rust modules"""
+        topobjdir = self.environment.topobjdir
+
+        include_tmpl = "include!(concat!(env!(\"MOZ_TOPOBJDIR\"), \"/dist/xpcrs/%s/%s.rs\"))"
+
+        with self._write_file(mozpath.join(topobjdir, 'dist', 'xpcrs', 'rt', 'all.rs')) as fh:
+            fh.write("// THIS FILE IS GENERATED - DO NOT EDIT\n\n")
+            for idl in manager.idls.values():
+                fh.write(include_tmpl % ("rt", idl['root']))
+                fh.write(";\n")
--- a/widget/nsIWidget.h
+++ b/widget/nsIWidget.h
@@ -145,16 +145,17 @@ typedef void* nsNativeWidget;
 #endif // MOZ_X11
 #endif
 #ifdef MOZ_WIDGET_ANDROID
 #define NS_JAVA_SURFACE                100
 #define NS_PRESENTATION_WINDOW         101
 #define NS_PRESENTATION_SURFACE        102
 #endif
 
+// Must be kept in sync with xpcom/rust/xpcom/src/interfaces/nonidl.rs
 #define NS_IWIDGET_IID \
 { 0x06396bf6, 0x2dd8, 0x45e5, \
   { 0xac, 0x45, 0x75, 0x26, 0x53, 0xb1, 0xc9, 0x80 } }
 
 
 /**
  * Transparency modes
  */
new file mode 100644
--- /dev/null
+++ b/xpcom/idl-parser/xpidl/rust.py
@@ -0,0 +1,557 @@
+# rust.py - Generate rust bindings from IDL.
+#
+# 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/.
+
+"""Print a runtime Rust bindings file for the IDL file specified"""
+
+# --- Safety Hazards ---
+
+# We currently don't generate some bindings for some IDL methods in rust code,
+# due to there being ABI safety hazards if we were to do so. This is the
+# documentation for the reasons why we don't generate certain types of bindings,
+# so that we don't accidentally start generating them in the future.
+
+# notxpcom methods return their results directly by value. The x86 windows
+# stdcall ABI returns aggregates by value differently for methods than
+# functions, and rust only exposes the function ABI, so that's the one we're
+# using. The correct ABI can be emulated for notxpcom methods returning
+# aggregates by passing an &mut ReturnType parameter as the second parameter.
+# This strategy is used by the winapi-rs crate.
+# https://github.com/retep998/winapi-rs/blob/7338a5216a6a7abeefcc6bb1bc34381c81d3e247/src/macros.rs#L220-L231
+#
+# Right now we can generate code for notxpcom methods, as we don't support
+# passing aggregates by value over these APIs ever (the types which are allowed
+# in xpidl.py shouldn't include any aggregates), so the code is correct. In the
+# future if we want to start supporting returning aggregates by value, we will
+# need to use a workaround such as the one used by winapi.rs.
+
+# nostdcall methods on x86 windows will use the thiscall ABI, which is not
+# stable in rust right now, so we cannot generate bindings to them.
+
+# In general, passing C++ objects by value over the C ABI is not a good idea,
+# and when possible we should avoid doing so. We don't generate bindings for
+# these methods here currently.
+
+import sys
+import os.path
+import re
+import xpidl
+
+
+class AutoIndent(object):
+    """A small autoindenting wrapper around a fd.
+    Used to make the code output more readable."""
+
+    def __init__(self, fd):
+        self.fd = fd
+        self.indent = 0
+
+    def write(self, string):
+        """A smart write function which automatically adjusts the
+        indentation of each line as it is written by counting braces"""
+        for s in string.split('\n'):
+            s = s.strip()
+            indent = self.indent
+            if len(s) == 0:
+                indent = 0
+            elif s[0] == '}':
+                indent -= 1
+
+            self.fd.write("    " * indent + s + "\n")
+            for c in s:
+                if c == '(' or c == '{' or c == '[':
+                    self.indent += 1
+                elif c == ')' or c == '}' or c == ']':
+                    self.indent -= 1
+
+
+def rustSanitize(s):
+    keywords = [
+        "abstract", "alignof", "as", "become", "box",
+        "break", "const", "continue", "crate", "do",
+        "else", "enum", "extern", "false", "final",
+        "fn", "for", "if", "impl", "in",
+        "let", "loop", "macro", "match", "mod",
+        "move", "mut", "offsetof", "override", "priv",
+        "proc", "pub", "pure", "ref", "return",
+        "Self", "self", "sizeof", "static", "struct",
+        "super", "trait", "true", "type", "typeof",
+        "unsafe", "unsized", "use", "virtual", "where",
+        "while", "yield"
+    ]
+    if s in keywords:
+        return s + "_"
+    return s
+
+
+# printdoccomments = False
+printdoccomments = True
+
+if printdoccomments:
+    def printComments(fd, clist, indent):
+        fd.write("%s%s" % (indent, doccomments(clist)))
+
+    def doccomments(clist):
+        if len(clist) == 0:
+            return ""
+        s = "/// ```text"
+        for c in clist:
+            for cc in c.splitlines():
+                s += "\n/// " + cc
+        s += "\n/// ```\n///\n"
+        return s
+
+else:
+    def printComments(fd, clist, indent):
+        pass
+
+    def doccomments(clist):
+        return ""
+
+
+def firstCap(str):
+    return str[0].upper() + str[1:]
+
+
+# Attribute VTable Methods
+def attributeNativeName(a, getter):
+    binaryname = rustSanitize(a.binaryname if a.binaryname else firstCap(a.name))
+    return "%s%s" % ('Get' if getter else 'Set', binaryname)
+
+
+def attributeParamName(a):
+    return "a" + firstCap(a.name)
+
+
+def attributeRawParamList(iface, a, getter):
+    l = [(attributeParamName(a),
+          a.realtype.rustType('out' if getter else 'in'))]
+    if a.implicit_jscontext:
+        raise xpidl.RustNoncompat("jscontext is unsupported")
+    if a.nostdcall:
+        raise xpidl.RustNoncompat("nostdcall is unsupported")
+    return l
+
+
+def attributeParamList(iface, a, getter):
+    l = ["this: *const " + iface.name]
+    l += ["%s: %s" % x for x in attributeRawParamList(iface, a, getter)]
+    return ", ".join(l)
+
+
+def attrAsVTableEntry(iface, m, getter):
+    try:
+        return "pub %s: unsafe extern \"system\" fn (%s) -> nsresult" % \
+            (attributeNativeName(m, getter),
+             attributeParamList(iface, m, getter))
+    except xpidl.RustNoncompat as reason:
+        return """\
+/// Unable to generate binding because `%s`
+pub %s: *const ::libc::c_void""" % (reason, attributeNativeName(m, getter))
+
+
+# Method VTable generation functions
+def methodNativeName(m):
+    binaryname = m.binaryname is not None and m.binaryname or firstCap(m.name)
+    return rustSanitize(binaryname)
+
+
+def methodReturnType(m):
+    if m.notxpcom:
+        return m.realtype.rustType('in').strip()
+    return "nsresult"
+
+
+def methodRawParamList(iface, m):
+    l = [(rustSanitize(p.name), p.rustType()) for p in m.params]
+
+    if m.implicit_jscontext:
+        raise xpidl.RustNoncompat("jscontext is unsupported")
+
+    if m.optional_argc:
+        raise xpidl.RustNoncompat("optional_argc is unsupported")
+
+    if m.nostdcall:
+        raise xpidl.RustNoncompat("nostdcall is unsupported")
+
+    if not m.notxpcom and m.realtype.name != 'void':
+        l.append(("_retval", m.realtype.rustType('out')))
+
+    return l
+
+
+def methodParamList(iface, m):
+    l = ["this: *const %s" % iface.name]
+    l += ["%s: %s" % x for x in methodRawParamList(iface, m)]
+    return ", ".join(l)
+
+
+def methodAsVTableEntry(iface, m):
+    try:
+        return "pub %s: unsafe extern \"system\" fn (%s) -> %s" % \
+            (methodNativeName(m),
+             methodParamList(iface, m),
+             methodReturnType(m))
+    except xpidl.RustNoncompat as reason:
+        return """\
+/// Unable to generate binding because `%s`
+pub %s: *const ::libc::c_void""" % (reason, methodNativeName(m))
+
+
+method_impl_tmpl = """\
+#[inline]
+pub unsafe fn %(name)s(&self, %(params)s) -> %(ret_ty)s {
+    ((*self.vtable).%(name)s)(self, %(args)s)
+}
+"""
+
+def methodAsWrapper(iface, m):
+    try:
+        param_list = methodRawParamList(iface, m)
+        params = ["%s: %s" % x for x in param_list]
+        args = [x[0] for x in param_list]
+
+        return method_impl_tmpl % {
+            'name': methodNativeName(m),
+            'params': ', '.join(params),
+            'ret_ty': methodReturnType(m),
+            'args': ', '.join(args),
+        }
+    except xpidl.RustNoncompat:
+        # Dummy field for the doc comments to attach to.
+        # Private so that it's not shown in rustdoc.
+        return "const _%s: () = ();" % methodNativeName(m)
+
+
+infallible_impl_tmpl = """\
+#[inline]
+pub unsafe fn %(name)s(&self) -> %(realtype)s {
+    let mut result = <%(realtype)s as ::std::default::Default>::default();
+    let _rv = ((*self.vtable).%(name)s)(self, &mut result);
+    debug_assert!(::nserror::NsresultExt::succeeded(_rv));
+    result
+}
+"""
+
+def attrAsWrapper(iface, m, getter):
+    try:
+        if m.implicit_jscontext:
+            raise xpidl.RustNoncompat("jscontext is unsupported")
+
+        if m.nostdcall:
+            raise xpidl.RustNoncompat("nostdcall is unsupported")
+
+        name = attributeParamName(m)
+
+        if getter and m.infallible:
+            return infallible_impl_tmpl % {
+                'name': attributeNativeName(m, getter),
+                'realtype': m.realtype.rustType('in'),
+            }
+
+        rust_type = m.realtype.rustType('out' if getter else 'in')
+        return method_impl_tmpl % {
+            'name': attributeNativeName(m, getter),
+            'params': name + ': ' + rust_type,
+            'ret_ty': 'nsresult',
+            'args': name,
+        }
+
+    except xpidl.RustNoncompat:
+        # Dummy field for the doc comments to attach to.
+        # Private so that it's not shown in rustdoc.
+        return "const _%s: () = ();" % attributeNativeName(m, getter)
+
+
+header = """\
+//
+// DO NOT EDIT.  THIS FILE IS GENERATED FROM %(filename)s
+//
+
+"""
+
+
+def idl_basename(f):
+    """returns the base name of a file with the last extension stripped"""
+    return os.path.splitext(os.path.basename(f))[0]
+
+
+def print_rust_bindings(idl, fd, filename):
+    fd = AutoIndent(fd)
+
+    fd.write(header % {'filename': filename})
+
+    # All of the idl files will be included into the same rust module, as we
+    # can't do forward declarations. Because of this, we want to ignore all
+    # import statements
+
+    for p in idl.productions:
+        if p.kind == 'include' or p.kind == 'cdata' or p.kind == 'forward':
+            continue
+
+        if p.kind == 'interface':
+            write_interface(p, fd)
+            continue
+
+        if p.kind == 'typedef':
+            try:
+                # We have to skip the typedef of bool to bool (it doesn't make any sense anyways)
+                if p.name == "bool":
+                    continue
+
+                if printdoccomments:
+                    fd.write("/// `typedef %s %s;`\n///\n" %
+                        (p.realtype.nativeType('in'), p.name))
+                    fd.write(doccomments(p.doccomments))
+                fd.write("pub type %s = %s;\n\n" % (p.name, p.realtype.rustType('in')))
+            except xpidl.RustNoncompat as reason:
+                fd.write("/* unable to generate %s typedef because `%s` */\n\n" %
+                         (p.name, reason))
+
+
+base_vtable_tmpl = """
+/// We need to include the members from the base interface's vtable at the start
+/// of the VTable definition.
+pub __base: %sVTable,
+
+"""
+
+
+vtable_tmpl = """\
+// This struct represents the interface's VTable. A pointer to a statically
+// allocated version of this struct is at the beginning of every %(name)s
+// object. It contains one pointer field for each method in the interface. In
+// the case where we can't generate a binding for a method, we include a void
+// pointer.
+#[doc(hidden)]
+#[repr(C)]
+pub struct %(name)sVTable {%(base)s%(entries)s}
+
+"""
+
+
+# NOTE: This template is not generated for nsISupports, as it has no base interfaces.
+deref_tmpl = """\
+// Every interface struct type implements `Deref` to its base interface. This
+// causes methods on the base interfaces to be directly avaliable on the
+// object. For example, you can call `.AddRef` or `.QueryInterface` directly
+// on any interface which inherits from `nsISupports`.
+impl ::std::ops::Deref for %(name)s {
+    type Target = %(base)s;
+    #[inline]
+    fn deref(&self) -> &%(base)s {
+        unsafe {
+            ::std::mem::transmute(self)
+        }
+    }
+}
+
+// Ensure we can use .coerce() to cast to our base types as well. Any type which
+// our base interface can coerce from should be coercable from us as well.
+impl<T: %(base)sCoerce> %(name)sCoerce for T {
+    #[inline]
+    fn coerce_from(v: &%(name)s) -> &Self {
+        T::coerce_from(v)
+    }
+}
+"""
+
+
+struct_tmpl = """\
+// The actual type definition for the interface. This struct has methods
+// declared on it which will call through its vtable. You never want to pass
+// this type around by value, always pass it behind a reference.
+
+#[repr(C)]
+pub struct %(name)s {
+    vtable: *const %(name)sVTable,
+
+    /// This field is a phantomdata to ensure that the VTable type and any
+    /// struct containing it is not safe to send across threads, as XPCOM is
+    /// generally not threadsafe.
+    ///
+    /// XPCOM interfaces in general are not safe to send across threads.
+    __nosync: ::std::marker::PhantomData<::std::rc::Rc<u8>>,
+}
+
+// Implementing XpCom for an interface exposes its IID, which allows for easy
+// use of the `.query_interface<T>` helper method. This also defines that
+// method for %(name)s.
+unsafe impl XpCom for %(name)s {
+    const IID: nsIID = nsID(0x%(m0)s, 0x%(m1)s, 0x%(m2)s,
+                            [%(m3joined)s]);
+}
+
+// We need to implement the RefCounted trait so we can be used with `RefPtr`.
+// This trait teaches `RefPtr` how to manage our memory.
+unsafe impl RefCounted for %(name)s {
+    #[inline]
+    unsafe fn addref(&self) {
+        self.AddRef();
+    }
+    #[inline]
+    unsafe fn release(&self) {
+        self.Release();
+    }
+}
+
+// This trait is implemented on all types which can be coerced to from %(name)s.
+// It is used in the implementation of `fn coerce<T>`. We hide it from the
+// documentation, because it clutters it up a lot.
+#[doc(hidden)]
+pub trait %(name)sCoerce {
+    /// Cheaply cast a value of this type from a `%(name)s`.
+    fn coerce_from(v: &%(name)s) -> &Self;
+}
+
+// The trivial implementation: We can obviously coerce ourselves to ourselves.
+impl %(name)sCoerce for %(name)s {
+    #[inline]
+    fn coerce_from(v: &%(name)s) -> &Self {
+        v
+    }
+}
+
+impl %(name)s {
+    /// Cast this `%(name)s` to one of its base interfaces.
+    #[inline]
+    pub fn coerce<T: %(name)sCoerce>(&self) -> &T {
+        T::coerce_from(self)
+    }
+}
+"""
+
+
+wrapper_tmpl = """\
+// The implementations of the function wrappers which are exposed to rust code.
+// Call these methods rather than manually calling through the VTable struct.
+impl %(name)s {
+%(consts)s
+%(methods)s
+}
+
+"""
+
+vtable_entry_tmpl = """\
+/* %(idl)s */
+%(entry)s,
+"""
+
+
+const_wrapper_tmpl = """\
+%(docs)s
+pub const %(name)s: i64 = %(val)s;
+"""
+
+
+method_wrapper_tmpl = """\
+%(docs)s
+/// `%(idl)s`
+%(wrapper)s
+"""
+
+
+uuid_decoder = re.compile(r"""(?P<m0>[a-f0-9]{8})-
+                              (?P<m1>[a-f0-9]{4})-
+                              (?P<m2>[a-f0-9]{4})-
+                              (?P<m3>[a-f0-9]{4})-
+                              (?P<m4>[a-f0-9]{12})$""", re.X)
+
+
+def write_interface(iface, fd):
+    if iface.namemap is None:
+        raise Exception("Interface was not resolved.")
+
+    # if we see a base class-less type other than nsISupports, we just need
+    # to discard anything else about it other than its constants.
+    if iface.base is None and iface.name != "nsISupports":
+        assert len([m for m in iface.members
+                    if type(m) == xpidl.Attribute or type(m) == xpidl.Method]) == 0
+        return
+
+    # Extract the UUID's information so that it can be written into the struct definition
+    names = uuid_decoder.match(iface.attributes.uuid).groupdict()
+    m3str = names['m3'] + names['m4']
+    names['m3joined'] = ", ".join(["0x%s" % m3str[i:i+2] for i in xrange(0, 16, 2)])
+    names['name'] = iface.name
+
+    if printdoccomments:
+        if iface.base is not None:
+            fd.write("/// `interface %s : %s`\n///\n" %
+                (iface.name, iface.base))
+        else:
+            fd.write("/// `interface %s`\n///\n" %
+                iface.name)
+    printComments(fd, iface.doccomments, '')
+    fd.write(struct_tmpl % names)
+
+    if iface.base is not None:
+        fd.write(deref_tmpl % {
+            'name': iface.name,
+            'base': iface.base,
+        })
+
+    entries = []
+    for member in iface.members:
+        if type(member) == xpidl.Attribute:
+            entries.append(vtable_entry_tmpl % {
+                'idl': member.toIDL(),
+                'entry': attrAsVTableEntry(iface, member, True),
+            })
+            if not member.readonly:
+                entries.append(vtable_entry_tmpl % {
+                    'idl': member.toIDL(),
+                    'entry': attrAsVTableEntry(iface, member, False),
+                })
+
+        elif type(member) == xpidl.Method:
+            entries.append(vtable_entry_tmpl % {
+                'idl': member.toIDL(),
+                'entry': methodAsVTableEntry(iface, member),
+            })
+
+    fd.write(vtable_tmpl % {
+        'name': iface.name,
+        'base': base_vtable_tmpl % iface.base if iface.base is not None else "",
+        'entries': '\n'.join(entries),
+    })
+
+    # Get all of the constants
+    consts = []
+    for member in iface.members:
+        if type(member) == xpidl.ConstMember:
+            consts.append(const_wrapper_tmpl % {
+                'docs': doccomments(member.doccomments),
+                'name': member.name,
+                'val': member.getValue(),
+            })
+
+    methods = []
+    for member in iface.members:
+        if type(member) == xpidl.Attribute:
+            methods.append(method_wrapper_tmpl % {
+                'docs': doccomments(member.doccomments),
+                'idl': member.toIDL(),
+                'wrapper': attrAsWrapper(iface, member, True),
+            })
+            if not member.readonly:
+                methods.append(method_wrapper_tmpl % {
+                    'docs': doccomments(member.doccomments),
+                    'idl': member.toIDL(),
+                    'wrapper': attrAsWrapper(iface, member, False),
+                })
+
+        elif type(member) == xpidl.Method:
+            methods.append(method_wrapper_tmpl % {
+                'docs': doccomments(member.doccomments),
+                'idl': member.toIDL(),
+                'wrapper': methodAsWrapper(iface, member),
+            })
+
+    fd.write(wrapper_tmpl % {
+        'name': iface.name,
+        'consts': '\n'.join(consts),
+        'methods': '\n'.join(methods),
+    })
new file mode 100644
--- /dev/null
+++ b/xpcom/rust/xpcom/src/base.rs
@@ -0,0 +1,48 @@
+/* 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 {
+    RefCounted,
+    RefPtr,
+    GetterAddrefs
+};
+use interfaces::nsISupports;
+use nserror::NsresultExt;
+
+#[repr(C)]
+#[derive(Copy, Clone, Eq, PartialEq)]
+/// A "unique identifier". This is modeled after OSF DCE UUIDs.
+pub struct nsID(pub u32, pub u16, pub u16, pub [u8; 8]);
+
+/// Interface IDs
+pub type nsIID = nsID;
+/// Class IDs
+pub type nsCID = nsID;
+
+/// A type which implements XpCom must follow the following rules:
+///
+/// * It must be a legal XPCOM interface.
+/// * The result of a QueryInterface or similar call, passing IID, must return a
+///   valid reference to an object of the given type.
+/// * It must be valid to cast a &self reference to a &nsISupports reference.
+pub unsafe trait XpCom : RefCounted {
+    const IID: nsIID;
+
+    /// Perform a QueryInterface call on this object, attempting to dynamically
+    /// cast it to the requested interface type. Returns Some(RefPtr<T>) if the
+    /// cast succeeded, and None otherwise.
+    fn query_interface<T: XpCom>(&self) -> Option<RefPtr<T>> {
+        let mut ga = GetterAddrefs::<T>::new();
+        unsafe {
+            if (*(self as *const Self as *const nsISupports)).QueryInterface(
+                &T::IID,
+                ga.void_ptr(),
+            ).succeeded() {
+                ga.refptr()
+            } else {
+                None
+            }
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/xpcom/rust/xpcom/src/interfaces/idl.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(bad_style)]
+
+use *;
+use interfaces::*;
+
+// NOTE: This file contains a series of `include!()` invocations, defining all
+// idl interfaces directly within this module.
+include!(concat!(env!("MOZ_TOPOBJDIR"), "/dist/xpcrs/rt/all.rs"));
new file mode 100644
--- /dev/null
+++ b/xpcom/rust/xpcom/src/interfaces/mod.rs
@@ -0,0 +1,31 @@
+/* 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 module contains the xpcom interfaces exposed to rust code.
+//!
+//! The items in this module come in a few flavours:
+//!
+//! 1. `nsI*`: These are the types for XPCOM interfaces. They should always be
+//!    passed behind a reference, pointer, or `RefPtr`. They may be coerced to
+//!    their base interfaces using the `coerce` method.
+//!
+//! 2. `nsI*Coerce`: These traits provide the implementation mechanics for the
+//!    `coerce` method, and can usually be ignored. *These traits are hidden in
+//!    rustdoc*
+//!
+//! 3. `nsI*VTable`: These structs are the vtable definitions for each type.
+//!    They contain the base interface's vtable, followed by pointers for each
+//!    of the vtable's methods. If direct access is needed, a `*const nsI*` can
+//!    be safely transmuted to a `*const nsI*VTable`. *These structs are hidden
+//!    in rustdoc*
+//!
+//! 4. Typedefs used in idl file definitions.
+
+// Interfaces defined in .idl files
+mod idl;
+pub use self::idl::*;
+
+// Other interfaces which are needed to compile
+mod nonidl;
+pub use self::nonidl::*;
new file mode 100644
--- /dev/null
+++ b/xpcom/rust/xpcom/src/interfaces/nonidl.rs
@@ -0,0 +1,110 @@
+/* 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 module contains definitions of interfaces which are used in idl files
+//! as forward declarations, but are not actually defined in an idl file.
+//!
+//! NOTE: The IIDs in these files must be kept in sync with the IDL definitions
+//! in the corresponding C++ files.
+
+use nsID;
+
+// XXX: This macro should have an option for a custom base interface instead of
+// nsISupports, such that nsIDocument can have nsINode as a base, etc. For now,
+// query_interface should be sufficient.
+macro_rules! nonidl {
+    ($name:ident, $iid:expr) => {
+        /// This interface is referenced from idl files, but not defined in
+        /// them. It exports no methods to rust code.
+        #[repr(C)]
+        pub struct $name {
+            _vtable: *const $crate::interfaces::nsISupportsVTable,
+        }
+
+        unsafe impl $crate::XpCom for $name {
+            const IID: $crate::nsIID = $iid;
+        }
+
+        unsafe impl $crate::RefCounted for $name {
+            #[inline]
+            unsafe fn addref(&self) {
+                self.AddRef();
+            }
+            #[inline]
+            unsafe fn release(&self) {
+                self.Release();
+            }
+        }
+
+        impl ::std::ops::Deref for $name {
+            type Target = $crate::interfaces::nsISupports;
+            #[inline]
+            fn deref(&self) -> &$crate::interfaces::nsISupports {
+                unsafe {
+                    ::std::mem::transmute(self)
+                }
+            }
+        }
+    }
+}
+
+// Must be kept in sync with nsIDocument.h
+nonidl!(nsIDocument,
+        nsID(0xce1f7627, 0x7109, 0x4977,
+             [0xba, 0x77, 0x49, 0x0f, 0xfd, 0xe0, 0x7a, 0xaa]));
+
+// Must be kept in sync with nsINode.h
+nonidl!(nsINode,
+        nsID(0x70ba4547, 0x7699, 0x44fc,
+             [0xb3, 0x20, 0x52, 0xdb, 0xe3, 0xd1, 0xf9, 0x0a]));
+
+// Must be kept in sync with nsIContent.h
+nonidl!(nsIContent,
+        nsID(0x8e1bab9d, 0x8815, 0x4d2c,
+             [0xa2, 0x4d, 0x7a, 0xba, 0x52, 0x39, 0xdc, 0x22]));
+
+// Must be kept in sync with nsIConsoleReportCollector.h
+nonidl!(nsIConsoleReportCollector,
+        nsID(0xdd98a481, 0xd2c4, 0x4203,
+             [0x8d, 0xfa, 0x85, 0xbf, 0xd7, 0xdc, 0xd7, 0x05]));
+
+// Must be kept in sync with nsIGlobalObject.h
+nonidl!(nsIGlobalObject,
+        nsID(0x11afa8be, 0xd997, 0x4e07,
+             [0xa6, 0xa3, 0x6f, 0x87, 0x2e, 0xc3, 0xee, 0x7f]));
+
+// Must be kept in sync with nsIScriptElement.h
+nonidl!(nsIScriptElement,
+        nsID(0xe60fca9b, 0x1b96, 0x4e4e,
+             [0xa9, 0xb4, 0xdc, 0x98, 0x4f, 0x88, 0x3f, 0x9c]));
+
+// Must be kept in sync with nsPIDOMWindow.h
+nonidl!(nsPIDOMWindowOuter,
+        nsID(0x769693d4, 0xb009, 0x4fe2,
+             [0xaf, 0x18, 0x7d, 0xc8, 0xdf, 0x74, 0x96, 0xdf]));
+
+// Must be kept in sync with nsPIDOMWindow.h
+nonidl!(nsPIDOMWindowInner,
+        nsID(0x775dabc9, 0x8f43, 0x4277,
+             [0x9a, 0xdb, 0xf1, 0x99, 0x0d, 0x77, 0xcf, 0xfb]));
+
+// Must be kept in sync with nsIScriptContext.h
+nonidl!(nsIScriptContext,
+        nsID(0x54cbe9cf, 0x7282, 0x421a,
+             [0x91, 0x6f, 0xd0, 0x70, 0x73, 0xde, 0xb8, 0xc0]));
+
+// Must be kept in sync with nsIScriptGlobalObject.h
+nonidl!(nsIScriptGlobalObject,
+        nsID(0x876f83bd, 0x6314, 0x460a,
+             [0xa0, 0x45, 0x1c, 0x8f, 0x46, 0x2f, 0xb8, 0xe1]));
+
+// Must be kept in sync with nsIScrollObserver.h
+nonidl!(nsIScrollObserver,
+        nsID(0xaa5026eb, 0x2f88, 0x4026,
+             [0xa4, 0x6b, 0xf4, 0x59, 0x6b, 0x4e, 0xdf, 0x00]));
+
+// Must be kept in sync with nsIWidget.h
+nonidl!(nsIWidget,
+        nsID(0x06396bf6, 0x2dd8, 0x45e5,
+             [0xac, 0x45, 0x75, 0x26, 0x53, 0xb1, 0xc9, 0x80]));
--- a/xpcom/rust/xpcom/src/lib.rs
+++ b/xpcom/rust/xpcom/src/lib.rs
@@ -10,8 +10,18 @@ extern crate nsstring;
 extern crate nserror;
 
 // re-export the xpcom_macros macro
 #[macro_use]
 #[allow(unused_imports)]
 extern crate xpcom_macros;
 #[doc(hidden)]
 pub use xpcom_macros::*;
+
+// Helper functions and data structures are exported in the root of the crate.
+mod base;
+pub use base::*;
+
+mod refptr;
+pub use refptr::*;
+
+// XPCOM interface definitions.
+pub mod interfaces;
new file mode 100644
--- /dev/null
+++ b/xpcom/rust/xpcom/src/refptr.rs
@@ -0,0 +1,199 @@
+/* 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::mem;
+use std::ptr;
+use std::ops::Deref;
+use std::marker::PhantomData;
+use std::cell::Cell;
+use std::sync::atomic::{self, AtomicUsize, Ordering};
+
+use nserror::{NsresultExt, nsresult, NS_OK};
+
+use libc;
+
+/// A trait representing a type which can be reference counted invasively.
+/// The object is responsible for freeing its backing memory when its
+/// reference count reaches 0.
+pub unsafe trait RefCounted {
+    /// Increment the reference count.
+    unsafe fn addref(&self);
+    /// Decrement the reference count, potentially freeing backing memory.
+    unsafe fn release(&self);
+}
+
+/// A smart pointer holding a RefCounted object. The object itself manages its
+/// own memory. RefPtr will invoke the addref and release methods at the
+/// appropriate times to facilitate the bookkeeping.
+pub struct RefPtr<T: RefCounted + 'static> {
+    // We're going to cheat and store the internal reference as an &'static T
+    // instead of an *const T or Shared<T>, because Shared and NonZero are
+    // unstable, and we need to build on stable rust.
+    // I believe that this is "safe enough", as this module is private and
+    // no other module can read this reference.
+    _ptr: &'static T,
+    // As we aren't using Shared<T>, we need to add this phantomdata to
+    // prevent unsoundness in dropck
+    _marker: PhantomData<T>,
+}
+
+impl <T: RefCounted + 'static> RefPtr<T> {
+    /// Construct a new RefPtr from a reference to the refcounted object.
+    #[inline]
+    pub fn new(p: &T) -> RefPtr<T> {
+        unsafe {
+            p.addref();
+            RefPtr {
+                _ptr: mem::transmute(p),
+                _marker: PhantomData,
+            }
+        }
+    }
+
+    /// Construct a RefPtr from a raw pointer, addrefing it.
+    #[inline]
+    pub unsafe fn from_raw(p: *const T) -> Option<RefPtr<T>> {
+        if p.is_null() {
+            return None;
+        }
+        (*p).addref();
+        Some(RefPtr {
+            _ptr: &*p,
+            _marker: PhantomData,
+        })
+    }
+
+    /// Construct a RefPtr from a raw pointer, without addrefing it.
+    #[inline]
+    pub unsafe fn from_raw_dont_addref(p: *const T) -> Option<RefPtr<T>> {
+        if p.is_null() {
+            return None;
+        }
+        Some(RefPtr {
+            _ptr: &*p,
+            _marker: PhantomData,
+        })
+    }
+
+    /// Write this RefPtr's value into an outparameter.
+    #[inline]
+    pub unsafe fn forget(self, into: &mut *const T) {
+        *into = &*self as *const T;
+        mem::forget(self);
+    }
+}
+
+impl <T: RefCounted + 'static> Deref for RefPtr<T> {
+    type Target = T;
+    #[inline]
+    fn deref(&self) -> &T {
+        self._ptr
+    }
+}
+
+impl <T: RefCounted + 'static> Drop for RefPtr<T> {
+    #[inline]
+    fn drop(&mut self) {
+        unsafe {
+            self._ptr.release();
+        }
+    }
+}
+
+impl <T: RefCounted + 'static> Clone for RefPtr<T> {
+    #[inline]
+    fn clone(&self) -> RefPtr<T> {
+        RefPtr::new(self)
+    }
+}
+
+/// A helper struct for constructing `RefPtr<T>` from raw pointer outparameters.
+/// Holds a `*const T` internally which will be released if non null when
+/// destructed, and can be easily transformed into an `Option<RefPtr<T>>`.
+///
+/// It many cases it may be easier to use the `getter_addrefs` method.
+pub struct GetterAddrefs<T: RefCounted + 'static> {
+    _ptr: *const T,
+    _marker: PhantomData<T>,
+}
+
+impl <T: RefCounted + 'static> GetterAddrefs<T> {
+    /// Create a `GetterAddrefs`, initializing it with the null pointer.
+    #[inline]
+    pub fn new() -> GetterAddrefs<T> {
+        GetterAddrefs {
+            _ptr: ptr::null(),
+            _marker: PhantomData,
+        }
+    }
+
+    /// Get a reference to the internal `*const T`. This method is unsafe,
+    /// as the destructor of this class depends on the internal `*const T`
+    /// being either a valid reference to a value of type `T`, or null.
+    #[inline]
+    pub unsafe fn ptr(&mut self) -> &mut *const T {
+        &mut self._ptr
+    }
+
+    /// Get a reference to the internal `*const T` as a `*mut libc::c_void`.
+    /// This is useful to pass to functions like `GetInterface` which take a
+    /// void pointer outparameter.
+    #[inline]
+    pub unsafe fn void_ptr(&mut self) -> *mut *mut libc::c_void {
+        &mut self._ptr as *mut *const T as *mut *mut libc::c_void
+    }
+
+    /// Transform this `GetterAddrefs` into an `Option<RefPtr<T>>`, without
+    /// performing any addrefs or releases.
+    #[inline]
+    pub fn refptr(self) -> Option<RefPtr<T>> {
+        let p = self._ptr;
+        // Don't run the destructor because we don't want to release the stored
+        // pointer.
+        mem::forget(self);
+        unsafe {
+            RefPtr::from_raw_dont_addref(p)
+        }
+    }
+}
+
+impl <T: RefCounted + 'static> Drop for GetterAddrefs<T> {
+    #[inline]
+    fn drop(&mut self) {
+        if !self._ptr.is_null() {
+            unsafe {
+                (*self._ptr).release();
+            }
+        }
+    }
+}
+
+/// Helper method for calling XPCOM methods which return a reference counted
+/// value through an outparameter. Takes a lambda, which is called with a valid
+/// outparameter argument (`*mut *const T`), and returns a `nsresult`. Returns
+/// either a `RefPtr<T>` with the value returned from the outparameter, or a
+/// `nsresult`.
+///
+/// # NOTE:
+///
+/// Can return `Err(NS_OK)` if the call succeeded, but the outparameter was set
+/// to NULL.
+///
+/// # Usage
+///
+/// ```
+/// let x: Result<RefPtr<T>, nsresult> =
+///     getter_addrefs(|p| iosvc.NewURI(uri, ptr::null(), ptr::null(), p));
+/// ```
+#[inline]
+pub fn getter_addrefs<T: RefCounted, F>(f: F) -> Result<RefPtr<T>, nsresult>
+    where F: FnOnce(*mut *const T) -> nsresult
+{
+    let mut ga = GetterAddrefs::<T>::new();
+    let rv = f(unsafe { ga.ptr() });
+    if rv.failed() {
+        return Err(rv);
+    }
+    ga.refptr().ok_or(NS_OK)
+}