Merge branch 'stable-3.3'

* stable-3.3:
  Stop propagation for all keys but exempt the save button

Change-Id: I57b4d93937458ce68a235d5867fd8fe5efdf92da
diff --git a/BUILD b/BUILD
index 8b11bd5..7e2c5c6 100644
--- a/BUILD
+++ b/BUILD
@@ -15,11 +15,14 @@
 
 genrule2(
     name = "cm-static",
-    srcs = [":codemirror_editor"],
+    srcs = [
+        ":codemirror-element",
+        ":codemirror_editor",
+    ],
     outs = ["cm-static.jar"],
     cmd = " && ".join([
         "mkdir $$TMP/static",
-        "cp -r $(locations :codemirror_editor) $$TMP/static",
+        "cp $(SRCS) $$TMP/static",
         "cd $$TMP",
         "zip -Drq $$ROOT/$@ -g .",
     ]),
@@ -40,7 +43,4 @@
 polygerrit_plugin(
     name = "codemirror_editor",
     app = "gr-editor/gr-editor.js",
-    assets = [
-        ":codemirror-element",
-    ],
 )
diff --git a/README.md b/README.md
index c85c098..96d3063 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@
 
 You will need `polymer-bridges` which is a submodule you can clone from: https://gerrit-review.googlesource.com/admin/repos/polymer-bridges
 
-As polymer 3 no longer support `Polymer.importHref` anymore, Gerrit still supports through a custom implementation, the implementation is copied here for testing purpose (`test/import-href.js`).
+As polymer 3 no longer support `Polymer.importHref` anymore, this plugin still supports it through a custom implementation in `gr-editor.js`.
 
 ## Test plugin on Gerrit
 
@@ -28,4 +28,4 @@
 
 If your plugin is already enabled, then you can block it and then inject the compiled local verison.
 
-See more about how to use dev helper extension to help you test here: https://gerrit.googlesource.com/gerrit-fe-dev-helper/+/master
\ No newline at end of file
+See more about how to use dev helper extension to help you test here: https://gerrit.googlesource.com/gerrit-fe-dev-helper/+/master
diff --git a/bower.json b/bower.json
index 6de966f..f932829 100644
--- a/bower.json
+++ b/bower.json
@@ -5,7 +5,7 @@
   ],
   "license": "http://www.apache.org/licenses/LICENSE-2.0",
   "dependencies": {
-    "codemirror-minified": "^5.50.2"
+    "codemirror-minified": "^5.59.1"
   },
   "devDependencies": {
     "es6-promise": "^3.3.1",
diff --git a/gr-editor/gr-editor.js b/gr-editor/gr-editor.js
index d7297ad..c88e2a8 100644
--- a/gr-editor/gr-editor.js
+++ b/gr-editor/gr-editor.js
@@ -15,6 +15,86 @@
  * limitations under the License.
  */
 
+// Run a callback when HTMLImports are ready or immediately if
+// this api is not available.
+function whenImportsReady(cb) {
+  if (window.HTMLImports) {
+    HTMLImports.whenReady(cb);
+  } else {
+    cb();
+  }
+}
+
+/**
+ * Convenience method for importing an HTML document imperatively. Mostly copied
+ * from polymer/lib/utils/import-href.html.
+ *
+ * This method creates a new `<link rel="import">` element with
+ * the provided URL and appends it to the document to start loading.
+ * In the `onload` callback, the `import` property of the `link`
+ * element will contain the imported document contents.
+ *
+ * @param {string} href URL to document to load.
+ * @param {?function(!Event):void=} onload Callback to notify when an import successfully
+ *   loaded.
+ * @param {?function(!ErrorEvent):void=} onerror Callback to notify when an import
+ *   unsuccessfully loaded.
+ */
+function importHref(href, onload, onerror) {
+  let link =
+      /** @type {HTMLLinkElement} */
+      (document.head.querySelector('link[href="' + href + '"][import-href]'));
+  if (!link) {
+    link = /** @type {HTMLLinkElement} */ (document.createElement('link'));
+    link.setAttribute('rel', 'import');
+    link.setAttribute('href', href);
+    link.setAttribute('import-href', '');
+  }
+  // NOTE: the link may now be in 3 states: (1) pending insertion,
+  // (2) inflight, (3) already loaded. In each case, we need to add
+  // event listeners to process callbacks.
+  const cleanup = function() {
+    link.removeEventListener('load', loadListener);
+    link.removeEventListener('error', errorListener);
+  };
+  const loadListener = function(event) {
+    cleanup();
+    // In case of a successful load, cache the load event on the link so
+    // that it can be used to short-circuit this method in the future when
+    // it is called with the same href param.
+    link.__dynamicImportLoaded = true;
+    if (onload) {
+      whenImportsReady(() => {
+        onload(event);
+      });
+    }
+  };
+  const errorListener = function(event) {
+    cleanup();
+    // In case of an error, remove the link from the document so that it
+    // will be automatically created again the next time `importHref` is
+    // called.
+    if (link.parentNode) {
+      link.parentNode.removeChild(link);
+    }
+    if (onerror) {
+      whenImportsReady(() => {
+        onerror(event);
+      });
+    }
+  };
+  link.addEventListener('load', loadListener);
+  link.addEventListener('error', errorListener);
+  if (link.parentNode == null) {
+    document.head.appendChild(link);
+    // if the link already loaded, dispatch a fake load event
+    // so that listeners are called and get a proper event argument.
+  } else if (link.__dynamicImportLoaded) {
+    link.dispatchEvent(new Event('load'));
+  }
+  return link;
+}
+
 // we need to be on codemirror 5.33.0+ to get the support for
 // text/x-php in CodeMirror.findModeByMIME
 const LANGUAGE_MAP = {
@@ -87,7 +167,7 @@
     const codemirrorElementFile = '/static/codemirror-element.html';
     const url = this.plugin.url(codemirrorElementFile);
     return new Promise((resolve, reject) => {
-      Polymer.importHref(url, resolve, reject);
+      importHref(url, resolve, reject);
     });
   }
 
@@ -159,9 +239,8 @@
 
 customElements.define(GrEditor.is, GrEditor);
 
-// Install the plugin
 if (window.Gerrit) {
   Gerrit.install(plugin => {
     plugin.registerCustomComponent('editor', 'gr-editor', {replace: true});
   });
-}
\ No newline at end of file
+}
diff --git a/gr-editor/gr-editor_test.html b/gr-editor/gr-editor_test.html
index 586d703..fcbcb5f 100644
--- a/gr-editor/gr-editor_test.html
+++ b/gr-editor/gr-editor_test.html
@@ -31,6 +31,7 @@
 <script type="module">
 import '../test/common-test-setup.js';
 import './gr-editor.js';
