webidl binder: define properties with JS accessors (#7298)

Currently C++ class and struct properties are defined in JavaScript bindings with get_foo and set_foo accessor methods. This PR adds support for directly accessing the properties using native JS accessors. For example:

// Current way
myObject.set_foo(1);
console.log(myObject.get_foo());

// After this PR:
myObject.foo = 1;
console.log(myObject.foo);

This is more idiomatic JavaScript, and means that the bindings match the IDL correctly. I have left the existing getters and setters in place, so this is be backward-compatible.
diff --git a/AUTHORS b/AUTHORS
index 67869e8..db8d710 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -364,3 +364,4 @@
 * Zoltán Žarkov <zeko@freecivweb.org>
 * Roman Yurchak <rth.yurchak@pm.me>
 * Hampton Maxwell <me@hamptonmaxwell.com>
+* Matt Kane <m@mk.gg>
diff --git a/site/source/docs/porting/connecting_cpp_and_javascript/WebIDL-Binder.rst b/site/source/docs/porting/connecting_cpp_and_javascript/WebIDL-Binder.rst
index 41621f5..015d10f 100644
--- a/site/source/docs/porting/connecting_cpp_and_javascript/WebIDL-Binder.rst
+++ b/site/source/docs/porting/connecting_cpp_and_javascript/WebIDL-Binder.rst
@@ -171,6 +171,33 @@
 
   You will usually need to destroy the objects which you create, but this depends on the library being ported.
 
+Attributes
+==========
+
+Object attributes are defined in IDL using the ``attribute`` keyword. These can then be accessed in JavaScript using either ``get_foo()``/``set_foo()`` accessor methods, or directly as a property of the object.
+
+.. code-block:: cpp
+	
+	// C++
+	int attr;
+
+.. code-block:: idl
+
+	// WebIDL
+	attribute long attr;
+
+.. code-block:: javascript
+
+	// JavaScript
+	var f = new Module.Foo();
+	f.attr = 7;
+	// Equivalent to:
+	f.set_attr(7);
+
+	console.log(f.attr);
+	console.log(f.get_attr());
+
+For read-only attributes, see :ref:`webidl-binder-const`.
 
 Pointers, References, Value types (Ref and Value)
 ====================================================
@@ -215,7 +242,7 @@
   // WebIDL
   [Value] MyClass process([Ref] MyClass input);
 
-
+.. _webidl-binder-const:
 
 Const
 =====
@@ -246,7 +273,7 @@
   // WebIDL
   readonly attribute long numericalConstant;
 
-This will generate a ``get_numericalConstant()`` method in the bindings, but not a corresponding setter.
+This will generate a ``get_numericalConstant()`` method in the bindings, but not a corresponding setter. The attribute will also be defined as read-only in JavaScript, meaning that trying to set it will have no effect on the value, and will throw an error in strict mode.
 
 .. tip:: It is possible for a return type to have multiple specifiers. For example, an method that returns a contant reference would be marked up in the IDL using ``[Ref, Const]``.
 
diff --git a/tests/webidl/output_ALL.txt b/tests/webidl/output_ALL.txt
index c50dd16..8be8599 100644
--- a/tests/webidl/output_ALL.txt
+++ b/tests/webidl/output_ALL.txt
@@ -4,6 +4,13 @@
 object
 object
 8
+8
+8
+6
+6
+9
+10
+10
 boolean
 true
 c1
diff --git a/tests/webidl/output_DEFAULT.txt b/tests/webidl/output_DEFAULT.txt
index d0d3faf..04335b4 100644
--- a/tests/webidl/output_DEFAULT.txt
+++ b/tests/webidl/output_DEFAULT.txt
@@ -4,6 +4,13 @@
 object
 object
 8
+8
+8
+6
+6
+9
+10
+10
 boolean
 true
 c1
diff --git a/tests/webidl/output_FAST.txt b/tests/webidl/output_FAST.txt
index d0d3faf..04335b4 100644
--- a/tests/webidl/output_FAST.txt
+++ b/tests/webidl/output_FAST.txt
@@ -4,6 +4,13 @@
 object
 object
 8
+8
+8
+6
+6
+9
+10
+10
 boolean
 true
 c1
diff --git a/tests/webidl/post.js b/tests/webidl/post.js
index 445a145..a1872c8 100644
--- a/tests/webidl/post.js
+++ b/tests/webidl/post.js
@@ -9,6 +9,20 @@
 console.log(typeof sme.getAsConst());
 console.log(typeof sme.voidStar(sme));
 console.log(sme.get_immutableAttr());
+console.log(sme.immutableAttr);
+
+try {
+  sme.immutableAttr = 1;
+} catch(e) {}
+console.log(sme.immutableAttr); // Should be unchanged
+console.log(sme.attr);
+console.log(sme.get_attr());
+sme.attr = 9;
+console.log(sme.attr);
+sme.set_attr(10);
+console.log(sme.attr);
+console.log(sme.get_attr());
+
 console.log(typeof sme.getBoolean());
 console.log(sme.getBoolean());
 
diff --git a/tests/webidl/test.cpp b/tests/webidl/test.cpp
index 8a3fe70..8d4b97e 100644
--- a/tests/webidl/test.cpp
+++ b/tests/webidl/test.cpp
@@ -5,8 +5,8 @@
 
 #include "test.h"
 
-Parent::Parent(int val) : value(val), immutableAttr(8) { printf("Parent:%d\n", val); }
-Parent::Parent(Parent *p, Parent *q) : value(p->value + q->value), immutableAttr(8) { printf("Parent:%d\n", value); }
+Parent::Parent(int val) : value(val), immutableAttr(8), attr(6) { printf("Parent:%d\n", val); }
+Parent::Parent(Parent *p, Parent *q) : value(p->value + q->value), immutableAttr(8), attr(6) { printf("Parent:%d\n", value); }
 void Parent::mulVal(int mul) { value *= mul; }
 
 typedef EnumClass::EnumWithinClass EnumClass_EnumWithinClass;
diff --git a/tests/webidl/test.h b/tests/webidl/test.h
index 8bfa398..20c770a 100644
--- a/tests/webidl/test.h
+++ b/tests/webidl/test.h
@@ -15,7 +15,7 @@
   const Parent *getAsConst() { return NULL; }
   void *voidStar(void *something) { return something; }
   bool getBoolean() { return true; }
-
+  int attr;
   const int immutableAttr;
 };
 
