Bug 1248498 - Check document cache status when loading HTML sources. r=jlast draft
authorJ. Ryan Stinnett <jryans@gmail.com>
Wed, 28 Feb 2018 19:48:54 -0600
changeset 762700 f938b7bf9e14780595745839d117b4e61be637c3
parent 761174 9b69507f6511d1b23cbbe515d03a1750cf5156d3
push id101236
push userbmo:jryans@gmail.com
push dateFri, 02 Mar 2018 21:11:48 +0000
reviewersjlast
bugs1248498
milestone60.0a1
Bug 1248498 - Check document cache status when loading HTML sources. r=jlast When getting the source for inline scripts in documents, we generally try to use the cache. However, the toolbox allows you to disable the HTTP cache while open if you wish. If the toolbox has disabled the cache, using `fetchFromCache: true` will give stale data (from the most recent cache-enabled request). We can avoid stale data here by checking the document's cache state. MozReview-Commit-ID: J5SJciSyQNj
devtools/client/debugger/new/test/mochitest/browser.ini
devtools/client/debugger/new/test/mochitest/browser_dbg-inline-cache.js
devtools/server/actors/source.js
--- a/devtools/client/debugger/new/test/mochitest/browser.ini
+++ b/devtools/client/debugger/new/test/mochitest/browser.ini
@@ -137,16 +137,17 @@ skip-if = !e10s # This test is only vali
 [browser_dbg-content-script-sources.js]
 [browser_dbg-debugger-buttons.js]
 [browser_dbg-editor-gutter.js]
 [browser_dbg-editor-select.js]
 [browser_dbg-editor-highlight.js]
 [browser_dbg-expressions.js]
 [browser_dbg-expressions-error.js]
 [browser_dbg-iframes.js]
