Bug 1060732 - Load sources from page loaded via POST from cache in the debugger. r=ejpbruel
☠☠ backed out by ea707a924390 ☠ ☠
authorAlexandre Poirot <poirot.alex@gmail.com>
Tue, 12 Apr 2016 13:05:36 -0700
changeset 330754 aa1a374f6bde61d7e39179bc95dadd1793b9b3f7
parent 330753 5abc57693914f76fae2616570e554f6a891df24e
child 330755 91274b84fa33cbe95c3a4f4951f77dee891cd8b6
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersejpbruel
bugs1060732
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1060732 - Load sources from page loaded via POST from cache in the debugger. r=ejpbruel
devtools/client/debugger/test/mochitest/browser.ini
devtools/client/debugger/test/mochitest/browser_dbg_post-page.js
devtools/client/debugger/test/mochitest/sjs_post-page.sjs
devtools/server/actors/source.js
devtools/shared/DevToolsUtils.js
--- a/devtools/client/debugger/test/mochitest/browser.ini
+++ b/devtools/client/debugger/test/mochitest/browser.ini
@@ -116,16 +116,17 @@ support-files =
   doc_watch-expressions.html
   doc_watch-expression-button.html
   doc_with-frame.html
   doc_worker-source-map.html
   doc_WorkerActor.attach-tab1.html
   doc_WorkerActor.attach-tab2.html
   doc_WorkerActor.attachThread-tab.html
   head.js
+  sjs_post-page.sjs
   sjs_random-javascript.sjs
   testactors.js
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/framework/test/shared-head.js
 
 [browser_dbg_aaa_run_first_leaktest.js]
 skip-if = e10s && debug
 [browser_dbg_addonactor.js]
@@ -347,16 +348,17 @@ skip-if = e10s && debug
 [browser_dbg_pause-no-step.js]
 skip-if = e10s && debug
 [browser_dbg_pause-resume.js]
 skip-if = e10s && debug
 [browser_dbg_pause-warning.js]
 skip-if = e10s && debug
 [browser_dbg_paused-keybindings.js]
 skip-if = e10s
