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 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
7 Cu.import("resource://gre/modules/Services.jsm");
8 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
10 XPCOMUtils.defineLazyModuleGetter(this, "SocialService", "resource://gre/modules/SocialService.jsm");
11 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", "resource://gre/modules/PrivateBrowsingUtils.jsm");
13 this.EXPORTED_SYMBOLS = ["MozSocialAPI", "openChatWindow", "findChromeWindowForChats", "closeAllChatWindows"];
15 this.MozSocialAPI = {
16 _enabled: false,
17 _everEnabled: false,
18 set enabled(val) {
19 let enable = !!val;
20 if (enable == this._enabled) {
21 return;
22 }
23 this._enabled = enable;
25 if (enable) {
26 Services.obs.addObserver(injectController, "document-element-inserted", false);
28 if (!this._everEnabled) {
29 this._everEnabled = true;
30 Services.telemetry.getHistogramById("SOCIAL_ENABLED_ON_SESSION").add(true);
31 }
33 } else {
34 Services.obs.removeObserver(injectController, "document-element-inserted");
35 }
36 }
37 };
39 // Called on document-element-inserted, checks that the API should be injected,
40 // and then calls attachToWindow as appropriate
41 function injectController(doc, topic, data) {
42 try {
43 let window = doc.defaultView;
44 if (!window || PrivateBrowsingUtils.isWindowPrivate(window))
45 return;
47 // Do not attempt to load the API into about: error pages
48 if (doc.documentURIObject.scheme == "about") {
49 return;
50 }
52 let containingBrowser = window.QueryInterface(Ci.nsIInterfaceRequestor)
53 .getInterface(Ci.nsIWebNavigation)
54 .QueryInterface(Ci.nsIDocShell)
55 .chromeEventHandler;
56 // limit injecting into social panels or same-origin browser tabs if
57 // social.debug.injectIntoTabs is enabled
58 let allowTabs = false;
59 try {
60 allowTabs = containingBrowser.contentWindow == window &&
61 Services.prefs.getBoolPref("social.debug.injectIntoTabs");
62 } catch(e) {}
64 let origin = containingBrowser.getAttribute("origin");
65 if (!allowTabs && !origin) {
66 return;
67 }
69 // we always handle window.close on social content, even if they are not
70 // "enabled". "enabled" is about the worker state and a provider may
71 // still be in e.g. the share panel without having their worker enabled.
72 handleWindowClose(window);
74 SocialService.getProvider(doc.nodePrincipal.origin, function(provider) {
75 if (provider && provider.enabled) {
76 attachToWindow(provider, window);
77 }
78 });
79 } catch(e) {
80 Cu.reportError("MozSocialAPI injectController: unable to attachToWindow for " + doc.location + ": " + e);
81 }
82 }
84 // Loads mozSocial support functions associated with provider into targetWindow
85 function attachToWindow(provider, targetWindow) {
86 // If the loaded document isn't from the provider's origin (or a protocol
87 // that inherits the principal), don't attach the mozSocial API.
88 let targetDocURI = targetWindow.document.documentURIObject;
89 if (!provider.isSameOrigin(targetDocURI)) {
90 let msg = "MozSocialAPI: not attaching mozSocial API for " + provider.origin +
91 " to " + targetDocURI.spec + " since origins differ."
92 Services.console.logStringMessage(msg);
93 return;
94 }
96 let port = provider.workerURL ? provider.getWorkerPort(targetWindow) : null;
98 let mozSocialObj = {
99 // Use a method for backwards compat with existing providers, but we
100 // should deprecate this in favor of a simple .port getter.
101 getWorker: {
102 enumerable: true,
103 configurable: true,
104 writable: true,
105 value: function() {
106 return {
107 port: port,
108 __exposedProps__: {
109 port: "r"
110 }
111 };
112 }
113 },
114 hasBeenIdleFor: {
115 enumerable: true,
116 configurable: true,
117 writable: true,
118 value: function() {
119 return false;
120 }
121 },
122 openChatWindow: {
123 enumerable: true,
124 configurable: true,
125 writable: true,
126 value: function(toURL, callback) {
127 let url = targetWindow.document.documentURIObject.resolve(toURL);
128 openChatWindow(getChromeWindow(targetWindow), provider, url, callback);
129 }
130 },
131 openPanel: {
132 enumerable: true,
133 configurable: true,
134 writable: true,
135 value: function(toURL, offset, callback) {
136 let chromeWindow = getChromeWindow(targetWindow);
137 if (!chromeWindow.SocialFlyout)
138 return;
139 let url = targetWindow.document.documentURIObject.resolve(toURL);
140 if (!provider.isSameOrigin(url))
141 return;
142 chromeWindow.SocialFlyout.open(url, offset, callback);
143 }
144 },
145 closePanel: {
146 enumerable: true,
147 configurable: true,
148 writable: true,
149 value: function(toURL, offset, callback) {
150 let chromeWindow = getChromeWindow(targetWindow);
151 if (!chromeWindow.SocialFlyout || !chromeWindow.SocialFlyout.panel)
152 return;
153 chromeWindow.SocialFlyout.panel.hidePopup();
154 }
155 },
156 // allow a provider to share to other providers through the browser
157 share: {
158 enumerable: true,
159 configurable: true,
160 writable: true,
161 value: function(data) {
162 let chromeWindow = getChromeWindow(targetWindow);
163 if (!chromeWindow.SocialShare || chromeWindow.SocialShare.shareButton.hidden)
164 throw new Error("Share is unavailable");
165 // ensure user action initates the share
166 let dwu = chromeWindow.QueryInterface(Ci.nsIInterfaceRequestor)
167 .getInterface(Ci.nsIDOMWindowUtils);
168 if (!dwu.isHandlingUserInput)
169 throw new Error("Attempt to share without user input");
171 // limit to a few params we want to support for now
172 let dataOut = {};
173 for (let sub of ["url", "title", "description", "source"]) {
174 dataOut[sub] = data[sub];
175 }
176 if (data.image)
177 dataOut.previews = [data.image];
179 chromeWindow.SocialShare.sharePage(null, dataOut);
180 }
181 },
182 getAttention: {
183 enumerable: true,
184 configurable: true,
185 writable: true,
186 value: function() {
187 getChromeWindow(targetWindow).getAttention();
188 }
189 },
190 isVisible: {
191 enumerable: true,
192 configurable: true,
193 get: function() {
194 return targetWindow.QueryInterface(Ci.nsIInterfaceRequestor)
195 .getInterface(Ci.nsIWebNavigation)
196 .QueryInterface(Ci.nsIDocShell).isActive;
197 }
198 }
199 };
201 let contentObj = Cu.createObjectIn(targetWindow);
202 Object.defineProperties(contentObj, mozSocialObj);
203 Cu.makeObjectPropsNormal(contentObj);
205 targetWindow.navigator.wrappedJSObject.__defineGetter__("mozSocial", function() {
206 // We do this in a getter, so that we create these objects
207 // only on demand (this is a potential concern, since
208 // otherwise we might add one per iframe, and keep them
209 // alive for as long as the window is alive).
210 delete targetWindow.navigator.wrappedJSObject.mozSocial;
211 return targetWindow.navigator.wrappedJSObject.mozSocial = contentObj;
212 });
214 if (port) {
215 targetWindow.addEventListener("unload", function () {
216 // We want to close the port, but also want the target window to be
217 // able to use the port during an unload event they setup - so we
218 // set a timer which will fire after the unload events have all fired.
219 schedule(function () { port.close(); });
220 });
221 }
222 }
224 function handleWindowClose(targetWindow) {
225 // We allow window.close() to close the panel, so add an event handler for
226 // this, then cancel the event (so the window itself doesn't die) and
227 // close the panel instead.
228 // However, this is typically affected by the dom.allow_scripts_to_close_windows
229 // preference, but we can avoid that check by setting a flag on the window.
230 let dwu = targetWindow.QueryInterface(Ci.nsIInterfaceRequestor)
231 .getInterface(Ci.nsIDOMWindowUtils);
232 dwu.allowScriptsToClose();
234 targetWindow.addEventListener("DOMWindowClose", function _mozSocialDOMWindowClose(evt) {
235 let elt = targetWindow.QueryInterface(Ci.nsIInterfaceRequestor)
236 .getInterface(Ci.nsIWebNavigation)
237 .QueryInterface(Ci.nsIDocShell)
238 .chromeEventHandler;
239 while (elt) {
240 if (elt.localName == "panel") {
241 elt.hidePopup();
242 break;
243 } else if (elt.localName == "chatbox") {
244 elt.close();
245 break;
246 }
247 elt = elt.parentNode;
248 }
249 // preventDefault stops the default window.close() function being called,
250 // which doesn't actually close anything but causes things to get into
251 // a bad state (an internal 'closed' flag is set and debug builds start
252 // asserting as the window is used.).
253 // None of the windows we inject this API into are suitable for this
254 // default close behaviour, so even if we took no action above, we avoid
255 // the default close from doing anything.
256 evt.preventDefault();
257 }, true);
258 }
260 function schedule(callback) {
261 Services.tm.mainThread.dispatch(callback, Ci.nsIThread.DISPATCH_NORMAL);
262 }
264 function getChromeWindow(contentWin) {
265 return contentWin.QueryInterface(Ci.nsIInterfaceRequestor)
266 .getInterface(Ci.nsIWebNavigation)
267 .QueryInterface(Ci.nsIDocShellTreeItem)
268 .rootTreeItem
269 .QueryInterface(Ci.nsIInterfaceRequestor)
270 .getInterface(Ci.nsIDOMWindow);
271 }
273 function isWindowGoodForChats(win) {
274 return win.SocialChatBar
275 && win.SocialChatBar.isAvailable
276 && !PrivateBrowsingUtils.isWindowPrivate(win);
277 }
279 function findChromeWindowForChats(preferredWindow) {
280 if (preferredWindow && isWindowGoodForChats(preferredWindow))
281 return preferredWindow;
282 // no good - we just use the "most recent" browser window which can host
283 // chats (we used to try and "group" all chats in the same browser window,
284 // but that didn't work out so well - see bug 835111
286 // Try first the most recent window as getMostRecentWindow works
287 // even on platforms where getZOrderDOMWindowEnumerator is broken
288 // (ie. Linux). This will handle most cases, but won't work if the
289 // foreground window is a popup.
291 let mostRecent = Services.wm.getMostRecentWindow("navigator:browser");
292 if (isWindowGoodForChats(mostRecent))
293 return mostRecent;
295 let topMost, enumerator;
296 // *sigh* - getZOrderDOMWindowEnumerator is broken except on Mac and
297 // Windows. We use BROKEN_WM_Z_ORDER as that is what some other code uses
298 // and a few bugs recommend searching mxr for this symbol to identify the
299 // workarounds - we want this code to be hit in such searches.
300 let os = Services.appinfo.OS;
301 const BROKEN_WM_Z_ORDER = os != "WINNT" && os != "Darwin";
302 if (BROKEN_WM_Z_ORDER) {
303 // this is oldest to newest and no way to change the order.
304 enumerator = Services.wm.getEnumerator("navigator:browser");
305 } else {
306 // here we explicitly ask for bottom-to-top so we can use the same logic
307 // where BROKEN_WM_Z_ORDER is true.
308 enumerator = Services.wm.getZOrderDOMWindowEnumerator("navigator:browser", false);
309 }
310 while (enumerator.hasMoreElements()) {
311 let win = enumerator.getNext();
312 if (!win.closed && isWindowGoodForChats(win))
313 topMost = win;
314 }
315 return topMost;
316 }
318 this.openChatWindow =
319 function openChatWindow(chromeWindow, provider, url, callback, mode) {
320 chromeWindow = findChromeWindowForChats(chromeWindow);
321 if (!chromeWindow) {
322 Cu.reportError("Failed to open a social chat window - no host window could be found.");
323 return;
324 }
325 let fullURI = provider.resolveUri(url);
326 if (!provider.isSameOrigin(fullURI)) {
327 Cu.reportError("Failed to open a social chat window - the requested URL is not the same origin as the provider.");
328 return;
329 }
330 if (!chromeWindow.SocialChatBar.openChat(provider, fullURI.spec, callback, mode)) {
331 Cu.reportError("Failed to open a social chat window - the chatbar is not available in the target window.");
332 return;
333 }
334 // getAttention is ignored if the target window is already foreground, so
335 // we can call it unconditionally.
336 chromeWindow.getAttention();
337 }
339 this.closeAllChatWindows =
340 function closeAllChatWindows(provider) {
341 // close all attached chat windows
342 let winEnum = Services.wm.getEnumerator("navigator:browser");
343 while (winEnum.hasMoreElements()) {
344 let win = winEnum.getNext();
345 if (!win.SocialChatBar)
346 continue;
347 let chats = [c for (c of win.SocialChatBar.chatbar.children) if (c.content.getAttribute("origin") == provider.origin)];
348 [c.close() for (c of chats)];
349 }
351 // close all standalone chat windows
352 winEnum = Services.wm.getEnumerator("Social:Chat");
353 while (winEnum.hasMoreElements()) {
354 let win = winEnum.getNext();
355 if (win.closed)
356 continue;
357 let origin = win.document.getElementById("chatter").content.getAttribute("origin");
358 if (provider.origin == origin)
359 win.close();
360 }
361 }