toolkit/components/social/MozSocialAPI.jsm

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

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.

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

mercurial