Bug 1293362 - Part 4: Generate runtime bindings for calling xpcom methods from rust, r=froydnj

MozReview-Commit-ID: K37KyHkKsSl
This commit is contained in:
Nika Layzell 2018-01-04 17:32:15 -05:00
parent 3fe123346a
commit c33284aec0
21 changed files with 1005 additions and 4 deletions

View File

@ -28,6 +28,7 @@ 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
@ -36,7 +37,7 @@ process_py := $(topsrcdir)/python/mozbuild/mozbuild/action/xpidl-process.py
$(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
@ -76,7 +77,7 @@ 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)

View File

@ -10,6 +10,7 @@
#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 } }

View File

@ -41,6 +41,7 @@ enum nsLinkState {
};
// 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 } }

View File

@ -182,6 +182,7 @@ 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 } }

View File

@ -16,6 +16,7 @@
#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 } }

View File

@ -279,6 +279,7 @@ private:
#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 } }

View File

@ -15,6 +15,7 @@
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 } }

View File

@ -32,6 +32,7 @@ NS_HandleScriptError(nsIScriptGlobalObject *aScriptGlobal,
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 } }

View File

@ -123,10 +123,12 @@ enum class LargeAllocStatus : uint8_t
} // 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 } }

View File

@ -14,6 +14,7 @@
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}}

View File

@ -17,6 +17,7 @@
#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 } }

View File

@ -17,6 +17,7 @@ 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
@ -26,7 +27,8 @@ 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 = {}
@ -45,6 +47,7 @@ def process(input_dir, inc_paths, cache_dir, header_dir, xpt_dir, deps_dir, modu
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)
@ -56,6 +59,9 @@ def process(input_dir, inc_paths, cache_dir, header_dir, xpt_dir, deps_dir, modu
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)
@ -77,6 +83,8 @@ def main(argv):
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',
@ -88,7 +96,7 @@ def main(argv):
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:])

View File

@ -327,6 +327,7 @@ class CommonBackend(BuildBackend):
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())
@ -556,3 +557,15 @@ class CommonBackend(BuildBackend):
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")

View File

@ -150,6 +150,7 @@ typedef void* nsNativeWidget;
#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 } }

View File

@ -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),
})

View File

@ -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
}
}
}
}

View File

@ -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"));

View File

@ -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::*;

View File

@ -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]));

View File

@ -15,3 +15,13 @@ extern crate nserror;
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;

View File

@ -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)
}