+import {importHref} from './gr-editor.js';
 suite('gr-editor tests', () => {
   let element;
   let sandbox;
@@ -41,7 +42,7 @@
     stub('gr-editor', {
       _importCodeMirror() {
         return new Promise((resolve, reject) => {
-          Polymer.importHref('./codemirror-element.html', resolve, reject);
+          importHref('./codemirror-element.html', resolve, reject);
         });
       },
     });
diff --git a/java/com/googlesource/gerrit/plugins/codemirror/CodemirrorModule.java b/java/com/googlesource/gerrit/plugins/codemirror/CodemirrorModule.java
index 0bba486..c1ccee3 100644
--- a/java/com/googlesource/gerrit/plugins/codemirror/CodemirrorModule.java
+++ b/java/com/googlesource/gerrit/plugins/codemirror/CodemirrorModule.java
@@ -23,6 +23,6 @@
   @Override
   protected void configure() {
     DynamicSet.bind(binder(), WebUiPlugin.class)
-        .toInstance(new JavaScriptPlugin("codemirror_editor.html"));
+        .toInstance(new JavaScriptPlugin("codemirror_editor.js"));
   }
 }
diff --git a/test/common-test-setup.js b/test/common-test-setup.js
index a0f1f1b..1158af4 100644
--- a/test/common-test-setup.js
+++ b/test/common-test-setup.js
@@ -57,6 +57,3 @@
 import 'polymer-bridges/polymer/polymer-legacy_bridge.js';
 
 import '@polymer/iron-test-helpers/iron-test-helpers.js';
-import {importHref} from './import-href.js';
-
-window.Polymer.importHref = importHref;
diff --git a/test/import-href.js b/test/import-href.js
deleted file mode 100644
index 80ae389..0000000
--- a/test/import-href.js
+++ /dev/null
@@ -1,109 +0,0 @@
-/**
- * @license
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// This file is a replacement for the
-// polymer-bridges/polymer/lib/utils/import-href.html file. The html
-// file contains code inside <script>...</script> and can't be imported
-// in es6 modules.
-
-// run a callback when HTMLImports are ready or immediately if
-// this api is not available.
-function whenImportsReady(cb) {
-  if (window.HTMLImports) {
-    HTMLImports.whenReady(cb);
-  } else {
-    cb();
-  }
-}
-
-/**
- * Convenience method for importing an HTML document imperatively.
- *
- * This method creates a new `<link rel="import">` element with
- * the provided URL and appends it to the document to start loading.
- * In the `onload` callback, the `import` property of the `link`
- * element will contain the imported document contents.
- *
- * @memberof Polymer
- * @param {string} href URL to document to load.
- * @param {?function(!Event):void=} onload Callback to notify when an import successfully
- *   loaded.
- * @param {?function(!ErrorEvent):void=} onerror Callback to notify when an import
- *   unsuccessfully loaded.
- * @param {boolean=} optAsync True if the import should be loaded `async`.
- *   Defaults to `false`.
- * @return {!HTMLLinkElement} The link element for the URL to be loaded.
- */
-export function importHref(href, onload, onerror, optAsync) {
-  let link =
-    /** @type {HTMLLinkElement} */
-    (document.head.querySelector('link[href="' + href + '"][import-href]'));
-  if (!link) {
-    link = /** @type {HTMLLinkElement} */ (document.createElement("link"));
-    link.rel = "import";
-    link.href = href;
-    link.setAttribute("import-href", "");
-  }
-  // always ensure link has `async` attribute if user specified one,
-  // even if it was previously not async. This is considered less confusing.
-  if (optAsync) {
-    link.setAttribute("async", "");
-  }
-  // NOTE: the link may now be in 3 states: (1) pending insertion,
-  // (2) inflight, (3) already loaded. In each case, we need to add
-  // event listeners to process callbacks.
-  const cleanup = function () {
-    link.removeEventListener("load", loadListener);
-    link.removeEventListener("error", errorListener);
-  };
-  const loadListener = function (event) {
-    cleanup();
-    // In case of a successful load, cache the load event on the link so
-    // that it can be used to short-circuit this method in the future when
-    // it is called with the same href param.
-    link.__dynamicImportLoaded = true;
-    if (onload) {
-      whenImportsReady(() => {
-        onload(event);
-      });
-    }
-  };
-  const errorListener = function (event) {
-    cleanup();
-    // In case of an error, remove the link from the document so that it
-    // will be automatically created again the next time `importHref` is
-    // called.
-    if (link.parentNode) {
-      link.parentNode.removeChild(link);
-    }
-    if (onerror) {
-      whenImportsReady(() => {
-        onerror(event);
-      });
-    }
-  };
-  link.addEventListener("load", loadListener);
-  link.addEventListener("error", errorListener);
-  if (link.parentNode == null) {
-    document.head.appendChild(link);
-    // if the link already loaded, dispatch a fake load event
-    // so that listeners are called and get a proper event argument.
-  } else if (link.__dynamicImportLoaded) {
-    link.dispatchEvent(new Event("load"));
-  }
-  return link;
-}