Bug 1165599 - Restore basic external view source. r=jsantell a=ritu
authorJ. Ryan Stinnett <jryans@gmail.com>
Wed, 09 Sep 2015 12:25:58 -0500
changeset 282200 deac8c4a83675ade470cc1f83eb8a82e7e5a2ef2
parent 282199 a3e17097503e908ce2a19fbf42f1e01e4388a63e
child 282201 5fd58765314cfcec069aef196be99a38e03a9aed
push id5052
push userjryans@gmail.com
push dateThu, 10 Sep 2015 15:26:17 +0000
treeherdermozilla-beta@deac8c4a8367 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjsantell, ritu
bugs1165599, 1025146
milestone41.0
Bug 1165599 - Restore basic external view source. r=jsantell a=ritu This restores external view source after changes in bug 1025146. It does function in e10s, however the page descriptor is not used, so viewing a POST result will GET the page instead. This is the same as it was before bug 1025146. Follow ups will add usage tracking and improve e10s behavior if there is enough usage.
browser/base/content/browser.js
toolkit/components/viewsource/content/viewSourceUtils.js
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -2350,37 +2350,53 @@ function BrowserViewSourceOfDocument(aAr
     let outerWindowID = requestor.getInterface(Ci.nsIDOMWindowUtils)
                                  .outerWindowID;
     let URL = browser.currentURI.spec;
     args = { browser, outerWindowID, URL };
   } else {
     args = aArgsOrDocument;
   }
 
