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.

     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 }

mercurial