[dart:html] Add file for JS interop workarounds

Creates a markdown file that documents issues to help users work
around limitations in dart:html and similar libraries.

Change-Id: I0f83661919f3d09bb2743a84cdcd141a9aa9a87e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/171629
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Commit-Queue: Srujan Gaddam <srujzs@google.com>
diff --git a/sdk/lib/html/doc/WORKAROUNDS.md b/sdk/lib/html/doc/WORKAROUNDS.md
new file mode 100644
index 0000000..d3d8456
--- /dev/null
+++ b/sdk/lib/html/doc/WORKAROUNDS.md
@@ -0,0 +1,128 @@
+Dart web platform libraries e.g. `dart:html` is partially hand-written and
+partially generated, with the code generation using the Chrome IDL as the source
+of truth for many browser interfaces. This introduces a dependency on the
+version of the IDL and doesn’t always match up with other browser interfaces.
+
+Currently, we do not intend on updating our scripts to use a newer version of
+the IDL, so APIs and classes in these libraries may be inaccurate.
+
+In order to work around this, we ask users to leverage JS interop. Longer term,
+we intend to revamp our web library offerings to be more robust and reliable.
+
+The following are workarounds to common issues you might see with using the web
+platform libraries.
+
+## Common Issues
+
+### Missing/broken APIs
+
+As mentioned above, there exists stale interfaces. While some of these may be
+fixed in the source code, many might not.
+
+In order to circumvent this, you can use the `js_util` library, like
+`getProperty`, `setProperty`, `callMethod`, and `callConstructor`.
+
+Let’s look at an example. `FileReader` is a `dart:html` interface that is
+missing the API `readAsBinaryString` ([#42834][]). We can work around this by
+doing something like the following:
+
+```
+import 'dart:html';
+import 'dart:js_util' as js_util;
+
+import 'package:async_helper/async_minitest.dart';
+import 'package:expect/expect.dart';
+
+void main() async {
+  var reader = new FileReader();
+  reader.onLoad.listen(expectAsync((event) {
+    String result = reader.result as String;
+    Expect.equals(result, '00000000');
+  }));
+  js_util.callMethod(reader, 'readAsBinaryString', [new Blob(['00000000'])]);
+  // We can manipulate properties as well.
+  js_util.setProperty(reader, 'foo', 'bar'); // reader.foo is now ‘bar’
+  Expect.equals(js_util.getProperty(reader, 'foo'), 'bar');
+}
+```
+
+In the case where the API is missing a constructor, we can use
+`callConstructor`. For example, instead of using the factory constructor for
+`KeyboardEvent`, we can do the following:
+
+```
+import 'dart:html';
+import 'dart:js_util' as js_util;
+
+import 'package:expect/expect.dart';
+
+void main() {
+  List<dynamic> eventArgs = <dynamic>[
+    'KeyboardEvent',
+    <String, dynamic>{'key': 'A'}
+  ];
+  KeyboardEvent event = js_util.callConstructor(
+      js_util.getProperty(window, 'KeyboardEvent'), js_util.jsify(eventArgs));
+  Expect.equals(event.key, 'A');
+}
+```
+
+### Private/unimplemented native types
+
+There are several native interfaces that are suppressed e.g.
+`USBDevice` ([#42200][]) due to historical reasons. These native interfaces are
+marked with `@Native`, are private, and have no attributes associated with them.
+Therefore, unlike other `@Native` objects, we can’t access any of the APIs or
+attributes associated with this interface. We can use the `js_util` library
+again to circumvent this issue. For example, we can manipulate a
+`_SubtleCrypto` object:
+
+```
+import 'dart:html';
+import 'dart:js_util' as js_util;
+import 'dart:typed_data';
+
+import 'package:js/js.dart';
+
+@JS()
+external Crypto get crypto;
+
+void main() async {
+  var subtle = crypto.subtle!;
+  var array = Uint8List(16);
+  var promise = js_util.promiseToFuture<ByteBuffer>(js_util
+      .callMethod(subtle, 'digest', ['SHA-256', array])); // SubtleCrypto.digest
+  var digest = await promise;
+}
+```
+
+What you shouldn’t do is attempt to cast these native objects using your own JS
+interop types, e.g.
+
+```
+import 'dart:html';
+
+import 'package:js/js.dart';
+
+@JS()
+external Crypto get crypto;
+
+@JS()
+class SubtleCrypto {}
+
+void main() {
+  SubtleCrypto subtle = crypto.subtle! as SubtleCrypto;
+}
+```
+
+With the above, you’ll see a type error:
+
+`Uncaught TypeError: Instance of 'SubtleCrypto': type 'Interceptor' is not a subtype of type 'SubtleCrypto'`
+
+This is because the types in the `@Native` annotation are reserved and the above
+leads to namespace conflicts between the `@Native` type and the user JS interop
+type in the compiler. These `@Native` types inherit the `Interceptor` class,
+which is why you see the message above.
+
+[#42834]: https://github.com/dart-lang/sdk/issues/42834
+[#42200]: https://github.com/dart-lang/sdk/issues/42200