diff --git a/tests/webidl/test.idl b/tests/webidl/test.idl
index 5ceda1d..b37a511 100644
--- a/tests/webidl/test.idl
+++ b/tests/webidl/test.idl
@@ -9,7 +9,7 @@
   [Const] Parent getAsConst();
   VoidPtr voidStar(VoidPtr something);
   boolean getBoolean();
-
+  attribute long attr;
   readonly attribute long immutableAttr;
 };
 
diff --git a/tools/webidl_binder.py b/tools/webidl_binder.py
index 54b788b..cde81fe 100644
--- a/tools/webidl_binder.py
+++ b/tools/webidl_binder.py
@@ -683,7 +683,10 @@
                     const=m.getExtendedAttribute('Const'),
                     array_attribute=m.type.isArray())
 
-    if not m.readonly:
+    if m.readonly:
+      mid_js += [r'''
+    Object.defineProperty(%s.prototype, '%s', { get: %s.prototype.%s }) ''' % (name, attr, name, get_name)]
+    else:
       set_name = 'set_' + attr
       mid_js += [r'''
     %s.prototype['%s'] = %s.prototype.%s = ''' % (name, set_name, name, set_name)]
@@ -697,6 +700,9 @@
                       call_content=set_call_content,
                       const=m.getExtendedAttribute('Const'),
                       array_attribute=m.type.isArray())
+      mid_js += [r'''
+    Object.defineProperty(%s.prototype, '%s', { get: %s.prototype.%s, set: %s.prototype.%s }) ''' % (name, attr, name, get_name, name, set_name)]
+
 
   if not interface.getExtendedAttribute('NoDelete'):
     mid_js += [r'''