forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			508 lines
		
	
	
	
		
			18 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			508 lines
		
	
	
	
		
			18 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,
 | |
|     SocialShare,
 | |
|     SocialActivationListener;
 | |
| 
 | |
| (function() {
 | |
| 
 | |
| 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;
 | |
| });
 | |
| 
 | |
| 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:providers-changed", false);
 | |
| 
 | |
|     CustomizableUI.addListener(this);
 | |
|     SocialActivationListener.init();
 | |
| 
 | |
|     Social.init().then((update) => {
 | |
|       if (update)
 | |
|         this._providersChanged();
 | |
|     });
 | |
| 
 | |
|     this._initialized = true;
 | |
|   },
 | |
| 
 | |
|   // Called on window unload
 | |
|   uninit: function SocialUI_uninit() {
 | |
|     if (!this._initialized) {
 | |
|       return;
 | |
|     }
 | |
|     Services.obs.removeObserver(this, "social:providers-changed");
 | |
| 
 | |
|     CustomizableUI.removeListener(this);
 | |
|     SocialActivationListener.uninit();
 | |
| 
 | |
|     this._initialized = false;
 | |
|   },
 | |
| 
 | |
|   observe: function SocialUI_observe(subject, topic, data) {
 | |
|     switch (topic) {
 | |
|       case "social:providers-changed":
 | |
|         this._providersChanged();
 | |
|         break;
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _providersChanged: function() {
 | |
|     SocialShare.populateProviderMenu();
 | |
|   },
 | |
| 
 | |
|   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.ownerGlobal;
 | |
|     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)
 | |
|       return false;
 | |
|     return Social.providers.length > 0;
 | |
|   },
 | |
| 
 | |
|   canSharePage: function(aURI) {
 | |
|     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.canSharePage(gBrowser.currentURI);
 | |
|     let shareButton = SocialShare.shareButton;
 | |
|     if (shareButton) {
 | |
|       if (canShare) {
 | |
|         shareButton.removeAttribute("disabled")
 | |
|       } else {
 | |
|         shareButton.setAttribute("disabled", "true")
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   // called on tab/urlbar/location changes and after customization. Update
 | |
|   // anything that is tab specific.
 | |
|   updateState: function() {
 | |
|     goSetCommandEnabled("Social:PageShareable", this.canSharePage(gBrowser.currentURI));
 | |
|   }
 | |
| }
 | |
| 
 | |
| // 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 };
 | |
|     }
 | |
| 
 | |
|     Social.installProvider(data, function(manifest) {
 | |
|       Social.activateFromOrigin(manifest.origin, function(provider) {
 | |
|         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);
 | |
|   }
 | |
| }
 | |
| 
 | |
| 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) {
 | |
|       let mm = this.messageManager;
 | |
|       mm.removeMessageListener("PageVisibility:Show", this);
 | |
|       mm.removeMessageListener("PageVisibility:Hide", this);
 | |
|       mm.removeMessageListener("Social:DOMWindowClose", this);
 | |
|       this.iframe.removeEventListener("load", this);
 | |
|       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 = this.messageManager;
 | |
|     mm.addMessageListener("PageVisibility:Show", this);
 | |
|     mm.addMessageListener("PageVisibility:Hide", this);
 | |
|     mm.sendAsyncMessage("Social:SetErrorURL",
 | |
|                         { template: "about:socialerror?mode=compactInfo&origin=%{origin}&url=%{url}" });
 | |
|     iframe.addEventListener("load", this, true);
 | |
|     mm.addMessageListener("Social:DOMWindowClose", this);
 | |
| 
 | |
|     this.populateProviderMenu();
 | |
|   },
 | |
| 
 | |
|   get messageManager() {
 | |
|     // The xbl bindings for the iframe may not exist yet, so we can't
 | |
|     // access iframe.messageManager directly - but can get at it with this dance.
 | |
|     return this.iframe.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader.messageManager;
 | |
|   },
 | |
| 
 | |
|   receiveMessage: function(aMessage) {
 | |
|     let iframe = this.iframe;
 | |
|     switch(aMessage.name) {
 | |
|       case "PageVisibility:Show":
 | |
|         SocialShare._dynamicResizer.start(iframe.parentNode, iframe);
 | |
|         break;
 | |
|       case "PageVisibility:Hide":
 | |
|         SocialShare._dynamicResizer.stop();
 | |
|         break;
 | |
|       case "Social:DOMWindowClose":
 | |
|         this.panel.hidePopup();
 | |
|         break;
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   handleEvent: function(event) {
 | |
|     switch (event.type) {
 | |
|       case "load": {
 | |
|         let iframe = this.iframe;
 | |
|         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;
 | |
|         this.messageManager.sendAsyncMessage("Social:HookWindowCloseForPanelClose");
 | |
|         this.messageManager.sendAsyncMessage("Social:DisableDialogs", {});
 | |
|         if (this.currentShare)
 | |
|           SocialShare.messageManager.sendAsyncMessage("Social:OpenGraphData", this.currentShare);
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   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.docShellIsActive = false;
 | |
|     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.messageManager.sendAsyncMessage("Social:ClearFrame");
 | |
|     this.currentShare = null;
 | |
|     // share panel use is over, purge any history
 | |
|     this.iframe.purgeSessionHistory();
 | |
|   },
 | |
| 
 | |
|   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.canSharePage(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", null, { target });
 | |
|       return;
 | |
|     }
 | |
|     // if this is a share of a selected item, get any microformats
 | |
|     if (!pageData.microformats && target) {
 | |
|       messageManager.addMessageListener("PageMetadata:MicroformatsResult", _dataFn = (msg) => {
 | |
|         messageManager.removeMessageListener("PageMetadata:MicroformatsResult", _dataFn);
 | |
|         pageData.microformats = msg.data;
 | |
|         this.sharePage(providerOrigin, pageData, target, anchor);
 | |
|       });
 | |
|       gBrowser.selectedBrowser.messageManager.sendAsyncMessage("PageMetadata:GetMicroformats", 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;
 | |
|       SocialShare.messageManager.sendAsyncMessage("Social:OpenGraphData", this.currentShare);
 | |
|     } else {
 | |
|       iframe.parentNode.setAttribute("loading", "true");
 | |
|     }
 | |
|     // if the user switched between share providers we do not want that history
 | |
|     // available.
 | |
|     iframe.purgeSessionHistory();
 | |
| 
 | |
|     // 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.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);
 | |
|   }
 | |
| };
 | |
| 
 | |
| })();
 | 
