forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			1435 lines
		
	
	
	
		
			53 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1435 lines
		
	
	
	
		
			53 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| // the "exported" symbols
 | |
| var SocialUI,
 | |
|     SocialFlyout,
 | |
|     SocialMarks,
 | |
|     SocialShare,
 | |
|     SocialSidebar,
 | |
|     SocialStatus,
 | |
|     SocialActivationListener;
 | |
| 
 | |
| (function() {
 | |
| 
 | |
| XPCOMUtils.defineLazyModuleGetter(this, "PanelFrame",
 | |
|   "resource:///modules/PanelFrame.jsm");
 | |
| 
 | |
| XPCOMUtils.defineLazyGetter(this, "OpenGraphBuilder", function() {
 | |
|   let tmp = {};
 | |
|   Cu.import("resource:///modules/Social.jsm", tmp);
 | |
|   return tmp.OpenGraphBuilder;
 | |
| });
 | |
| 
 | |
| XPCOMUtils.defineLazyGetter(this, "DynamicResizeWatcher", function() {
 | |
|   let tmp = {};
 | |
|   Cu.import("resource:///modules/Social.jsm", tmp);
 | |
|   return tmp.DynamicResizeWatcher;
 | |
| });
 | |
| 
 | |
| XPCOMUtils.defineLazyGetter(this, "sizeSocialPanelToContent", function() {
 | |
|   let tmp = {};
 | |
|   Cu.import("resource:///modules/Social.jsm", tmp);
 | |
|   return tmp.sizeSocialPanelToContent;
 | |
| });
 | |
| 
 | |
| XPCOMUtils.defineLazyGetter(this, "CreateSocialStatusWidget", function() {
 | |
|   let tmp = {};
 | |
|   Cu.import("resource:///modules/Social.jsm", tmp);
 | |
|   return tmp.CreateSocialStatusWidget;
 | |
| });
 | |
| 
 | |
| XPCOMUtils.defineLazyGetter(this, "CreateSocialMarkWidget", function() {
 | |
|   let tmp = {};
 | |
|   Cu.import("resource:///modules/Social.jsm", tmp);
 | |
|   return tmp.CreateSocialMarkWidget;
 | |
| });
 | |
| 
 | |
| XPCOMUtils.defineLazyGetter(this, "hookWindowCloseForPanelClose", function() {
 | |
|   let tmp = {};
 | |
|   Cu.import("resource://gre/modules/MozSocialAPI.jsm", tmp);
 | |
|   return tmp.hookWindowCloseForPanelClose;
 | |
| });
 | |
| 
 | |
| SocialUI = {
 | |
|   _initialized: false,
 | |
| 
 | |
|   // Called on delayed startup to initialize the UI
 | |
|   init: function SocialUI_init() {
 | |
|     if (this._initialized) {
 | |
|       return;
 | |
|     }
 | |
|     let mm = window.getGroupMessageManager("social");
 | |
|     mm.loadFrameScript("chrome://browser/content/content.js", true);
 | |
|     mm.loadFrameScript("chrome://browser/content/social-content.js", true);
 | |
| 
 | |
|     Services.obs.addObserver(this, "social:ambient-notification-changed", false);
 | |
|     Services.obs.addObserver(this, "social:profile-changed", false);
 | |
|     Services.obs.addObserver(this, "social:frameworker-error", false);
 | |
|     Services.obs.addObserver(this, "social:providers-changed", false);
 | |
|     Services.obs.addObserver(this, "social:provider-reload", false);
 | |
|     Services.obs.addObserver(this, "social:provider-enabled", false);
 | |
|     Services.obs.addObserver(this, "social:provider-disabled", false);
 | |
| 
 | |
|     Services.prefs.addObserver("social.toast-notifications.enabled", this, false);
 | |
| 
 | |
|     CustomizableUI.addListener(this);
 | |
|     SocialActivationListener.init();
 | |
| 
 | |
|     // menupopups that list social providers. we only populate them when shown,
 | |
|     // and if it has not been done already.
 | |
|     document.getElementById("viewSidebarMenu").addEventListener("popupshowing", SocialSidebar.populateSidebarMenu, true);
 | |
|     document.getElementById("social-statusarea-popup").addEventListener("popupshowing", SocialSidebar.populateSidebarMenu, true);
 | |
| 
 | |
|     Social.init().then((update) => {
 | |
|       if (update)
 | |
|         this._providersChanged();
 | |
|       // handle SessionStore for the sidebar state
 | |
|       SocialSidebar.restoreWindowState();
 | |
|     });
 | |
| 
 | |
|     this._initialized = true;
 | |
|   },
 | |
| 
 | |
|   // Called on window unload
 | |
|   uninit: function SocialUI_uninit() {
 | |
|     if (!this._initialized) {
 | |
|       return;
 | |
|     }
 | |
|     SocialSidebar.saveWindowState();
 | |
| 
 | |
|     Services.obs.removeObserver(this, "social:ambient-notification-changed");
 | |
|     Services.obs.removeObserver(this, "social:profile-changed");
 | |
|     Services.obs.removeObserver(this, "social:frameworker-error");
 | |
|     Services.obs.removeObserver(this, "social:providers-changed");
 | |
|     Services.obs.removeObserver(this, "social:provider-reload");
 | |
|     Services.obs.removeObserver(this, "social:provider-enabled");
 | |
|     Services.obs.removeObserver(this, "social:provider-disabled");
 | |
| 
 | |
|     Services.prefs.removeObserver("social.toast-notifications.enabled", this);
 | |
|     CustomizableUI.removeListener(this);
 | |
|     SocialActivationListener.uninit();
 | |
| 
 | |
|     document.getElementById("viewSidebarMenu").removeEventListener("popupshowing", SocialSidebar.populateSidebarMenu, true);
 | |
|     document.getElementById("social-statusarea-popup").removeEventListener("popupshowing", SocialSidebar.populateSidebarMenu, true);
 | |
| 
 | |
|     this._initialized = false;
 | |
|   },
 | |
| 
 | |
|   observe: function SocialUI_observe(subject, topic, data) {
 | |
|     switch (topic) {
 | |
|       case "social:provider-enabled":
 | |
|         SocialMarks.populateToolbarPalette();
 | |
|         SocialStatus.populateToolbarPalette();
 | |
|         break;
 | |
|       case "social:provider-disabled":
 | |
|         SocialMarks.removeProvider(data);
 | |
|         SocialStatus.removeProvider(data);
 | |
|         SocialSidebar.disableProvider(data);
 | |
|         break;
 | |
|       case "social:provider-reload":
 | |
|         SocialStatus.reloadProvider(data);
 | |
|         // if the reloaded provider is our current provider, fall through
 | |
|         // to social:providers-changed so the ui will be reset
 | |
|         if (!SocialSidebar.provider || SocialSidebar.provider.origin != data)
 | |
|           return;
 | |
|         // currently only the sidebar and flyout have a selected provider.
 | |
|         // sidebar provider has changed (possibly to null), ensure the content
 | |
|         // is unloaded and the frames are reset, they will be loaded in
 | |
|         // providers-changed below if necessary.
 | |
|         SocialSidebar.unloadSidebar();
 | |
|         SocialFlyout.unload();
 | |
|         // fall through to providers-changed to ensure the reloaded provider
 | |
|         // is correctly reflected in any UI and the multi-provider menu
 | |
|       case "social:providers-changed":
 | |
|         this._providersChanged();
 | |
|         break;
 | |
|       // Provider-specific notifications
 | |
|       case "social:ambient-notification-changed":
 | |
|         SocialStatus.updateButton(data);
 | |
|         break;
 | |
|       case "social:profile-changed":
 | |
|         // make sure anything that happens here only affects the provider for
 | |
|         // which the profile is changing, and that anything we call actually
 | |
|         // needs to change based on profile data.
 | |
|         SocialStatus.updateButton(data);
 | |
|         break;
 | |
|       case "social:frameworker-error":
 | |
|         if (this.enabled && SocialSidebar.provider && SocialSidebar.provider.origin == data) {
 | |
|           SocialSidebar.loadFrameworkerFailure();
 | |
|         }
 | |
|         break;
 | |
|       case "nsPref:changed":
 | |
|         if (data == "social.toast-notifications.enabled") {
 | |
|           SocialSidebar.updateToggleNotifications();
 | |
|         }
 | |
|         break;
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _providersChanged: function() {
 | |
|     SocialSidebar.clearProviderMenus();
 | |
|     SocialSidebar.update();
 | |
|     SocialShare.populateProviderMenu();
 | |
|     SocialStatus.populateToolbarPalette();
 | |
|     SocialMarks.populateToolbarPalette();
 | |
|   },
 | |
| 
 | |
|   showLearnMore: function() {
 | |
|     let url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "social-api";
 | |
|     openUILinkIn(url, "tab");
 | |
|   },
 | |
| 
 | |
|   closeSocialPanelForLinkTraversal: function (target, linkNode) {
 | |
|     // No need to close the panel if this traversal was not retargeted
 | |
|     if (target == "" || target == "_self")
 | |
|       return;
 | |
| 
 | |
|     // Check to see whether this link traversal was in a social panel
 | |
|     let win = linkNode.ownerDocument.defaultView;
 | |
|     let container = win.QueryInterface(Ci.nsIInterfaceRequestor)
 | |
|                                   .getInterface(Ci.nsIWebNavigation)
 | |
|                                   .QueryInterface(Ci.nsIDocShell)
 | |
|                                   .chromeEventHandler;
 | |
|     let containerParent = container.parentNode;
 | |
|     if (containerParent.classList.contains("social-panel") &&
 | |
|         containerParent instanceof Ci.nsIDOMXULPopupElement) {
 | |
|       // allow the link traversal to finish before closing the panel
 | |
|       setTimeout(() => {
 | |
|         containerParent.hidePopup();
 | |
|       }, 0);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   get _chromeless() {
 | |
|     // Is this a popup window that doesn't want chrome shown?
 | |
|     let docElem = document.documentElement;
 | |
|     // extrachrome is not restored during session restore, so we need
 | |
|     // to check for the toolbar as well.
 | |
|     let chromeless = docElem.getAttribute("chromehidden").includes("extrachrome") ||
 | |
|                      docElem.getAttribute('chromehidden').includes("toolbar");
 | |
|     // This property is "fixed" for a window, so avoid doing the check above
 | |
|     // multiple times...
 | |
|     delete this._chromeless;
 | |
|     this._chromeless = chromeless;
 | |
|     return chromeless;
 | |
|   },
 | |
| 
 | |
|   get enabled() {
 | |
|     // Returns whether social is enabled *for this window*.
 | |
|     if (this._chromeless || PrivateBrowsingUtils.isWindowPrivate(window))
 | |
|       return false;
 | |
|     return Social.providers.length > 0;
 | |
|   },
 | |
| 
 | |
|   canShareOrMarkPage: function(aURI) {
 | |
|     // Bug 898706 we do not enable social in private sessions since frameworker
 | |
|     // would be shared between private and non-private windows
 | |
|     if (PrivateBrowsingUtils.isWindowPrivate(window))
 | |
|       return false;
 | |
| 
 | |
|     return (aURI && (aURI.schemeIs('http') || aURI.schemeIs('https')));
 | |
|   },
 | |
| 
 | |
|   onCustomizeEnd: function(aWindow) {
 | |
|     if (aWindow != window)
 | |
|       return;
 | |
|     // customization mode gets buttons out of sync with command updating, fix
 | |
|     // the disabled state
 | |
|     let canShare = this.canShareOrMarkPage(gBrowser.currentURI);
 | |
|     let shareButton = SocialShare.shareButton;
 | |
|     if (shareButton) {
 | |
|       if (canShare) {
 | |
|         shareButton.removeAttribute("disabled")
 | |
|       } else {
 | |
|         shareButton.setAttribute("disabled", "true")
 | |
|       }
 | |
|     }
 | |
|     // update the disabled state of the button based on the command
 | |
|     for (let node of SocialMarks.nodes) {
 | |
|       if (canShare) {
 | |
|         node.removeAttribute("disabled")
 | |
|       } else {
 | |
|         node.setAttribute("disabled", "true")
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   // called on tab/urlbar/location changes and after customization. Update
 | |
|   // anything that is tab specific.
 | |
|   updateState: function() {
 | |
|     if (location == "about:customizing")
 | |
|       return;
 | |
|     goSetCommandEnabled("Social:PageShareOrMark", this.canShareOrMarkPage(gBrowser.currentURI));
 | |
|     if (!SocialUI.enabled)
 | |
|       return;
 | |
|     // larger update that may change button icons
 | |
|     SocialMarks.update();
 | |
|   }
 | |
| }
 | |
| 
 | |
| // message manager handlers
 | |
| SocialActivationListener = {
 | |
|   init: function() {
 | |
|     messageManager.addMessageListener("Social:Activation", this);
 | |
|   },
 | |
|   uninit: function() {
 | |
|     messageManager.removeMessageListener("Social:Activation", this);
 | |
|   },
 | |
|   receiveMessage: function(aMessage) {
 | |
|     let data = aMessage.json;
 | |
|     let browser = aMessage.target;
 | |
|     data.window = window;
 | |
|     // if the source if the message is the share panel, we do a one-click
 | |
|     // installation. The source of activations is controlled by the
 | |
|     // social.directories preference
 | |
|     let options;
 | |
|     if (browser == SocialShare.iframe && Services.prefs.getBoolPref("social.share.activationPanelEnabled")) {
 | |
|       options = { bypassContentCheck: true, bypassInstallPanel: true };
 | |
|     }
 | |
| 
 | |
|     // If we are in PB mode, we silently do nothing (bug 829404 exists to
 | |
|     // do something sensible here...)
 | |
|     if (PrivateBrowsingUtils.isWindowPrivate(window))
 | |
|       return;
 | |
|     Social.installProvider(data, function(manifest) {
 | |
|       Social.activateFromOrigin(manifest.origin, function(provider) {
 | |
|         if (provider.sidebarURL) {
 | |
|           SocialSidebar.show(provider.origin);
 | |
|         }
 | |
|         if (provider.shareURL) {
 | |
|           // Ensure that the share button is somewhere usable.
 | |
|           // SocialShare.shareButton may return null if it is in the menu-panel
 | |
|           // and has never been visible, so we check the widget directly. If
 | |
|           // there is no area for the widget we move it into the toolbar.
 | |
|           let widget = CustomizableUI.getWidget("social-share-button");
 | |
|           // If the panel is already open, we can be sure that the provider can
 | |
|           // already be accessed, possibly anchored to another toolbar button.
 | |
|           // In that case we don't move the widget.
 | |
|           if (!widget.areaType && SocialShare.panel.state != "open") {
 | |
|             CustomizableUI.addWidgetToArea("social-share-button", CustomizableUI.AREA_NAVBAR);
 | |
|             // Ensure correct state.
 | |
|             SocialUI.onCustomizeEnd(window);
 | |
|           }
 | |
| 
 | |
|           // make this new provider the selected provider. If the panel hasn't
 | |
|           // been opened, we need to make the frame first.
 | |
|           SocialShare._createFrame();
 | |
|           SocialShare.iframe.setAttribute('src', 'data:text/plain;charset=utf8,');
 | |
|           SocialShare.iframe.setAttribute('origin', provider.origin);
 | |
|           // get the right button selected
 | |
|           SocialShare.populateProviderMenu();
 | |
|           if (SocialShare.panel.state == "open") {
 | |
|             SocialShare.sharePage(provider.origin);
 | |
|           }
 | |
|         }
 | |
|         if (provider.postActivationURL) {
 | |
|           // if activated from an open share panel, we load the landing page in
 | |
|           // a background tab
 | |
|           gBrowser.loadOneTab(provider.postActivationURL, {inBackground: SocialShare.panel.state == "open"});
 | |
|         }
 | |
|       });
 | |
|     }, options);
 | |
|   }
 | |
| }
 | |
| 
 | |
| SocialFlyout = {
 | |
|   get panel() {
 | |
|     return document.getElementById("social-flyout-panel");
 | |
|   },
 | |
| 
 | |
|   get iframe() {
 | |
|     if (!this.panel.firstChild)
 | |
|       this._createFrame();
 | |
|     return this.panel.firstChild;
 | |
|   },
 | |
| 
 | |
|   dispatchPanelEvent: function(name) {
 | |
|     let doc = this.iframe.contentDocument;
 | |
|     let evt = doc.createEvent("CustomEvent");
 | |
|     evt.initCustomEvent(name, true, true, {});
 | |
|     doc.documentElement.dispatchEvent(evt);
 | |
|   },
 | |
| 
 | |
|   _createFrame: function() {
 | |
|     let panel = this.panel;
 | |
|     if (!SocialUI.enabled || panel.firstChild)
 | |
|       return;
 | |
|     // create and initialize the panel for this window
 | |
|     let iframe = document.createElement("browser");
 | |
|     iframe.setAttribute("type", "content");
 | |
|     iframe.setAttribute("class", "social-panel-frame");
 | |
|     iframe.setAttribute("flex", "1");
 | |
|     iframe.setAttribute("message", "true");
 | |
|     iframe.setAttribute("messagemanagergroup", "social");
 | |
|     iframe.setAttribute("tooltip", "aHTMLTooltip");
 | |
|     iframe.setAttribute("context", "contentAreaContextMenu");
 | |
|     iframe.setAttribute("origin", SocialSidebar.provider.origin);
 | |
|     panel.appendChild(iframe);
 | |
|     // the xbl bindings for the iframe probably don't exist yet, so we can't
 | |
|     // access iframe.messageManager directly - but can get at it with this dance.
 | |
|     let mm = iframe.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader.messageManager;
 | |
|     mm.sendAsyncMessage("Social:SetErrorURL", null,
 | |
|                         { template: "about:socialerror?mode=compactInfo&origin=%{origin}" });
 | |
|   },
 | |
| 
 | |
|   unload: function() {
 | |
|     let panel = this.panel;
 | |
|     panel.hidePopup();
 | |
|     if (!panel.firstChild)
 | |
|       return
 | |
|     let iframe = panel.firstChild;
 | |
|     panel.removeChild(iframe);
 | |
|   },
 | |
| 
 | |
|   onShown: function(aEvent) {
 | |
|     let panel = this.panel;
 | |
|     let iframe = this.iframe;
 | |
|     this._dynamicResizer = new DynamicResizeWatcher();
 | |
|     iframe.docShellIsActive = true;
 | |
|     if (iframe.contentDocument.readyState == "complete") {
 | |
|       this._dynamicResizer.start(panel, iframe);
 | |
|       this.dispatchPanelEvent("socialFrameShow");
 | |
|     } else {
 | |
|       // first time load, wait for load and dispatch after load
 | |
|       iframe.addEventListener("load", function panelBrowserOnload(e) {
 | |
|         iframe.removeEventListener("load", panelBrowserOnload, true);
 | |
|         setTimeout(function() {
 | |
|           if (SocialFlyout._dynamicResizer) { // may go null if hidden quickly
 | |
|             SocialFlyout._dynamicResizer.start(panel, iframe);
 | |
|             SocialFlyout.dispatchPanelEvent("socialFrameShow");
 | |
|           }
 | |
|         }, 0);
 | |
|       }, true);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   onHidden: function(aEvent) {
 | |
|     this._dynamicResizer.stop();
 | |
|     this._dynamicResizer = null;
 | |
|     this.iframe.docShellIsActive = false;
 | |
|     this.dispatchPanelEvent("socialFrameHide");
 | |
|   },
 | |
| 
 | |
|   load: function(aURL, cb) {
 | |
|     if (!SocialSidebar.provider)
 | |
|       return;
 | |
| 
 | |
|     this.panel.hidden = false;
 | |
|     let iframe = this.iframe;
 | |
|     // same url with only ref difference does not cause a new load, so we
 | |
|     // want to go right to the callback
 | |
|     let src = iframe.contentDocument && iframe.contentDocument.documentURIObject;
 | |
|     if (!src || !src.equalsExceptRef(Services.io.newURI(aURL, null, null))) {
 | |
|       iframe.addEventListener("load", function documentLoaded() {
 | |
|         iframe.removeEventListener("load", documentLoaded, true);
 | |
|         cb();
 | |
|       }, true);
 | |
|       iframe.setAttribute("src", aURL);
 | |
|     } else {
 | |
|       // we still need to set the src to trigger the contents hashchange event
 | |
|       // for ref changes
 | |
|       iframe.setAttribute("src", aURL);
 | |
|       cb();
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   open: function(aURL, yOffset, aCallback) {
 | |
|     // Hide any other social panels that may be open.
 | |
|     document.getElementById("social-notification-panel").hidePopup();
 | |
| 
 | |
|     if (!SocialUI.enabled)
 | |
|       return;
 | |
|     let panel = this.panel;
 | |
|     let iframe = this.iframe;
 | |
| 
 | |
|     this.load(aURL, function() {
 | |
|       sizeSocialPanelToContent(panel, iframe);
 | |
|       let anchor = document.getElementById("social-sidebar-browser");
 | |
|       if (panel.state == "open") {
 | |
|         panel.moveToAnchor(anchor, "start_before", 0, yOffset, false);
 | |
|       } else {
 | |
|         panel.openPopup(anchor, "start_before", 0, yOffset, false, false);
 | |
|       }
 | |
|       if (aCallback) {
 | |
|         try {
 | |
|           aCallback(iframe.contentWindow);
 | |
|         } catch(e) {
 | |
|           Cu.reportError(e);
 | |
|         }
 | |
|       }
 | |
|     });
 | |
|   }
 | |
| }
 | |
| 
 | |
| SocialShare = {
 | |
|   get _dynamicResizer() {
 | |
|     delete this._dynamicResizer;
 | |
|     this._dynamicResizer = new DynamicResizeWatcher();
 | |
|     return this._dynamicResizer;
 | |
|   },
 | |
| 
 | |
|   // Share panel may be attached to the overflow or menu button depending on
 | |
|   // customization, we need to manage open state of the anchor.
 | |
|   get anchor() {
 | |
|     let widget = CustomizableUI.getWidget("social-share-button");
 | |
|     return widget.forWindow(window).anchor;
 | |
|   },
 | |
|   // Holds the anchor node in use whilst the panel is open, because it may vary.
 | |
|   _currentAnchor: null,
 | |
| 
 | |
|   get panel() {
 | |
|     return document.getElementById("social-share-panel");
 | |
|   },
 | |
| 
 | |
|   get iframe() {
 | |
|     // panel.firstChild is our toolbar hbox, panel.lastChild is the iframe
 | |
|     // container hbox used for an interstitial "loading" graphic
 | |
|     return this.panel.lastChild.firstChild;
 | |
|   },
 | |
| 
 | |
|   uninit: function () {
 | |
|     if (this.iframe) {
 | |
|       this.iframe.remove();
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _createFrame: function() {
 | |
|     let panel = this.panel;
 | |
|     if (this.iframe)
 | |
|       return;
 | |
|     this.panel.hidden = false;
 | |
|     // create and initialize the panel for this window
 | |
|     let iframe = document.createElement("browser");
 | |
|     iframe.setAttribute("type", "content");
 | |
|     iframe.setAttribute("class", "social-share-frame");
 | |
|     iframe.setAttribute("context", "contentAreaContextMenu");
 | |
|     iframe.setAttribute("tooltip", "aHTMLTooltip");
 | |
|     iframe.setAttribute("disableglobalhistory", "true");
 | |
|     iframe.setAttribute("flex", "1");
 | |
|     iframe.setAttribute("message", "true");
 | |
|     iframe.setAttribute("messagemanagergroup", "social");
 | |
|     panel.lastChild.appendChild(iframe);
 | |
|     let mm = iframe.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader.messageManager;
 | |
|     mm.sendAsyncMessage("Social:SetErrorURL", null,
 | |
|                         { template: "about:socialerror?mode=compactInfo&origin=%{origin}&url=%{url}" });
 | |
| 
 | |
|     this.populateProviderMenu();
 | |
|   },
 | |
| 
 | |
|   getSelectedProvider: function() {
 | |
|     let provider;
 | |
|     let lastProviderOrigin = this.iframe && this.iframe.getAttribute("origin");
 | |
|     if (lastProviderOrigin) {
 | |
|       provider = Social._getProviderFromOrigin(lastProviderOrigin);
 | |
|     }
 | |
|     return provider;
 | |
|   },
 | |
| 
 | |
|   createTooltip: function(event) {
 | |
|     let tt = event.target;
 | |
|     let provider = Social._getProviderFromOrigin(tt.triggerNode.getAttribute("origin"));
 | |
|     tt.firstChild.setAttribute("value", provider.name);
 | |
|     tt.lastChild.setAttribute("value", provider.origin);
 | |
|   },
 | |
| 
 | |
|   populateProviderMenu: function() {
 | |
|     if (!this.iframe)
 | |
|       return;
 | |
|     let providers = Social.providers.filter(p => p.shareURL);
 | |
|     let hbox = document.getElementById("social-share-provider-buttons");
 | |
|     // remove everything before the add-share-provider button (which should also
 | |
|     // be lastChild if any share providers were added)
 | |
|     let addButton = document.getElementById("add-share-provider");
 | |
|     while (hbox.lastChild != addButton) {
 | |
|       hbox.removeChild(hbox.lastChild);
 | |
|     }
 | |
|     let selectedProvider = this.getSelectedProvider();
 | |
|     for (let provider of providers) {
 | |
|       let button = document.createElement("toolbarbutton");
 | |
|       button.setAttribute("class", "toolbarbutton-1 share-provider-button");
 | |
|       button.setAttribute("type", "radio");
 | |
|       button.setAttribute("group", "share-providers");
 | |
|       button.setAttribute("image", provider.iconURL);
 | |
|       button.setAttribute("tooltip", "share-button-tooltip");
 | |
|       button.setAttribute("origin", provider.origin);
 | |
|       button.setAttribute("label", provider.name);
 | |
|       button.setAttribute("oncommand", "SocialShare.sharePage(this.getAttribute('origin'));");
 | |
|       if (provider == selectedProvider) {
 | |
|         this.defaultButton = button;
 | |
|       }
 | |
|       hbox.appendChild(button);
 | |
|     }
 | |
|     if (!this.defaultButton) {
 | |
|       this.defaultButton = addButton;
 | |
|     }
 | |
|     this.defaultButton.setAttribute("checked", "true");
 | |
|   },
 | |
| 
 | |
|   get shareButton() {
 | |
|     // web-panels (bookmark/sidebar) don't include customizableui, so
 | |
|     // nsContextMenu fails when accessing shareButton, breaking
 | |
|     // browser_bug409481.js.
 | |
|     if (!window.CustomizableUI)
 | |
|       return null;
 | |
|     let widget = CustomizableUI.getWidget("social-share-button");
 | |
|     if (!widget || !widget.areaType)
 | |
|       return null;
 | |
|     return widget.forWindow(window).node;
 | |
|   },
 | |
| 
 | |
|   _onclick: function() {
 | |
|     Services.telemetry.getHistogramById("SOCIAL_PANEL_CLICKS").add(0);
 | |
|   },
 | |
| 
 | |
|   onShowing: function() {
 | |
|     (this._currentAnchor || this.anchor).setAttribute("open", "true");
 | |
|     this.iframe.addEventListener("click", this._onclick, true);
 | |
|   },
 | |
| 
 | |
|   onHidden: function() {
 | |
|     (this._currentAnchor || this.anchor).removeAttribute("open");
 | |
|     this._currentAnchor = null;
 | |
|     this.iframe.removeEventListener("click", this._onclick, true);
 | |
|     this.iframe.setAttribute("src", "data:text/plain;charset=utf8,");
 | |
|     // make sure that the frame is unloaded after it is hidden
 | |
|     this.iframe.docShell.createAboutBlankContentViewer(null);
 | |
|     this.currentShare = null;
 | |
|     // share panel use is over, purge any history
 | |
|     if (this.iframe.sessionHistory) {
 | |
|       let purge = this.iframe.sessionHistory.count;
 | |
|       if (purge > 0)
 | |
|         this.iframe.sessionHistory.PurgeHistory(purge);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   sharePage: function(providerOrigin, graphData, target, anchor) {
 | |
|     // if providerOrigin is undefined, we use the last-used provider, or the
 | |
|     // current/default provider.  The provider selection in the share panel
 | |
|     // will call sharePage with an origin for us to switch to.
 | |
|     this._createFrame();
 | |
|     let iframe = this.iframe;
 | |
| 
 | |
|     // graphData is an optional param that either defines the full set of data
 | |
|     // to be shared, or partial data about the current page. It is set by a call
 | |
|     // in mozSocial API, or via nsContentMenu calls. If it is present, it MUST
 | |
|     // define at least url. If it is undefined, we're sharing the current url in
 | |
|     // the browser tab.
 | |
|     let pageData = graphData ? graphData : this.currentShare;
 | |
|     let sharedURI = pageData ? Services.io.newURI(pageData.url, null, null) :
 | |
|                                 gBrowser.currentURI;
 | |
|     if (!SocialUI.canShareOrMarkPage(sharedURI))
 | |
|       return;
 | |
| 
 | |
|     // the point of this action type is that we can use existing share
 | |
|     // endpoints (e.g. oexchange) that do not support additional
 | |
|     // socialapi functionality.  One tweak is that we shoot an event
 | |
|     // containing the open graph data.
 | |
|     let _dataFn;
 | |
|     if (!pageData || sharedURI == gBrowser.currentURI) {
 | |
|       messageManager.addMessageListener("PageMetadata:PageDataResult", _dataFn = (msg) => {
 | |
|         messageManager.removeMessageListener("PageMetadata:PageDataResult", _dataFn);
 | |
|         let pageData = msg.json;
 | |
|         if (graphData) {
 | |
|           // overwrite data retreived from page with data given to us as a param
 | |
|           for (let p in graphData) {
 | |
|             pageData[p] = graphData[p];
 | |
|           }
 | |
|         }
 | |
|         this.sharePage(providerOrigin, pageData, target, anchor);
 | |
|       });
 | |
|       gBrowser.selectedBrowser.messageManager.sendAsyncMessage("PageMetadata:GetPageData");
 | |
|       return;
 | |
|     }
 | |
|     // if this is a share of a selected item, get any microdata
 | |
|     if (!pageData.microdata && target) {
 | |
|       messageManager.addMessageListener("PageMetadata:MicrodataResult", _dataFn = (msg) => {
 | |
|         messageManager.removeMessageListener("PageMetadata:MicrodataResult", _dataFn);
 | |
|         pageData.microdata = msg.data;
 | |
|         this.sharePage(providerOrigin, pageData, target, anchor);
 | |
|       });
 | |
|       gBrowser.selectedBrowser.messageManager.sendAsyncMessage("PageMetadata:GetMicrodata", null, { target });
 | |
|       return;
 | |
|     }
 | |
|     this.currentShare = pageData;
 | |
| 
 | |
|     let provider;
 | |
|     if (providerOrigin)
 | |
|       provider = Social._getProviderFromOrigin(providerOrigin);
 | |
|     else
 | |
|       provider = this.getSelectedProvider();
 | |
|     if (!provider || !provider.shareURL) {
 | |
|       this.showDirectory(anchor);
 | |
|       return;
 | |
|     }
 | |
|     // check the menu button
 | |
|     let hbox = document.getElementById("social-share-provider-buttons");
 | |
|     let btn = hbox.querySelector("[origin='" + provider.origin + "']");
 | |
|     if (btn)
 | |
|       btn.checked = true;
 | |
| 
 | |
|     let shareEndpoint = OpenGraphBuilder.generateEndpointURL(provider.shareURL, pageData);
 | |
| 
 | |
|     this._dynamicResizer.stop();
 | |
|     let size = provider.getPageSize("share");
 | |
|     if (size) {
 | |
|       // let the css on the share panel define width, but height
 | |
|       // calculations dont work on all sites, so we allow that to be
 | |
|       // defined.
 | |
|       delete size.width;
 | |
|     }
 | |
| 
 | |
|     // if we've already loaded this provider/page share endpoint, we don't want
 | |
|     // to add another load event listener.
 | |
|     let endpointMatch = shareEndpoint == iframe.getAttribute("src");
 | |
|     if (endpointMatch) {
 | |
|       this._dynamicResizer.start(iframe.parentNode, iframe, size);
 | |
|       iframe.docShellIsActive = true;
 | |
|       let evt = iframe.contentDocument.createEvent("CustomEvent");
 | |
|       evt.initCustomEvent("OpenGraphData", true, true, JSON.stringify(pageData));
 | |
|       iframe.contentDocument.documentElement.dispatchEvent(evt);
 | |
|     } else {
 | |
|       iframe.parentNode.setAttribute("loading", "true");
 | |
|       // first time load, wait for load and dispatch after load
 | |
|       iframe.addEventListener("load", function panelBrowserOnload(e) {
 | |
|         iframe.removeEventListener("load", panelBrowserOnload, true);
 | |
|         iframe.docShellIsActive = true;
 | |
|         iframe.parentNode.removeAttribute("loading");
 | |
|         // to support standard share endpoints mimick window.open by setting
 | |
|         // window.opener, some share endpoints rely on w.opener to know they
 | |
|         // should close the window when done.
 | |
|         iframe.contentWindow.opener = iframe.contentWindow;
 | |
| 
 | |
|         SocialShare._dynamicResizer.start(iframe.parentNode, iframe, size);
 | |
| 
 | |
|         let evt = iframe.contentDocument.createEvent("CustomEvent");
 | |
|         evt.initCustomEvent("OpenGraphData", true, true, JSON.stringify(pageData));
 | |
|         iframe.contentDocument.documentElement.dispatchEvent(evt);
 | |
|       }, true);
 | |
|     }
 | |
|     // if the user switched between share providers we do not want that history
 | |
|     // available.
 | |
|     if (iframe.sessionHistory) {
 | |
|       let purge = iframe.sessionHistory.count;
 | |
|       if (purge > 0)
 | |
|         iframe.sessionHistory.PurgeHistory(purge);
 | |
|     }
 | |
| 
 | |
|     // always ensure that origin belongs to the endpoint
 | |
|     let uri = Services.io.newURI(shareEndpoint, null, null);
 | |
|     iframe.setAttribute("origin", provider.origin);
 | |
|     iframe.setAttribute("src", shareEndpoint);
 | |
|     this._openPanel(anchor);
 | |
|   },
 | |
| 
 | |
|   showDirectory: function(anchor) {
 | |
|     this._createFrame();
 | |
|     let iframe = this.iframe;
 | |
|     if (iframe.getAttribute("src") == "about:providerdirectory")
 | |
|       return;
 | |
|     iframe.removeAttribute("origin");
 | |
|     iframe.parentNode.setAttribute("loading", "true");
 | |
|     iframe.addEventListener("DOMContentLoaded", function _dcl(e) {
 | |
|       iframe.removeEventListener("DOMContentLoaded", _dcl, true);
 | |
|       iframe.parentNode.removeAttribute("loading");
 | |
|     }, true);
 | |
| 
 | |
|     iframe.addEventListener("load", function panelBrowserOnload(e) {
 | |
|       iframe.removeEventListener("load", panelBrowserOnload, true);
 | |
| 
 | |
|       hookWindowCloseForPanelClose(iframe.contentWindow);
 | |
|       SocialShare._dynamicResizer.start(iframe.parentNode, iframe);
 | |
| 
 | |
|       iframe.addEventListener("unload", function panelBrowserOnload(e) {
 | |
|         iframe.removeEventListener("unload", panelBrowserOnload, true);
 | |
|         SocialShare._dynamicResizer.stop();
 | |
|       }, true);
 | |
|     }, true);
 | |
|     iframe.setAttribute("src", "about:providerdirectory");
 | |
|     this._openPanel(anchor);
 | |
|   },
 | |
| 
 | |
|   _openPanel: function(anchor) {
 | |
|     this._currentAnchor = anchor || this.anchor;
 | |
|     anchor = document.getAnonymousElementByAttribute(this._currentAnchor, "class", "toolbarbutton-icon");
 | |
|     this.panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, false);
 | |
|     Services.telemetry.getHistogramById("SOCIAL_TOOLBAR_BUTTONS").add(0);
 | |
|   }
 | |
| };
 | |
| 
 | |
| SocialSidebar = {
 | |
|   _openStartTime: 0,
 | |
| 
 | |
|   // Whether the sidebar can be shown for this window.
 | |
|   get canShow() {
 | |
|     if (!SocialUI.enabled || document.mozFullScreen)
 | |
|       return false;
 | |
|     return Social.providers.some(p => p.sidebarURL);
 | |
|   },
 | |
| 
 | |
|   // Whether the user has toggled the sidebar on (for windows where it can appear)
 | |
|   get opened() {
 | |
|     let broadcaster = document.getElementById("socialSidebarBroadcaster");
 | |
|     return !broadcaster.hidden;
 | |
|   },
 | |
| 
 | |
|   restoreWindowState: function() {
 | |
|     // Window state is used to allow different sidebar providers in each window.
 | |
|     // We also store the provider used in a pref as the default sidebar to
 | |
|     // maintain that state for users who do not restore window state. The
 | |
|     // existence of social.sidebar.provider means the sidebar is open with that
 | |
|     // provider.
 | |
|     this._initialized = true;
 | |
|     if (!this.canShow)
 | |
|       return;
 | |
| 
 | |
|     if (Services.prefs.prefHasUserValue("social.provider.current")) {
 | |
|       // "upgrade" when the first window opens if we have old prefs.  We get the
 | |
|       // values from prefs this one time, window state will be saved when this
 | |
|       // window is closed.
 | |
|       let origin = Services.prefs.getCharPref("social.provider.current");
 | |
|       Services.prefs.clearUserPref("social.provider.current");
 | |
|       // social.sidebar.open default was true, but we only opened if there was
 | |
|       // a current provider
 | |
|       let opened = origin && true;
 | |
|       if (Services.prefs.prefHasUserValue("social.sidebar.open")) {
 | |
|         opened = origin && Services.prefs.getBoolPref("social.sidebar.open");
 | |
|         Services.prefs.clearUserPref("social.sidebar.open");
 | |
|       }
 | |
|       let data = {
 | |
|         "hidden": !opened,
 | |
|         "origin": origin
 | |
|       };
 | |
|       SessionStore.setWindowValue(window, "socialSidebar", JSON.stringify(data));
 | |
|     }
 | |
| 
 | |
|     let data = SessionStore.getWindowValue(window, "socialSidebar");
 | |
|     // if this window doesn't have it's own state, use the state from the opener
 | |
|     if (!data && window.opener && !window.opener.closed) {
 | |
|       try {
 | |
|         data = SessionStore.getWindowValue(window.opener, "socialSidebar");
 | |
|       } catch(e) {
 | |
|         // Window is not tracked, which happens on osx if the window is opened
 | |
|         // from the hidden window. That happens when you close the last window
 | |
|         // without quiting firefox, then open a new window.
 | |
|       }
 | |
|     }
 | |
|     if (data) {
 | |
|       data = JSON.parse(data);
 | |
|       document.getElementById("social-sidebar-browser").setAttribute("origin", data.origin);
 | |
|       if (!data.hidden)
 | |
|         this.show(data.origin);
 | |
|     } else if (Services.prefs.prefHasUserValue("social.sidebar.provider")) {
 | |
|       // no window state, use the global state if it is available
 | |
|       this.show(Services.prefs.getCharPref("social.sidebar.provider"));
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   saveWindowState: function() {
 | |
|     let broadcaster = document.getElementById("socialSidebarBroadcaster");
 | |
|     let sidebarOrigin = document.getElementById("social-sidebar-browser").getAttribute("origin");
 | |
|     let data = {
 | |
|       "hidden": broadcaster.hidden,
 | |
|       "origin": sidebarOrigin
 | |
|     };
 | |
|     if (broadcaster.hidden) {
 | |
|       Services.telemetry.getHistogramById("SOCIAL_SIDEBAR_OPEN_DURATION").add(Date.now()  / 1000 - this._openStartTime);
 | |
|     } else {
 | |
|       this._openStartTime = Date.now() / 1000;
 | |
|     }
 | |
| 
 | |
|     // Save a global state for users who do not restore state.
 | |
|     if (broadcaster.hidden)
 | |
|       Services.prefs.clearUserPref("social.sidebar.provider");
 | |
|     else
 | |
|       Services.prefs.setCharPref("social.sidebar.provider", sidebarOrigin);
 | |
| 
 | |
|     try {
 | |
|       SessionStore.setWindowValue(window, "socialSidebar", JSON.stringify(data));
 | |
|     } catch(e) {
 | |
|       // window not tracked during uninit
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   setSidebarVisibilityState: function(aEnabled) {
 | |
|     let sbrowser = document.getElementById("social-sidebar-browser");
 | |
|     // it's possible we'll be called twice with aEnabled=false so let's
 | |
|     // just assume we may often be called with the same state.
 | |
|     if (aEnabled == sbrowser.docShellIsActive)
 | |
|       return;
 | |
|     sbrowser.docShellIsActive = aEnabled;
 | |
|     let evt = sbrowser.contentDocument.createEvent("CustomEvent");
 | |
|     evt.initCustomEvent(aEnabled ? "socialFrameShow" : "socialFrameHide", true, true, {});
 | |
|     sbrowser.contentDocument.documentElement.dispatchEvent(evt);
 | |
|   },
 | |
| 
 | |
|   updateToggleNotifications: function() {
 | |
|     let command = document.getElementById("Social:ToggleNotifications");
 | |
|     command.setAttribute("checked", Services.prefs.getBoolPref("social.toast-notifications.enabled"));
 | |
|     command.setAttribute("hidden", !SocialUI.enabled);
 | |
|   },
 | |
| 
 | |
|   update: function SocialSidebar_update() {
 | |
|     // ensure we never update before restoreWindowState
 | |
|     if (!this._initialized)
 | |
|       return;
 | |
|     this.ensureProvider();
 | |
|     this.updateToggleNotifications();
 | |
|     this._updateHeader();
 | |
|     clearTimeout(this._unloadTimeoutId);
 | |
|     // Hide the toggle menu item if the sidebar cannot appear
 | |
|     let command = document.getElementById("Social:ToggleSidebar");
 | |
|     command.setAttribute("hidden", this.canShow ? "false" : "true");
 | |
| 
 | |
|     // Hide the sidebar if it cannot appear, or has been toggled off.
 | |
|     // Also set the command "checked" state accordingly.
 | |
|     let hideSidebar = !this.canShow || !this.opened;
 | |
|     let broadcaster = document.getElementById("socialSidebarBroadcaster");
 | |
|     broadcaster.hidden = hideSidebar;
 | |
|     command.setAttribute("checked", !hideSidebar);
 | |
| 
 | |
|     let sbrowser = document.getElementById("social-sidebar-browser");
 | |
| 
 | |
|     if (hideSidebar) {
 | |
|       sbrowser.removeEventListener("load", SocialSidebar._loadListener, true);
 | |
|       this.setSidebarVisibilityState(false);
 | |
|       // If we've been disabled, unload the sidebar content immediately;
 | |
|       // if the sidebar was just toggled to invisible, wait a timeout
 | |
|       // before unloading.
 | |
|       if (!this.canShow) {
 | |
|         this.unloadSidebar();
 | |
|       } else {
 | |
|         this._unloadTimeoutId = setTimeout(
 | |
|           this.unloadSidebar,
 | |
|           Services.prefs.getIntPref("social.sidebar.unload_timeout_ms")
 | |
|         );
 | |
|       }
 | |
|     } else {
 | |
|       sbrowser.setAttribute("origin", this.provider.origin);
 | |
| 
 | |
|       // Make sure the right sidebar URL is loaded
 | |
|       if (sbrowser.getAttribute("src") != this.provider.sidebarURL) {
 | |
|         // we check readyState right after setting src, we need a new content
 | |
|         // viewer to ensure we are checking against the correct document.
 | |
|         sbrowser.docShell.createAboutBlankContentViewer(null);
 | |
|         sbrowser.setAttribute("src", this.provider.sidebarURL);
 | |
|         PopupNotifications.locationChange(sbrowser);
 | |
|       }
 | |
| 
 | |
|       // if the document has not loaded, delay until it is
 | |
|       if (sbrowser.contentDocument.readyState != "complete") {
 | |
|         document.getElementById("social-sidebar-button").setAttribute("loading", "true");
 | |
|         sbrowser.addEventListener("load", SocialSidebar._loadListener, true);
 | |
|       } else {
 | |
|         this.setSidebarVisibilityState(true);
 | |
|       }
 | |
|     }
 | |
|     this._updateCheckedMenuItems(this.opened && this.provider ? this.provider.origin : null);
 | |
|   },
 | |
| 
 | |
|   _onclick: function() {
 | |
|     Services.telemetry.getHistogramById("SOCIAL_PANEL_CLICKS").add(3);
 | |
|   },
 | |
| 
 | |
|   _loadListener: function SocialSidebar_loadListener() {
 | |
|     let sbrowser = document.getElementById("social-sidebar-browser");
 | |
|     sbrowser.removeEventListener("load", SocialSidebar._loadListener, true);
 | |
|     document.getElementById("social-sidebar-button").removeAttribute("loading");
 | |
|     SocialSidebar.setSidebarVisibilityState(true);
 | |
|     sbrowser.addEventListener("click", SocialSidebar._onclick, true);
 | |
|   },
 | |
| 
 | |
|   unloadSidebar: function SocialSidebar_unloadSidebar() {
 | |
|     let sbrowser = document.getElementById("social-sidebar-browser");
 | |
|     if (!sbrowser.hasAttribute("origin"))
 | |
|       return;
 | |
| 
 | |
|     sbrowser.removeEventListener("click", SocialSidebar._onclick, true);
 | |
|     sbrowser.stop();
 | |
|     sbrowser.removeAttribute("origin");
 | |
|     sbrowser.setAttribute("src", "about:blank");
 | |
|     // We need to explicitly create a new content viewer because the old one
 | |
|     // doesn't get destroyed until about:blank has loaded (which does not happen
 | |
|     // as long as the element is hidden).
 | |
|     sbrowser.docShell.createAboutBlankContentViewer(null);
 | |
|     SocialFlyout.unload();
 | |
|   },
 | |
| 
 | |
|   _unloadTimeoutId: 0,
 | |
| 
 | |
|   loadFrameworkerFailure: function() {
 | |
|     if (this.provider && this.provider.errorState == "frameworker-error") {
 | |
|       // we have to explicitly load this error page since it is not being
 | |
|       // handled via the normal error page paths.
 | |
|       let sbrowser = document.getElementById("social-sidebar-browser");
 | |
|       sbrowser.setAttribute("src", "about:socialerror?mode=workerFailure&origin=" +
 | |
|                             encodeURIComponent(this.provider.origin));
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _provider: null,
 | |
|   ensureProvider: function() {
 | |
|     if (this._provider)
 | |
|       return;
 | |
|     // origin for sidebar is persisted, so get the previously selected sidebar
 | |
|     // first, otherwise fallback to the first provider in the list
 | |
|     let sbrowser = document.getElementById("social-sidebar-browser");
 | |
|     let origin = sbrowser.getAttribute("origin");
 | |
|     let providers = Social.providers.filter(p => p.sidebarURL);
 | |
|     let provider;
 | |
|     if (origin)
 | |
|       provider = Social._getProviderFromOrigin(origin);
 | |
|     if (!provider && providers.length > 0)
 | |
|       provider = providers[0];
 | |
|     if (provider)
 | |
|       this.provider = provider;
 | |
|   },
 | |
| 
 | |
|   get provider() {
 | |
|     return this._provider;
 | |
|   },
 | |
| 
 | |
|   set provider(provider) {
 | |
|     if (!provider || provider.sidebarURL) {
 | |
|       this._provider = provider;
 | |
|       this._updateHeader();
 | |
|       this._updateCheckedMenuItems(provider && provider.origin);
 | |
|       this.update();
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   disableProvider: function(origin) {
 | |
|     if (this._provider && this._provider.origin == origin) {
 | |
|       this._provider = null;
 | |
|       // force a selection of the next provider if there is one
 | |
|       this.ensureProvider();
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _updateHeader: function() {
 | |
|     let provider = this.provider;
 | |
|     let image, title;
 | |
|     if (provider) {
 | |
|       image = "url(" + (provider.icon32URL || provider.iconURL) + ")";
 | |
|       title = provider.name;
 | |
|     }
 | |
|     document.getElementById("social-sidebar-favico").style.listStyleImage = image;
 | |
|     document.getElementById("social-sidebar-title").value = title;
 | |
|   },
 | |
| 
 | |
|   _updateCheckedMenuItems: function(origin) {
 | |
|     // update selected menuitems
 | |
|     let menuitems = document.getElementsByClassName("social-provider-menuitem");
 | |
|     for (let mi of menuitems) {
 | |
|       if (origin && mi.getAttribute("origin") == origin) {
 | |
|         mi.setAttribute("checked", "true");
 | |
|         mi.setAttribute("oncommand", "SocialSidebar.hide();");
 | |
|       } else if (mi.getAttribute("checked")) {
 | |
|         mi.removeAttribute("checked");
 | |
|         mi.setAttribute("oncommand", "SocialSidebar.show(this.getAttribute('origin'));");
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   show: function(origin) {
 | |
|     // always show the sidebar, and set the provider
 | |
|     let broadcaster = document.getElementById("socialSidebarBroadcaster");
 | |
|     broadcaster.hidden = false;
 | |
|     if (origin)
 | |
|       this.provider = Social._getProviderFromOrigin(origin);
 | |
|     else
 | |
|       SocialSidebar.update();
 | |
|     this.saveWindowState();
 | |
|     Services.telemetry.getHistogramById("SOCIAL_SIDEBAR_STATE").add(true);
 | |
|   },
 | |
| 
 | |
|   hide: function() {
 | |
|     let broadcaster = document.getElementById("socialSidebarBroadcaster");
 | |
|     broadcaster.hidden = true;
 | |
|     this._updateCheckedMenuItems();
 | |
|     this.clearProviderMenus();
 | |
|     SocialSidebar.update();
 | |
|     this.saveWindowState();
 | |
|     Services.telemetry.getHistogramById("SOCIAL_SIDEBAR_STATE").add(false);
 | |
|   },
 | |
| 
 | |
|   toggleSidebar: function SocialSidebar_toggle() {
 | |
|     let broadcaster = document.getElementById("socialSidebarBroadcaster");
 | |
|     if (broadcaster.hidden)
 | |
|       this.show();
 | |
|     else
 | |
|       this.hide();
 | |
|   },
 | |
| 
 | |
|   populateSidebarMenu: function(event) {
 | |
|     // Providers are removed from the view->sidebar menu when there is a change
 | |
|     // in providers, so we only have to populate onshowing if there are no
 | |
|     // provider menus. We populate this menu so long as there are enabled
 | |
|     // providers with sidebars.
 | |
|     let popup = event.target;
 | |
|     let providerMenuSeps = popup.getElementsByClassName("social-provider-menu");
 | |
|     if (providerMenuSeps[0].previousSibling.nodeName == "menuseparator")
 | |
|       SocialSidebar.populateProviderMenu(providerMenuSeps[0]);
 | |
|   },
 | |
| 
 | |
|   clearProviderMenus: function() {
 | |
|     // called when there is a change in the provider list we clear all menus,
 | |
|     // they will be repopulated when the menu is shown
 | |
|     let providerMenuSeps = document.getElementsByClassName("social-provider-menu");
 | |
|     for (let providerMenuSep of providerMenuSeps) {
 | |
|       while (providerMenuSep.previousSibling.nodeName == "menuitem") {
 | |
|         let menu = providerMenuSep.parentNode;
 | |
|         menu.removeChild(providerMenuSep.previousSibling);
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   populateProviderMenu: function(providerMenuSep) {
 | |
|     let menu = providerMenuSep.parentNode;
 | |
|     // selectable providers are inserted before the provider-menu seperator,
 | |
|     // remove any menuitems in that area
 | |
|     while (providerMenuSep.previousSibling.nodeName == "menuitem") {
 | |
|       menu.removeChild(providerMenuSep.previousSibling);
 | |
|     }
 | |
|     // only show a selection in the sidebar header menu if there is more than one
 | |
|     let providers = Social.providers.filter(p => p.sidebarURL);
 | |
|     if (providers.length < 2 && menu.id != "viewSidebarMenu") {
 | |
|       providerMenuSep.hidden = true;
 | |
|       return;
 | |
|     }
 | |
|     let topSep = providerMenuSep.previousSibling;
 | |
|     for (let provider of providers) {
 | |
|       let menuitem = document.createElement("menuitem");
 | |
|       menuitem.className = "menuitem-iconic social-provider-menuitem";
 | |
|       menuitem.setAttribute("image", provider.iconURL);
 | |
|       menuitem.setAttribute("label", provider.name);
 | |
|       menuitem.setAttribute("origin", provider.origin);
 | |
|       if (this.opened && provider == this.provider) {
 | |
|         menuitem.setAttribute("checked", "true");
 | |
|         menuitem.setAttribute("oncommand", "SocialSidebar.hide();");
 | |
|       } else {
 | |
|         menuitem.setAttribute("oncommand", "SocialSidebar.show(this.getAttribute('origin'));");
 | |
|       }
 | |
|       menu.insertBefore(menuitem, providerMenuSep);
 | |
|     }
 | |
|     topSep.hidden = topSep.nextSibling == providerMenuSep;
 | |
|     providerMenuSep.hidden = !providerMenuSep.nextSibling;
 | |
|   }
 | |
| }
 | |
| 
 | |
| // this helper class is used by removable/customizable buttons to handle
 | |
| // widget creation/destruction
 | |
| 
 | |
| // When a provider is installed we show all their UI so the user will see the
 | |
| // functionality of what they installed. The user can later customize the UI,
 | |
| // moving buttons around or off the toolbar.
 | |
| //
 | |
| // On startup, we create the button widgets of any enabled provider.
 | |
| // CustomizableUI handles placement and persistence of placement.
 | |
| function ToolbarHelper(type, createButtonFn, listener) {
 | |
|   this._createButton = createButtonFn;
 | |
|   this._type = type;
 | |
| 
 | |
|   if (listener) {
 | |
|     CustomizableUI.addListener(listener);
 | |
|     // remove this listener on window close
 | |
|     window.addEventListener("unload", () => {
 | |
|       CustomizableUI.removeListener(listener);
 | |
|     });
 | |
|   }
 | |
| }
 | |
| 
 | |
| ToolbarHelper.prototype = {
 | |
|   idFromOrigin: function(origin) {
 | |
|     // this id needs to pass the checks in CustomizableUI, so remove characters
 | |
|     // that wont pass.
 | |
|     return this._type + "-" + Services.io.newURI(origin, null, null).hostPort.replace(/[\.:]/g,'-');
 | |
|   },
 | |
| 
 | |
|   // should be called on disable of a provider
 | |
|   removeProviderButton: function(origin) {
 | |
|     CustomizableUI.destroyWidget(this.idFromOrigin(origin));
 | |
|   },
 | |
| 
 | |
|   clearPalette: function() {
 | |
|     for (let p of Social.providers) {
 | |
|       this.removeProviderButton(p.origin);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   // should be called on enable of a provider
 | |
|   populatePalette: function() {
 | |
|     if (!Social.enabled) {
 | |
|       this.clearPalette();
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // create any buttons that do not exist yet if they have been persisted
 | |
|     // as a part of the UI (otherwise they belong in the palette).
 | |
|     for (let provider of Social.providers) {
 | |
|       let id = this.idFromOrigin(provider.origin);
 | |
|       this._createButton(id, provider);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| var SocialStatusWidgetListener = {
 | |
|   _getNodeOrigin: function(aWidgetId) {
 | |
|     // we rely on the button id being the same as the widget.
 | |
|     let node = document.getElementById(aWidgetId);
 | |
|     if (!node)
 | |
|       return null
 | |
|     if (!node.classList.contains("social-status-button"))
 | |
|       return null
 | |
|     return node.getAttribute("origin");
 | |
|   },
 | |
|   onWidgetAdded: function(aWidgetId, aArea, aPosition) {
 | |
|     let origin = this._getNodeOrigin(aWidgetId);
 | |
|     if (origin)
 | |
|       SocialStatus.updateButton(origin);
 | |
|   },
 | |
|   onWidgetRemoved: function(aWidgetId, aPrevArea) {
 | |
|     let origin = this._getNodeOrigin(aWidgetId);
 | |
|     if (!origin)
 | |
|       return;
 | |
|     // When a widget is demoted to the palette ('removed'), it's visual
 | |
|     // style should change.
 | |
|     SocialStatus.updateButton(origin);
 | |
|     SocialStatus._removeFrame(origin);
 | |
|   }
 | |
| }
 | |
| 
 | |
| SocialStatus = {
 | |
|   populateToolbarPalette: function() {
 | |
|     this._toolbarHelper.populatePalette();
 | |
| 
 | |
|     for (let provider of Social.providers)
 | |
|       this.updateButton(provider.origin);
 | |
|   },
 | |
| 
 | |
|   removeProvider: function(origin) {
 | |
|     this._removeFrame(origin);
 | |
|     this._toolbarHelper.removeProviderButton(origin);
 | |
|   },
 | |
| 
 | |
|   reloadProvider: function(origin) {
 | |
|     let button = document.getElementById(this._toolbarHelper.idFromOrigin(origin));
 | |
|     if (button && button.getAttribute("open") == "true")
 | |
|       document.getElementById("social-notification-panel").hidePopup();
 | |
|     this._removeFrame(origin);
 | |
|   },
 | |
| 
 | |
|   _removeFrame: function(origin) {
 | |
|     let notificationFrameId = "social-status-" + origin;
 | |
|     let frame = document.getElementById(notificationFrameId);
 | |
|     if (frame) {
 | |
|       frame.parentNode.removeChild(frame);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   get _toolbarHelper() {
 | |
|     delete this._toolbarHelper;
 | |
|     this._toolbarHelper = new ToolbarHelper("social-status-button",
 | |
|                                             CreateSocialStatusWidget,
 | |
|                                             SocialStatusWidgetListener);
 | |
|     return this._toolbarHelper;
 | |
|   },
 | |
| 
 | |
|   updateButton: function(origin) {
 | |
|     let id = this._toolbarHelper.idFromOrigin(origin);
 | |
|     let widget = CustomizableUI.getWidget(id);
 | |
|     if (!widget)
 | |
|       return;
 | |
|     let button = widget.forWindow(window).node;
 | |
|     if (button) {
 | |
|       // we only grab the first notification, ignore all others
 | |
|       let provider = Social._getProviderFromOrigin(origin);
 | |
|       let icons = provider.ambientNotificationIcons;
 | |
|       let iconNames = Object.keys(icons);
 | |
|       let notif = icons[iconNames[0]];
 | |
| 
 | |
|       // The image and tooltip need to be updated for both
 | |
|       // ambient notification and profile changes.
 | |
|       let iconURL = provider.icon32URL || provider.iconURL;
 | |
|       let tooltiptext;
 | |
|       if (!notif || !widget.areaType) {
 | |
|         button.style.listStyleImage = "url(" + iconURL + ")";
 | |
|         button.setAttribute("badge", "");
 | |
|         button.setAttribute("aria-label", "");
 | |
|         button.setAttribute("tooltiptext", provider.name);
 | |
|         return;
 | |
|       }
 | |
|       button.style.listStyleImage = "url(" + (notif.iconURL || iconURL) + ")";
 | |
|       button.setAttribute("tooltiptext", notif.label || provider.name);
 | |
| 
 | |
|       let badge = notif.counter || "";
 | |
|       button.setAttribute("badge", badge);
 | |
|       let ariaLabel = notif.label;
 | |
|       // if there is a badge value, we must use a localizable string to insert it.
 | |
|       if (badge)
 | |
|         ariaLabel = gNavigatorBundle.getFormattedString("social.aria.toolbarButtonBadgeText",
 | |
|                                                         [ariaLabel, badge]);
 | |
|       button.setAttribute("aria-label", ariaLabel);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _onclose: function(frame) {
 | |
|     frame.removeEventListener("close", this._onclose, true);
 | |
|     frame.removeEventListener("click", this._onclick, true);
 | |
|   },
 | |
| 
 | |
|   _onclick: function() {
 | |
|     Services.telemetry.getHistogramById("SOCIAL_PANEL_CLICKS").add(1);
 | |
|   },
 | |
| 
 | |
|   showPopup: function(aToolbarButton) {
 | |
|     // attach our notification panel if necessary
 | |
|     let origin = aToolbarButton.getAttribute("origin");
 | |
|     let provider = Social._getProviderFromOrigin(origin);
 | |
| 
 | |
|     PanelFrame.showPopup(window, aToolbarButton, "social", origin,
 | |
|                          provider.statusURL, provider.getPageSize("status"),
 | |
|                          (frame) => {
 | |
|                           frame.addEventListener("close", () => { SocialStatus._onclose(frame) }, true);
 | |
|                           frame.addEventListener("click", this._onclick, true);
 | |
|                         });
 | |
|     Services.telemetry.getHistogramById("SOCIAL_TOOLBAR_BUTTONS").add(1);
 | |
|   }
 | |
| };
 | |
| 
 | |
| 
 | |
| var SocialMarksWidgetListener = {
 | |
|   onWidgetAdded: function(aWidgetId, aArea, aPosition) {
 | |
|     let node = document.getElementById(aWidgetId);
 | |
|     if (!node || !node.classList.contains("social-mark-button"))
 | |
|       return;
 | |
|     node._receiveMessage = node.receiveMessage.bind(node);
 | |
|     messageManager.addMessageListener("Social:ErrorPageNotify", node._receiveMessage);
 | |
|   },
 | |
|   onWidgetBeforeDOMChange: function(aNode, aNextNode, aContainer, isRemoval) {
 | |
|     if (!isRemoval || !aNode || !aNode.classList.contains("social-mark-button"))
 | |
|       return;
 | |
|     messageManager.removeMessageListener("Social:ErrorPageNotify", aNode._receiveMessage);
 | |
|     delete aNode._receiveMessage;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * SocialMarks
 | |
|  *
 | |
|  * Handles updates to toolbox and signals all buttons to update when necessary.
 | |
|  */
 | |
| SocialMarks = {
 | |
|   get nodes() {
 | |
|     for (let p of Social.providers.filter(p => p.markURL)) {
 | |
|       let widgetId = SocialMarks._toolbarHelper.idFromOrigin(p.origin);
 | |
|       let widget = CustomizableUI.getWidget(widgetId);
 | |
|       if (!widget)
 | |
|         continue;
 | |
|       let node = widget.forWindow(window).node;
 | |
|       if (node)
 | |
|         yield node;
 | |
|     }
 | |
|   },
 | |
|   update: function() {
 | |
|     // querySelectorAll does not work on the menu panel, so we have to do this
 | |
|     // the hard way.
 | |
|     for (let node of this.nodes) {
 | |
|       // xbl binding is not complete on startup when buttons are not in toolbar,
 | |
|       // verify update is available
 | |
|       if (node.update) {
 | |
|         node.update();
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   getProviders: function() {
 | |
|     // only rely on providers that the user has placed in the UI somewhere. This
 | |
|     // also means that populateToolbarPalette must be called prior to using this
 | |
|     // method, otherwise you get a big fat zero. For our use case with context
 | |
|     // menu's, this is ok.
 | |
|     let tbh = this._toolbarHelper;
 | |
|     return Social.providers.filter(p => p.markURL &&
 | |
|                                         document.getElementById(tbh.idFromOrigin(p.origin)));
 | |
|   },
 | |
| 
 | |
|   populateContextMenu: function() {
 | |
|     // only show a selection if enabled and there is more than one
 | |
|     let providers = this.getProviders();
 | |
| 
 | |
|     // remove all previous entries by class
 | |
|     let menus = [...document.getElementsByClassName("context-socialmarks")];
 | |
|     for (let m of menus) {
 | |
|       m.parentNode.removeChild(m);
 | |
|     }
 | |
| 
 | |
|     let contextMenus = [
 | |
|       {
 | |
|         type: "link",
 | |
|         id: "context-marklinkMenu",
 | |
|         label: "social.marklinkMenu.label"
 | |
|       },
 | |
|       {
 | |
|         type: "page",
 | |
|         id: "context-markpageMenu",
 | |
|         label: "social.markpageMenu.label"
 | |
|       }
 | |
|     ];
 | |
|     for (let cfg of contextMenus) {
 | |
|       this._populateContextPopup(cfg, providers);
 | |
|     }
 | |
|     this.update();
 | |
|   },
 | |
| 
 | |
|   MENU_LIMIT: 3, // adjustable for testing
 | |
|   _populateContextPopup: function(menuInfo, providers) {
 | |
|     let menu = document.getElementById(menuInfo.id);
 | |
|     let popup = menu.firstChild;
 | |
|     for (let provider of providers) {
 | |
|       // We show up to MENU_LIMIT providers as single menuitems's at the top
 | |
|       // level of the context menu, if we have more than that, dump them *all*
 | |
|       // into the menu popup.
 | |
|       let mi = document.createElement("menuitem");
 | |
|       mi.setAttribute("oncommand", "gContextMenu.markLink(this.getAttribute('origin'));");
 | |
|       mi.setAttribute("origin", provider.origin);
 | |
|       mi.setAttribute("image", provider.iconURL);
 | |
|       if (providers.length <= this.MENU_LIMIT) {
 | |
|         // an extra class to make enable/disable easy
 | |
|         mi.setAttribute("class", "menuitem-iconic context-socialmarks context-mark"+menuInfo.type);
 | |
|         let menuLabel = gNavigatorBundle.getFormattedString(menuInfo.label, [provider.name]);
 | |
|         mi.setAttribute("label", menuLabel);
 | |
|         menu.parentNode.insertBefore(mi, menu);
 | |
|       } else {
 | |
|         mi.setAttribute("class", "menuitem-iconic context-socialmarks");
 | |
|         mi.setAttribute("label", provider.name);
 | |
|         popup.appendChild(mi);
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   populateToolbarPalette: function() {
 | |
|     this._toolbarHelper.populatePalette();
 | |
|     this.populateContextMenu();
 | |
|   },
 | |
| 
 | |
|   removeProvider: function(origin) {
 | |
|     this._toolbarHelper.removeProviderButton(origin);
 | |
|   },
 | |
| 
 | |
|   get _toolbarHelper() {
 | |
|     delete this._toolbarHelper;
 | |
|     this._toolbarHelper = new ToolbarHelper("social-mark-button",
 | |
|                                             CreateSocialMarkWidget,
 | |
|                                             SocialMarksWidgetListener);
 | |
|     return this._toolbarHelper;
 | |
|   },
 | |
| 
 | |
|   markLink: function(aOrigin, aUrl, aTarget) {
 | |
|     // find the button for this provider, and open it
 | |
|     let id = this._toolbarHelper.idFromOrigin(aOrigin);
 | |
|     document.getElementById(id).markLink(aUrl, aTarget);
 | |
|   }
 | |
| };
 | |
| 
 | |
| })();
 | 
