Support strict CSP in dart2js.

dart2js will now set the CSP nonce on scripts that are inserted for deferred
loads. Note that this change only applies to the default hunk loader. Custom
hooks need to set the nonce value manually.

Fixes https://github.com/dart-lang/sdk/issues/33061

Change-Id: I04abc7904dff22dad586690d175b87c77c3f1fe2
Reviewed-on: https://dart-review.googlesource.com/54702
Reviewed-by: Stephen Adams <sra@google.com>
Commit-Queue: Sigmund Cherem <sigmund@google.com>
diff --git a/sdk/lib/_internal/js_runtime/lib/js_helper.dart b/sdk/lib/_internal/js_runtime/lib/js_helper.dart
index 43c687b..dc4b6e3 100644
--- a/sdk/lib/_internal/js_runtime/lib/js_helper.dart
+++ b/sdk/lib/_internal/js_runtime/lib/js_helper.dart
@@ -6,6 +6,7 @@
 
 import 'dart:_js_embedded_names'
     show
+        CURRENT_SCRIPT,
         DEFERRED_LIBRARY_PARTS,
         DEFERRED_PART_URIS,
         DEFERRED_PART_HASHES,
@@ -3788,6 +3789,15 @@
   });
 }
 
+/// The `nonce` value on the current script used for strict-CSP, if any.
+String _cspNonce = _computeCspNonce();
+
+String _computeCspNonce() {
+  var currentScript = JS_EMBEDDED_GLOBAL('', CURRENT_SCRIPT);
+  if (currentScript == null) return null;
+  return JS('String', 'String(#.nonce)', currentScript);
+}
+
 Future<Null> _loadHunk(String hunkName) {
   Future<Null> future = _loadingLibraries[hunkName];
   _eventLog.add(' - _loadHunk: $hunkName');
@@ -3872,6 +3882,9 @@
     var script = JS('', 'document.createElement("script")');
     JS('', '#.type = "text/javascript"', script);
     JS('', '#.src = #', script, uri);
+    if (_cspNonce != '') {
+      JS('', '#.nonce = #', script, _cspNonce);
+    }
     JS('', '#.addEventListener("load", #, false)', script, jsSuccess);
     JS('', '#.addEventListener("error", #, false)', script, jsFailure);
     JS('', 'document.body.appendChild(#)', script);
diff --git a/tests/compiler/dart2js_extra/dart2js_extra.status b/tests/compiler/dart2js_extra/dart2js_extra.status
index 3d8c648..f60ebac 100644
--- a/tests/compiler/dart2js_extra/dart2js_extra.status
+++ b/tests/compiler/dart2js_extra/dart2js_extra.status
@@ -43,6 +43,7 @@
 
 [ $compiler == dart2js && $runtime == d8 && $fasta ]
 deferred_fail_and_retry_test: RuntimeError # Uses XHR in dart:html
+deferred_with_csp_nonce_test: RuntimeError # Uses dart:html
 unconditional_dartio_import_test: RuntimeError # Uses dart:io
 
 [ $compiler == dart2js && $runtime == ff && $system == windows ]
diff --git a/tests/compiler/dart2js_extra/deferred_with_csp_nonce_lib.dart b/tests/compiler/dart2js_extra/deferred_with_csp_nonce_lib.dart
new file mode 100644
index 0000000..48fbce6
--- /dev/null
+++ b/tests/compiler/dart2js_extra/deferred_with_csp_nonce_lib.dart
@@ -0,0 +1,7 @@
+// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+foo() {
+  return "loaded";
+}
diff --git a/tests/compiler/dart2js_extra/deferred_with_csp_nonce_test.dart b/tests/compiler/dart2js_extra/deferred_with_csp_nonce_test.dart
new file mode 100644
index 0000000..c9fd2db
--- /dev/null
+++ b/tests/compiler/dart2js_extra/deferred_with_csp_nonce_test.dart
@@ -0,0 +1,36 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Test that code loaded via deferred imports uses the same nonce value as the
+// main page.
+
+import "deferred_with_csp_nonce_lib.dart" deferred as lib;
+import "package:expect/expect.dart";
+import "package:async_helper/async_helper.dart";
+import "dart:html";
+
+main() {
+  asyncStart();
+
+  var scripts = document
+      .querySelectorAll<ScriptElement>('script')
+      .where((s) => s.src.contains("generated_compilations"))
+      .toList();
+  Expect.equals(1, scripts.length);
+  Expect.equals('', scripts.first.nonce);
+  scripts.first.nonce = "an-example-nonce-string";
+
+  lib.loadLibrary().then((_) {
+    print(lib.foo());
+    var scripts = document
+        .querySelectorAll<ScriptElement>('script')
+        .where((s) => s.src.contains("generated_compilations"))
+        .toList();
+    Expect.equals(2, scripts.length);
+    for (var script in scripts) {
+      Expect.equals("an-example-nonce-string", script.nonce);
+    }
+    asyncEnd();
+  });
+}