browser/base/content/browser-social.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     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
     3 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
     5 // the "exported" symbols
     6 let SocialUI,
     7     SocialChatBar,
     8     SocialFlyout,
     9     SocialMarks,
    10     SocialShare,
    11     SocialSidebar,
    12     SocialStatus;
    14 (function() {
    16 // The minimum sizes for the auto-resize panel code.
    17 const PANEL_MIN_HEIGHT = 100;
    18 const PANEL_MIN_WIDTH = 330;
    20 XPCOMUtils.defineLazyModuleGetter(this, "SharedFrame",
    21   "resource:///modules/SharedFrame.jsm");
    23 XPCOMUtils.defineLazyGetter(this, "OpenGraphBuilder", function() {
    24   let tmp = {};
    25   Cu.import("resource:///modules/Social.jsm", tmp);
    26   return tmp.OpenGraphBuilder;
    27 });
    29 XPCOMUtils.defineLazyGetter(this, "DynamicResizeWatcher", function() {
    30   let tmp = {};
    31   Cu.import("resource:///modules/Social.jsm", tmp);
    32   return tmp.DynamicResizeWatcher;
    33 });
    35 XPCOMUtils.defineLazyGetter(this, "sizeSocialPanelToContent", function() {
    36   let tmp = {};
    37   Cu.import("resource:///modules/Social.jsm", tmp);
    38   return tmp.sizeSocialPanelToContent;
    39 });
    41 XPCOMUtils.defineLazyGetter(this, "CreateSocialStatusWidget", function() {
    42   let tmp = {};
    43   Cu.import("resource:///modules/Social.jsm", tmp);
    44   return tmp.CreateSocialStatusWidget;
    45 });
    47 XPCOMUtils.defineLazyGetter(this, "CreateSocialMarkWidget", function() {
    48   let tmp = {};
    49   Cu.import("resource:///modules/Social.jsm", tmp);
    50   return tmp.CreateSocialMarkWidget;
    51 });
    53 SocialUI = {
    54   _initialized: false,
    56   // Called on delayed startup to initialize the UI
    57   init: function SocialUI_init() {
    58     if (this._initialized) {
    59       return;
    60     }
    62     Services.obs.addObserver(this, "social:ambient-notification-changed", false);
    63     Services.obs.addObserver(this, "social:profile-changed", false);
    64     Services.obs.addObserver(this, "social:frameworker-error", false);
    65     Services.obs.addObserver(this, "social:providers-changed", false);
    66     Services.obs.addObserver(this, "social:provider-reload", false);
    67     Services.obs.addObserver(this, "social:provider-enabled", false);
    68     Services.obs.addObserver(this, "social:provider-disabled", false);
    70     Services.prefs.addObserver("social.toast-notifications.enabled", this, false);
    72     gBrowser.addEventListener("ActivateSocialFeature", this._activationEventHandler.bind(this), true, true);
    73     document.getElementById("PanelUI-popup").addEventListener("popupshown", SocialMarks.updatePanelButtons, true);
    75     // menupopups that list social providers. we only populate them when shown,
    76     // and if it has not been done already.
    77     document.getElementById("viewSidebarMenu").addEventListener("popupshowing", SocialSidebar.populateSidebarMenu, true);
    78     document.getElementById("social-statusarea-popup").addEventListener("popupshowing", SocialSidebar.populateSidebarMenu, true);
    80     Social.init().then((update) => {
    81       if (update)
    82         this._providersChanged();
    83       // handle SessionStore for the sidebar state
    84       SocialSidebar.restoreWindowState();
    85     });
    87     this._initialized = true;
    88   },
    90   // Called on window unload
    91   uninit: function SocialUI_uninit() {
    92     if (!this._initialized) {
    93       return;
    94     }
    95     SocialSidebar.saveWindowState();
    97     Services.obs.removeObserver(this, "social:ambient-notification-changed");
    98     Services.obs.removeObserver(this, "social:profile-changed");
    99     Services.obs.removeObserver(this, "social:frameworker-error");
   100     Services.obs.removeObserver(this, "social:providers-changed");
   101     Services.obs.removeObserver(this, "social:provider-reload");
   102     Services.obs.removeObserver(this, "social:provider-enabled");
   103     Services.obs.removeObserver(this, "social:provider-disabled");
   105     Services.prefs.removeObserver("social.toast-notifications.enabled", this);
   107     document.getElementById("PanelUI-popup").removeEventListener("popupshown", SocialMarks.updatePanelButtons, true);
   108     document.getElementById("viewSidebarMenu").removeEventListener("popupshowing", SocialSidebar.populateSidebarMenu, true);
   109     document.getElementById("social-statusarea-popup").removeEventListener("popupshowing", SocialSidebar.populateSidebarMenu, true);
   111     this._initialized = false;
   112   },
   114   observe: function SocialUI_observe(subject, topic, data) {
   115     // Exceptions here sometimes don't get reported properly, report them
   116     // manually :(
   117     try {
   118       switch (topic) {
   119         case "social:provider-enabled":
   120           SocialMarks.populateToolbarPalette();
   121           SocialStatus.populateToolbarPalette();
   122           break;
   123         case "social:provider-disabled":
   124           SocialMarks.removeProvider(data);
   125           SocialStatus.removeProvider(data);
   126           SocialSidebar.disableProvider(data);
   127           break;
   128         case "social:provider-reload":
   129           SocialStatus.reloadProvider(data);
   130           // if the reloaded provider is our current provider, fall through
   131           // to social:providers-changed so the ui will be reset
   132           if (!SocialSidebar.provider || SocialSidebar.provider.origin != data)
   133             return;
   134           // currently only the sidebar and flyout have a selected provider.
   135           // sidebar provider has changed (possibly to null), ensure the content
   136           // is unloaded and the frames are reset, they will be loaded in
   137           // providers-changed below if necessary.
   138           SocialSidebar.unloadSidebar();
   139           SocialFlyout.unload();
   140           // fall through to providers-changed to ensure the reloaded provider
   141           // is correctly reflected in any UI and the multi-provider menu
   142         case "social:providers-changed":
   143           this._providersChanged();
   144           break;
   146         // Provider-specific notifications
   147         case "social:ambient-notification-changed":
   148           SocialStatus.updateButton(data);
   149           break;
   150         case "social:profile-changed":
   151           // make sure anything that happens here only affects the provider for
   152           // which the profile is changing, and that anything we call actually
   153           // needs to change based on profile data.
   154           SocialStatus.updateButton(data);
   155           break;
   156         case "social:frameworker-error":
   157           if (this.enabled && SocialSidebar.provider && SocialSidebar.provider.origin == data) {
   158             SocialSidebar.setSidebarErrorMessage();
   159           }
   160           break;
   162         case "nsPref:changed":
   163           if (data == "social.toast-notifications.enabled") {
   164             SocialSidebar.updateToggleNotifications();
   165           }
   166           break;
   167       }
   168     } catch (e) {
   169       Components.utils.reportError(e + "\n" + e.stack);
   170       throw e;
   171     }
   172   },
   174   _providersChanged: function() {
   175     SocialSidebar.clearProviderMenus();
   176     SocialSidebar.update();
   177     SocialChatBar.update();
   178     SocialShare.populateProviderMenu();
   179     SocialStatus.populateToolbarPalette();
   180     SocialMarks.populateToolbarPalette();
   181     SocialShare.update();
   182   },
   184   // This handles "ActivateSocialFeature" events fired against content documents
   185   // in this window.
   186   _activationEventHandler: function SocialUI_activationHandler(e) {
   187     let targetDoc;
   188     let node;
   189     if (e.target instanceof HTMLDocument) {
   190       // version 0 support
   191       targetDoc = e.target;
   192       node = targetDoc.documentElement
   193     } else {
   194       targetDoc = e.target.ownerDocument;
   195       node = e.target;
   196     }
   197     if (!(targetDoc instanceof HTMLDocument))
   198       return;
   200     // Ignore events fired in background tabs or iframes
   201     if (targetDoc.defaultView != content)
   202       return;
   204     // If we are in PB mode, we silently do nothing (bug 829404 exists to
   205     // do something sensible here...)
   206     if (PrivateBrowsingUtils.isWindowPrivate(window))
   207       return;
   209     // If the last event was received < 1s ago, ignore this one
   210     let now = Date.now();
   211     if (now - Social.lastEventReceived < 1000)
   212       return;
   213     Social.lastEventReceived = now;
   215     // We only want to activate if it is as a result of user input.
   216     let dwu = window.QueryInterface(Ci.nsIInterfaceRequestor)
   217                     .getInterface(Ci.nsIDOMWindowUtils);
   218     if (!dwu.isHandlingUserInput) {
   219       Cu.reportError("attempt to activate provider without user input from " + targetDoc.nodePrincipal.origin);
   220       return;
   221     }
   223     let data = node.getAttribute("data-service");
   224     if (data) {
   225       try {
   226         data = JSON.parse(data);
   227       } catch(e) {
   228         Cu.reportError("Social Service manifest parse error: "+e);
   229         return;
   230       }
   231     }
   232     Social.installProvider(targetDoc, data, function(manifest) {
   233       Social.activateFromOrigin(manifest.origin, function(provider) {
   234         if (provider.sidebarURL) {
   235           SocialSidebar.show(provider.origin);
   236         }
   237         if (provider.postActivationURL) {
   238           openUILinkIn(provider.postActivationURL, "tab");
   239         }
   240       });
   241     });
   242   },
   244   showLearnMore: function() {
   245     let url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "social-api";
   246     openUILinkIn(url, "tab");
   247   },
   249   closeSocialPanelForLinkTraversal: function (target, linkNode) {
   250     // No need to close the panel if this traversal was not retargeted
   251     if (target == "" || target == "_self")
   252       return;
   254     // Check to see whether this link traversal was in a social panel
   255     let win = linkNode.ownerDocument.defaultView;
   256     let container = win.QueryInterface(Ci.nsIInterfaceRequestor)
   257                                   .getInterface(Ci.nsIWebNavigation)
   258                                   .QueryInterface(Ci.nsIDocShell)
   259                                   .chromeEventHandler;
   260     let containerParent = container.parentNode;
   261     if (containerParent.classList.contains("social-panel") &&
   262         containerParent instanceof Ci.nsIDOMXULPopupElement) {
   263       // allow the link traversal to finish before closing the panel
   264       setTimeout(() => {
   265         containerParent.hidePopup();
   266       }, 0);
   267     }
   268   },
   270   get _chromeless() {
   271     // Is this a popup window that doesn't want chrome shown?
   272     let docElem = document.documentElement;
   273     // extrachrome is not restored during session restore, so we need
   274     // to check for the toolbar as well.
   275     let chromeless = docElem.getAttribute("chromehidden").contains("extrachrome") ||
   276                      docElem.getAttribute('chromehidden').contains("toolbar");
   277     // This property is "fixed" for a window, so avoid doing the check above
   278     // multiple times...
   279     delete this._chromeless;
   280     this._chromeless = chromeless;
   281     return chromeless;
   282   },
   284   get enabled() {
   285     // Returns whether social is enabled *for this window*.
   286     if (this._chromeless || PrivateBrowsingUtils.isWindowPrivate(window))
   287       return false;
   288     return Social.providers.length > 0;
   289   },
   291   // called on tab/urlbar/location changes and after customization. Update
   292   // anything that is tab specific.
   293   updateState: function() {
   294     if (!this.enabled)
   295       return;
   296     SocialMarks.update();
   297     SocialShare.update();
   298   }
   299 }
   301 SocialChatBar = {
   302   get chatbar() {
   303     return document.getElementById("pinnedchats");
   304   },
   305   // Whether the chatbar is available for this window.  Note that in full-screen
   306   // mode chats are available, but not shown.
   307   get isAvailable() {
   308     return SocialUI.enabled;
   309   },
   310   // Does this chatbar have any chats (whether minimized, collapsed or normal)
   311   get hasChats() {
   312     return !!this.chatbar.firstElementChild;
   313   },
   314   openChat: function(aProvider, aURL, aCallback, aMode) {
   315     this.update();
   316     if (!this.isAvailable)
   317       return false;
   318     this.chatbar.openChat(aProvider, aURL, aCallback, aMode);
   319     // We only want to focus the chat if it is as a result of user input.
   320     let dwu = window.QueryInterface(Ci.nsIInterfaceRequestor)
   321                     .getInterface(Ci.nsIDOMWindowUtils);
   322     if (dwu.isHandlingUserInput)
   323       this.chatbar.focus();
   324     return true;
   325   },
   326   update: function() {
   327     let command = document.getElementById("Social:FocusChat");
   328     if (!this.isAvailable) {
   329       this.chatbar.hidden = command.hidden = true;
   330     } else {
   331       this.chatbar.hidden = command.hidden = false;
   332     }
   333     command.setAttribute("disabled", command.hidden ? "true" : "false");
   334   },
   335   focus: function SocialChatBar_focus() {
   336     this.chatbar.focus();
   337   }
   338 }
   340 SocialFlyout = {
   341   get panel() {
   342     return document.getElementById("social-flyout-panel");
   343   },
   345   get iframe() {
   346     if (!this.panel.firstChild)
   347       this._createFrame();
   348     return this.panel.firstChild;
   349   },
   351   dispatchPanelEvent: function(name) {
   352     let doc = this.iframe.contentDocument;
   353     let evt = doc.createEvent("CustomEvent");
   354     evt.initCustomEvent(name, true, true, {});
   355     doc.documentElement.dispatchEvent(evt);
   356   },
   358   _createFrame: function() {
   359     let panel = this.panel;
   360     if (!SocialUI.enabled || panel.firstChild)
   361       return;
   362     // create and initialize the panel for this window
   363     let iframe = document.createElement("iframe");
   364     iframe.setAttribute("type", "content");
   365     iframe.setAttribute("class", "social-panel-frame");
   366     iframe.setAttribute("flex", "1");
   367     iframe.setAttribute("tooltip", "aHTMLTooltip");
   368     iframe.setAttribute("origin", SocialSidebar.provider.origin);
   369     panel.appendChild(iframe);
   370   },
   372   setFlyoutErrorMessage: function SF_setFlyoutErrorMessage() {
   373     this.iframe.removeAttribute("src");
   374     this.iframe.webNavigation.loadURI("about:socialerror?mode=compactInfo&origin=" +
   375                                  encodeURIComponent(this.iframe.getAttribute("origin")),
   376                                  null, null, null, null);
   377     sizeSocialPanelToContent(this.panel, this.iframe);
   378   },
   380   unload: function() {
   381     let panel = this.panel;
   382     panel.hidePopup();
   383     if (!panel.firstChild)
   384       return
   385     let iframe = panel.firstChild;
   386     if (iframe.socialErrorListener)
   387       iframe.socialErrorListener.remove();
   388     panel.removeChild(iframe);
   389   },
   391   onShown: function(aEvent) {
   392     let panel = this.panel;
   393     let iframe = this.iframe;
   394     this._dynamicResizer = new DynamicResizeWatcher();
   395     iframe.docShell.isActive = true;
   396     iframe.docShell.isAppTab = true;
   397     if (iframe.contentDocument.readyState == "complete") {
   398       this._dynamicResizer.start(panel, iframe);
   399       this.dispatchPanelEvent("socialFrameShow");
   400     } else {
   401       // first time load, wait for load and dispatch after load
   402       iframe.addEventListener("load", function panelBrowserOnload(e) {
   403         iframe.removeEventListener("load", panelBrowserOnload, true);
   404         setTimeout(function() {
   405           if (SocialFlyout._dynamicResizer) { // may go null if hidden quickly
   406             SocialFlyout._dynamicResizer.start(panel, iframe);
   407             SocialFlyout.dispatchPanelEvent("socialFrameShow");
   408           }
   409         }, 0);
   410       }, true);
   411     }
   412   },
   414   onHidden: function(aEvent) {
   415     this._dynamicResizer.stop();
   416     this._dynamicResizer = null;
   417     this.iframe.docShell.isActive = false;
   418     this.dispatchPanelEvent("socialFrameHide");
   419   },
   421   load: function(aURL, cb) {
   422     if (!SocialSidebar.provider)
   423       return;
   425     this.panel.hidden = false;
   426     let iframe = this.iframe;
   427     // same url with only ref difference does not cause a new load, so we
   428     // want to go right to the callback
   429     let src = iframe.contentDocument && iframe.contentDocument.documentURIObject;
   430     if (!src || !src.equalsExceptRef(Services.io.newURI(aURL, null, null))) {
   431       iframe.addEventListener("load", function documentLoaded() {
   432         iframe.removeEventListener("load", documentLoaded, true);
   433         cb();
   434       }, true);
   435       // Force a layout flush by calling .clientTop so
   436       // that the docShell of this frame is created
   437       iframe.clientTop;
   438       Social.setErrorListener(iframe, SocialFlyout.setFlyoutErrorMessage.bind(SocialFlyout))
   439       iframe.setAttribute("src", aURL);
   440     } else {
   441       // we still need to set the src to trigger the contents hashchange event
   442       // for ref changes
   443       iframe.setAttribute("src", aURL);
   444       cb();
   445     }
   446   },
   448   open: function(aURL, yOffset, aCallback) {
   449     // Hide any other social panels that may be open.
   450     document.getElementById("social-notification-panel").hidePopup();
   452     if (!SocialUI.enabled)
   453       return;
   454     let panel = this.panel;
   455     let iframe = this.iframe;
   457     this.load(aURL, function() {
   458       sizeSocialPanelToContent(panel, iframe);
   459       let anchor = document.getElementById("social-sidebar-browser");
   460       if (panel.state == "open") {
   461         panel.moveToAnchor(anchor, "start_before", 0, yOffset, false);
   462       } else {
   463         panel.openPopup(anchor, "start_before", 0, yOffset, false, false);
   464       }
   465       if (aCallback) {
   466         try {
   467           aCallback(iframe.contentWindow);
   468         } catch(e) {
   469           Cu.reportError(e);
   470         }
   471       }
   472     });
   473   }
   474 }
   476 SocialShare = {
   477   get panel() {
   478     return document.getElementById("social-share-panel");
   479   },
   481   get iframe() {
   482     // first element is our menu vbox.
   483     if (this.panel.childElementCount == 1)
   484       return null;
   485     else
   486       return this.panel.lastChild;
   487   },
   489   uninit: function () {
   490     if (this.iframe) {
   491       this.iframe.remove();
   492     }
   493   },
   495   _createFrame: function() {
   496     let panel = this.panel;
   497     if (!SocialUI.enabled || this.iframe)
   498       return;
   499     this.panel.hidden = false;
   500     // create and initialize the panel for this window
   501     let iframe = document.createElement("iframe");
   502     iframe.setAttribute("type", "content");
   503     iframe.setAttribute("class", "social-share-frame");
   504     iframe.setAttribute("context", "contentAreaContextMenu");
   505     iframe.setAttribute("tooltip", "aHTMLTooltip");
   506     iframe.setAttribute("flex", "1");
   507     panel.appendChild(iframe);
   508     this.populateProviderMenu();
   509   },
   511   getSelectedProvider: function() {
   512     let provider;
   513     let lastProviderOrigin = this.iframe && this.iframe.getAttribute("origin");
   514     if (lastProviderOrigin) {
   515       provider = Social._getProviderFromOrigin(lastProviderOrigin);
   516     }
   517     // if they have a provider selected in the sidebar use that for the initial
   518     // default in share
   519     if (!provider)
   520       provider = SocialSidebar.provider;
   521     // if our provider has no shareURL, select the first one that does
   522     if (!provider || !provider.shareURL) {
   523       let providers = [p for (p of Social.providers) if (p.shareURL)];
   524       provider = providers.length > 0  && providers[0];
   525     }
   526     return provider;
   527   },
   529   populateProviderMenu: function() {
   530     if (!this.iframe)
   531       return;
   532     let providers = [p for (p of Social.providers) if (p.shareURL)];
   533     let hbox = document.getElementById("social-share-provider-buttons");
   534     // selectable providers are inserted before the provider-menu seperator,
   535     // remove any menuitems in that area
   536     while (hbox.firstChild) {
   537       hbox.removeChild(hbox.firstChild);
   538     }
   539     // reset our share toolbar
   540     // only show a selection if there is more than one
   541     if (!SocialUI.enabled || providers.length < 2) {
   542       this.panel.firstChild.hidden = true;
   543       return;
   544     }
   545     let selectedProvider = this.getSelectedProvider();
   546     for (let provider of providers) {
   547       let button = document.createElement("toolbarbutton");
   548       button.setAttribute("class", "toolbarbutton share-provider-button");
   549       button.setAttribute("type", "radio");
   550       button.setAttribute("group", "share-providers");
   551       button.setAttribute("image", provider.iconURL);
   552       button.setAttribute("tooltiptext", provider.name);
   553       button.setAttribute("origin", provider.origin);
   554       button.setAttribute("oncommand", "SocialShare.sharePage(this.getAttribute('origin')); this.checked=true;");
   555       if (provider == selectedProvider) {
   556         this.defaultButton = button;
   557       }
   558       hbox.appendChild(button);
   559     }
   560     if (!this.defaultButton) {
   561       this.defaultButton = hbox.firstChild
   562     }
   563     this.defaultButton.setAttribute("checked", "true");
   564     this.panel.firstChild.hidden = false;
   565   },
   567   get shareButton() {
   568     return document.getElementById("social-share-button");
   569   },
   571   canSharePage: function(aURI) {
   572     // we do not enable sharing from private sessions
   573     if (PrivateBrowsingUtils.isWindowPrivate(window))
   574       return false;
   576     if (!aURI || !(aURI.schemeIs('http') || aURI.schemeIs('https')))
   577       return false;
   578     return true;
   579   },
   581   update: function() {
   582     let shareButton = this.shareButton;
   583     shareButton.hidden = !SocialUI.enabled ||
   584                          [p for (p of Social.providers) if (p.shareURL)].length == 0;
   585     shareButton.disabled = shareButton.hidden || !this.canSharePage(gBrowser.currentURI);
   587     // also update the relevent command's disabled state so the keyboard
   588     // shortcut only works when available.
   589     let cmd = document.getElementById("Social:SharePage");
   590     if (shareButton.disabled)
   591       cmd.setAttribute("disabled", "true");
   592     else
   593       cmd.removeAttribute("disabled");
   594   },
   596   onShowing: function() {
   597     this.shareButton.setAttribute("open", "true");
   598   },
   600   onHidden: function() {
   601     this.shareButton.removeAttribute("open");
   602     this.iframe.setAttribute("src", "data:text/plain;charset=utf8,");
   603     this.currentShare = null;
   604   },
   606   setErrorMessage: function() {
   607     let iframe = this.iframe;
   608     if (!iframe)
   609       return;
   611     iframe.removeAttribute("src");
   612     iframe.webNavigation.loadURI("about:socialerror?mode=compactInfo&origin=" +
   613                                  encodeURIComponent(iframe.getAttribute("origin")),
   614                                  null, null, null, null);
   615     sizeSocialPanelToContent(this.panel, iframe);
   616   },
   618   sharePage: function(providerOrigin, graphData) {
   619     // if providerOrigin is undefined, we use the last-used provider, or the
   620     // current/default provider.  The provider selection in the share panel
   621     // will call sharePage with an origin for us to switch to.
   622     this._createFrame();
   623     let iframe = this.iframe;
   624     let provider;
   625     if (providerOrigin)
   626       provider = Social._getProviderFromOrigin(providerOrigin);
   627     else
   628       provider = this.getSelectedProvider();
   629     if (!provider || !provider.shareURL)
   630       return;
   632     // graphData is an optional param that either defines the full set of data
   633     // to be shared, or partial data about the current page. It is set by a call
   634     // in mozSocial API, or via nsContentMenu calls. If it is present, it MUST
   635     // define at least url. If it is undefined, we're sharing the current url in
   636     // the browser tab.
   637     let sharedURI = graphData ? Services.io.newURI(graphData.url, null, null) :
   638                                 gBrowser.currentURI;
   639     if (!this.canSharePage(sharedURI))
   640       return;
   642     // the point of this action type is that we can use existing share
   643     // endpoints (e.g. oexchange) that do not support additional
   644     // socialapi functionality.  One tweak is that we shoot an event
   645     // containing the open graph data.
   646     let pageData = graphData ? graphData : this.currentShare;
   647     if (!pageData || sharedURI == gBrowser.currentURI) {
   648       pageData = OpenGraphBuilder.getData(gBrowser);
   649       if (graphData) {
   650         // overwrite data retreived from page with data given to us as a param
   651         for (let p in graphData) {
   652           pageData[p] = graphData[p];
   653         }
   654       }
   655     }
   656     this.currentShare = pageData;
   658     let shareEndpoint = OpenGraphBuilder.generateEndpointURL(provider.shareURL, pageData);
   660     this._dynamicResizer = new DynamicResizeWatcher();
   661     // if we've already loaded this provider/page share endpoint, we don't want
   662     // to add another load event listener.
   663     let reload = true;
   664     let endpointMatch = shareEndpoint == iframe.getAttribute("src");
   665     let docLoaded = iframe.contentDocument && iframe.contentDocument.readyState == "complete";
   666     if (endpointMatch && docLoaded) {
   667       reload = shareEndpoint != iframe.contentDocument.location.spec;
   668     }
   669     if (!reload) {
   670       this._dynamicResizer.start(this.panel, iframe);
   671       iframe.docShell.isActive = true;
   672       iframe.docShell.isAppTab = true;
   673       let evt = iframe.contentDocument.createEvent("CustomEvent");
   674       evt.initCustomEvent("OpenGraphData", true, true, JSON.stringify(pageData));
   675       iframe.contentDocument.documentElement.dispatchEvent(evt);
   676     } else {
   677       // first time load, wait for load and dispatch after load
   678       iframe.addEventListener("load", function panelBrowserOnload(e) {
   679         iframe.removeEventListener("load", panelBrowserOnload, true);
   680         iframe.docShell.isActive = true;
   681         iframe.docShell.isAppTab = true;
   682         setTimeout(function() {
   683           if (SocialShare._dynamicResizer) { // may go null if hidden quickly
   684             SocialShare._dynamicResizer.start(iframe.parentNode, iframe);
   685           }
   686         }, 0);
   687         let evt = iframe.contentDocument.createEvent("CustomEvent");
   688         evt.initCustomEvent("OpenGraphData", true, true, JSON.stringify(pageData));
   689         iframe.contentDocument.documentElement.dispatchEvent(evt);
   690       }, true);
   691     }
   692     // always ensure that origin belongs to the endpoint
   693     let uri = Services.io.newURI(shareEndpoint, null, null);
   694     iframe.setAttribute("origin", provider.origin);
   695     iframe.setAttribute("src", shareEndpoint);
   697     let navBar = document.getElementById("nav-bar");
   698     let anchor = document.getAnonymousElementByAttribute(this.shareButton, "class", "toolbarbutton-icon");
   699     this.panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, false);
   700     Social.setErrorListener(iframe, this.setErrorMessage.bind(this));
   701   }
   702 };
   704 SocialSidebar = {
   705   // Whether the sidebar can be shown for this window.
   706   get canShow() {
   707     if (!SocialUI.enabled || document.mozFullScreen)
   708       return false;
   709     return Social.providers.some(p => p.sidebarURL);
   710   },
   712   // Whether the user has toggled the sidebar on (for windows where it can appear)
   713   get opened() {
   714     let broadcaster = document.getElementById("socialSidebarBroadcaster");
   715     return !broadcaster.hidden;
   716   },
   718   restoreWindowState: function() {
   719     // Window state is used to allow different sidebar providers in each window.
   720     // We also store the provider used in a pref as the default sidebar to
   721     // maintain that state for users who do not restore window state. The
   722     // existence of social.sidebar.provider means the sidebar is open with that
   723     // provider.
   724     this._initialized = true;
   725     if (!this.canShow)
   726       return;
   728     if (Services.prefs.prefHasUserValue("social.provider.current")) {
   729       // "upgrade" when the first window opens if we have old prefs.  We get the
   730       // values from prefs this one time, window state will be saved when this
   731       // window is closed.
   732       let origin = Services.prefs.getCharPref("social.provider.current");
   733       Services.prefs.clearUserPref("social.provider.current");
   734       // social.sidebar.open default was true, but we only opened if there was
   735       // a current provider
   736       let opened = origin && true;
   737       if (Services.prefs.prefHasUserValue("social.sidebar.open")) {
   738         opened = origin && Services.prefs.getBoolPref("social.sidebar.open");
   739         Services.prefs.clearUserPref("social.sidebar.open");
   740       }
   741       let data = {
   742         "hidden": !opened,
   743         "origin": origin
   744       };
   745       SessionStore.setWindowValue(window, "socialSidebar", JSON.stringify(data));
   746     }
   748     let data = SessionStore.getWindowValue(window, "socialSidebar");
   749     // if this window doesn't have it's own state, use the state from the opener
   750     if (!data && window.opener && !window.opener.closed) {
   751       try {
   752         data = SessionStore.getWindowValue(window.opener, "socialSidebar");
   753       } catch(e) {
   754         // Window is not tracked, which happens on osx if the window is opened
   755         // from the hidden window. That happens when you close the last window
   756         // without quiting firefox, then open a new window.
   757       }
   758     }
   759     if (data) {
   760       data = JSON.parse(data);
   761       document.getElementById("social-sidebar-browser").setAttribute("origin", data.origin);
   762       if (!data.hidden)
   763         this.show(data.origin);
   764     } else if (Services.prefs.prefHasUserValue("social.sidebar.provider")) {
   765       // no window state, use the global state if it is available
   766       this.show(Services.prefs.getCharPref("social.sidebar.provider"));
   767     }
   768   },
   770   saveWindowState: function() {
   771     let broadcaster = document.getElementById("socialSidebarBroadcaster");
   772     let sidebarOrigin = document.getElementById("social-sidebar-browser").getAttribute("origin");
   773     let data = {
   774       "hidden": broadcaster.hidden,
   775       "origin": sidebarOrigin
   776     };
   778     // Save a global state for users who do not restore state.
   779     if (broadcaster.hidden)
   780       Services.prefs.clearUserPref("social.sidebar.provider");
   781     else
   782       Services.prefs.setCharPref("social.sidebar.provider", sidebarOrigin);
   784     try {
   785       SessionStore.setWindowValue(window, "socialSidebar", JSON.stringify(data));
   786     } catch(e) {
   787       // window not tracked during uninit
   788     }
   789   },
   791   setSidebarVisibilityState: function(aEnabled) {
   792     let sbrowser = document.getElementById("social-sidebar-browser");
   793     // it's possible we'll be called twice with aEnabled=false so let's
   794     // just assume we may often be called with the same state.
   795     if (aEnabled == sbrowser.docShellIsActive)
   796       return;
   797     sbrowser.docShellIsActive = aEnabled;
   798     let evt = sbrowser.contentDocument.createEvent("CustomEvent");
   799     evt.initCustomEvent(aEnabled ? "socialFrameShow" : "socialFrameHide", true, true, {});
   800     sbrowser.contentDocument.documentElement.dispatchEvent(evt);
   801   },
   803   updateToggleNotifications: function() {
   804     let command = document.getElementById("Social:ToggleNotifications");
   805     command.setAttribute("checked", Services.prefs.getBoolPref("social.toast-notifications.enabled"));
   806     command.setAttribute("hidden", !SocialUI.enabled);
   807   },
   809   update: function SocialSidebar_update() {
   810     // ensure we never update before restoreWindowState
   811     if (!this._initialized)
   812       return;
   813     this.ensureProvider();
   814     this.updateToggleNotifications();
   815     this._updateHeader();
   816     clearTimeout(this._unloadTimeoutId);
   817     // Hide the toggle menu item if the sidebar cannot appear
   818     let command = document.getElementById("Social:ToggleSidebar");
   819     command.setAttribute("hidden", this.canShow ? "false" : "true");
   821     // Hide the sidebar if it cannot appear, or has been toggled off.
   822     // Also set the command "checked" state accordingly.
   823     let hideSidebar = !this.canShow || !this.opened;
   824     let broadcaster = document.getElementById("socialSidebarBroadcaster");
   825     broadcaster.hidden = hideSidebar;
   826     command.setAttribute("checked", !hideSidebar);
   828     let sbrowser = document.getElementById("social-sidebar-browser");
   830     if (hideSidebar) {
   831       sbrowser.removeEventListener("load", SocialSidebar._loadListener, true);
   832       this.setSidebarVisibilityState(false);
   833       // If we've been disabled, unload the sidebar content immediately;
   834       // if the sidebar was just toggled to invisible, wait a timeout
   835       // before unloading.
   836       if (!this.canShow) {
   837         this.unloadSidebar();
   838       } else {
   839         this._unloadTimeoutId = setTimeout(
   840           this.unloadSidebar,
   841           Services.prefs.getIntPref("social.sidebar.unload_timeout_ms")
   842         );
   843       }
   844     } else {
   845       sbrowser.setAttribute("origin", this.provider.origin);
   846       if (this.provider.errorState == "frameworker-error") {
   847         SocialSidebar.setSidebarErrorMessage();
   848         return;
   849       }
   851       // Make sure the right sidebar URL is loaded
   852       if (sbrowser.getAttribute("src") != this.provider.sidebarURL) {
   853         // we check readyState right after setting src, we need a new content
   854         // viewer to ensure we are checking against the correct document.
   855         sbrowser.docShell.createAboutBlankContentViewer(null);
   856         Social.setErrorListener(sbrowser, this.setSidebarErrorMessage.bind(this));
   857         // setting isAppTab causes clicks on untargeted links to open new tabs
   858         sbrowser.docShell.isAppTab = true;
   859         sbrowser.setAttribute("src", this.provider.sidebarURL);
   860         PopupNotifications.locationChange(sbrowser);
   861       }
   863       // if the document has not loaded, delay until it is
   864       if (sbrowser.contentDocument.readyState != "complete") {
   865         document.getElementById("social-sidebar-button").setAttribute("loading", "true");
   866         sbrowser.addEventListener("load", SocialSidebar._loadListener, true);
   867       } else {
   868         this.setSidebarVisibilityState(true);
   869       }
   870     }
   871     this._updateCheckedMenuItems(this.opened && this.provider ? this.provider.origin : null);
   872   },
   874   _loadListener: function SocialSidebar_loadListener() {
   875     let sbrowser = document.getElementById("social-sidebar-browser");
   876     sbrowser.removeEventListener("load", SocialSidebar._loadListener, true);
   877     document.getElementById("social-sidebar-button").removeAttribute("loading");
   878     SocialSidebar.setSidebarVisibilityState(true);
   879   },
   881   unloadSidebar: function SocialSidebar_unloadSidebar() {
   882     let sbrowser = document.getElementById("social-sidebar-browser");
   883     if (!sbrowser.hasAttribute("origin"))
   884       return;
   886     sbrowser.stop();
   887     sbrowser.removeAttribute("origin");
   888     sbrowser.setAttribute("src", "about:blank");
   889     // We need to explicitly create a new content viewer because the old one
   890     // doesn't get destroyed until about:blank has loaded (which does not happen
   891     // as long as the element is hidden).
   892     sbrowser.docShell.createAboutBlankContentViewer(null);
   893     SocialFlyout.unload();
   894   },
   896   _unloadTimeoutId: 0,
   898   setSidebarErrorMessage: function() {
   899     let sbrowser = document.getElementById("social-sidebar-browser");
   900     // a frameworker error "trumps" a sidebar error.
   901     let origin = sbrowser.getAttribute("origin");
   902     if (origin) {
   903       origin = "&origin=" + encodeURIComponent(origin);
   904     }
   905     if (this.provider.errorState == "frameworker-error") {
   906       sbrowser.setAttribute("src", "about:socialerror?mode=workerFailure" + origin);
   907     } else {
   908       let url = encodeURIComponent(this.provider.sidebarURL);
   909       sbrowser.loadURI("about:socialerror?mode=tryAgain&url=" + url + origin, null, null);
   910     }
   911   },
   913   _provider: null,
   914   ensureProvider: function() {
   915     if (this._provider)
   916       return;
   917     // origin for sidebar is persisted, so get the previously selected sidebar
   918     // first, otherwise fallback to the first provider in the list
   919     let sbrowser = document.getElementById("social-sidebar-browser");
   920     let origin = sbrowser.getAttribute("origin");
   921     let providers = [p for (p of Social.providers) if (p.sidebarURL)];
   922     let provider;
   923     if (origin)
   924       provider = Social._getProviderFromOrigin(origin);
   925     if (!provider && providers.length > 0)
   926       provider = providers[0];
   927     if (provider)
   928       this.provider = provider;
   929   },
   931   get provider() {
   932     return this._provider;
   933   },
   935   set provider(provider) {
   936     if (!provider || provider.sidebarURL) {
   937       this._provider = provider;
   938       this._updateHeader();
   939       this._updateCheckedMenuItems(provider && provider.origin);
   940       this.update();
   941     }
   942   },
   944   disableProvider: function(origin) {
   945     if (this._provider && this._provider.origin == origin) {
   946       this._provider = null;
   947       // force a selection of the next provider if there is one
   948       this.ensureProvider();
   949     }
   950   },
   952   _updateHeader: function() {
   953     let provider = this.provider;
   954     let image, title;
   955     if (provider) {
   956       image = "url(" + (provider.icon32URL || provider.iconURL) + ")";
   957       title = provider.name;
   958     }
   959     document.getElementById("social-sidebar-favico").style.listStyleImage = image;
   960     document.getElementById("social-sidebar-title").value = title;
   961   },
   963   _updateCheckedMenuItems: function(origin) {
   964     // update selected menuitems
   965     let menuitems = document.getElementsByClassName("social-provider-menuitem");
   966     for (let mi of menuitems) {
   967       if (origin && mi.getAttribute("origin") == origin) {
   968         mi.setAttribute("checked", "true");
   969         mi.setAttribute("oncommand", "SocialSidebar.hide();");
   970       } else if (mi.getAttribute("checked")) {
   971         mi.removeAttribute("checked");
   972         mi.setAttribute("oncommand", "SocialSidebar.show(this.getAttribute('origin'));");
   973       }
   974     }
   975   },
   977   show: function(origin) {
   978     // always show the sidebar, and set the provider
   979     let broadcaster = document.getElementById("socialSidebarBroadcaster");
   980     broadcaster.hidden = false;
   981     if (origin)
   982       this.provider = Social._getProviderFromOrigin(origin);
   983     else
   984       SocialSidebar.update();
   985     this.saveWindowState();
   986   },
   988   hide: function() {
   989     let broadcaster = document.getElementById("socialSidebarBroadcaster");
   990     broadcaster.hidden = true;
   991     this._updateCheckedMenuItems();
   992     this.clearProviderMenus();
   993     SocialSidebar.update();
   994     this.saveWindowState();
   995   },
   997   toggleSidebar: function SocialSidebar_toggle() {
   998     let broadcaster = document.getElementById("socialSidebarBroadcaster");
   999     if (broadcaster.hidden)
  1000       this.show();
  1001     else
  1002       this.hide();
  1003   },
  1005   populateSidebarMenu: function(event) {
  1006     // Providers are removed from the view->sidebar menu when there is a change
  1007     // in providers, so we only have to populate onshowing if there are no
  1008     // provider menus. We populate this menu so long as there are enabled
  1009     // providers with sidebars.
  1010     let popup = event.target;
  1011     let providerMenuSeps = popup.getElementsByClassName("social-provider-menu");
  1012     if (providerMenuSeps[0].previousSibling.nodeName == "menuseparator")
  1013       SocialSidebar.populateProviderMenu(providerMenuSeps[0]);
  1014   },
  1016   clearProviderMenus: function() {
  1017     // called when there is a change in the provider list we clear all menus,
  1018     // they will be repopulated when the menu is shown
  1019     let providerMenuSeps = document.getElementsByClassName("social-provider-menu");
  1020     for (let providerMenuSep of providerMenuSeps) {
  1021       while (providerMenuSep.previousSibling.nodeName == "menuitem") {
  1022         let menu = providerMenuSep.parentNode;
  1023         menu.removeChild(providerMenuSep.previousSibling);
  1026   },
  1028   populateProviderMenu: function(providerMenuSep) {
  1029     let menu = providerMenuSep.parentNode;
  1030     // selectable providers are inserted before the provider-menu seperator,
  1031     // remove any menuitems in that area
  1032     while (providerMenuSep.previousSibling.nodeName == "menuitem") {
  1033       menu.removeChild(providerMenuSep.previousSibling);
  1035     // only show a selection in the sidebar header menu if there is more than one
  1036     let providers = [p for (p of Social.providers) if (p.sidebarURL)];
  1037     if (providers.length < 2 && menu.id != "viewSidebarMenu") {
  1038       providerMenuSep.hidden = true;
  1039       return;
  1041     let topSep = providerMenuSep.previousSibling;
  1042     for (let provider of providers) {
  1043       let menuitem = document.createElement("menuitem");
  1044       menuitem.className = "menuitem-iconic social-provider-menuitem";
  1045       menuitem.setAttribute("image", provider.iconURL);
  1046       menuitem.setAttribute("label", provider.name);
  1047       menuitem.setAttribute("origin", provider.origin);
  1048       if (this.opened && provider == this.provider) {
  1049         menuitem.setAttribute("checked", "true");
  1050         menuitem.setAttribute("oncommand", "SocialSidebar.hide();");
  1051       } else {
  1052         menuitem.setAttribute("oncommand", "SocialSidebar.show(this.getAttribute('origin'));");
  1054       menu.insertBefore(menuitem, providerMenuSep);
  1056     topSep.hidden = topSep.nextSibling == providerMenuSep;
  1057     providerMenuSep.hidden = !providerMenuSep.nextSibling;
  1061 // this helper class is used by removable/customizable buttons to handle
  1062 // widget creation/destruction
  1064 // When a provider is installed we show all their UI so the user will see the
  1065 // functionality of what they installed. The user can later customize the UI,
  1066 // moving buttons around or off the toolbar.
  1067 //
  1068 // On startup, we create the button widgets of any enabled provider.
  1069 // CustomizableUI handles placement and persistence of placement.
  1070 function ToolbarHelper(type, createButtonFn, listener) {
  1071   this._createButton = createButtonFn;
  1072   this._type = type;
  1074   if (listener) {
  1075     CustomizableUI.addListener(listener);
  1076     // remove this listener on window close
  1077     window.addEventListener("unload", () => {
  1078       CustomizableUI.removeListener(listener);
  1079     });
  1083 ToolbarHelper.prototype = {
  1084   idFromOrigin: function(origin) {
  1085     // this id needs to pass the checks in CustomizableUI, so remove characters
  1086     // that wont pass.
  1087     return this._type + "-" + Services.io.newURI(origin, null, null).hostPort.replace(/[\.:]/g,'-');
  1088   },
  1090   // should be called on disable of a provider
  1091   removeProviderButton: function(origin) {
  1092     CustomizableUI.destroyWidget(this.idFromOrigin(origin));
  1093   },
  1095   clearPalette: function() {
  1096     [this.removeProviderButton(p.origin) for (p of Social.providers)];
  1097   },
  1099   // should be called on enable of a provider
  1100   populatePalette: function() {
  1101     if (!Social.enabled) {
  1102       this.clearPalette();
  1103       return;
  1106     // create any buttons that do not exist yet if they have been persisted
  1107     // as a part of the UI (otherwise they belong in the palette).
  1108     for (let provider of Social.providers) {
  1109       let id = this.idFromOrigin(provider.origin);
  1110       this._createButton(id, provider);
  1115 let SocialStatusWidgetListener = {
  1116   _getNodeOrigin: function(aWidgetId) {
  1117     // we rely on the button id being the same as the widget.
  1118     let node = document.getElementById(aWidgetId);
  1119     if (!node)
  1120       return null
  1121     if (!node.classList.contains("social-status-button"))
  1122       return null
  1123     return node.getAttribute("origin");
  1124   },
  1125   onWidgetAdded: function(aWidgetId, aArea, aPosition) {
  1126     let origin = this._getNodeOrigin(aWidgetId);
  1127     if (origin)
  1128       SocialStatus.updateButton(origin);
  1129   },
  1130   onWidgetRemoved: function(aWidgetId, aPrevArea) {
  1131     let origin = this._getNodeOrigin(aWidgetId);
  1132     if (!origin)
  1133       return;
  1134     // When a widget is demoted to the palette ('removed'), it's visual
  1135     // style should change.
  1136     SocialStatus.updateButton(origin);
  1137     SocialStatus._removeFrame(origin);
  1141 SocialStatus = {
  1142   populateToolbarPalette: function() {
  1143     this._toolbarHelper.populatePalette();
  1145     for (let provider of Social.providers)
  1146       this.updateButton(provider.origin);
  1147   },
  1149   removeProvider: function(origin) {
  1150     this._removeFrame(origin);
  1151     this._toolbarHelper.removeProviderButton(origin);
  1152   },
  1154   reloadProvider: function(origin) {
  1155     let button = document.getElementById(this._toolbarHelper.idFromOrigin(origin));
  1156     if (button && button.getAttribute("open") == "true")
  1157       document.getElementById("social-notification-panel").hidePopup();
  1158     this._removeFrame(origin);
  1159   },
  1161   _removeFrame: function(origin) {
  1162     let notificationFrameId = "social-status-" + origin;
  1163     let frame = document.getElementById(notificationFrameId);
  1164     if (frame) {
  1165       SharedFrame.forgetGroup(frame.id);
  1166       frame.parentNode.removeChild(frame);
  1168   },
  1170   get _toolbarHelper() {
  1171     delete this._toolbarHelper;
  1172     this._toolbarHelper = new ToolbarHelper("social-status-button",
  1173                                             CreateSocialStatusWidget,
  1174                                             SocialStatusWidgetListener);
  1175     return this._toolbarHelper;
  1176   },
  1178   get _dynamicResizer() {
  1179     delete this._dynamicResizer;
  1180     this._dynamicResizer = new DynamicResizeWatcher();
  1181     return this._dynamicResizer;
  1182   },
  1184   // status panels are one-per button per-process, we swap the docshells between
  1185   // windows when necessary
  1186   _attachNotificatonPanel: function(aParent, aButton, provider) {
  1187     aParent.hidden = !SocialUI.enabled;
  1188     let notificationFrameId = "social-status-" + provider.origin;
  1189     let frame = document.getElementById(notificationFrameId);
  1191     // If the button was customized to a new location, we we'll destroy the
  1192     // iframe and start fresh.
  1193     if (frame && frame.parentNode != aParent) {
  1194       SharedFrame.forgetGroup(frame.id);
  1195       frame.parentNode.removeChild(frame);
  1196       frame = null;
  1199     if (!frame) {
  1200       frame = SharedFrame.createFrame(
  1201         notificationFrameId, /* frame name */
  1202         aParent, /* parent */
  1204           "type": "content",
  1205           "mozbrowser": "true",
  1206           "class": "social-panel-frame",
  1207           "id": notificationFrameId,
  1208           "tooltip": "aHTMLTooltip",
  1209           "context": "contentAreaContextMenu",
  1210           "flex": "1",
  1212           // work around bug 793057 - by making the panel roughly the final size
  1213           // we are more likely to have the anchor in the correct position.
  1214           "style": "width: " + PANEL_MIN_WIDTH + "px;",
  1216           "origin": provider.origin,
  1217           "src": provider.statusURL
  1219       );
  1221       if (frame.socialErrorListener)
  1222         frame.socialErrorListener.remove();
  1223       if (frame.docShell) {
  1224         frame.docShell.isActive = false;
  1225         Social.setErrorListener(frame, this.setPanelErrorMessage.bind(this));
  1227     } else {
  1228       frame.setAttribute("origin", provider.origin);
  1229       SharedFrame.updateURL(notificationFrameId, provider.statusURL);
  1231     aButton.setAttribute("notificationFrameId", notificationFrameId);
  1232   },
  1234   updateButton: function(origin) {
  1235     let id = this._toolbarHelper.idFromOrigin(origin);
  1236     let widget = CustomizableUI.getWidget(id);
  1237     if (!widget)
  1238       return;
  1239     let button = widget.forWindow(window).node;
  1240     if (button) {
  1241       // we only grab the first notification, ignore all others
  1242       let place = CustomizableUI.getPlaceForItem(button);
  1243       let provider = Social._getProviderFromOrigin(origin);
  1244       let icons = provider.ambientNotificationIcons;
  1245       let iconNames = Object.keys(icons);
  1246       let notif = icons[iconNames[0]];
  1248       // The image and tooltip need to be updated for both
  1249       // ambient notification and profile changes.
  1250       let iconURL = provider.icon32URL || provider.iconURL;
  1251       let tooltiptext;
  1252       if (!notif || place == "palette") {
  1253         button.style.listStyleImage = "url(" + iconURL + ")";
  1254         button.setAttribute("badge", "");
  1255         button.setAttribute("aria-label", "");
  1256         button.setAttribute("tooltiptext", provider.name);
  1257         return;
  1259       button.style.listStyleImage = "url(" + (notif.iconURL || iconURL) + ")";
  1260       button.setAttribute("tooltiptext", notif.label || provider.name);
  1262       let badge = notif.counter || "";
  1263       button.setAttribute("badge", badge);
  1264       let ariaLabel = notif.label;
  1265       // if there is a badge value, we must use a localizable string to insert it.
  1266       if (badge)
  1267         ariaLabel = gNavigatorBundle.getFormattedString("social.aria.toolbarButtonBadgeText",
  1268                                                         [ariaLabel, badge]);
  1269       button.setAttribute("aria-label", ariaLabel);
  1271   },
  1273   showPopup: function(aToolbarButton) {
  1274     // attach our notification panel if necessary
  1275     let origin = aToolbarButton.getAttribute("origin");
  1276     let provider = Social._getProviderFromOrigin(origin);
  1278     // if we're a slice in the hamburger, use that panel instead
  1279     let widgetGroup = CustomizableUI.getWidget(aToolbarButton.getAttribute("id"));
  1280     let widget = widgetGroup.forWindow(window);
  1281     let panel, showingEvent, hidingEvent;
  1282     let inMenuPanel = widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL;
  1283     if (inMenuPanel) {
  1284       panel = document.getElementById("PanelUI-socialapi");
  1285       this._attachNotificatonPanel(panel, aToolbarButton, provider);
  1286       widget.node.setAttribute("closemenu", "none");
  1287       showingEvent = "ViewShowing";
  1288       hidingEvent = "ViewHiding";
  1289     } else {
  1290       panel = document.getElementById("social-notification-panel");
  1291       this._attachNotificatonPanel(panel, aToolbarButton, provider);
  1292       showingEvent = "popupshown";
  1293       hidingEvent = "popuphidden";
  1295     let notificationFrameId = aToolbarButton.getAttribute("notificationFrameId");
  1296     let notificationFrame = document.getElementById(notificationFrameId);
  1298     let wasAlive = SharedFrame.isGroupAlive(notificationFrameId);
  1299     SharedFrame.setOwner(notificationFrameId, notificationFrame);
  1301     // Clear dimensions on all browsers so the panel size will
  1302     // only use the selected browser.
  1303     let frameIter = panel.firstElementChild;
  1304     while (frameIter) {
  1305       frameIter.collapsed = (frameIter != notificationFrame);
  1306       frameIter = frameIter.nextElementSibling;
  1309     function dispatchPanelEvent(name) {
  1310       let evt = notificationFrame.contentDocument.createEvent("CustomEvent");
  1311       evt.initCustomEvent(name, true, true, {});
  1312       notificationFrame.contentDocument.documentElement.dispatchEvent(evt);
  1315     // we only use a dynamic resizer when we're located the toolbar.
  1316     let dynamicResizer = inMenuPanel ? null : this._dynamicResizer;
  1317     panel.addEventListener(hidingEvent, function onpopuphiding() {
  1318       panel.removeEventListener(hidingEvent, onpopuphiding);
  1319       aToolbarButton.removeAttribute("open");
  1320       if (dynamicResizer)
  1321         dynamicResizer.stop();
  1322       notificationFrame.docShell.isActive = false;
  1323       dispatchPanelEvent("socialFrameHide");
  1324     });
  1326     panel.addEventListener(showingEvent, function onpopupshown() {
  1327       panel.removeEventListener(showingEvent, onpopupshown);
  1328       // This attribute is needed on both the button and the
  1329       // containing toolbaritem since the buttons on OS X have
  1330       // moz-appearance:none, while their container gets
  1331       // moz-appearance:toolbarbutton due to the way that toolbar buttons
  1332       // get combined on OS X.
  1333       let initFrameShow = () => {
  1334         notificationFrame.docShell.isActive = true;
  1335         notificationFrame.docShell.isAppTab = true;
  1336         if (dynamicResizer)
  1337           dynamicResizer.start(panel, notificationFrame);
  1338         dispatchPanelEvent("socialFrameShow");
  1339       };
  1340       if (!inMenuPanel)
  1341         aToolbarButton.setAttribute("open", "true");
  1342       if (notificationFrame.contentDocument &&
  1343           notificationFrame.contentDocument.readyState == "complete" && wasAlive) {
  1344         initFrameShow();
  1345       } else {
  1346         // first time load, wait for load and dispatch after load
  1347         notificationFrame.addEventListener("load", function panelBrowserOnload(e) {
  1348           notificationFrame.removeEventListener("load", panelBrowserOnload, true);
  1349           initFrameShow();
  1350         }, true);
  1352     });
  1354     if (inMenuPanel) {
  1355       PanelUI.showSubView("PanelUI-socialapi", widget.node,
  1356                           CustomizableUI.AREA_PANEL);
  1357     } else {
  1358       let anchor = document.getAnonymousElementByAttribute(aToolbarButton, "class", "toolbarbutton-badge-container");
  1359       // Bug 849216 - open the popup in a setTimeout so we avoid the auto-rollup
  1360       // handling from preventing it being opened in some cases.
  1361       setTimeout(function() {
  1362         panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, false);
  1363       }, 0);
  1365   },
  1367   setPanelErrorMessage: function(aNotificationFrame) {
  1368     if (!aNotificationFrame)
  1369       return;
  1371     let src = aNotificationFrame.getAttribute("src");
  1372     aNotificationFrame.removeAttribute("src");
  1373     let origin = aNotificationFrame.getAttribute("origin");
  1374     aNotificationFrame.webNavigation.loadURI("about:socialerror?mode=tryAgainOnly&url=" +
  1375                                             encodeURIComponent(src) + "&origin=" +
  1376                                             encodeURIComponent(origin),
  1377                                             null, null, null, null);
  1378     let panel = aNotificationFrame.parentNode;
  1379     sizeSocialPanelToContent(panel, aNotificationFrame);
  1380   },
  1382 };
  1385 /**
  1386  * SocialMarks
  1388  * Handles updates to toolbox and signals all buttons to update when necessary.
  1389  */
  1390 SocialMarks = {
  1391   update: function() {
  1392     // signal each button to update itself
  1393     let currentButtons = document.querySelectorAll('toolbarbutton[type="socialmark"]');
  1394     for (let elt of currentButtons)
  1395       elt.update();
  1396   },
  1398   updatePanelButtons: function() {
  1399     // querySelectorAll does not work on the menu panel the panel, so we have to
  1400     // do this the hard way.
  1401     let providers = SocialMarks.getProviders();
  1402     let panel =  document.getElementById("PanelUI-popup");
  1403     for (let p of providers) {
  1404       let widgetId = SocialMarks._toolbarHelper.idFromOrigin(p.origin);
  1405       let widget = CustomizableUI.getWidget(widgetId);
  1406       if (!widget)
  1407         continue;
  1408       let node = widget.forWindow(window).node;
  1409       if (node)
  1410         node.update();
  1412   },
  1414   getProviders: function() {
  1415     // only rely on providers that the user has placed in the UI somewhere. This
  1416     // also means that populateToolbarPalette must be called prior to using this
  1417     // method, otherwise you get a big fat zero. For our use case with context
  1418     // menu's, this is ok.
  1419     let tbh = this._toolbarHelper;
  1420     return [p for (p of Social.providers) if (p.markURL &&
  1421                                               document.getElementById(tbh.idFromOrigin(p.origin)))];
  1422   },
  1424   populateContextMenu: function() {
  1425     // only show a selection if enabled and there is more than one
  1426     let providers = this.getProviders();
  1428     // remove all previous entries by class
  1429     let menus = [m for (m of document.getElementsByClassName("context-socialmarks"))];
  1430     [m.parentNode.removeChild(m) for (m of menus)];
  1432     let contextMenus = [
  1434         type: "link",
  1435         id: "context-marklinkMenu",
  1436         label: "social.marklinkMenu.label"
  1437       },
  1439         type: "page",
  1440         id: "context-markpageMenu",
  1441         label: "social.markpageMenu.label"
  1443     ];
  1444     for (let cfg of contextMenus) {
  1445       this._populateContextPopup(cfg, providers);
  1447     this.updatePanelButtons();
  1448   },
  1450   MENU_LIMIT: 3, // adjustable for testing
  1451   _populateContextPopup: function(menuInfo, providers) {
  1452     let menu = document.getElementById(menuInfo.id);
  1453     let popup = menu.firstChild;
  1454     for (let provider of providers) {
  1455       // We show up to MENU_LIMIT providers as single menuitems's at the top
  1456       // level of the context menu, if we have more than that, dump them *all*
  1457       // into the menu popup.
  1458       let mi = document.createElement("menuitem");
  1459       mi.setAttribute("oncommand", "gContextMenu.markLink(this.getAttribute('origin'));");
  1460       mi.setAttribute("origin", provider.origin);
  1461       mi.setAttribute("image", provider.iconURL);
  1462       if (providers.length <= this.MENU_LIMIT) {
  1463         // an extra class to make enable/disable easy
  1464         mi.setAttribute("class", "menuitem-iconic context-socialmarks context-mark"+menuInfo.type);
  1465         let menuLabel = gNavigatorBundle.getFormattedString(menuInfo.label, [provider.name]);
  1466         mi.setAttribute("label", menuLabel);
  1467         menu.parentNode.insertBefore(mi, menu);
  1468       } else {
  1469         mi.setAttribute("class", "menuitem-iconic context-socialmarks");
  1470         mi.setAttribute("label", provider.name);
  1471         popup.appendChild(mi);
  1474   },
  1476   populateToolbarPalette: function() {
  1477     this._toolbarHelper.populatePalette();
  1478     this.populateContextMenu();
  1479   },
  1481   removeProvider: function(origin) {
  1482     this._toolbarHelper.removeProviderButton(origin);
  1483   },
  1485   get _toolbarHelper() {
  1486     delete this._toolbarHelper;
  1487     this._toolbarHelper = new ToolbarHelper("social-mark-button", CreateSocialMarkWidget);
  1488     return this._toolbarHelper;
  1489   },
  1491   markLink: function(aOrigin, aUrl) {
  1492     // find the button for this provider, and open it
  1493     let id = this._toolbarHelper.idFromOrigin(aOrigin);
  1494     document.getElementById(id).markLink(aUrl);
  1496 };
  1498 })();

mercurial