-  let inTab = Services.prefs.getBoolPref("view_source.tab");
-  if (inTab) {
-    let viewSourceURL = `view-source:${args.URL}`;
-    let tabBrowser = gBrowser;
-    // In the case of sidebars and chat windows, gBrowser is defined but null,
-    // because no #content element exists.  For these cases, we need to find
-    // the most recent browser window.
-    // In the case of popups, we need to find a non-popup browser window.
-    if (!tabBrowser || !window.toolbar.visible) {
-      // This returns only non-popup browser windows by default.
-      let browserWindow = RecentWindow.getMostRecentBrowserWindow();
-      tabBrowser = browserWindow.gBrowser;
-    }
-    let tab = tabBrowser.loadOneTab(viewSourceURL, {
-      relatedToCurrent: true,
-      inBackground: false
+  let viewInternal = () => {
+    let inTab = Services.prefs.getBoolPref("view_source.tab");
+    if (inTab) {
+      let viewSourceURL = `view-source:${args.URL}`;
+      let tabBrowser = gBrowser;
+      // In the case of sidebars and chat windows, gBrowser is defined but null,
+      // because no #content element exists.  For these cases, we need to find
+      // the most recent browser window.
+      // In the case of popups, we need to find a non-popup browser window.
+      if (!tabBrowser || !window.toolbar.visible) {
+        // This returns only non-popup browser windows by default.
+        let browserWindow = RecentWindow.getMostRecentBrowserWindow();
+        tabBrowser = browserWindow.gBrowser;
+      }
+      let tab = tabBrowser.loadOneTab(viewSourceURL, {
+        relatedToCurrent: true,
+        inBackground: false
+      });
+      args.viewSourceBrowser = tabBrowser.getBrowserForTab(tab);
+      top.gViewSourceUtils.viewSourceInBrowser(args);
+    } else {
+      top.gViewSourceUtils.viewSource(args);
+    }
+  }
+
+  // Check if external view source is enabled.  If so, try it.  If it fails,
+  // fallback to internal view source.
+  if (Services.prefs.getBoolPref("view_source.editor.external")) {
+    top.gViewSourceUtils
+       .openInExternalEditor(args, null, null, null, result => {
+      if (!result) {
+        viewInternal();
+      }
     });
-    args.viewSourceBrowser = tabBrowser.getBrowserForTab(tab);
-    top.gViewSourceUtils.viewSourceInBrowser(args);
   } else {
-    top.gViewSourceUtils.viewSource(args);
+    // Display using internal view source
+    viewInternal();
   }
 }
 
 /**
  * Opens the View Source dialog for the source loaded in the root
  * top-level document of the browser. This is really just a
  * convenience wrapper around BrowserViewSourceOfDocument.
  *
--- a/toolkit/components/viewsource/content/viewSourceUtils.js
+++ b/toolkit/components/viewsource/content/viewSourceUtils.js
@@ -10,16 +10,18 @@
  *
  * This file silently depends on contentAreaUtils.js for
  * getDefaultFileName, getNormalizedLeafName and getDefaultExtension
  */
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ViewSourceBrowser",
   "resource://gre/modules/ViewSourceBrowser.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
+  "resource://gre/modules/Deprecated.jsm");
 
 var gViewSourceUtils = {
 
   mnsIWebBrowserPersist: Components.interfaces.nsIWebBrowserPersist,
   mnsIWebProgress: Components.interfaces.nsIWebProgress,
   mnsIWebPageDescriptor: Components.interfaces.nsIWebPageDescriptor,
 
   /**
@@ -165,48 +167,101 @@ var gViewSourceUtils = {
       const argumentRE = /"([^"]+)"|(\S+)/g;
       while (argumentRE.test(args))
         editorArgs.push(RegExp.$1 || RegExp.$2);
     }
     editorArgs.push(aPath);
     return editorArgs;
   },
 
-  // aCallBack is a function accepting two arguments - result (true=success) and a data object
-  // It defaults to openInInternalViewer if undefined.
-  openInExternalEditor: function(aURL, aPageDescriptor, aDocument, aLineNumber, aCallBack)
-  {
-    var data = {url: aURL, pageDescriptor: aPageDescriptor, doc: aDocument,
-                lineNumber: aLineNumber};
+  /**
+   * Opens an external editor with the view source content.
+   *
+   * @param aArgsOrURL (required)
+   *        This is either an Object containing parameters, or a string
+   *        URL for the page we want to view the source of. In the latter
+   *        case we will be paying attention to the other parameters, as
+   *        we will be supporting the old API for this method.
+   *        If aArgsOrURL is an Object, the other parameters will be ignored.
+   *        aArgsOrURL as an Object can include the following properties:
+   *
+   *        URL (required):
+   *          A string URL for the page we'd like to view the source of.
+   *        browser (optional):
+   *          The browser containing the document that we would like to view the
+   *          source of. This is required if outerWindowID is passed.
+   *        outerWindowID (optional):
+   *          The outerWindowID of the content window containing the document that
+   *          we want to view the source of. Pass this if you want to attempt to
+   *          load the document source out of the network cache.
+   *        lineNumber (optional):
+   *          The line number to focus on once the source is loaded.
+   *
+   * @param aPageDescriptor (deprecated, optional)
+   *        Accepted for compatibility reasons, but is otherwise ignored.
+   * @param aDocument (deprecated, optional)
+   *        The content document we would like to view the source of. This
+   *        function will throw if aDocument is a CPOW.
+   * @param aLineNumber (deprecated, optional)
+   *        The line number to focus on once the source is loaded.
+   * @param aCallBack
+   *        A function accepting two arguments:
+   *          * result (true = success)
+   *          * data object
+   *        The function defaults to opening an internal viewer if external
+   *        viewing fails.
+   */
+  openInExternalEditor: function(aArgsOrURL, aPageDescriptor, aDocument,
+                                 aLineNumber, aCallBack) {
+    let data;
+    if (typeof aArgsOrURL == "string") {
+      Deprecated.warning("The arguments you're passing to " +
+                         "openInExternalEditor are using an out-of-date API.",
+                         "https://developer.mozilla.org/en-US/Add-ons/" +
+                         "Code_snippets/View_Source_for_XUL_Applications");
+      data = {
+        url: aArgsOrURL,
+        pageDescriptor: aPageDescriptor,
+        doc: aDocument,
+        lineNumber: aLineNumber
+      };
+    } else {
+      let { URL, outerWindowID, lineNumber } = aArgsOrURL;
+      data = {
+        url: URL,
+        lineNumber
+      };
+    }
 
     try {
       var editor = this.getExternalViewSourceEditor();
       if (!editor) {
         this.handleCallBack(aCallBack, false, data);
         return;
       }
 
       // make a uri
       var ios = Components.classes["@mozilla.org/network/io-service;1"]
                           .getService(Components.interfaces.nsIIOService);
       var charset = aDocument ? aDocument.characterSet : null;
-      var uri = ios.newURI(aURL, charset, null);
+      var uri = ios.newURI(data.url, charset, null);
       data.uri = uri;
 
       var path;
       var contentType = aDocument ? aDocument.contentType : null;
       if (uri.scheme == "file") {
         // it's a local file; we can open it directly
         path = uri.QueryInterface(Components.interfaces.nsIFileURL).file.path;
 
         var editorArgs = this.buildEditorArgs(path, data.lineNumber);
         editor.runw(false, editorArgs, editorArgs.length);
         this.handleCallBack(aCallBack, true, data);
       } else {
         // set up the progress listener with what we know so far
+        this.viewSourceProgressListener.contentLoaded = false;
         this.viewSourceProgressListener.editor = editor;
         this.viewSourceProgressListener.callBack = aCallBack;
         this.viewSourceProgressListener.data = data;
         if (!aPageDescriptor) {
           // without a page descriptor, loadPage has no chance of working. download the file.
           var file = this.getTemporaryFile(uri, aDocument, contentType);
           this.viewSourceProgressListener.file = file;
 
@@ -349,16 +404,21 @@ var gViewSourceUtils = {
           webNavigation.document.addEventListener("DOMContentLoaded",
                                                   this.onContentLoaded.bind(this));
         }
       }
       return 0;
     },
 
     onContentLoaded: function() {
+      // The progress listener may call this multiple times, so be sure we only
+      // run once.
+      if (this.contentLoaded) {
+        return;
+      }
       try {
         if (!this.file) {
           // it's not saved to file yet, it's in the webshell
 
           // get a temporary filename using the attributes from the data object that
           // openInExternalEditor gave us
           this.file = gViewSourceUtils.getTemporaryFile(this.data.uri, this.data.doc,
                                                         this.data.doc.contentType);
@@ -396,16 +456,17 @@ var gViewSourceUtils = {
             helperService.deleteTemporaryFileOnExit(this.file);
           }
         }
 
         var editorArgs = gViewSourceUtils.buildEditorArgs(this.file.path,
                                                           this.data.lineNumber);
         this.editor.runw(false, editorArgs, editorArgs.length);
 
+        this.contentLoaded = true;
         gViewSourceUtils.handleCallBack(this.callBack, true, this.data);
       } catch (ex) {
         // we failed loading it with the external editor.
         Components.utils.reportError(ex);
         gViewSourceUtils.handleCallBack(this.callBack, false, this.data);
       } finally {
         this.destroy();
       }