title: How can I run a JavaScript function when the URI source of an image renders?
body: <h1>Background</h1>
  <p>My big-picture goal is to display an image such that the following conditions are satisfied:</p>
  <ul>
  <li>The entire viewport is filled by the image</li>
  <li>At least one dimension of the image fits within the viewport</li>
  <li>The image is not stretched</li>
  </ul>
  <p>My solution is contained in the function <code>updateRatioSettings</code>, which works as follows:</p>
  <ol>
  <li>Determine the aspect ratios of the source image (using <code>naturalWidth</code> and <code>naturalHeight</code>) and the viewport</li>
  <li>If the viewport is wider than the image, <code>width = &quot;100%&quot;</code> and <code>height = &quot;auto&quot;</code>.</li>
  <li>If the image is wider than the viewport, <code>height = &quot;100%&quot;</code> and <code>width = &quot;auto&quot;</code>.</li>
  </ol>
  <p>This works!</p>
  <h1>The Problem</h1>
  <p>Now I just need to get <code>updateRatioSettings</code> to run when either of the aspect ratios changes. I have a working connection to the <code>resize</code> event of <code>window</code>, so now I just need to figure out how to make the function run when I change the image source.</p>
  <p>Each of the below code snippets shows a square centered in a horizontal viewport. A working solution will show the entire viewport filled with this nice grey color.</p>
  <h1>Attempt #1</h1>
  <p>My first strategy was to call <code>updateRatioSettings</code> immediately after setting <code>img.src</code>. However, I ran into an issue where <code>sourceRatio</code>, which is calculated as <code>img.naturalWidth / img.naturalHeight</code>, printed as <code>NaN</code>.
  <div class="snippet" data-lang="js" data-hide="false" data-console="true" data-babel="false">
  <div class="snippet-code">
  <pre class="snippet-code-js lang-js prettyprint-override"><code>// function to listen for window size changes and move image accordingly
  let img;
  let windowWider = false; // assume the screen is some ratio narrower than the image... to start at least
  let ratioUpdateReady = true; // debounce variable
  
  function updateRatioSettings() {
      // if the function is running, don't run the function
      if (ratioUpdateReady) {
          ratioUpdateReady = false;
          // begin debounced code
          
          const sourceRatio = img.naturalWidth / img.naturalHeight;
          const windowRatio = window.innerWidth / window.innerHeight;
          console.log("sourceRatio: " + sourceRatio, "windowRatio: " + windowRatio);
          if (windowRatio &gt; sourceRatio != windowWider) { // if something has changed
              windowWider = !windowWider // log the new status
              if (windowWider) {
                  // settings for window aspect ratio &gt; source aspect ratio
                  img.style.width = "100%";
                  img.style.height = "auto";
              } else {
                  // settings for source aspect ratio &gt; window aspect ratio
                  img.style.height = "100%";
                  img.style.width = "auto";
              }
          }
          
          // end debounced code
          ratioUpdateReady = true;
      }
  }
  
  document.addEventListener('DOMContentLoaded', function(){
      // identify the element containing the image
      img = document.getElementById("background");
      
      // load an initial image
    (function (){ // this normally executes after sending a request to another part of the chrome extension
      const uri = "data:image/bmp;base64,Qk06AAAAAAAAADYAAAAoAAAAAQAAAAEAAAABABgAAAAAAAAAAADDDgAAww4AAAAAAAAAAAAAQEBAAA=="; // this is the string returned by the extension in my test case
          img.src = uri;
          updateRatioSettings(); // this gets a sourceRatio that is NaN
    })()
      
      // set up an event to update the ratio settings when the window resizes
      // commenting out for the purpose of this question, since it doesn't need debugging
      // window.addEventListener('resize', updateRatioSettings);
  });</code></pre>
  <pre class="snippet-code-css lang-css prettyprint-override"><code>html,body{
      margin:0;
      height:100%;
      overflow:hidden;
  }
  img{
      height:100%;
      width:auto;
      position:absolute;
      top:-100%; bottom:-100%;
      left:-100%; right:-100%;
      margin:auto;
  }</code></pre>
  <pre class="snippet-code-html lang-html prettyprint-override"><code>&lt;html&gt;
      &lt;head&gt;
          &lt;title&gt;New tab&lt;/title&gt;
      &lt;/head&gt;
      &lt;body&gt;
          &lt;img id="background" src="" alt=""&gt;
      &lt;/body&gt;
  &lt;/html&gt;</code></pre>
  </div>
  </div>
  </p>
  <h1>Attempt #2</h1>
  <p>Here I attempted to use <code>image.onLoad</code> to run <code>updateRatioSettings</code> when the image URI is loaded.
  <div class="snippet" data-lang="js" data-hide="false" data-console="true" data-babel="false">
  <div class="snippet-code">
  <pre class="snippet-code-js lang-js prettyprint-override"><code>// function to listen for window size changes and move image accordingly
  let img;
  let windowWider = false; // assume the screen is some ratio narrower than the image... to start at least
  let ratioUpdateReady = true; // debounce variable
  
  function updateRatioSettings() {
      // if the function is running, don't run the function
      if (ratioUpdateReady) {
          ratioUpdateReady = false;
          // begin debounced code
          
          const sourceRatio = img.naturalWidth / img.naturalHeight;
          const windowRatio = window.innerWidth / window.innerHeight;
          console.log("sourceRatio: " + sourceRatio, "windowRatio: " + windowRatio);
          if (windowRatio &gt; sourceRatio != windowWider) { // if something has changed
              windowWider = !windowWider // log the new status
              if (windowWider) {
                  // settings for window aspect ratio &gt; source aspect ratio
                  img.style.width = "100%";
                  img.style.height = "auto";
              } else {
                  // settings for source aspect ratio &gt; window aspect ratio
                  img.style.height = "100%";
                  img.style.width = "auto";
              }
          }
          
          // end debounced code
          ratioUpdateReady = true;
      }
  }
  
  document.addEventListener('DOMContentLoaded', function(){
      // identify the element containing the image
      img = document.getElementById("background");
      
      // load an initial image
    (function (){ // this normally executes after sending a request to another part of the chrome extension
      const uri = "data:image/bmp;base64,Qk06AAAAAAAAADYAAAAoAAAAAQAAAAEAAAABABgAAAAAAAAAAADDDgAAww4AAAAAAAAAAAAAQEBAAA=="; // this is the string returned by the extension in my test case
      img.onLoad = updateRatioSettings; // this is the function I want to run when naturalWidth and naturalHeight have updated
          img.src = uri; // updateRatioSettings is not called
    })()
      
      // set up an event to update the ratio settings when the window resizes
      // commenting out for the purpose of this question, since it doesn't need debugging
      // window.addEventListener('resize', updateRatioSettings);
  });</code></pre>
  <pre class="snippet-code-css lang-css prettyprint-override"><code>html,body{
      margin:0;
      height:100%;
      overflow:hidden;
  }
  img{
      height:100%;
      width:auto;
      position:absolute;
      top:-100%; bottom:-100%;
      left:-100%; right:-100%;
      margin:auto;
  }</code></pre>
  <pre class="snippet-code-html lang-html prettyprint-override"><code>&lt;html&gt;
      &lt;head&gt;
          &lt;title&gt;New tab&lt;/title&gt;
      &lt;/head&gt;
      &lt;body&gt;
          &lt;img id="background" src="" alt=""&gt;
      &lt;/body&gt;
  &lt;/html&gt;</code></pre>
  </div>
  </div>
  </p>
  <h1>Attempt #3</h1>
  <p>Next, I tried using a <a href="https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver" rel="nofollow noreferrer"><code>MutationObserver</code></a> to watch for the point at which <code>naturalWidth</code> and <code>naturalHeight</code> actually update, then run <code>updateRatioSettings</code> in repsonse. Like in Attempt #2, <code>updateRatioSettings</code> never actually ran.</p>
  <p><div class="snippet" data-lang="js" data-hide="false" data-console="true" data-babel="false">
  <div class="snippet-code">
  <pre class="snippet-code-js lang-js prettyprint-override"><code>// function to listen for window size changes and move image accordingly
  let img;
  let windowWider = false; // assume the screen is some ratio narrower than the image... to start at least
  let ratioUpdateReady = true; // debounce variable
  
  function updateRatioSettings() {
      // if the function is running, don't run the function
      if (ratioUpdateReady) {
          ratioUpdateReady = false;
          // begin debounced code
          
          const sourceRatio = img.naturalWidth / img.naturalHeight;
          const windowRatio = window.innerWidth / window.innerHeight;
          console.log("sourceRatio: " + sourceRatio, "windowRatio: " + windowRatio);
          if (windowRatio &gt; sourceRatio != windowWider) { // if something has changed
              windowWider = !windowWider // log the new status
              if (windowWider) {
                  // settings for window aspect ratio &gt; source aspect ratio
                  img.style.width = "100%";
                  img.style.height = "auto";
              } else {
                  // settings for source aspect ratio &gt; window aspect ratio
                  img.style.height = "100%";
                  img.style.width = "auto";
              }
          }
          
          // end debounced code
          ratioUpdateReady = true;
      }
  }
  
  const watchingAttributes = ["naturalWidth", "naturalHeight"];
  
  document.addEventListener('DOMContentLoaded', function(){
    // identify the element containing the image
    img = document.getElementById("background");
  
    // set up the image resize listener
    function processMutation(mutationRecord) {
      if (watchingAttributes.includes(mutationRecord.attributeName)) {
        updateRatioSettings();
      }
    }
    let watcher = new MutationObserver((mutations) =&gt; {
      mutations.forEach(processMutation);
    });
    watcher.observe(img, {
      attributes: true
    });
  
    // load an initial image
    (function (){ // this normally executes after sending a request to another part of the chrome extension
      const uri = "data:image/bmp;base64,Qk06AAAAAAAAADYAAAAoAAAAAQAAAAEAAAABABgAAAAAAAAAAADDDgAAww4AAAAAAAAAAAAAQEBAAA=="; // this is the string returned by the extension in my test case
      img.src = uri;
    })()
      
    // set up an event to update the ratio settings when the window resizes
    // commenting out for the purpose of this question, since it doesn't need debugging
    // window.addEventListener('resize', updateRatioSettings);
  });</code></pre>
  <pre class="snippet-code-css lang-css prettyprint-override"><code>html,body{
      margin:0;
      height:100%;
      overflow:hidden;
  }
  img{
      height:100%;
      width:auto;
      position:absolute;
      top:-100%; bottom:-100%;
      left:-100%; right:-100%;
      margin:auto;
  }</code></pre>
  <pre class="snippet-code-html lang-html prettyprint-override"><code>&lt;html&gt;
      &lt;head&gt;
          &lt;title&gt;New tab&lt;/title&gt;
      &lt;/head&gt;
      &lt;body&gt;
          &lt;img id="background" src="" alt=""&gt;
      &lt;/body&gt;
  &lt;/html&gt;</code></pre>
  </div>
  </div>
  </p>
  <h1>Conclusion</h1>
  <p>So... what do I do now?</p>