зеркало из https://github.com/mozilla/gecko-dev.git
504 строки
18 KiB
JavaScript
504 строки
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": {
|
|
this.iframe.parentNode.removeAttribute("loading");
|
|
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;
|
|
|
|
let browserMM = gBrowser.selectedBrowser.messageManager;
|
|
|
|
// 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) {
|
|
browserMM.addMessageListener("PageMetadata:PageDataResult", _dataFn = (msg) => {
|
|
browserMM.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);
|
|
});
|
|
browserMM.sendAsyncMessage("PageMetadata:GetPageData", null, { target });
|
|
return;
|
|
}
|
|
// if this is a share of a selected item, get any microformats
|
|
if (!pageData.microformats && target) {
|
|
browserMM.addMessageListener("PageMetadata:MicroformatsResult", _dataFn = (msg) => {
|
|
browserMM.removeMessageListener("PageMetadata:MicroformatsResult", _dataFn);
|
|
pageData.microformats = msg.data;
|
|
this.sharePage(providerOrigin, pageData, target, anchor);
|
|
});
|
|
browserMM.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);
|
|
}
|
|
};
|
|
|
|
})();
|