+[browser_dbg_post-page.js]
 [browser_dbg_pretty-print-01.js]
 skip-if = e10s && debug
 [browser_dbg_pretty-print-02.js]
 skip-if = e10s && debug
 [browser_dbg_pretty-print-03.js]
 skip-if = e10s && debug
 [browser_dbg_pretty-print-04.js]
 skip-if = e10s && debug
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_post-page.js
@@ -0,0 +1,50 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that source contents are invalidated when the target navigates.
+ */
+
+const TAB_URL = EXAMPLE_URL + "sjs_post-page.sjs";
+
+const FORM = "<form method=\"POST\"><input type=\"submit\"></form>";
+const GET_CONTENT = "<script>\"GET\";</script>" + FORM;
+const POST_CONTENT = "<script>\"POST\";</script>" + FORM;
+
+add_task(function* () {
+  let [tab,, panel] = yield initDebugger(TAB_URL);
+  let win = panel.panelWin;
+  let editor = win.DebuggerView.editor;
+  let queries = win.require('./content/queries');
+  let getState = win.DebuggerController.getState;
+
+  yield waitForSourceShown(panel, TAB_URL);
+  let source = queries.getSelectedSource(getState());
+
+  is(queries.getSourceCount(getState()), 1,
+    "There should be one source displayed in the view.")
+  is(source.url, TAB_URL,
+    "The correct source is currently selected in the view.");
+  is(editor.getText(), GET_CONTENT,
+    "The currently shown source contains bacon. Mmm, delicious!");
+
+  // Submit the form and wait for debugger update
+  let onSourceUpdated = waitForSourceShown(panel, TAB_URL);
+  yield ContentTask.spawn(tab.linkedBrowser, null, function() {
+    content.document.querySelector("input[type=\"submit\"]").click();
+  });
+  yield onSourceUpdated;
+
+  // Verify that the source updates to the POST page content
+  source = queries.getSelectedSource(getState());
+  is(queries.getSourceCount(getState()), 1,
+    "There should be one source displayed in the view.")
+  is(source.url, TAB_URL,
+    "The correct source is currently selected in the view.");
+  is(editor.getText(), POST_CONTENT,
+    "The currently shown source contains bacon. Mmm, delicious!");
+
+  yield closeDebuggerAndFinish(panel);
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/sjs_post-page.sjs
@@ -0,0 +1,16 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const CC = Components.Constructor;
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+                             "nsIBinaryInputStream",
+                             "setInputStream");
+
+function handleRequest(request, response)
+{
+  let method = request.method;
+  let body = "<script>\"" + method + "\";</script>";
+  body += "<form method=\"POST\"><input type=\"submit\"></form>";
+  response.bodyOutputStream.write(body, body.length);
+}
--- a/devtools/server/actors/source.js
+++ b/devtools/server/actors/source.js
@@ -129,16 +129,18 @@ function resolveURIToLocalPath(aURI) {
  *        The source object we are representing.
  * @param ThreadActor thread
  *        The current thread actor.
  * @param String originalUrl
  *        Optional. For sourcemapped urls, the original url this is representing.
  * @param Debugger.Source generatedSource
  *        Optional, passed in when aSourceMap is also passed in. The generated
  *        source object that introduced this source.
+ * @param Boolean isInlineSource
+ *        Optional. True if this is an inline source from a HTML or XUL page.
  * @param String contentType
  *        Optional. The content type of this source, if immediately available.
  */
 let SourceActor = ActorClass({
   typeName: "source",
 
   initialize: function ({ source, thread, originalUrl, generatedSource,
                           isInlineSource, contentType }) {
@@ -340,17 +342,46 @@ let SourceActor = ActorClass({
       }
       else {
         // 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 sourceFetched = fetch(this.url, { loadFromCache: this.isInlineSource });
+        let loadFromCache = this.isInlineSource;
+
+        // 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);
+          let channel = webNav.currentDocumentChannel;
+          principal = channel.loadInfo.loadingPrincipal;
+
+          // Retrieve the cacheKey in order to load POST requests from cache
+          // Note that chrome:// URLs don't support this interface.
+          if (loadFromCache &&
+            webNav.currentDocumentChannel instanceof Ci.nsICacheInfoChannel) {
+            cacheKey = webNav.currentDocumentChannel.cacheKey;
+            assert(
+              cacheKey,
+              "Could not fetch the cacheKey from the related document."
+            );
+          }
+        }
+
+        let sourceFetched = fetch(this.url, {
+          policy: isWorker ? null : Ci.nsIContentPolicy.TYPE_DOCUMENT,
+          principal,
+          cacheKey,
+          loadFromCache
+        });
 
         // Record the contentType we just learned during fetching
         return sourceFetched
           .then(result => {
             this._contentType = result.contentType;
             return result;
           }, error => {
             this._reportLoadSourceError(error, map);
--- a/devtools/shared/DevToolsUtils.js
+++ b/devtools/shared/DevToolsUtils.js
@@ -377,47 +377,62 @@ exports.defineLazyGetter(this, "NetworkH
  *
  * @param aURL String
  *        The URL we will request.
  * @param aOptions Object
  *        An object with the following optional properties:
  *        - loadFromCache: if false, will bypass the cache and
  *          always load fresh from the network (default: true)
  *        - policy: the nsIContentPolicy type to apply when fetching the URL
+ *                  (only works when loading from system principal)
  *        - window: the window to get the loadGroup from
  *        - charset: the charset to use if the channel doesn't provide one
+ *        - principal: the principal to use, if omitted, the request is loaded
+ *                     with the system principal
+ *        - cacheKey: when loading from cache, use this key to retrieve a cache
+ *                    specific to a given SHEntry. (Allows loading POST
+ *                    requests from cache)
  * @returns Promise that resolves with an object with the following members on
  *          success:
  *           - content: the document at that URL, as a string,
  *           - contentType: the content type of the document
  *
  *          If an error occurs, the promise is rejected with that error.
  *
  * XXX: It may be better to use nsITraceableChannel to get to the sources
  * without relying on caching when we can (not for eval, etc.):
  * http://www.softwareishard.com/blog/firebug/nsitraceablechannel-intercept-http-traffic/
  */
 function mainThreadFetch(aURL, aOptions={ loadFromCache: true,
                                           policy: Ci.nsIContentPolicy.TYPE_OTHER,
                                           window: null,
-                                          charset: null }) {
+                                          charset: null,
+                                          principal: null,
+                                          cacheKey: null }) {
   // Create a channel.
   let url = aURL.split(" -> ").pop();
   let channel;
   try {
     channel = newChannelForURL(url, aOptions);
   } catch (ex) {
     return promise.reject(ex);
   }
 
   // Set the channel options.
   channel.loadFlags = aOptions.loadFromCache
     ? channel.LOAD_FROM_CACHE
     : channel.LOAD_BYPASS_CACHE;
 
+  // When loading from cache, the cacheKey allows us to target a specific
+  // SHEntry and offer ways to restore POST requests from cache.
+  if (aOptions.loadFromCache &&
+      aOptions.cacheKey && channel instanceof Ci.nsICacheInfoChannel) {
+    channel.cacheKey = aOptions.cacheKey;
+  }
+
   if (aOptions.window) {
     // Respect private browsing.
     channel.loadGroup = aOptions.window.QueryInterface(Ci.nsIInterfaceRequestor)
                           .getInterface(Ci.nsIWebNavigation)
                           .QueryInterface(Ci.nsIDocumentLoader)
                           .loadGroup;
   }
 
@@ -492,22 +507,26 @@ function mainThreadFetch(aURL, aOptions=
 
 /**
  * Opens a channel for given URL. Tries a bit harder than NetUtil.newChannel.
  *
  * @param {String} url - The URL to open a channel for.
  * @param {Object} options - The options object passed to @method fetch.
  * @return {nsIChannel} - The newly created channel. Throws on failure.
  */
-function newChannelForURL(url, { policy }) {
+function newChannelForURL(url, { policy, principal }) {
   let channelOptions = {
-    contentPolicyType: policy,
-    loadUsingSystemPrincipal: true,
-    uri: url
+    uri: url,
+    contentPolicyType: policy
   };
+  if (principal) {
+    channelOptions.loadingPrincipal = principal;
+  } else {
+    channelOptions.loadUsingSystemPrincipal = true;
+  }
 
   try {
     return NetUtil.newChannel(channelOptions);
   } catch (e) {
     // In the xpcshell tests, the script url is the absolute path of the test
     // file, which will make a malformed URI error be thrown. Add the file
     // scheme to see if it helps.
     channelOptions.uri = "file://" + url;