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