diff options
Diffstat (limited to 'browser/base/content/browser-social.js')
-rw-r--r-- | browser/base/content/browser-social.js | 1406 |
1 files changed, 0 insertions, 1406 deletions
diff --git a/browser/base/content/browser-social.js b/browser/base/content/browser-social.js deleted file mode 100644 index 7a0ab726c..000000000 --- a/browser/base/content/browser-social.js +++ /dev/null @@ -1,1406 +0,0 @@ -// 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 -let SocialUI, - SocialChatBar, - SocialFlyout, - SocialMark, - SocialShare, - SocialMenu, - SocialToolbar, - SocialSidebar; - -(function() { - -// The minimum sizes for the auto-resize panel code. -const PANEL_MIN_HEIGHT = 100; -const PANEL_MIN_WIDTH = 330; - -XPCOMUtils.defineLazyModuleGetter(this, "SharedFrame", - "resource:///modules/SharedFrame.jsm"); - -XPCOMUtils.defineLazyGetter(this, "OpenGraphBuilder", function() { - let tmp = {}; - Cu.import("resource:///modules/Social.jsm", tmp); - return tmp.OpenGraphBuilder; -}); - -SocialUI = { - // Called on delayed startup to initialize the UI - init: function SocialUI_init() { - Services.obs.addObserver(this, "social:ambient-notification-changed", false); - Services.obs.addObserver(this, "social:profile-changed", false); - Services.obs.addObserver(this, "social:page-mark-config", false); - Services.obs.addObserver(this, "social:frameworker-error", false); - Services.obs.addObserver(this, "social:provider-set", false); - Services.obs.addObserver(this, "social:providers-changed", false); - - Services.prefs.addObserver("social.sidebar.open", this, false); - Services.prefs.addObserver("social.toast-notifications.enabled", this, false); - - gBrowser.addEventListener("ActivateSocialFeature", this._activationEventHandler.bind(this), true, true); - - SocialChatBar.init(); - SocialMark.init(); - SocialShare.init(); - SocialMenu.init(); - SocialToolbar.init(); - SocialSidebar.init(); - - if (!Social.initialized) { - Social.init(); - } else { - // social was previously initialized, so it's not going to notify us of - // anything, so handle that now. - this.observe(null, "social:providers-changed", null); - this.observe(null, "social:provider-set", Social.provider ? Social.provider.origin : null); - } - }, - - // Called on window unload - uninit: function SocialUI_uninit() { - Services.obs.removeObserver(this, "social:ambient-notification-changed"); - Services.obs.removeObserver(this, "social:profile-changed"); - Services.obs.removeObserver(this, "social:page-mark-config"); - Services.obs.removeObserver(this, "social:frameworker-error"); - Services.obs.removeObserver(this, "social:provider-set"); - Services.obs.removeObserver(this, "social:providers-changed"); - - Services.prefs.removeObserver("social.sidebar.open", this); - Services.prefs.removeObserver("social.toast-notifications.enabled", this); - }, - - _matchesCurrentProvider: function (origin) { - return Social.provider && Social.provider.origin == origin; - }, - - observe: function SocialUI_observe(subject, topic, data) { - // Exceptions here sometimes don't get reported properly, report them - // manually :( - try { - switch (topic) { - case "social:provider-set": - // Social.provider has changed (possibly to null), update any state - // which depends on it. - this._updateActiveUI(); - this._updateMenuItems(); - - SocialFlyout.unload(); - SocialChatBar.update(); - SocialShare.update(); - SocialSidebar.update(); - SocialMark.update(); - SocialToolbar.update(); - SocialMenu.populate(); - break; - case "social:providers-changed": - // the list of providers changed - this may impact the "active" UI. - this._updateActiveUI(); - // and the multi-provider menu - SocialToolbar.populateProviderMenus(); - SocialShare.populateProviderMenu(); - break; - - // Provider-specific notifications - case "social:ambient-notification-changed": - if (this._matchesCurrentProvider(data)) { - SocialToolbar.updateButton(); - SocialMenu.populate(); - } - break; - case "social:profile-changed": - if (this._matchesCurrentProvider(data)) { - SocialToolbar.updateProvider(); - SocialMark.update(); - SocialChatBar.update(); - } - break; - case "social:page-mark-config": - if (this._matchesCurrentProvider(data)) { - SocialMark.updateMarkState(); - } - break; - case "social:frameworker-error": - if (this.enabled && Social.provider.origin == data) { - SocialSidebar.setSidebarErrorMessage(); - } - break; - - case "nsPref:changed": - if (data == "social.sidebar.open") { - SocialSidebar.update(); - } else if (data == "social.toast-notifications.enabled") { - SocialToolbar.updateButton(); - } - break; - } - } catch (e) { - Components.utils.reportError(e + "\n" + e.stack); - throw e; - } - }, - - nonBrowserWindowInit: function SocialUI_nonBrowserInit() { - // Disable the social menu item in non-browser windows - document.getElementById("menu_socialAmbientMenu").hidden = true; - }, - - // Miscellaneous helpers - showProfile: function SocialUI_showProfile() { - if (Social.haveLoggedInUser()) - openUILinkIn(Social.provider.profile.profileURL, "tab"); - else { - // XXX Bug 789585 will implement an API for provider-specified login pages. - openUILinkIn(Social.provider.origin, "tab"); - } - }, - - _updateActiveUI: function SocialUI_updateActiveUI() { - // The "active" UI isn't dependent on there being a provider, just on - // social being "active" (but also chromeless/PB) - let enabled = Social.providers.length > 0 && !this._chromeless && - !PrivateBrowsingUtils.isWindowPrivate(window); - let broadcaster = document.getElementById("socialActiveBroadcaster"); - broadcaster.hidden = !enabled; - - let toggleCommand = document.getElementById("Social:Toggle"); - toggleCommand.setAttribute("hidden", enabled ? "false" : "true"); - - if (enabled) { - // enabled == true means we at least have a defaultProvider - let provider = Social.provider || Social.defaultProvider; - // We only need to update the command itself - all our menu items use it. - let label = gNavigatorBundle.getFormattedString(Social.provider ? - "social.turnOff.label" : - "social.turnOn.label", - [provider.name]); - let accesskey = gNavigatorBundle.getString(Social.provider ? - "social.turnOff.accesskey" : - "social.turnOn.accesskey"); - toggleCommand.setAttribute("label", label); - toggleCommand.setAttribute("accesskey", accesskey); - } - }, - - _updateMenuItems: function () { - let provider = Social.provider || Social.defaultProvider; - if (!provider) - return; - // The View->Sidebar and Menubar->Tools menu. - for (let id of ["menu_socialSidebar", "menu_socialAmbientMenu"]) - document.getElementById(id).setAttribute("label", provider.name); - }, - - // This handles "ActivateSocialFeature" events fired against content documents - // in this window. - _activationEventHandler: function SocialUI_activationHandler(e) { - let targetDoc; - let node; - if (e.target instanceof HTMLDocument) { - // version 0 support - targetDoc = e.target; - node = targetDoc.documentElement - } else { - targetDoc = e.target.ownerDocument; - node = e.target; - } - if (!(targetDoc instanceof HTMLDocument)) - return; - - // Ignore events fired in background tabs or iframes - if (targetDoc.defaultView != content) - return; - - // If we are in PB mode, we silently do nothing (bug 829404 exists to - // do something sensible here...) - if (PrivateBrowsingUtils.isWindowPrivate(window)) - return; - - // If the last event was received < 1s ago, ignore this one - let now = Date.now(); - if (now - Social.lastEventReceived < 1000) - return; - Social.lastEventReceived = now; - - // We only want to activate if it is as a result of user input. - let dwu = window.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils); - if (!dwu.isHandlingUserInput) { - Cu.reportError("attempt to activate provider without user input from " + targetDoc.nodePrincipal.origin); - return; - } - - let data = node.getAttribute("data-service"); - if (data) { - try { - data = JSON.parse(data); - } catch(e) { - Cu.reportError("Social Service manifest parse error: "+e); - return; - } - } - Social.installProvider(targetDoc, data, function(manifest) { - this.doActivation(manifest.origin); - }.bind(this)); - }, - - doActivation: function SocialUI_doActivation(origin) { - // Keep track of the old provider in case of undo - let oldOrigin = Social.provider ? Social.provider.origin : ""; - - // Enable the social functionality, and indicate that it was activated - Social.activateFromOrigin(origin, function(provider) { - // Provider to activate may not have been found - if (!provider) - return; - - // Show a warning, allow undoing the activation - let description = document.getElementById("social-activation-message"); - let labels = description.getElementsByTagName("label"); - let uri = Services.io.newURI(provider.origin, null, null) - labels[0].setAttribute("value", uri.host); - labels[1].setAttribute("onclick", "BrowserOpenAddonsMgr('addons://list/service'); SocialUI.activationPanel.hidePopup();") - - let icon = document.getElementById("social-activation-icon"); - if (provider.icon64URL || provider.icon32URL) { - icon.setAttribute('src', provider.icon64URL || provider.icon32URL); - icon.hidden = false; - } else { - icon.removeAttribute('src'); - icon.hidden = true; - } - - let notificationPanel = SocialUI.activationPanel; - // Set the origin being activated and the previously active one, to allow undo - notificationPanel.setAttribute("origin", provider.origin); - notificationPanel.setAttribute("oldorigin", oldOrigin); - - // Show the panel - notificationPanel.hidden = false; - setTimeout(function () { - notificationPanel.openPopup(SocialToolbar.button, "bottomcenter topright"); - }, 0); - }); - }, - - undoActivation: function SocialUI_undoActivation() { - let origin = this.activationPanel.getAttribute("origin"); - let oldOrigin = this.activationPanel.getAttribute("oldorigin"); - Social.deactivateFromOrigin(origin, oldOrigin); - this.activationPanel.hidePopup(); - Social.uninstallProvider(origin); - }, - - showLearnMore: function() { - this.activationPanel.hidePopup(); - let url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "social-api"; - openUILinkIn(url, "tab"); - }, - - get activationPanel() { - return document.getElementById("socialActivatedNotification"); - }, - - 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").contains("extrachrome") || - docElem.getAttribute('chromehidden').contains("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.provider; - }, - -} - -SocialChatBar = { - init: function() { - }, - get chatbar() { - return document.getElementById("pinnedchats"); - }, - // Whether the chatbar is available for this window. Note that in full-screen - // mode chats are available, but not shown. - get isAvailable() { - return SocialUI.enabled && Social.haveLoggedInUser(); - }, - // Does this chatbar have any chats (whether minimized, collapsed or normal) - get hasChats() { - return !!this.chatbar.firstElementChild; - }, - openChat: function(aProvider, aURL, aCallback, aMode) { - if (!this.isAvailable) - return false; - this.chatbar.openChat(aProvider, aURL, aCallback, aMode); - // We only want to focus the chat if it is as a result of user input. - let dwu = window.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils); - if (dwu.isHandlingUserInput) - this.chatbar.focus(); - return true; - }, - update: function() { - let command = document.getElementById("Social:FocusChat"); - if (!this.isAvailable) { - this.chatbar.removeAll(); - this.chatbar.hidden = command.hidden = true; - } else { - this.chatbar.hidden = command.hidden = false; - } - command.setAttribute("disabled", command.hidden ? "true" : "false"); - }, - focus: function SocialChatBar_focus() { - this.chatbar.focus(); - } -} - -function sizeSocialPanelToContent(panel, iframe) { - // FIXME: bug 764787: Maybe we can use nsIDOMWindowUtils.getRootBounds() here? - let doc = iframe.contentDocument; - if (!doc || !doc.body) { - return; - } - // We need an element to use for sizing our panel. See if the body defines - // an id for that element, otherwise use the body itself. - let body = doc.body; - let bodyId = body.getAttribute("contentid"); - if (bodyId) { - body = doc.getElementById(bodyId) || doc.body; - } - // offsetHeight/Width don't include margins, so account for that. - let cs = doc.defaultView.getComputedStyle(body); - let computedHeight = parseInt(cs.marginTop) + body.offsetHeight + parseInt(cs.marginBottom); - let height = Math.max(computedHeight, PANEL_MIN_HEIGHT); - let computedWidth = parseInt(cs.marginLeft) + body.offsetWidth + parseInt(cs.marginRight); - let width = Math.max(computedWidth, PANEL_MIN_WIDTH); - iframe.style.width = width + "px"; - iframe.style.height = height + "px"; - // since we do not use panel.sizeTo, we need to adjust the arrow ourselves - if (panel.state == "open") - panel.adjustArrowPosition(); -} - -function DynamicResizeWatcher() { - this._mutationObserver = null; -} - -DynamicResizeWatcher.prototype = { - start: function DynamicResizeWatcher_start(panel, iframe) { - this.stop(); // just in case... - let doc = iframe.contentDocument; - this._mutationObserver = new iframe.contentWindow.MutationObserver(function(mutations) { - sizeSocialPanelToContent(panel, iframe); - }); - // Observe anything that causes the size to change. - let config = {attributes: true, characterData: true, childList: true, subtree: true}; - this._mutationObserver.observe(doc, config); - // and since this may be setup after the load event has fired we do an - // initial resize now. - sizeSocialPanelToContent(panel, iframe); - }, - stop: function DynamicResizeWatcher_stop() { - if (this._mutationObserver) { - try { - this._mutationObserver.disconnect(); - } catch (ex) { - // may get "TypeError: can't access dead object" which seems strange, - // but doesn't seem to indicate a real problem, so ignore it... - } - this._mutationObserver = null; - } - } -} - -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("iframe"); - iframe.setAttribute("type", "content"); - iframe.setAttribute("class", "social-panel-frame"); - iframe.setAttribute("flex", "1"); - iframe.setAttribute("tooltip", "aHTMLTooltip"); - iframe.setAttribute("origin", Social.provider.origin); - panel.appendChild(iframe); - }, - - setFlyoutErrorMessage: function SF_setFlyoutErrorMessage() { - this.iframe.removeAttribute("src"); - this.iframe.webNavigation.loadURI("about:socialerror?mode=compactInfo", null, null, null, null); - sizeSocialPanelToContent(this.panel, this.iframe); - }, - - unload: function() { - let panel = this.panel; - panel.hidePopup(); - if (!panel.firstChild) - return - let iframe = panel.firstChild; - if (iframe.socialErrorListener) - iframe.socialErrorListener.remove(); - panel.removeChild(iframe); - }, - - onShown: function(aEvent) { - let panel = this.panel; - let iframe = this.iframe; - this._dynamicResizer = new DynamicResizeWatcher(); - iframe.docShell.isActive = true; - iframe.docShell.isAppTab = 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.docShell.isActive = false; - this.dispatchPanelEvent("socialFrameHide"); - }, - - load: function(aURL, cb) { - if (!Social.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); - // Force a layout flush by calling .clientTop so - // that the docShell of this frame is created - iframe.clientTop; - Social.setErrorListener(iframe, SocialFlyout.setFlyoutErrorMessage.bind(SocialFlyout)) - 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 = { - // Called once, after window load, when the Social.provider object is initialized - init: function() {}, - - get panel() { - return document.getElementById("social-share-panel"); - }, - - get iframe() { - // first element is our menu vbox. - if (this.panel.childElementCount == 1) - return null; - else - return this.panel.lastChild; - }, - - _createFrame: function() { - let panel = this.panel; - if (!SocialUI.enabled || this.iframe) - return; - this.panel.hidden = false; - // create and initialize the panel for this window - let iframe = document.createElement("iframe"); - iframe.setAttribute("type", "content"); - iframe.setAttribute("class", "social-share-frame"); - iframe.setAttribute("flex", "1"); - panel.appendChild(iframe); - this.populateProviderMenu(); - }, - - getSelectedProvider: function() { - let provider; - let lastProviderOrigin = this.iframe && this.iframe.getAttribute("origin"); - if (lastProviderOrigin) { - provider = Social._getProviderFromOrigin(lastProviderOrigin); - } - if (!provider) - provider = Social.provider || Social.defaultProvider; - // if our provider has no shareURL, select the first one that does - if (provider && !provider.shareURL) { - let providers = [p for (p of Social.providers) if (p.shareURL)]; - provider = providers.length > 0 && providers[0]; - } - return provider; - }, - - populateProviderMenu: function() { - if (!this.iframe) - return; - let providers = [p for (p of Social.providers) if (p.shareURL)]; - let hbox = document.getElementById("social-share-provider-buttons"); - // selectable providers are inserted before the provider-menu seperator, - // remove any menuitems in that area - while (hbox.firstChild) { - hbox.removeChild(hbox.firstChild); - } - // reset our share toolbar - // only show a selection if there is more than one - if (!SocialUI.enabled || providers.length < 2) { - this.panel.firstChild.hidden = true; - return; - } - let selectedProvider = this.getSelectedProvider(); - for (let provider of providers) { - let button = document.createElement("toolbarbutton"); - button.setAttribute("class", "toolbarbutton share-provider-button"); - button.setAttribute("type", "radio"); - button.setAttribute("group", "share-providers"); - button.setAttribute("image", provider.iconURL); - button.setAttribute("tooltiptext", provider.name); - button.setAttribute("origin", provider.origin); - button.setAttribute("oncommand", "SocialShare.sharePage(this.getAttribute('origin')); this.checked=true;"); - if (provider == selectedProvider) { - this.defaultButton = button; - } - hbox.appendChild(button); - } - if (!this.defaultButton) { - this.defaultButton = hbox.firstChild - } - this.defaultButton.setAttribute("checked", "true"); - this.panel.firstChild.hidden = false; - }, - - get shareButton() { - return document.getElementById("social-share-button"); - }, - - canSharePage: function(aURI) { - // we do not enable sharing from private sessions - if (PrivateBrowsingUtils.isWindowPrivate(window)) - return false; - - if (!aURI || !(aURI.schemeIs('http') || aURI.schemeIs('https'))) - return false; - return true; - }, - - update: function() { - let shareButton = this.shareButton; - shareButton.hidden = !SocialUI.enabled || - [p for (p of Social.providers) if (p.shareURL)].length == 0; - shareButton.disabled = shareButton.hidden || !this.canSharePage(gBrowser.currentURI); - - // also update the relevent command's disabled state so the keyboard - // shortcut only works when available. - let cmd = document.getElementById("Social:SharePage"); - cmd.setAttribute("disabled", shareButton.disabled ? "true" : "false"); - }, - - onShowing: function() { - this.shareButton.setAttribute("open", "true"); - }, - - onHidden: function() { - this.shareButton.removeAttribute("open"); - this.iframe.setAttribute("src", "data:text/plain;charset=utf8,"); - this.currentShare = null; - }, - - setErrorMessage: function() { - let iframe = this.iframe; - if (!iframe) - return; - - iframe.removeAttribute("src"); - iframe.webNavigation.loadURI("about:socialerror?mode=compactInfo&origin=" + - encodeURIComponent(iframe.getAttribute("origin")), - null, null, null, null); - sizeSocialPanelToContent(this.panel, iframe); - }, - - sharePage: function(providerOrigin, graphData) { - // 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; - let provider; - if (providerOrigin) - provider = Social._getProviderFromOrigin(providerOrigin); - else - provider = this.getSelectedProvider(); - if (!provider || !provider.shareURL) - return; - - // 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 sharedURI = graphData ? Services.io.newURI(graphData.url, null, null) : - gBrowser.currentURI; - if (!this.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 pageData = graphData ? graphData : this.currentShare; - if (!pageData || sharedURI == gBrowser.currentURI) { - pageData = OpenGraphBuilder.getData(gBrowser); - 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.currentShare = pageData; - - let shareEndpoint = this._generateShareEndpointURL(provider.shareURL, pageData); - - this._dynamicResizer = new DynamicResizeWatcher(); - // if we've already loaded this provider/page share endpoint, we don't want - // to add another load event listener. - let reload = true; - let endpointMatch = shareEndpoint == iframe.getAttribute("src"); - let docLoaded = iframe.contentDocument && iframe.contentDocument.readyState == "complete"; - if (endpointMatch && docLoaded) { - reload = shareEndpoint != iframe.contentDocument.location.spec; - } - if (!reload) { - this._dynamicResizer.start(this.panel, iframe); - iframe.docShell.isActive = true; - iframe.docShell.isAppTab = true; - let evt = iframe.contentDocument.createEvent("CustomEvent"); - evt.initCustomEvent("OpenGraphData", true, true, JSON.stringify(pageData)); - iframe.contentDocument.documentElement.dispatchEvent(evt); - } else { - // first time load, wait for load and dispatch after load - iframe.addEventListener("load", function panelBrowserOnload(e) { - iframe.removeEventListener("load", panelBrowserOnload, true); - iframe.docShell.isActive = true; - iframe.docShell.isAppTab = true; - setTimeout(function() { - if (SocialShare._dynamicResizer) { // may go null if hidden quickly - SocialShare._dynamicResizer.start(iframe.parentNode, iframe); - } - }, 0); - let evt = iframe.contentDocument.createEvent("CustomEvent"); - evt.initCustomEvent("OpenGraphData", true, true, JSON.stringify(pageData)); - iframe.contentDocument.documentElement.dispatchEvent(evt); - }, true); - } - // 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); - - let navBar = document.getElementById("nav-bar"); - let anchor = navBar.getAttribute("mode") == "text" ? - document.getAnonymousElementByAttribute(this.shareButton, "class", "toolbarbutton-text") : - document.getAnonymousElementByAttribute(this.shareButton, "class", "toolbarbutton-icon"); - this.panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, false); - Social.setErrorListener(iframe, this.setErrorMessage.bind(this)); - }, - - _generateShareEndpointURL: function(shareURL, pageData) { - // support for existing share endpoints by supporting their querystring - // arguments. parse the query string template and do replacements where - // necessary the query names may be different than ours, so we could see - // u=%{url} or url=%{url} - let [shareEndpoint, queryString] = shareURL.split("?"); - let query = {}; - if (queryString) { - queryString.split('&').forEach(function (val) { - let [name, value] = val.split('='); - let p = /%\{(.+)\}/.exec(value); - if (!p) { - // preserve non-template query vars - query[name] = value; - } else if (pageData[p[1]]) { - query[name] = pageData[p[1]]; - } else if (p[1] == "body") { - // build a body for emailers - let body = ""; - if (pageData.title) - body += pageData.title + "\n\n"; - if (pageData.description) - body += pageData.description + "\n\n"; - if (pageData.text) - body += pageData.text + "\n\n"; - body += pageData.url; - query["body"] = body; - } - }); - } - var str = []; - for (let p in query) - str.push(p + "=" + encodeURIComponent(query[p])); - if (str.length) - shareEndpoint = shareEndpoint + "?" + str.join("&"); - return shareEndpoint; - } -}; - -SocialMark = { - // Called once, after window load, when the Social.provider object is initialized - init: function SSB_init() { - }, - - get button() { - return document.getElementById("social-mark-button"); - }, - - canMarkPage: function SSB_canMarkPage(aURI) { - // We only allow sharing of http or https - return aURI && (aURI.schemeIs('http') || aURI.schemeIs('https')); - }, - - // Called when the Social.provider changes - update: function SSB_updateButtonState() { - let markButton = this.button; - // always show button if provider supports marks - markButton.hidden = !SocialUI.enabled || Social.provider.pageMarkInfo == null; - markButton.disabled = markButton.hidden || !this.canMarkPage(gBrowser.currentURI); - - // also update the relevent command's disabled state so the keyboard - // shortcut only works when available. - let cmd = document.getElementById("Social:TogglePageMark"); - cmd.setAttribute("disabled", markButton.disabled ? "true" : "false"); - }, - - togglePageMark: function(aCallback) { - if (this.button.disabled) - return; - this.toggleURIMark(gBrowser.currentURI, aCallback) - }, - - toggleURIMark: function(aURI, aCallback) { - let update = function(marked) { - this._updateMarkState(marked); - if (aCallback) - aCallback(marked); - }.bind(this); - Social.isURIMarked(aURI, function(marked) { - if (marked) { - Social.unmarkURI(aURI, update); - } else { - Social.markURI(aURI, update); - } - }); - }, - - updateMarkState: function SSB_updateMarkState() { - this.update(); - if (!this.button.hidden) - Social.isURIMarked(gBrowser.currentURI, this._updateMarkState.bind(this)); - }, - - _updateMarkState: function(currentPageMarked) { - // callback for isURIMarked - let markButton = this.button; - let pageMarkInfo = SocialUI.enabled ? Social.provider.pageMarkInfo : null; - - // Update the mark button, if present - if (!markButton || markButton.hidden || !pageMarkInfo) - return; - - let imageURL; - if (!markButton.disabled && currentPageMarked) { - markButton.setAttribute("marked", "true"); - markButton.setAttribute("label", pageMarkInfo.messages.markedLabel); - markButton.setAttribute("tooltiptext", pageMarkInfo.messages.markedTooltip); - imageURL = pageMarkInfo.images.marked; - } else { - markButton.removeAttribute("marked"); - markButton.setAttribute("label", pageMarkInfo.messages.unmarkedLabel); - markButton.setAttribute("tooltiptext", pageMarkInfo.messages.unmarkedTooltip); - imageURL = pageMarkInfo.images.unmarked; - } - markButton.style.listStyleImage = "url(" + imageURL + ")"; - } -}; - -SocialMenu = { - init: function SocialMenu_init() { - }, - - populate: function SocialMenu_populate() { - let submenu = document.getElementById("menu_social-statusarea-popup"); - let ambientMenuItems = submenu.getElementsByClassName("ambient-menuitem"); - while (ambientMenuItems.length) - submenu.removeChild(ambientMenuItems.item(0)); - - let separator = document.getElementById("socialAmbientMenuSeparator"); - separator.hidden = true; - let provider = SocialUI.enabled ? Social.provider : null; - if (!provider) - return; - - let iconNames = Object.keys(provider.ambientNotificationIcons); - for (let name of iconNames) { - let icon = provider.ambientNotificationIcons[name]; - if (!icon.label || !icon.menuURL) - continue; - separator.hidden = false; - let menuitem = document.createElement("menuitem"); - menuitem.setAttribute("label", icon.label); - menuitem.classList.add("ambient-menuitem"); - menuitem.addEventListener("command", function() { - openUILinkIn(icon.menuURL, "tab"); - }, false); - submenu.insertBefore(menuitem, separator); - } - } -}; - -// XXX Need to audit that this is being initialized correctly -SocialToolbar = { - // Called once, after window load, when the Social.provider object is - // initialized. - init: function SocialToolbar_init() { - this._dynamicResizer = new DynamicResizeWatcher(); - }, - - update: function() { - this._updateButtonHiddenState(); - this.updateProvider(); - this.populateProviderMenus(); - }, - - // Called when the Social.provider changes - updateProvider: function () { - let provider = Social.provider; - if (provider) { - this.button.setAttribute("label", provider.name); - this.button.setAttribute("tooltiptext", provider.name); - this.button.style.listStyleImage = "url(" + provider.iconURL + ")"; - - this.updateProfile(); - } else { - this.button.setAttribute("label", gNavigatorBundle.getString("service.toolbarbutton.label")); - this.button.setAttribute("tooltiptext", gNavigatorBundle.getString("service.toolbarbutton.tooltiptext")); - this.button.style.removeProperty("list-style-image"); - } - this.updateButton(); - }, - - get button() { - return document.getElementById("social-provider-button"); - }, - - // Note: this doesn't actually handle hiding the toolbar button, - // socialActiveBroadcaster is responsible for that. - _updateButtonHiddenState: function SocialToolbar_updateButtonHiddenState() { - let socialEnabled = SocialUI.enabled; - for (let className of ["social-statusarea-separator", "social-statusarea-user"]) { - for (let element of document.getElementsByClassName(className)) - element.hidden = !socialEnabled; - } - let toggleNotificationsCommand = document.getElementById("Social:ToggleNotifications"); - toggleNotificationsCommand.setAttribute("hidden", !socialEnabled); - - if (!Social.haveLoggedInUser() || !socialEnabled) { - let parent = document.getElementById("social-notification-panel"); - while (parent.hasChildNodes()) { - let frame = parent.firstChild; - SharedFrame.forgetGroup(frame.id); - parent.removeChild(frame); - } - - let tbi = document.getElementById("social-toolbar-item"); - if (tbi) { - // SocialMark is the last button allways - let next = SocialMark.button.previousSibling; - while (next != this.button) { - tbi.removeChild(next); - next = SocialMark.button.previousSibling; - } - } - } - }, - - updateProfile: function SocialToolbar_updateProfile() { - // Profile may not have been initialized yet, since it depends on a worker - // response. In that case we'll be called again when it's available, via - // social:profile-changed - if (!Social.provider) - return; - let profile = Social.provider.profile || {}; - let userPortrait = profile.portrait; - - let userDetailsBroadcaster = document.getElementById("socialBroadcaster_userDetails"); - let loggedInStatusValue = profile.userName || - userDetailsBroadcaster.getAttribute("notLoggedInLabel"); - - // "image" and "label" are used by Mac's native menus that do not render the menuitem's children - // elements. "src" and "value" are used by the image/label children on the other platforms. - if (userPortrait) { - userDetailsBroadcaster.setAttribute("src", userPortrait); - userDetailsBroadcaster.setAttribute("image", userPortrait); - } else { - userDetailsBroadcaster.removeAttribute("src"); - userDetailsBroadcaster.removeAttribute("image"); - } - - userDetailsBroadcaster.setAttribute("value", loggedInStatusValue); - userDetailsBroadcaster.setAttribute("label", loggedInStatusValue); - }, - - updateButton: function SocialToolbar_updateButton() { - this._updateButtonHiddenState(); - let panel = document.getElementById("social-notification-panel"); - panel.hidden = !SocialUI.enabled; - - let command = document.getElementById("Social:ToggleNotifications"); - command.setAttribute("checked", Services.prefs.getBoolPref("social.toast-notifications.enabled")); - - const CACHE_PREF_NAME = "social.cached.ambientNotificationIcons"; - // provider.profile == undefined means no response yet from the provider - // to tell us whether the user is logged in or not. - if (!SocialUI.enabled || - (!Social.haveLoggedInUser() && Social.provider.profile !== undefined)) { - // Either no enabled provider, or there is a provider and it has - // responded with a profile and the user isn't loggedin. The icons - // etc have already been removed by updateButtonHiddenState, so we want - // to nuke any cached icons we have and get out of here! - Services.prefs.clearUserPref(CACHE_PREF_NAME); - return; - } - let icons = Social.provider.ambientNotificationIcons; - let iconNames = Object.keys(icons); - - if (Social.provider.profile === undefined) { - // provider has not told us about the login state yet - see if we have - // a cached version for this provider. - let cached; - try { - cached = JSON.parse(Services.prefs.getComplexValue(CACHE_PREF_NAME, - Ci.nsISupportsString).data); - } catch (ex) {} - if (cached && cached.provider == Social.provider.origin && cached.data) { - icons = cached.data; - iconNames = Object.keys(icons); - // delete the counter data as it is almost certainly stale. - for each(let name in iconNames) { - icons[name].counter = ''; - } - } - } else { - // We have a logged in user - save the current set of icons back to the - // "cache" so we can use them next startup. - let str = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString); - str.data = JSON.stringify({provider: Social.provider.origin, data: icons}); - Services.prefs.setComplexValue(CACHE_PREF_NAME, - Ci.nsISupportsString, - str); - } - - let toolbarButtons = document.createDocumentFragment(); - - let createdFrames = []; - - for each(let name in iconNames) { - let icon = icons[name]; - - let notificationFrameId = "social-status-" + icon.name; - let notificationFrame = document.getElementById(notificationFrameId); - - if (!notificationFrame) { - notificationFrame = SharedFrame.createFrame( - notificationFrameId, /* frame name */ - panel, /* parent */ - { - "type": "content", - "mozbrowser": "true", - "class": "social-panel-frame", - "id": notificationFrameId, - "tooltip": "aHTMLTooltip", - - // work around bug 793057 - by making the panel roughly the final size - // we are more likely to have the anchor in the correct position. - "style": "width: " + PANEL_MIN_WIDTH + "px;", - - "origin": Social.provider.origin, - "src": icon.contentPanel - } - ); - - createdFrames.push(notificationFrame); - } else { - notificationFrame.setAttribute("origin", Social.provider.origin); - SharedFrame.updateURL(notificationFrameId, icon.contentPanel); - } - - let toolbarButtonId = "social-notification-icon-" + icon.name; - let toolbarButton = document.getElementById(toolbarButtonId); - if (!toolbarButton) { - toolbarButton = document.createElement("toolbarbutton"); - toolbarButton.setAttribute("type", "badged"); - toolbarButton.classList.add("toolbarbutton-1"); - toolbarButton.setAttribute("id", toolbarButtonId); - toolbarButton.setAttribute("notificationFrameId", notificationFrameId); - toolbarButton.addEventListener("mousedown", function (event) { - if (event.button == 0 && panel.state == "closed") - SocialToolbar.showAmbientPopup(toolbarButton); - }); - - toolbarButtons.appendChild(toolbarButton); - } - - toolbarButton.style.listStyleImage = "url(" + icon.iconURL + ")"; - toolbarButton.setAttribute("label", icon.label); - toolbarButton.setAttribute("tooltiptext", icon.label); - - let badge = icon.counter || ""; - toolbarButton.setAttribute("badge", badge); - let ariaLabel = icon.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]); - toolbarButton.setAttribute("aria-label", ariaLabel); - } - let socialToolbarItem = document.getElementById("social-toolbar-item"); - socialToolbarItem.insertBefore(toolbarButtons, SocialMark.button); - - for (let frame of createdFrames) { - if (frame.socialErrorListener) { - frame.socialErrorListener.remove(); - } - if (frame.docShell) { - frame.docShell.isActive = false; - Social.setErrorListener(frame, this.setPanelErrorMessage.bind(this)); - } - } - }, - - showAmbientPopup: function SocialToolbar_showAmbientPopup(aToolbarButton) { - // Hide any other social panels that may be open. - SocialFlyout.panel.hidePopup(); - - let panel = document.getElementById("social-notification-panel"); - let notificationFrameId = aToolbarButton.getAttribute("notificationFrameId"); - let notificationFrame = document.getElementById(notificationFrameId); - - let wasAlive = SharedFrame.isGroupAlive(notificationFrameId); - SharedFrame.setOwner(notificationFrameId, notificationFrame); - - // Clear dimensions on all browsers so the panel size will - // only use the selected browser. - let frameIter = panel.firstElementChild; - while (frameIter) { - frameIter.collapsed = (frameIter != notificationFrame); - frameIter = frameIter.nextElementSibling; - } - - function dispatchPanelEvent(name) { - let evt = notificationFrame.contentDocument.createEvent("CustomEvent"); - evt.initCustomEvent(name, true, true, {}); - notificationFrame.contentDocument.documentElement.dispatchEvent(evt); - } - - let dynamicResizer = this._dynamicResizer; - panel.addEventListener("popuphidden", function onpopuphiding() { - panel.removeEventListener("popuphidden", onpopuphiding); - aToolbarButton.removeAttribute("open"); - aToolbarButton.parentNode.removeAttribute("open"); - dynamicResizer.stop(); - notificationFrame.docShell.isActive = false; - dispatchPanelEvent("socialFrameHide"); - }); - - panel.addEventListener("popupshown", function onpopupshown() { - panel.removeEventListener("popupshown", onpopupshown); - // This attribute is needed on both the button and the - // containing toolbaritem since the buttons on OS X have - // moz-appearance:none, while their container gets - // moz-appearance:toolbarbutton due to the way that toolbar buttons - // get combined on OS X. - aToolbarButton.setAttribute("open", "true"); - aToolbarButton.parentNode.setAttribute("open", "true"); - notificationFrame.docShell.isActive = true; - notificationFrame.docShell.isAppTab = true; - if (notificationFrame.contentDocument.readyState == "complete" && wasAlive) { - dynamicResizer.start(panel, notificationFrame); - dispatchPanelEvent("socialFrameShow"); - } else { - // first time load, wait for load and dispatch after load - notificationFrame.addEventListener("load", function panelBrowserOnload(e) { - notificationFrame.removeEventListener("load", panelBrowserOnload, true); - dynamicResizer.start(panel, notificationFrame); - setTimeout(function() { - dispatchPanelEvent("socialFrameShow"); - }, 0); - }, true); - } - }); - - let navBar = document.getElementById("nav-bar"); - let anchor = navBar.getAttribute("mode") == "text" ? - document.getAnonymousElementByAttribute(aToolbarButton, "class", "toolbarbutton-text") : - document.getAnonymousElementByAttribute(aToolbarButton, "class", "toolbarbutton-badge-container"); - // Bug 849216 - open the popup in a setTimeout so we avoid the auto-rollup - // handling from preventing it being opened in some cases. - setTimeout(function() { - panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, false); - }, 0); - }, - - setPanelErrorMessage: function SocialToolbar_setPanelErrorMessage(aNotificationFrame) { - if (!aNotificationFrame) - return; - - let src = aNotificationFrame.getAttribute("src"); - aNotificationFrame.removeAttribute("src"); - aNotificationFrame.webNavigation.loadURI("about:socialerror?mode=tryAgainOnly&url=" + - encodeURIComponent(src), null, null, null, null); - let panel = aNotificationFrame.parentNode; - sizeSocialPanelToContent(panel, aNotificationFrame); - }, - - populateProviderMenus: function SocialToolbar_renderProviderMenus() { - let providerMenuSeps = document.getElementsByClassName("social-provider-menu"); - for (let providerMenuSep of providerMenuSeps) - this._populateProviderMenu(providerMenuSep); - }, - - _populateProviderMenu: function SocialToolbar_renderProviderMenu(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 if enabled and there is more than one - let providers = [p for (p of Social.providers) if (p.workerURL || p.sidebarURL)]; - if (providers.length < 2) { - providerMenuSep.hidden = true; - return; - } - 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 (provider == Social.provider) { - menuitem.setAttribute("checked", "true"); - } else { - menuitem.setAttribute("oncommand", "Social.setProviderByOrigin(this.getAttribute('origin'));"); - } - menu.insertBefore(menuitem, providerMenuSep); - } - providerMenuSep.hidden = false; - } -} - -SocialSidebar = { - // Called once, after window load, when the Social.provider object is initialized - init: function SocialSidebar_init() { - let sbrowser = document.getElementById("social-sidebar-browser"); - Social.setErrorListener(sbrowser, this.setSidebarErrorMessage.bind(this)); - // setting isAppTab causes clicks on untargeted links to open new tabs - sbrowser.docShell.isAppTab = true; - }, - - // Whether the sidebar can be shown for this window. - get canShow() { - return SocialUI.enabled && Social.provider.sidebarURL; - }, - - // Whether the user has toggled the sidebar on (for windows where it can appear) - get opened() { - return Services.prefs.getBoolPref("social.sidebar.open") && !document.mozFullScreen; - }, - - 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); - }, - - update: function SocialSidebar_update() { - 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", Social.provider.origin); - if (Social.provider.errorState == "frameworker-error") { - SocialSidebar.setSidebarErrorMessage(); - return; - } - - // Make sure the right sidebar URL is loaded - if (sbrowser.getAttribute("src") != Social.provider.sidebarURL) { - sbrowser.setAttribute("src", Social.provider.sidebarURL); - PopupNotifications.locationChange(sbrowser); - } - - // if the document has not loaded, delay until it is - if (sbrowser.contentDocument.readyState != "complete") { - sbrowser.addEventListener("load", SocialSidebar._loadListener, true); - } else { - this.setSidebarVisibilityState(true); - } - } - }, - - _loadListener: function SocialSidebar_loadListener() { - let sbrowser = document.getElementById("social-sidebar-browser"); - sbrowser.removeEventListener("load", SocialSidebar._loadListener, true); - SocialSidebar.setSidebarVisibilityState(true); - }, - - unloadSidebar: function SocialSidebar_unloadSidebar() { - let sbrowser = document.getElementById("social-sidebar-browser"); - if (!sbrowser.hasAttribute("origin")) - return; - - sbrowser.stop(); - sbrowser.removeAttribute("origin"); - sbrowser.setAttribute("src", "about:blank"); - SocialFlyout.unload(); - }, - - _unloadTimeoutId: 0, - - setSidebarErrorMessage: function() { - let sbrowser = document.getElementById("social-sidebar-browser"); - // a frameworker error "trumps" a sidebar error. - if (Social.provider.errorState == "frameworker-error") { - sbrowser.setAttribute("src", "about:socialerror?mode=workerFailure"); - } else { - let url = encodeURIComponent(Social.provider.sidebarURL); - sbrowser.loadURI("about:socialerror?mode=tryAgain&url=" + url, null, null); - } - } -} - -})(); |