| Index: pkg/compiler/lib/src/native/behavior.dart
 | 
| diff --git a/pkg/compiler/lib/src/native/behavior.dart b/pkg/compiler/lib/src/native/behavior.dart
 | 
| index 7dbab30fe5e6f1e7f29b4a3b0715474e6abca661..5a03ca015c54f41294b23cef3691e78d089dcba4 100644
 | 
| --- a/pkg/compiler/lib/src/native/behavior.dart
 | 
| +++ b/pkg/compiler/lib/src/native/behavior.dart
 | 
| @@ -645,12 +645,26 @@ class NativeBehavior {
 | 
|    static NativeBehavior ofMethod(FunctionElement method,  Compiler compiler) {
 | 
|      FunctionType type = method.computeType(compiler.resolution);
 | 
|      var behavior = new NativeBehavior();
 | 
| -    behavior.typesReturned.add(type.returnType);
 | 
| +    var returnType = type.returnType;
 | 
| +    bool isInterop = compiler.backend.isJsInterop(method);
 | 
| +    // Note: For dart:html and other internal libraries we maintain, we can
 | 
| +    // trust the return type and use it to limit what we enqueue. We have to
 | 
| +    // be more conservative about JS interop types and assume they can return
 | 
| +    // anything (unless the user provides the experimental flag to trust the
 | 
| +    // type of js-interop APIs). We do restrict the allocation effects and say
 | 
| +    // that interop calls create only interop types (which may be unsound if
 | 
| +    // an interop call returns a DOM type and declares a dynamic return type,
 | 
| +    // but otherwise we would include a lot of code by default).
 | 
| +    // TODO(sigmund,sra): consider doing something better for numeric types.
 | 
| +    behavior.typesReturned.add(
 | 
| +        !isInterop || compiler.trustJSInteropTypeAnnotations ? returnType
 | 
| +        : const DynamicType());
 | 
|      if (!type.returnType.isVoid) {
 | 
|        // Declared types are nullable.
 | 
|        behavior.typesReturned.add(compiler.coreTypes.nullType);
 | 
|      }
 | 
| -    behavior._capture(type, compiler.resolution);
 | 
| +    behavior._capture(type, compiler.resolution,
 | 
| +        isInterop: isInterop, compiler: compiler);
 | 
|  
 | 
|      // TODO(sra): Optional arguments are currently missing from the
 | 
|      // DartType. This should be fixed so the following work-around can be
 | 
| @@ -668,10 +682,15 @@ class NativeBehavior {
 | 
|      Resolution resolution = compiler.resolution;
 | 
|      DartType type = field.computeType(resolution);
 | 
|      var behavior = new NativeBehavior();
 | 
| -    behavior.typesReturned.add(type);
 | 
| +    bool isInterop = compiler.backend.isJsInterop(field);
 | 
| +    // TODO(sigmund,sra): consider doing something better for numeric types.
 | 
| +    behavior.typesReturned.add(
 | 
| +        !isInterop || compiler.trustJSInteropTypeAnnotations ? type
 | 
| +        : const DynamicType());
 | 
|      // Declared types are nullable.
 | 
|      behavior.typesReturned.add(resolution.coreTypes.nullType);
 | 
| -    behavior._capture(type, resolution);
 | 
| +    behavior._capture(type, resolution,
 | 
| +        isInterop: isInterop, compiler: compiler);
 | 
|      behavior._overrideWithAnnotations(field, compiler);
 | 
|      return behavior;
 | 
|    }
 | 
| @@ -765,17 +784,49 @@ class NativeBehavior {
 | 
|    /// Models the behavior of Dart code receiving instances and methods of [type]
 | 
|    /// from native code.  We usually start the analysis by capturing a native
 | 
|    /// method that has been used.
 | 
| -  void _capture(DartType type, Resolution resolution) {
 | 
| +  ///
 | 
| +  /// We assume that JS-interop APIs cannot instantiate Dart types or
 | 
| +  /// non-JSInterop native types.
 | 
| +  void _capture(DartType type, Resolution resolution,
 | 
| +      {bool isInterop: false, Compiler compiler}) {
 | 
|      type.computeUnaliased(resolution);
 | 
|      type = type.unaliased;
 | 
|      if (type is FunctionType) {
 | 
|        FunctionType functionType = type;
 | 
| -      _capture(functionType.returnType, resolution);
 | 
| +      _capture(functionType.returnType, resolution,
 | 
| +          isInterop: isInterop, compiler: compiler);
 | 
|        for (DartType parameter in functionType.parameterTypes) {
 | 
|          _escape(parameter, resolution);
 | 
|        }
 | 
|      } else {
 | 
| -      typesInstantiated.add(type);
 | 
| +      DartType instantiated = null;
 | 
| +      JavaScriptBackend backend = compiler?.backend;
 | 
| +      if (!isInterop) {
 | 
| +        typesInstantiated.add(type);
 | 
| +      } else {
 | 
| +        if (type.element != null && backend.isNative(type.element)) {
 | 
| +          // Any declared native or interop type (isNative implies isJsInterop)
 | 
| +          // is assumed to be allocated.
 | 
| +          typesInstantiated.add(type);
 | 
| +        }
 | 
| +
 | 
| +        if (!compiler.trustJSInteropTypeAnnotations ||
 | 
| +          type.isDynamic || type.isObject) {
 | 
| +          // By saying that only JS-interop types can be created, we prevent
 | 
| +          // pulling in every other native type (e.g. all of dart:html) when a
 | 
| +          // JS interop API returns dynamic or when we don't trust the type
 | 
| +          // annotations. This means that to some degree we still use the return
 | 
| +          // type to decide whether to include native types, even if we don't
 | 
| +          // trust the type annotation.
 | 
| +          typesInstantiated.add(
 | 
| +              backend.helpers.jsJavaScriptObjectClass.thisType);
 | 
| +        } else {
 | 
| +          // Otherwise, when the declared type is a Dart type, we do not
 | 
| +          // register an allocation because we assume it cannot be instantiated
 | 
| +          // from within the JS-interop code. It must have escaped from another
 | 
| +          // API.
 | 
| +        }
 | 
| +      }
 | 
|      }
 | 
|    }
 | 
|  
 | 
| 
 |