Migrate UI components to Lit

Inlines Polymer templates, converts base classes to LitElement,
and updates data bindings to standard Lit properties. Replaces
asynchronous test assertions and updates DOM queries to use standard
shadow root properties.

Change-Id: I4511632d53dbb85a1f89230c091a004c181893d1
diff --git a/gr-image-diff-tool/gr-image-diff-tool.js b/gr-image-diff-tool/gr-image-diff-tool.js
index 287db5e..e5593fe 100644
--- a/gr-image-diff-tool/gr-image-diff-tool.js
+++ b/gr-image-diff-tool/gr-image-diff-tool.js
@@ -14,7 +14,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {htmlTemplate} from './gr-image-diff-tool_html.js';
+
+import {html, css, LitElement} from 'lit';
 import '../gr-opacity-diff-mode/gr-opacity-diff-mode.js';
 import '../gr-resemble-diff-mode/gr-resemble-diff-mode.js';
 
@@ -23,33 +24,100 @@
   RESEMBLE: 'resemble',
 };
 
-class ImageDiffTool extends Polymer.Element {
+class ImageDiffTool extends LitElement {
   static get is() {
     return 'gr-image-diff-tool';
   }
 
-  static get template() {
-    return htmlTemplate;
+  static get styles() {
+    return [
+      css`
+        :host {
+          background-color: var(--table-header-background-color, #fafafa);
+          display: block;
+          font-family: var(--font-family);
+        }
+        #header {
+          align-items: center;
+          border-bottom: 1px solid var(--border-color, #ddd);
+          border-top: 1px solid var(--border-color, #ddd);
+          display: inline-flex;
+          padding: .5em;
+          width: 100%;
+        }
+        h3 {
+          padding: 0 .5em;
+        }
+        #dropdown {
+          background-color: var(--view-background-color);
+          border: 1px solid var(--border-color);
+          border-radius: 2px;
+          color: var(--primary-text-color);
+          font-size: var(--font-size-normal);
+          height: 2em;
+          margin-left: 1em;
+          padding: 0 .15em;
+        }
+        .diffmode {
+          align-items: center;
+          display: flex;
+          justify-content: center;
+        }
+      `
+    ];
+  }
+
+  render() {
+    return html`
+      <div id="header">
+        <h3>Image diff</h3>
+        <select .value=${this._observeMode} @change=${this._handleSelectChange} id="dropdown">
+          <option value="resemble" title="Scale the images to the same size and compute a diff with highlights">Highlight differences</option>
+          <option value="opacity" title="Overlay the new image over the old and use an opacity control to view the differences">Onion skin</option>
+        </select>
+      </div>
+      <div class="diffmode">
+        ${this._showResembleMode ? html`
+          <gr-resemble-diff-mode
+              .baseImage=${this.baseImage}
+              .revisionImage=${this.revisionImage}></gr-resemble-diff-mode>
+        ` : ''}
+      </div>
+      <div class="diffmode">
+        ${this._showOpacityMode ? html`
+          <gr-opacity-diff-mode
+              .baseImage=${this.baseImage}
+              .revisionImage=${this.revisionImage}></gr-opacity-diff-mode>
+        ` : ''}
+      </div>
+    `;
   }
 
   static get properties() {
     return {
-      baseImage: Object,
-      revisionImage: Object,
-      hidden: {
-        type: Boolean,
-        value: false,
-        reflectToAttribute: true,
-      },
-      _showResembleMode: Boolean,
-      _showOpacityMode: Boolean,
-      _observeMode: {
-        type: String,
-        observer: '_handleSelect',
-      },
+      baseImage: {type: Object},
+      revisionImage: {type: Object},
+      hidden: {type: Boolean, reflect: true},
+      _showResembleMode: {type: Boolean},
+      _showOpacityMode: {type: Boolean},
+      _observeMode: {type: String},
     };
   }
 
+  constructor() {
+    super();
+    this.hidden = false;
+    this._showResembleMode = false;
+    this._showOpacityMode = false;
+    this._observeMode = '';
+  }
+
+  updated(changedProperties) {
+    if (changedProperties.has('_observeMode')) {
+      this._handleSelect(this._observeMode);
+    }
+  }
+
   connectedCallback() {
     super.connectedCallback();
     if (!this.baseImage || !this.revisionImage) {
@@ -70,6 +138,10 @@
     window.localStorage.setItem('image-diff-mode', mode);
   }
 
+  _handleSelectChange(e) {
+    this._observeMode = e.target.value;
+  }
+
   _handleSelect(mode) {
     mode === DiffModes.OPACITY
       ? this._displayOpacityMode()
diff --git a/gr-image-diff-tool/gr-image-diff-tool_html.js b/gr-image-diff-tool/gr-image-diff-tool_html.js
deleted file mode 100644
index 887a028..0000000
--- a/gr-image-diff-tool/gr-image-diff-tool_html.js
+++ /dev/null
@@ -1,72 +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.
- */
-
-export const htmlTemplate = Polymer.html`
-<style include="shared-styles">
-  :host {
-    background-color: var(--table-header-background-color, #fafafa);
-    display: block;
-    font-family: var(--font-family);
-  }
-  #header {
-    align-items: center;
-    border-bottom: 1px solid var(--border-color, #ddd);
-    border-top: 1px solid var(--border-color, #ddd);
-    display: inline-flex;
-    padding: .5em;
-    width: 100%;
-  }
-  h3 {
-    padding: 0 .5em;
-  }
-  #dropdown {
-    background-color: var(--view-background-color);
-    border: 1px solid var(--border-color);
-    border-radius: 2px;
-    color: var(--primary-text-color);
-    font-size: var(--font-size-normal);
-    height: 2em;
-    margin-left: 1em;
-    padding: 0 .15em;
-  }
-  .diffmode {
-    align-items: center;
-    display: flex;
-    justify-content: center;
-  }
-</style>
-<div id="header">
-  <h3>Image diff</h3>
-  <select value="{{_observeMode::change}}" id="dropdown">
-    <option value="resemble" title="Scale the images to the same size and compute a diff with highlights">Highlight differences</option>
-    <option value="opacity" title="Overlay the new image over the old and use an opacity control to view the differences">Onion skin</option>
-  </select>
-</div>
-<div class="diffmode">
-  <template is="dom-if" if="[[_showResembleMode]]" restamp="true">
-    <gr-resemble-diff-mode
-        base-image="[[baseImage]]"
-        revision-image="[[revisionImage]]"></gr-resemble-diff-mode>
-  </template>
-</div>
-<div class="diffmode">
-  <template is="dom-if" if="[[_showOpacityMode]]" restamp="true">
-    <gr-opacity-diff-mode
-        base-image="[[baseImage]]"
-        revision-image="[[revisionImage]]"></gr-opacity-diff-mode>
-  </template>
-</div>`;
\ No newline at end of file
diff --git a/gr-image-diff-tool/gr-image-diff-tool_test.html b/gr-image-diff-tool/gr-image-diff-tool_test.html
index 58905f7..06d86a1 100644
--- a/gr-image-diff-tool/gr-image-diff-tool_test.html
+++ b/gr-image-diff-tool/gr-image-diff-tool_test.html
@@ -32,39 +32,39 @@
 <script type="module">
   import '../test/common-test-setup.js';
   import "./gr-image-diff-tool.js";
-  suite('gr-image-diff-tool tests', () => {
+  suite('gr-image-diff-tool tests', async () => {
     let element;
     let sandbox;
 
-    setup(() => {
+    setup(async () => {
       sandbox = sinon.sandbox.create();
       element = fixture('basicFixture');
-      flushAsynchronousOperations();
+      await element.updateComplete;
     });
 
-    teardown(() => { sandbox.restore(); });
+    teardown(async () => { sandbox.restore(); });
 
-    test('test opacity mode', () => {
+    test('test opacity mode', async () => {
       element._observeMode = 'opacity';
       assert.isTrue(element._showOpacityMode);
       assert.isFalse(element._showResembleMode);
-      flushAsynchronousOperations();
+      await element.updateComplete;
 
       assert.ok(element.shadowRoot.querySelector('gr-opacity-diff-mode'));
       assert.equal(element.shadowRoot.querySelector('gr-resemble-diff-mode'), null);
     });
 
-    test('test resemble mode', () => {
+    test('test resemble mode', async () => {
       element._observeMode = 'resemble';
       assert.isTrue(element._showResembleMode);
       assert.isFalse(element._showOpacityMode);
-      flushAsynchronousOperations();
+      await element.updateComplete;
 
       assert.ok(element.shadowRoot.querySelector('gr-resemble-diff-mode'));
       assert.equal(element.shadowRoot.querySelector('gr-opacity-diff-mode'), null);
     });
 
-    test('localStorage persists', () => {
+    test('localStorage persists', async () => {
       sandbox.stub(element, '_setMode');
       sandbox.stub(element, '_getMode');
       element.connectedCallback();
diff --git a/gr-opacity-diff-mode/gr-opacity-diff-mode.js b/gr-opacity-diff-mode/gr-opacity-diff-mode.js
index 28b8957..96eb7f2 100644
--- a/gr-opacity-diff-mode/gr-opacity-diff-mode.js
+++ b/gr-opacity-diff-mode/gr-opacity-diff-mode.js
@@ -14,96 +14,167 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {htmlTemplate} from './gr-opacity-diff-mode_html.js';
 
-class OpacityDiffMode extends Polymer.Element {
+import {html, css, LitElement} from 'lit';
+
+class OpacityDiffMode extends LitElement {
   static get is() {
     return 'gr-opacity-diff-mode';
   }
 
-  static get template() {
-    return htmlTemplate;
+  static get styles() {
+    return [
+      css`
+        :host {
+          display: block;
+        }
+        .wrapper {
+          box-shadow: 0 1px 3px rgba(0, 0, 0, .3);
+          margin: 1em 0;
+        }
+        img {
+          display: block;
+          height: var(--img-height);
+          margin: auto;
+          position: absolute;
+          width: var(--img-width);
+        }
+        #imageRevision {
+          opacity: var(--my-opacity-value);
+          z-index: 0.5;
+        }
+        #imageDiffContainer {
+          height: var(--div-height);
+          margin: auto;
+          width: var(--div-width);
+        }
+        #controlsContainer {
+          border-top: 1px solid var(--border-color, #ddd);
+          display: flex;
+        }
+        #controlsBox {
+          display: flex;
+          justify-content: space-between;
+          margin: 0 .5em;
+          min-width: 32em;
+          width: 100%;
+        }
+        label {
+          align-items: center;
+          display: flex;
+          padding: 1em .5em;
+        }
+        input {
+          margin: .5em;
+        }
+        #opacitySlider {
+          width: 10em;
+        }
+      `
+    ];
+  }
+
+  render() {
+    return html`
+      <div class="wrapper" style="
+          --my-opacity-value: ${this.opacityValue};
+          --img-width: ${this._scaledWidth ? this._scaledWidth + 'px' : 'initial'};
+          --img-height: ${this._scaledHeight ? this._scaledHeight + 'px' : 'initial'};
+          --div-width: ${this._maxWidth ? this._maxWidth + 'px' : 'initial'};
+          --div-height: ${this._maxHeight ? this._maxHeight + 'px' : 'initial'};
+      ">
+        <div id="imageDiffContainer">
+          <img
+              @load=${this._onImageLoad}
+              id="imageBase"
+              src=${this.computeSrcString(this.baseImage)} />
+          <img
+              @load=${this._onImageLoad}
+              data-opacity=${this.opacityValue}
+              id="imageRevision"
+              src=${this.computeSrcString(this.revisionImage)} />
+        </div>
+        <div id="controlsContainer">
+          <div id="controlsBox">
+            <label>
+              <input
+                  id="scaleSizesToggle"
+                  @click=${this.handleScaleSizesToggle}
+                  type="checkbox">
+              Scale to same size
+            </label>
+            <label>
+              Revision image opacity
+              <input
+                  id="opacitySlider"
+                  max="1.0"
+                  min="0.0"
+                  @input=${this.handleOpacityChange}
+                  step=".01"
+                  type="range"
+                  .value=${this.opacityValue || '0.5'}/>
+            </label>
+          </div>
+        </div>
+      </div>
+    `;
   }
 
   static get properties() {
     return {
-      baseImage: Object,
-      revisionImage: Object,
-      opacityValue: Number,
-      _maxHeight: {
-        type: Number,
-        value: 0,
-      },
-      _maxWidth: {
-        type: Number,
-        value: 0,
-      },
+      baseImage: {type: Object},
+      revisionImage: {type: Object},
+      opacityValue: {type: Number},
+      _maxHeight: {type: Number},
+      _maxWidth: {type: Number},
+      _scaledWidth: {type: Number},
+      _scaledHeight: {type: Number},
     };
   }
 
-  static get observers() {
-    return [
-      '_handleImageChange(baseImage, revisionImage)',
-      '_handleHeightChange(_maxHeight)',
-      '_handleWidthChange(_maxWidth)',
-    ];
+  constructor() {
+    super();
+    this._maxHeight = 0;
+    this._maxWidth = 0;
+    this.opacityValue = 0.5;
+  }
+
+  updated(changedProperties) {
+    if (changedProperties.has('baseImage') || changedProperties.has('revisionImage')) {
+      this._handleImageChange();
+    }
   }
 
   _onImageLoad(e) {
-    this._maxHeight = Math.max(
-        this._maxHeight,
-        Polymer.dom(e).rootTarget.naturalHeight
-    );
-    this._maxWidth = Math.max(
-        this._maxWidth,
-        Polymer.dom(e).rootTarget.naturalWidth
-    );
+    const target = e.target;
+    this._maxHeight = Math.max(this._maxHeight, target.naturalHeight);
+    this._maxWidth = Math.max(this._maxWidth, target.naturalWidth);
   }
 
-  _handleImageChange(baseImage, revisionImage) {
-    if ([baseImage, revisionImage].includes(undefined)) return;
-    this.$.imageRevision.setAttribute(
-        'src',
-        this.computeSrcString(revisionImage)
-    );
-    this.$.imageBase.setAttribute('src', this.computeSrcString(baseImage));
+  _handleImageChange() {
+    if (this.baseImage === undefined || this.revisionImage === undefined) return;
     this.handleOpacityChange();
   }
 
-  handleOpacityChange() {
-    this.updateStyles({'--my-opacity-value': this.$.opacitySlider.value});
+  handleOpacityChange(e) {
+    const value = e ? e.target.value : this.opacityValue;
+    this.opacityValue = Number(value);
   }
 
   computeSrcString(image) {
+    if (!image) return '';
     return 'data:' + image['type'] + ';base64, ' + image['body'];
   }
 
-  handleScaleSizesToggle() {
-    let width;
-    let height;
-    if (this.$.scaleSizesToggle.checked) {
-      width = this._maxWidth;
-      height = this._maxHeight;
+  handleScaleSizesToggle(e) {
+    const isChecked = e ? e.target.checked : false;
+    if (isChecked) {
+      this._scaledWidth = this._maxWidth;
+      this._scaledHeight = this._maxHeight;
+    } else {
+      this._scaledWidth = null;
+      this._scaledHeight = null;
     }
-
-    this.updateStyles({
-      '--img-width': width ? width + 'px' : null,
-      '--img-height': height ? height + 'px' : null,
-    });
-  }
-
-  _handleHeightChange(height) {
-    if (!height) {
-      return;
-    }
-    this.updateStyles({'--div-height': `${height}px`});
-  }
-
-  _handleWidthChange(width) {
-    if (!width) {
-      return;
-    }
-    this.updateStyles({'--div-width': `${width}px`});
   }
 }
 
diff --git a/gr-opacity-diff-mode/gr-opacity-diff-mode_html.js b/gr-opacity-diff-mode/gr-opacity-diff-mode_html.js
deleted file mode 100644
index 6aacd55..0000000
--- a/gr-opacity-diff-mode/gr-opacity-diff-mode_html.js
+++ /dev/null
@@ -1,93 +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.
- */
-
-export const htmlTemplate = Polymer.html`
-    <style>
-      :host {
-        display: block;
-      }
-      .wrapper {
-        box-shadow: 0 1px 3px rgba(0, 0, 0, .3);
-        margin: 1em 0;
-      }
-      img {
-        display: block;
-        height: var(--img-height);
-        margin: auto;
-        position: absolute;
-        width: var(--img-width);
-      }
-      #imageRevision {
-        opacity: var(--my-opacity-value);
-        z-index: 0.5;
-      }
-      #imageDiffContainer {
-        height: var(--div-height);
-        margin: auto;
-        width: var(--div-width);
-      }
-      #controlsContainer {
-        border-top: 1px solid var(--border-color, #ddd);
-        display: flex;
-      }
-      #controlsBox {
-        display: flex;
-        justify-content: space-between;
-        margin: 0 .5em;
-        min-width: 32em;
-        width: 100%;
-      }
-      label {
-        align-items: center;
-        display: flex;
-        padding: 1em .5em;
-      }
-      input {
-        margin: .5em;
-      }
-      #opacitySlider {
-        width: 10em;
-      }
-    </style>
-    <div class="wrapper">
-      <div id="imageDiffContainer">
-        <img on-load="_onImageLoad" id="imageBase"/>
-        <img on-load="_onImageLoad" data-opacity$="{{opacityValue}}" id="imageRevision"/>
-      </div>
-      <div id="controlsContainer">
-        <div id="controlsBox">
-          <label>
-            <input
-                id="scaleSizesToggle"
-                on-click="handleScaleSizesToggle"
-                type="checkbox">
-            Scale to same size
-          </label>
-          <label>
-            Revision image opacity
-            <input
-                id="opacitySlider"
-                max="1.0"
-                min="0.0"
-                on-input="handleOpacityChange"
-                step=".01"
-                type="range"
-                value="0.5"/>
-          </label>
-        </div>
-      </div>
-    </div>`;
\ No newline at end of file
diff --git a/gr-resemble-diff-mode/gr-resemble-diff-mode.js b/gr-resemble-diff-mode/gr-resemble-diff-mode.js
index 372f554..f0c895f 100644
--- a/gr-resemble-diff-mode/gr-resemble-diff-mode.js
+++ b/gr-resemble-diff-mode/gr-resemble-diff-mode.js
@@ -14,53 +14,153 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {htmlTemplate} from './gr-resemble-diff-mode_html.js';
+
+import {html, css, LitElement} from 'lit';
 
 const DEFAULT_SETTING = {
   errorType: 'flat',
   largeImageThreshold: 1200,
 };
 
-class ResembleDiffMode extends Polymer.Element {
+class ResembleDiffMode extends LitElement {
   static get is() {
     return 'gr-resemble-diff-mode';
   }
 
-  static get template() {
-    return htmlTemplate;
+  static get styles() {
+    return [
+      css`
+        :host {
+          display: block;
+        }
+        h2 {
+          display: none;
+        }
+        :host([loading]) #imageDiff {
+          display: none;
+        }
+        :host([loading]) h2 {
+          display: inline;
+          padding: 1em 0;
+        }
+        .toggle {
+          padding: .5em;
+        }
+        #controlsContainer {
+          align-items: center;
+          border-top: 1px solid var(--border-color, #ddd);
+          display: flex;
+          justify-content: space-between;
+          padding: 1em;
+          white-space: nowrap;
+          width: 100%;
+        }
+        #diffContainer {
+          display: block;
+          width: 100%;
+        }
+        #color {
+          border: 1px solid var(--border-color, #ddd);
+          border-radius: 2px;
+        }
+        #fullscreen {
+          border: 1px solid var(--border-color, #ddd);
+          border-radius: 2px;
+          color: var(--primary-text-color, #000);
+          padding: .5em;
+        }
+        #controlsContainer,
+        #color,
+        #fullscreen {
+          background-color: var(--table-header-background-color, #fafafa);
+        }
+        #color:hover,
+        #fullscreen:hover {
+          background-color: var(--header-background-color, #eeeeee);
+        }
+        #imageDiff {
+          display: block;
+          height: auto;
+          margin: auto;
+          max-width: 50em;
+        }
+        #modeContainer {
+          box-shadow: 0 1px 3px rgba(0, 0, 0, .3);
+          display: block;
+          margin: 1em 0em;
+          width: 50em;
+        }
+      `
+    ];
+  }
+
+  render() {
+    return html`
+      <div id="modeContainer">
+        <div id="diffContainer">
+          <h2>Loading...</h2>
+          <img id="imageDiff" src=${this._diffImageSrc || ''}>
+        </div>
+        <div id="controlsContainer">
+          <div>${this._difference}% changed</div>
+          <label class="toggle">
+            <input
+                id="ignoreColorsToggle"
+                type="checkbox"
+                .checked=${this._ignoreColors}
+                @click=${this._handleIgnoreColorsToggle}>
+            Ignore colors
+          </label>
+          <label class="toggle">
+            <input
+                id="transparentToggle"
+                type="checkbox"
+                .checked=${this._transparent}
+                @click=${this._handleTransparentToggle}>
+            Transparent
+          </label>
+          <input
+              id="color"
+              type="color"
+              .value=${this._colorValue}
+              @change=${this._handleColorChange}>
+          <button
+              id="fullscreen"
+              @click=${this._handleFullScreen}>
+            View full sized
+          </button>
+        </div>
+      </div>
+    `;
   }
 
   static get properties() {
     return {
-      baseImage: Object,
-      revisionImage: Object,
-      _colorValue: {
-        type: String,
-        observer: '_handleColorChange',
-        value: '#00ffff',
-      },
-      _difference: {
-        type: Number,
-        value: 0,
-      },
-      _ignoreColors: {
-        type: Boolean,
-        value: false,
-      },
-      _transparent: {
-        type: Boolean,
-        value: false,
-      },
-      loading: {
-        type: Boolean,
-        value: false,
-        reflectToAttribute: true,
-      },
+      baseImage: {type: Object},
+      revisionImage: {type: Object},
+      _colorValue: {type: String},
+      _difference: {type: Number},
+      _ignoreColors: {type: Boolean},
+      _transparent: {type: Boolean},
+      _diffImageSrc: {type: String},
+      loading: {type: Boolean, reflect: true},
     };
   }
 
-  static get observers() {
-    return ['_handleImageDiff(baseImage, revisionImage)'];
+  constructor() {
+    super();
+    this._colorValue = '#00ffff';
+    this._difference = 0;
+    this._ignoreColors = false;
+    this._transparent = false;
+    this._diffImageSrc = '';
+    this.loading = false;
+  }
+
+  updated(changedProperties) {
+    if (changedProperties.has('baseImage') || changedProperties.has('revisionImage')) {
+      this._handleImageDiff(this.baseImage, this.revisionImage);
+    }
   }
 
   connectedCallback() {
@@ -76,8 +176,7 @@
   }
 
   _setImageDiffSrc(src) {
-    this.$.imageDiff.removeAttribute('src');
-    this.$.imageDiff.setAttribute('src', src);
+    this._diffImageSrc = src;
   }
 
   _setDifferenceValue(percentage) {
@@ -138,17 +237,18 @@
     this.loading = false;
   }
 
-  _handleIgnoreColorsToggle() {
-    this._ignoreColors = !this._ignoreColors;
+  _handleIgnoreColorsToggle(e) {
+    this._ignoreColors = e.target.checked;
     this.reload();
   }
 
-  _handleTransparentToggle() {
-    this._transparent = !this._transparent;
+  _handleTransparentToggle(e) {
+    this._transparent = e.target.checked;
     this.reload();
   }
 
-  _handleColorChange() {
+  _handleColorChange(e) {
+    this._colorValue = e.target.value;
     this.reload();
   }
 
@@ -165,7 +265,10 @@
 
   _handleFullScreen() {
     const w = window.open('about:blank', '_blank');
-    w.document.body.appendChild(this.$.imageDiff.cloneNode(true));
+    const imageDiff = this.shadowRoot.getElementById('imageDiff');
+    if (imageDiff) {
+      w.document.body.appendChild(imageDiff.cloneNode(true));
+    }
   }
 }
 
diff --git a/gr-resemble-diff-mode/gr-resemble-diff-mode_html.js b/gr-resemble-diff-mode/gr-resemble-diff-mode_html.js
deleted file mode 100644
index 31188d8..0000000
--- a/gr-resemble-diff-mode/gr-resemble-diff-mode_html.js
+++ /dev/null
@@ -1,114 +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.
- */
-
-export const htmlTemplate = Polymer.html`
-    <style>
-      :host {
-        display: block;
-      }
-      h2 {
-        display: none;
-      }
-      :host([loading]) #imageDiff {
-        display: none;
-      }
-      :host([loading]) h2 {
-        display: inline;
-        padding: 1em 0;
-      }
-      .toggle {
-        padding: .5em;
-      }
-      #controlsContainer {
-        align-items: center;
-        border-top: 1px solid var(--border-color, #ddd);
-        display: flex;
-        justify-content: space-between;
-        padding: 1em;
-        white-space: nowrap;
-        width: 100%;
-      }
-      #diffContainer {
-        display: block;
-        width: 100%;
-      }
-      #color {
-        border: 1px solid var(--border-color, #ddd);
-        border-radius: 2px;
-      }
-      #fullscreen {
-        border: 1px solid var(--border-color, #ddd);
-        border-radius: 2px;
-        color: var(--primary-text-color, #000);
-        padding: .5em;
-      }
-      #controlsContainer,
-      #color,
-      #fullscreen {
-        background-color: var(--table-header-background-color, #fafafa);
-      }
-      #color:hover,
-      #fullscreen:hover {
-        background-color: var(--header-background-color, #eeeeee);
-      }
-      #imageDiff {
-        display: block;
-        height: auto;
-        margin: auto;
-        max-width: 50em;
-      }
-      #modeContainer {
-        box-shadow: 0 1px 3px rgba(0, 0, 0, .3);
-        display: block;
-        margin: 1em 0em;
-        width: 50em;
-      }
-    </style>
-    <div id="modeContainer">
-      <div id="diffContainer">
-        <h2>Loading...</h2>
-        <img id="imageDiff">
-      </div>
-      <div id="controlsContainer">
-        <div>[[_difference]]% changed</div>
-        <label class="toggle">
-          <input
-              id="ignoreColorsToggle"
-              type="checkbox"
-              checked$="[[_ignoreColors]]"
-              on-click="_handleIgnoreColorsToggle">
-          Ignore colors
-        </label>
-        <label class="toggle">
-          <input
-              id="transparentToggle"
-              type="checkbox"
-              checked$="[[_transparent]]"
-              on-click="_handleTransparentToggle">
-          Transparent
-        </label>
-        <input
-            id="color"
-            type="color"
-            value="{{_colorValue::change}}">
-        <button
-            id="fullscreen"
-            on-click="_handleFullScreen">
-          View full sized
-        </button>
-      </div>
-    </div>`;