toolkit/components/viewsource/content/viewSourceUtils.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 // -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
michael@0 2
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 /*
michael@0 8 * To keep the global namespace safe, don't define global variables and
michael@0 9 * functions in this file.
michael@0 10 *
michael@0 11 * This file silently depends on contentAreaUtils.js for
michael@0 12 * getDefaultFileName, getNormalizedLeafName and getDefaultExtension
michael@0 13 */
michael@0 14
michael@0 15 var gViewSourceUtils = {
michael@0 16
michael@0 17 mnsIWebBrowserPersist: Components.interfaces.nsIWebBrowserPersist,
michael@0 18 mnsIWebProgress: Components.interfaces.nsIWebProgress,
michael@0 19 mnsIWebPageDescriptor: Components.interfaces.nsIWebPageDescriptor,
michael@0 20
michael@0 21 // Opens view source
michael@0 22 viewSource: function(aURL, aPageDescriptor, aDocument, aLineNumber)
michael@0 23 {
michael@0 24 var prefs = Components.classes["@mozilla.org/preferences-service;1"]
michael@0 25 .getService(Components.interfaces.nsIPrefBranch);
michael@0 26 if (prefs.getBoolPref("view_source.editor.external"))
michael@0 27 this.openInExternalEditor(aURL, aPageDescriptor, aDocument, aLineNumber);
michael@0 28 else
michael@0 29 this.openInInternalViewer(aURL, aPageDescriptor, aDocument, aLineNumber);
michael@0 30 },
michael@0 31
michael@0 32 // Opens the interval view source viewer
michael@0 33 openInInternalViewer: function(aURL, aPageDescriptor, aDocument, aLineNumber)
michael@0 34 {
michael@0 35 // try to open a view-source window while inheriting the charset (if any)
michael@0 36 var charset = null;
michael@0 37 var isForcedCharset = false;
michael@0 38 if (aDocument) {
michael@0 39 charset = "charset=" + aDocument.characterSet;
michael@0 40 try {
michael@0 41 isForcedCharset =
michael@0 42 aDocument.defaultView
michael@0 43 .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
michael@0 44 .getInterface(Components.interfaces.nsIDOMWindowUtils)
michael@0 45 .docCharsetIsForced;
michael@0 46 } catch (ex) {
michael@0 47 }
michael@0 48 }
michael@0 49 openDialog("chrome://global/content/viewSource.xul",
michael@0 50 "_blank",
michael@0 51 "all,dialog=no",
michael@0 52 aURL, charset, aPageDescriptor, aLineNumber, isForcedCharset);
michael@0 53 },
michael@0 54
michael@0 55 buildEditorArgs: function(aPath, aLineNumber) {
michael@0 56 // Determine the command line arguments to pass to the editor.
michael@0 57 // We currently support a %LINE% placeholder which is set to the passed
michael@0 58 // line number (or to 0 if there's none)
michael@0 59 var editorArgs = [];
michael@0 60 var prefs = Components.classes["@mozilla.org/preferences-service;1"]
michael@0 61 .getService(Components.interfaces.nsIPrefBranch);
michael@0 62 var args = prefs.getCharPref("view_source.editor.args");
michael@0 63 if (args) {
michael@0 64 args = args.replace("%LINE%", aLineNumber || "0");
michael@0 65 // add the arguments to the array (keeping quoted strings intact)
michael@0 66 const argumentRE = /"([^"]+)"|(\S+)/g;
michael@0 67 while (argumentRE.test(args))
michael@0 68 editorArgs.push(RegExp.$1 || RegExp.$2);
michael@0 69 }
michael@0 70 editorArgs.push(aPath);
michael@0 71 return editorArgs;
michael@0 72 },
michael@0 73
michael@0 74 // aCallBack is a function accepting two arguments - result (true=success) and a data object
michael@0 75 // It defaults to openInInternalViewer if undefined.
michael@0 76 openInExternalEditor: function(aURL, aPageDescriptor, aDocument, aLineNumber, aCallBack)
michael@0 77 {
michael@0 78 var data = {url: aURL, pageDescriptor: aPageDescriptor, doc: aDocument,
michael@0 79 lineNumber: aLineNumber};
michael@0 80
michael@0 81 try {
michael@0 82 var editor = this.getExternalViewSourceEditor();
michael@0 83 if (!editor) {
michael@0 84 this.handleCallBack(aCallBack, false, data);
michael@0 85 return;
michael@0 86 }
michael@0 87
michael@0 88 // make a uri
michael@0 89 var ios = Components.classes["@mozilla.org/network/io-service;1"]
michael@0 90 .getService(Components.interfaces.nsIIOService);
michael@0 91 var charset = aDocument ? aDocument.characterSet : null;
michael@0 92 var uri = ios.newURI(aURL, charset, null);
michael@0 93 data.uri = uri;
michael@0 94
michael@0 95 var path;
michael@0 96 var contentType = aDocument ? aDocument.contentType : null;
michael@0 97 if (uri.scheme == "file") {
michael@0 98 // it's a local file; we can open it directly
michael@0 99 path = uri.QueryInterface(Components.interfaces.nsIFileURL).file.path;
michael@0 100
michael@0 101 var editorArgs = this.buildEditorArgs(path, data.lineNumber);
michael@0 102 editor.runw(false, editorArgs, editorArgs.length);
michael@0 103 this.handleCallBack(aCallBack, true, data);
michael@0 104 } else {
michael@0 105 // set up the progress listener with what we know so far
michael@0 106 this.viewSourceProgressListener.editor = editor;
michael@0 107 this.viewSourceProgressListener.callBack = aCallBack;
michael@0 108 this.viewSourceProgressListener.data = data;
michael@0 109 if (!aPageDescriptor) {
michael@0 110 // without a page descriptor, loadPage has no chance of working. download the file.
michael@0 111 var file = this.getTemporaryFile(uri, aDocument, contentType);
michael@0 112 this.viewSourceProgressListener.file = file;
michael@0 113
michael@0 114 let fromPrivateWindow = false;
michael@0 115 if (aDocument) {
michael@0 116 try {
michael@0 117 fromPrivateWindow =
michael@0 118 aDocument.defaultView
michael@0 119 .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
michael@0 120 .getInterface(Components.interfaces.nsIWebNavigation)
michael@0 121 .QueryInterface(Components.interfaces.nsILoadContext)
michael@0 122 .usePrivateBrowsing;
michael@0 123 } catch (e) {
michael@0 124 }
michael@0 125 }
michael@0 126
michael@0 127 var webBrowserPersist = Components
michael@0 128 .classes["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
michael@0 129 .createInstance(this.mnsIWebBrowserPersist);
michael@0 130 // the default setting is to not decode. we need to decode.
michael@0 131 webBrowserPersist.persistFlags = this.mnsIWebBrowserPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES;
michael@0 132 webBrowserPersist.progressListener = this.viewSourceProgressListener;
michael@0 133 webBrowserPersist.savePrivacyAwareURI(uri, null, null, null, null, file, fromPrivateWindow);
michael@0 134
michael@0 135 let helperService = Components.classes["@mozilla.org/uriloader/external-helper-app-service;1"]
michael@0 136 .getService(Components.interfaces.nsPIExternalAppLauncher);
michael@0 137 if (fromPrivateWindow) {
michael@0 138 // register the file to be deleted when possible
michael@0 139 helperService.deleteTemporaryPrivateFileWhenPossible(file);
michael@0 140 } else {
michael@0 141 // register the file to be deleted on app exit
michael@0 142 helperService.deleteTemporaryFileOnExit(file);
michael@0 143 }
michael@0 144 } else {
michael@0 145 // we'll use nsIWebPageDescriptor to get the source because it may
michael@0 146 // not have to refetch the file from the server
michael@0 147 // XXXbz this is so broken... This code doesn't set up this docshell
michael@0 148 // at all correctly; if somehow the view-source stuff managed to
michael@0 149 // execute script we'd be in big trouble here, I suspect.
michael@0 150 var webShell = Components.classes["@mozilla.org/docshell;1"].createInstance();
michael@0 151 webShell.QueryInterface(Components.interfaces.nsIBaseWindow).create();
michael@0 152 this.viewSourceProgressListener.webShell = webShell;
michael@0 153 var progress = webShell.QueryInterface(this.mnsIWebProgress);
michael@0 154 progress.addProgressListener(this.viewSourceProgressListener,
michael@0 155 this.mnsIWebProgress.NOTIFY_STATE_DOCUMENT);
michael@0 156 var pageLoader = webShell.QueryInterface(this.mnsIWebPageDescriptor);
michael@0 157 pageLoader.loadPage(aPageDescriptor, this.mnsIWebPageDescriptor.DISPLAY_AS_SOURCE);
michael@0 158 }
michael@0 159 }
michael@0 160 } catch (ex) {
michael@0 161 // we failed loading it with the external editor.
michael@0 162 Components.utils.reportError(ex);
michael@0 163 this.handleCallBack(aCallBack, false, data);
michael@0 164 return;
michael@0 165 }
michael@0 166 },
michael@0 167
michael@0 168 // Default callback - opens the internal viewer if the external editor failed
michael@0 169 internalViewerFallback: function(result, data)
michael@0 170 {
michael@0 171 if (!result) {
michael@0 172 this.openInInternalViewer(data.url, data.pageDescriptor, data.doc, data.lineNumber);
michael@0 173 }
michael@0 174 },
michael@0 175
michael@0 176 // Calls the callback, keeping in mind undefined or null values.
michael@0 177 handleCallBack: function(aCallBack, result, data)
michael@0 178 {
michael@0 179 // ifcallback is undefined, default to the internal viewer
michael@0 180 if (aCallBack === undefined) {
michael@0 181 this.internalViewerFallback(result, data);
michael@0 182 } else if (aCallBack) {
michael@0 183 aCallBack(result, data);
michael@0 184 }
michael@0 185 },
michael@0 186
michael@0 187 // Returns nsIProcess of the external view source editor or null
michael@0 188 getExternalViewSourceEditor: function()
michael@0 189 {
michael@0 190 try {
michael@0 191 let viewSourceAppPath =
michael@0 192 Components.classes["@mozilla.org/preferences-service;1"]
michael@0 193 .getService(Components.interfaces.nsIPrefBranch)
michael@0 194 .getComplexValue("view_source.editor.path",
michael@0 195 Components.interfaces.nsIFile);
michael@0 196 let editor = Components.classes['@mozilla.org/process/util;1']
michael@0 197 .createInstance(Components.interfaces.nsIProcess);
michael@0 198 editor.init(viewSourceAppPath);
michael@0 199
michael@0 200 return editor;
michael@0 201 }
michael@0 202 catch (ex) {
michael@0 203 Components.utils.reportError(ex);
michael@0 204 }
michael@0 205
michael@0 206 return null;
michael@0 207 },
michael@0 208
michael@0 209 viewSourceProgressListener: {
michael@0 210
michael@0 211 mnsIWebProgressListener: Components.interfaces.nsIWebProgressListener,
michael@0 212
michael@0 213 QueryInterface: function(aIID) {
michael@0 214 if (aIID.equals(this.mnsIWebProgressListener) ||
michael@0 215 aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
michael@0 216 aIID.equals(Components.interfaces.nsISupports))
michael@0 217 return this;
michael@0 218 throw Components.results.NS_NOINTERFACE;
michael@0 219 },
michael@0 220
michael@0 221 destroy: function() {
michael@0 222 if (this.webShell) {
michael@0 223 this.webShell.QueryInterface(Components.interfaces.nsIBaseWindow).destroy();
michael@0 224 }
michael@0 225 this.webShell = null;
michael@0 226 this.editor = null;
michael@0 227 this.callBack = null;
michael@0 228 this.data = null;
michael@0 229 this.file = null;
michael@0 230 },
michael@0 231
michael@0 232 // This listener is used both for tracking the progress of an HTML parse
michael@0 233 // in one case and for tracking the progress of nsIWebBrowserPersist in
michael@0 234 // another case.
michael@0 235 onStateChange: function(aProgress, aRequest, aFlag, aStatus) {
michael@0 236 // once it's done loading...
michael@0 237 if ((aFlag & this.mnsIWebProgressListener.STATE_STOP) && aStatus == 0) {
michael@0 238 if (!this.webShell) {
michael@0 239 // We aren't waiting for the parser. Instead, we are waiting for
michael@0 240 // an nsIWebBrowserPersist.
michael@0 241 this.onContentLoaded();
michael@0 242 return 0;
michael@0 243 }
michael@0 244 var webNavigation = this.webShell.QueryInterface(Components.interfaces.nsIWebNavigation);
michael@0 245 if (webNavigation.document.readyState == "complete") {
michael@0 246 // This branch is probably never taken. Including it for completeness.
michael@0 247 this.onContentLoaded();
michael@0 248 } else {
michael@0 249 webNavigation.document.addEventListener("DOMContentLoaded",
michael@0 250 this.onContentLoaded.bind(this));
michael@0 251 }
michael@0 252 }
michael@0 253 return 0;
michael@0 254 },
michael@0 255
michael@0 256 onContentLoaded: function() {
michael@0 257 try {
michael@0 258 if (!this.file) {
michael@0 259 // it's not saved to file yet, it's in the webshell
michael@0 260
michael@0 261 // get a temporary filename using the attributes from the data object that
michael@0 262 // openInExternalEditor gave us
michael@0 263 this.file = gViewSourceUtils.getTemporaryFile(this.data.uri, this.data.doc,
michael@0 264 this.data.doc.contentType);
michael@0 265
michael@0 266 // we have to convert from the source charset.
michael@0 267 var webNavigation = this.webShell.QueryInterface(Components.interfaces.nsIWebNavigation);
michael@0 268 var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
michael@0 269 .createInstance(Components.interfaces.nsIFileOutputStream);
michael@0 270 foStream.init(this.file, 0x02 | 0x08 | 0x20, -1, 0); // write | create | truncate
michael@0 271 var coStream = Components.classes["@mozilla.org/intl/converter-output-stream;1"]
michael@0 272 .createInstance(Components.interfaces.nsIConverterOutputStream);
michael@0 273 coStream.init(foStream, this.data.doc.characterSet, 0, null);
michael@0 274
michael@0 275 // write the source to the file
michael@0 276 coStream.writeString(webNavigation.document.body.textContent);
michael@0 277
michael@0 278 // clean up
michael@0 279 coStream.close();
michael@0 280 foStream.close();
michael@0 281
michael@0 282 let fromPrivateWindow =
michael@0 283 this.data.doc.defaultView
michael@0 284 .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
michael@0 285 .getInterface(Components.interfaces.nsIWebNavigation)
michael@0 286 .QueryInterface(Components.interfaces.nsILoadContext)
michael@0 287 .usePrivateBrowsing;
michael@0 288
michael@0 289 let helperService = Components.classes["@mozilla.org/uriloader/external-helper-app-service;1"]
michael@0 290 .getService(Components.interfaces.nsPIExternalAppLauncher);
michael@0 291 if (fromPrivateWindow) {
michael@0 292 // register the file to be deleted when possible
michael@0 293 helperService.deleteTemporaryPrivateFileWhenPossible(this.file);
michael@0 294 } else {
michael@0 295 // register the file to be deleted on app exit
michael@0 296 helperService.deleteTemporaryFileOnExit(this.file);
michael@0 297 }
michael@0 298 }
michael@0 299
michael@0 300 var editorArgs = gViewSourceUtils.buildEditorArgs(this.file.path,
michael@0 301 this.data.lineNumber);
michael@0 302 this.editor.runw(false, editorArgs, editorArgs.length);
michael@0 303
michael@0 304 gViewSourceUtils.handleCallBack(this.callBack, true, this.data);
michael@0 305 } catch (ex) {
michael@0 306 // we failed loading it with the external editor.
michael@0 307 Components.utils.reportError(ex);
michael@0 308 gViewSourceUtils.handleCallBack(this.callBack, false, this.data);
michael@0 309 } finally {
michael@0 310 this.destroy();
michael@0 311 }
michael@0 312 },
michael@0 313
michael@0 314 onLocationChange: function() {return 0;},
michael@0 315 onProgressChange: function() {return 0;},
michael@0 316 onStatusChange: function() {return 0;},
michael@0 317 onSecurityChange: function() {return 0;},
michael@0 318
michael@0 319 webShell: null,
michael@0 320 editor: null,
michael@0 321 callBack: null,
michael@0 322 data: null,
michael@0 323 file: null
michael@0 324 },
michael@0 325
michael@0 326 // returns an nsIFile for the passed document in the system temp directory
michael@0 327 getTemporaryFile: function(aURI, aDocument, aContentType) {
michael@0 328 // include contentAreaUtils.js in our own context when we first need it
michael@0 329 if (!this._caUtils) {
michael@0 330 var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
michael@0 331 .getService(Components.interfaces.mozIJSSubScriptLoader);
michael@0 332 this._caUtils = {};
michael@0 333 scriptLoader.loadSubScript("chrome://global/content/contentAreaUtils.js", this._caUtils);
michael@0 334 }
michael@0 335
michael@0 336 var fileLocator = Components.classes["@mozilla.org/file/directory_service;1"]
michael@0 337 .getService(Components.interfaces.nsIProperties);
michael@0 338 var tempFile = fileLocator.get("TmpD", Components.interfaces.nsIFile);
michael@0 339 var fileName = this._caUtils.getDefaultFileName(null, aURI, aDocument, aContentType);
michael@0 340 var extension = this._caUtils.getDefaultExtension(fileName, aURI, aContentType);
michael@0 341 var leafName = this._caUtils.getNormalizedLeafName(fileName, extension);
michael@0 342 tempFile.append(leafName);
michael@0 343 return tempFile;
michael@0 344 }
michael@0 345 }

mercurial