DevTools: Polyfill Custom Elements V0 for old frontends

Bug: 685385
Change-Id: I1adae3ac86a2af361b1253f7e723aa86a7ce397e
Reviewed-on: https://chromium-review.googlesource.com/c/1371086
Reviewed-by: Dmitry Gozman <[email protected]>
Commit-Queue: Joel Einbinder <[email protected]>
Cr-Original-Commit-Position: refs/heads/master@{#615780}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 64e9bfb30b51952e956210ce1c60a6b69e2b405f
diff --git a/front_end/devtools_compatibility.js b/front_end/devtools_compatibility.js
index 788ebe8..8ff215b 100644
--- a/front_end/devtools_compatibility.js
+++ b/front_end/devtools_compatibility.js
@@ -1208,14 +1208,59 @@
           this.name = selector;
         }
       });
-      const origCreateElementWithClass = Document.prototype.createElementWithClass;
-      Document.prototype.createElementWithClass = function(tagName, className, ...rest) {
-        if (tagName !== 'button' || (className !== 'soft-dropdown' && className !== 'dropdown-button'))
-          return origCreateElementWithClass.call(this, tagName, className, ...rest);
-        const element = origCreateElementWithClass.call(this, 'div', className, ...rest);
-        element.tabIndex = 0;
-        element.role = 'button';
-        return element;
+
+      // Document.prototype.createElementWithClass is a DevTools method, so we
+      // need to wait for DOMContentLoaded in order to override it.
+      if (window.document.head &&
+          (window.document.readyState === 'complete' || window.document.readyState === 'interactive'))
+        overrideCreateElementWithClass();
+      else
+        window.addEventListener('DOMContentLoaded', overrideCreateElementWithClass);
+
+      function overrideCreateElementWithClass() {
+        window.removeEventListener('DOMContentLoaded', overrideCreateElementWithClass);
+
+        const origCreateElementWithClass = Document.prototype.createElementWithClass;
+        Document.prototype.createElementWithClass = function(tagName, className, ...rest) {
+          if (tagName !== 'button' || (className !== 'soft-dropdown' && className !== 'dropdown-button'))
+            return origCreateElementWithClass.call(this, tagName, className, ...rest);
+          const element = origCreateElementWithClass.call(this, 'div', className, ...rest);
+          element.tabIndex = 0;
+          element.role = 'button';
+          return element;
+        };
+      }
+    }
+
+    // Custom Elements V0 polyfill
+    if (majorVersion <= 73 && !Document.prototype.registerElement) {
+      const fakeRegistry = new Map();
+      Document.prototype.registerElement = function(typeExtension, options) {
+        const {prototype, extends: localName} = options;
+        const document = this;
+        const callback = function() {
+          const element = document.createElement(localName || typeExtension);
+          const skip = new Set(['constructor', '__proto__']);
+          for (const key of Object.keys(Object.getOwnPropertyDescriptors(prototype.__proto__ || {}))) {
+            if (skip.has(key))
+              continue;
+            element[key] = prototype[key];
+          }
+          element.setAttribute('is', typeExtension);
+          if (element['createdCallback'])
+            element['createdCallback']();
+          return element;
+        };
+        fakeRegistry.set(typeExtension, callback);
+        return callback;
+      };
+
+      const origCreateElement = Document.prototype.createElement;
+      Document.prototype.createElement = function(tagName, fakeCustomElementType) {
+        const fakeConstructor = fakeRegistry.get(fakeCustomElementType);
+        if (fakeConstructor)
+          return fakeConstructor();
+        return origCreateElement.call(this, tagName, fakeCustomElementType);
       };
     }
 
@@ -1359,16 +1404,7 @@
     return style;
   }
 
-  function windowLoaded() {
-    window.removeEventListener('DOMContentLoaded', windowLoaded, false);
-    installBackwardsCompatibility();
-  }
-
-  if (window.document.head &&
-      (window.document.readyState === 'complete' || window.document.readyState === 'interactive'))
-    installBackwardsCompatibility();
-  else
-    window.addEventListener('DOMContentLoaded', windowLoaded, false);
+  installBackwardsCompatibility();
 
   /** @type {(!function(string, boolean=):boolean)|undefined} */
   DOMTokenList.prototype.__originalDOMTokenListToggle;