+[browser_dbg-inline-cache.js]
 [browser_dbg-keyboard-navigation.js]
 [browser_dbg-keyboard-shortcuts.js]
 skip-if = os == "linux" # bug 1351952
 [browser_dbg-layout-changes.js]
 [browser_dbg-outline.js]
 [browser_dbg-pause-exceptions.js]
 [browser_dbg-pause-ux.js]
 skip-if = os == "win"
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-inline-cache.js
@@ -0,0 +1,132 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/*
+ * Test loading inline scripts from cache:
+ *   - Load document with inline script
+ *   - Reload inside debugger with toolbox caching disabled
+ *   - Reload inside debugger with toolbox caching enabled
+ */
+
+const server = createTestHTTPServer();
+
+let docValue = 1;
+server.registerPathHandler("/inline-cache.html", (request, response) => {
+  response.setHeader("Content-Type", "text/html");
+  // HTTP should assume cacheable by default.
+  // Toolbox defaults to disabling cache for subsequent loads when open.
+  response.setHeader("Cache-Control", "public, max-age=3600");
+  response.write(`
+    <html>
+    <head>
+    <script>
+      let x = ${docValue};
+    </script>
+    </head>
+    </html>
+  `);
+});
+
+const SOURCE_URL = `http://localhost:${server.identity.primaryPort}/inline-cache.html`;
+
+/**
+ * This is meant to simulate the developer editing the inline source and saving.
+ * Effectively, we change the source during the test at specific controlled points.
+ */
+function makeChanges() {
+  docValue++;
+}
+
+function getPageValue(tab) {
+  return ContentTask.spawn(tab.linkedBrowser, {}, function () {
+    return content.document.querySelector("script").textContent.trim();
+  });
+}
+
+async function reloadTabAndDebugger(tab, dbg) {
+  let navigated = waitForDispatch(dbg, "NAVIGATE");
+  let loaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+  await reload(dbg, "inline-cache.html");
+  return Promise.all([ navigated, loaded ]);
+}
+
+add_task(async function () {
+  info("Load document with inline script");
+  const tab = await addTab(SOURCE_URL);
+  info("Open debugger");
+  clearDebuggerPreferences();
+  const toolbox = await openToolboxForTab(tab, "jsdebugger");
+  const dbg = createDebuggerContext(toolbox);
+  info("Reload tab to ensure debugger finds script");
+  await reloadTabAndDebugger(tab, dbg);
+  let pageValue = await getPageValue(tab);
+  is(pageValue, "let x = 1;", "Content loads from network, has doc value 1");
+  await waitForSource(dbg, "inline-cache.html");
+  await selectSource(dbg, "inline-cache.html");
+  await waitForLoadedSource(dbg, "inline-cache.html");
+  let dbgValue = await findSource(dbg, "inline-cache.html");
+  info(`Debugger text: ${dbgValue.text}`);
+  ok(dbgValue.text.includes(pageValue),
+     "Debugger loads from cache, gets value 1 like page");
+
+  info("Disable HTTP cache for page");
+  await toolbox.target.activeTab.reconfigure({ cacheDisabled: true });
+  makeChanges();
+
+  info("Reload inside debugger with toolbox caching disabled (attempt 1)");
+  await reloadTabAndDebugger(tab, dbg);
+  pageValue = await getPageValue(tab);
+  is(pageValue, "let x = 2;", "Content loads from network, has doc value 2");
+  await waitForLoadedSource(dbg, "inline-cache.html");
+  dbgValue = await findSource(dbg, "inline-cache.html");
+  info(`Debugger text: ${dbgValue.text}`);
+  ok(dbgValue.text.includes(pageValue),
+     "Debugger loads from network, gets value 2 like page");
+
+  makeChanges();
+
+  info("Reload inside debugger with toolbox caching disabled (attempt 2)");
+  await reloadTabAndDebugger(tab, dbg);
+  pageValue = await getPageValue(tab);
+  is(pageValue, "let x = 3;", "Content loads from network, has doc value 3");
+  await waitForLoadedSource(dbg, "inline-cache.html");
+  dbgValue = await findSource(dbg, "inline-cache.html");
+  info(`Debugger text: ${dbgValue.text}`);
+  ok(dbgValue.text.includes(pageValue),
+     "Debugger loads from network, gets value 3 like page");
+
+  info("Enable HTTP cache for page");
+  await toolbox.target.activeTab.reconfigure({ cacheDisabled: false });
+  makeChanges();
+
+  // Even though the HTTP cache is now enabled, Gecko sets the VALIDATE_ALWAYS flag when
+  // reloading the page.  So, it will always make a request to the server for the main
+  // document contents.
+
+  info("Reload inside debugger with toolbox caching enabled (attempt 1)");
+  await reloadTabAndDebugger(tab, dbg);
+  pageValue = await getPageValue(tab);
+  is(pageValue, "let x = 4;", "Content loads from network, has doc value 4");
+  await waitForLoadedSource(dbg, "inline-cache.html");
+  dbgValue = await findSource(dbg, "inline-cache.html");
+  info(`Debugger text: ${dbgValue.text}`);
+  ok(dbgValue.text.includes(pageValue),
+     "Debugger loads from cache, gets value 4 like page");
+
+  makeChanges();
+
+  info("Reload inside debugger with toolbox caching enabled (attempt 2)");
+  await reloadTabAndDebugger(tab, dbg);
+  pageValue = await getPageValue(tab);
+  is(pageValue, "let x = 5;", "Content loads from network, has doc value 5");
+  await waitForLoadedSource(dbg, "inline-cache.html");
+  dbgValue = await findSource(dbg, "inline-cache.html");
+  info(`Debugger text: ${dbgValue.text}`);
+  ok(dbgValue.text.includes(pageValue),
+     "Debugger loads from cache, gets value 5 like page");
+
+  await toolbox.destroy();
+  await removeTab(tab);
+});
--- a/devtools/server/actors/source.js
+++ b/devtools/server/actors/source.js
@@ -207,16 +207,23 @@ let SourceActor = ActorClassWithSpec(sou
   get addonPath() {
     return this._addonPath;
   },
 
   get prettyPrintWorker() {
     return this.threadActor.prettyPrintWorker;
   },
 
+  get isCacheEnabled() {
+    if (this.threadActor._parent._getCacheDisabled) {
+      return !this.threadActor._parent._getCacheDisabled();
+    }
+    return true;
+  },
+
   form: function () {
     let source = this.source || this.generatedSource;
     // This might not have a source or a generatedSource because we
     // treat HTML pages with inline scripts as a special SourceActor
     // that doesn't have either
     let introductionUrl = null;
     if (source && source.introductionScript) {
       introductionUrl = source.introductionScript.source.url;
@@ -371,17 +378,20 @@ let SourceActor = ActorClassWithSpec(sou
       }
 
       // Only load the HTML page source from cache (which exists when
       // there are inline sources). Otherwise, we can't trust the
       // cache because we are most likely here because we are
       // fetching the original text for sourcemapped code, and the
       // page hasn't requested it before (if it has, it was a
       // previous debugging session).
-      let loadFromCache = this.isInlineSource;
+      // Additionally, we should only try the cache if it is currently enabled
+      // for the document.  Without this check, the cache may return stale data
+      // that doesn't match the document shown in the browser.
+      let loadFromCache = this.isInlineSource && this.isCacheEnabled;
 
       // Fetch the sources with the same principal as the original document
       let win = this.threadActor._parent.window;
       let principal, cacheKey;
       // On xpcshell, we don't have a window but a Sandbox
       if (!isWorker && win instanceof Ci.nsIDOMWindow) {
         let webNav = win.QueryInterface(Ci.nsIInterfaceRequestor)
                         .getInterface(Ci.nsIWebNavigation);