зеркало из https://github.com/mozilla/gecko-dev.git
bug 818675 implement new social share panel, r=felipe
This commit is contained in:
Родитель
dada7e49b3
Коммит
dee9a7fd1e
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -47,6 +47,8 @@
|
|||
|
||||
let modeMatch = queryString.match(/mode=([^&]+)/);
|
||||
let mode = modeMatch && modeMatch[1] ? modeMatch[1] : "";
|
||||
let originMatch = queryString.match(/origin=([^&]+)/);
|
||||
config.origin = originMatch && originMatch[1] ? decodeURIComponent(originMatch[1]) : "";
|
||||
|
||||
switch (mode) {
|
||||
case "compactInfo":
|
||||
|
@ -77,7 +79,11 @@
|
|||
let browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
|
||||
|
||||
let productName = brandBundle.GetStringFromName("brandShortName");
|
||||
let providerName = Social && Social.provider && Social.provider.name;
|
||||
let provider = Social && Social.provider;
|
||||
if (config.origin) {
|
||||
provider = Social && Social._getProviderFromOrigin(config.origin);
|
||||
}
|
||||
let providerName = provider && provider.name;
|
||||
|
||||
// Sets up the error message
|
||||
let msg = browserBundle.formatStringFromName("social.error.message", [productName, providerName], 2);
|
||||
|
|
|
@ -40,6 +40,10 @@
|
|||
<menuitem id="context-marklink"
|
||||
accesskey="&social.marklink.accesskey;"
|
||||
oncommand="gContextMenu.markLink();"/>
|
||||
<menuitem id="context-sharelink"
|
||||
label="&shareLinkCmd.label;"
|
||||
accesskey="&shareLinkCmd.accesskey;"
|
||||
oncommand="gContextMenu.shareLink();"/>
|
||||
<menuitem id="context-savelink"
|
||||
label="&saveLinkCmd.label;"
|
||||
accesskey="&saveLinkCmd.accesskey;"
|
||||
|
@ -160,6 +164,10 @@
|
|||
label="&saveImageCmd.label;"
|
||||
accesskey="&saveImageCmd.accesskey;"
|
||||
oncommand="gContextMenu.saveMedia();"/>
|
||||
<menuitem id="context-shareimage"
|
||||
label="&shareImageCmd.label;"
|
||||
accesskey="&shareImageCmd.accesskey;"
|
||||
oncommand="gContextMenu.shareImage();"/>
|
||||
<menuitem id="context-sendimage"
|
||||
label="&emailImageCmd.label;"
|
||||
accesskey="&emailImageCmd.accesskey;"
|
||||
|
@ -176,6 +184,10 @@
|
|||
label="&saveVideoCmd.label;"
|
||||
accesskey="&saveVideoCmd.accesskey;"
|
||||
oncommand="gContextMenu.saveMedia();"/>
|
||||
<menuitem id="context-sharevideo"
|
||||
label="&shareVideoCmd.label;"
|
||||
accesskey="&shareVideoCmd.accesskey;"
|
||||
oncommand="gContextMenu.shareVideo();"/>
|
||||
<menuitem id="context-saveaudio"
|
||||
label="&saveAudioCmd.label;"
|
||||
accesskey="&saveAudioCmd.accesskey;"
|
||||
|
@ -228,6 +240,10 @@
|
|||
<menuitem id="context-markpage"
|
||||
accesskey="&social.markpage.accesskey;"
|
||||
command="Social:TogglePageMark"/>
|
||||
<menuitem id="context-sharepage"
|
||||
label="&sharePageCmd.label;"
|
||||
accesskey="&sharePageCmd.accesskey;"
|
||||
oncommand="SocialShare.sharePage();"/>
|
||||
<menuitem id="context-savepage"
|
||||
label="&savePageCmd.label;"
|
||||
accesskey="&savePageCmd.accesskey2;"
|
||||
|
@ -271,6 +287,10 @@
|
|||
oncommand="AddKeywordForSearchField();"/>
|
||||
<menuitem id="context-searchselect"
|
||||
oncommand="BrowserSearch.loadSearchFromContext(getBrowserSelection());"/>
|
||||
<menuitem id="context-shareselect"
|
||||
label="&shareSelectCmd.label;"
|
||||
accesskey="&shareSelectCmd.accesskey;"
|
||||
oncommand="gContextMenu.shareSelect(getBrowserSelection());"/>
|
||||
<menuseparator id="frame-sep"/>
|
||||
<menu id="frame" label="&thisFrameMenu.label;" accesskey="&thisFrameMenu.accesskey;">
|
||||
<menupopup>
|
||||
|
|
|
@ -110,6 +110,7 @@
|
|||
<command id="History:UndoCloseWindow" oncommand="undoCloseWindow();"/>
|
||||
<command id="Browser:ToggleAddonBar" oncommand="toggleAddonBar();"/>
|
||||
<command id="Social:TogglePageMark" oncommand="SocialMark.togglePageMark();" disabled="true"/>
|
||||
<command id="Social:SharePage" oncommand="SocialShare.sharePage();" disabled="true"/>
|
||||
<command id="Social:ToggleSidebar" oncommand="Social.toggleSidebar();"/>
|
||||
<command id="Social:ToggleNotifications" oncommand="Social.toggleNotifications();" hidden="true"/>
|
||||
<command id="Social:FocusChat" oncommand="SocialChatBar.focus();" hidden="true" disabled="true"/>
|
||||
|
|
|
@ -7,6 +7,7 @@ let SocialUI,
|
|||
SocialChatBar,
|
||||
SocialFlyout,
|
||||
SocialMark,
|
||||
SocialShare,
|
||||
SocialMenu,
|
||||
SocialToolbar,
|
||||
SocialSidebar;
|
||||
|
@ -20,6 +21,12 @@ 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() {
|
||||
|
@ -43,6 +50,7 @@ SocialUI = {
|
|||
|
||||
SocialChatBar.init();
|
||||
SocialMark.init();
|
||||
SocialShare.init();
|
||||
SocialMenu.init();
|
||||
SocialToolbar.init();
|
||||
SocialSidebar.init();
|
||||
|
@ -87,6 +95,7 @@ SocialUI = {
|
|||
|
||||
SocialFlyout.unload();
|
||||
SocialChatBar.update();
|
||||
SocialShare.update();
|
||||
SocialSidebar.update();
|
||||
SocialMark.update();
|
||||
SocialToolbar.update();
|
||||
|
@ -97,6 +106,7 @@ SocialUI = {
|
|||
this._updateActiveUI();
|
||||
// and the multi-provider menu
|
||||
SocialToolbar.populateProviderMenus();
|
||||
SocialShare.populateProviderMenu();
|
||||
break;
|
||||
|
||||
// Provider-specific notifications
|
||||
|
@ -384,7 +394,13 @@ function sizeSocialPanelToContent(panel, iframe) {
|
|||
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);
|
||||
|
@ -566,6 +582,301 @@ SocialFlyout = {
|
|||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// The share button and context menus are disabled if the current tab has
|
||||
// defined no-store. However, a share from other content is still possible
|
||||
// (eg. via mozSocial or future use of web activities). If the URI is not
|
||||
// the current tab URI, we cannot validate the no-store header on the URI.
|
||||
if (aURI != gBrowser.currentURI)
|
||||
return true;
|
||||
|
||||
// we want to ensure this is a successful load and that the page is locally
|
||||
// cacheable since that is a common mechanism for sensitive pages to avoid
|
||||
// storing sensitive data in cache.
|
||||
let channel = gBrowser.docShell.currentDocumentChannel;
|
||||
let httpChannel;
|
||||
try {
|
||||
httpChannel = channel.QueryInterface(Ci.nsIHttpChannel);
|
||||
} catch (e) {
|
||||
/* Not an HTTP channel. */
|
||||
Cu.reportError("cannot share without httpChannel");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Continue only if we have a 2xx status code.
|
||||
try {
|
||||
if (!httpChannel.requestSucceeded)
|
||||
return false;
|
||||
} catch (e) {
|
||||
// Can't get response information from the httpChannel
|
||||
// because mResponseHead is not available.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Cache-Control: no-store.
|
||||
if (httpChannel.isNoStoreResponse()) {
|
||||
Cu.reportError("cannot share cache-control: no-share");
|
||||
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() {
|
||||
|
@ -598,7 +909,7 @@ SocialMark = {
|
|||
return;
|
||||
this.toggleURIMark(gBrowser.currentURI, aCallback)
|
||||
},
|
||||
|
||||
|
||||
toggleURIMark: function(aURI, aCallback) {
|
||||
let update = function(marked) {
|
||||
this._updateMarkState(marked);
|
||||
|
@ -998,11 +1309,12 @@ SocialToolbar = {
|
|||
menu.removeChild(providerMenuSep.previousSibling);
|
||||
}
|
||||
// only show a selection if enabled and there is more than one
|
||||
if (Social.providers.length < 2) {
|
||||
let providers = [p for (p of Social.providers) if (p.workerURL || p.sidebarURL)];
|
||||
if (!SocialUI.enabled || providers.length < 2) {
|
||||
providerMenuSep.hidden = true;
|
||||
return;
|
||||
}
|
||||
for (let provider of Social.providers) {
|
||||
for (let provider of providers) {
|
||||
let menuitem = document.createElement("menuitem");
|
||||
menuitem.className = "menuitem-iconic social-provider-menuitem";
|
||||
menuitem.setAttribute("image", provider.iconURL);
|
||||
|
|
|
@ -3406,6 +3406,7 @@ function BrowserToolboxCustomizeDone(aToolboxChanged) {
|
|||
XULBrowserWindow.asyncUpdateUI();
|
||||
BookmarksMenuButton.updateStarState();
|
||||
SocialMark.updateMarkState();
|
||||
SocialShare.update();
|
||||
}
|
||||
|
||||
TabsInTitlebar.allowedBy("customizing-toolbars", true);
|
||||
|
@ -3880,6 +3881,7 @@ var XULBrowserWindow = {
|
|||
// Update starring UI
|
||||
BookmarksMenuButton.updateStarState();
|
||||
SocialMark.updateMarkState();
|
||||
SocialShare.update();
|
||||
}
|
||||
|
||||
// Show or hide browser chrome based on the whitelist
|
||||
|
|
|
@ -205,6 +205,19 @@
|
|||
</vbox>
|
||||
</panel>
|
||||
|
||||
<panel id="social-share-panel"
|
||||
class="social-panel"
|
||||
type="arrow"
|
||||
orient="horizontal"
|
||||
onpopupshowing="SocialShare.onShowing()"
|
||||
onpopuphidden="SocialShare.onHidden()"
|
||||
consumeoutsideclicks="true"
|
||||
hidden="true">
|
||||
<vbox class="social-share-toolbar">
|
||||
<vbox id="social-share-provider-buttons" flex="1"/>
|
||||
</vbox>
|
||||
</panel>
|
||||
|
||||
<panel id="social-notification-panel"
|
||||
class="social-panel"
|
||||
type="arrow"
|
||||
|
@ -704,6 +717,13 @@
|
|||
onclick="BrowserGoHome(event);"
|
||||
aboutHomeOverrideTooltip="&abouthome.pageTitle;"/>
|
||||
|
||||
<toolbarbutton id="social-share-button"
|
||||
class="toolbarbutton-1 chromeclass-toolbar-additional"
|
||||
hidden="true"
|
||||
label="&sharePageCmd.label;"
|
||||
tooltiptext="&sharePageCmd.label;"
|
||||
command="Social:SharePage"/>
|
||||
|
||||
<toolbaritem id="social-toolbar-item"
|
||||
class="chromeclass-toolbar-additional"
|
||||
removable="false"
|
||||
|
|
|
@ -318,6 +318,19 @@ nsContextMenu.prototype = {
|
|||
}.bind(this));
|
||||
}
|
||||
this.showItem("context-marklink", enableLinkMark);
|
||||
|
||||
// SocialShare
|
||||
let shareButton = SocialShare.shareButton;
|
||||
let shareEnabled = shareButton && !shareButton.disabled && !this.onSocial;
|
||||
let pageShare = shareEnabled && !(this.isContentSelected ||
|
||||
this.onTextInput || this.onLink || this.onImage ||
|
||||
this.onVideo || this.onAudio);
|
||||
this.showItem("context-sharepage", pageShare);
|
||||
this.showItem("context-shareselect", shareEnabled && this.isContentSelected);
|
||||
this.showItem("context-sharelink", shareEnabled && (this.onLink || this.onPlainTextLink) && !this.onMailtoLink);
|
||||
this.showItem("context-shareimage", shareEnabled && this.onImage);
|
||||
this.showItem("context-sharevideo", shareEnabled && this.onVideo);
|
||||
this.setItemAttr("context-sharevideo", "disabled", !this.mediaURL);
|
||||
},
|
||||
|
||||
initSpellingItems: function() {
|
||||
|
@ -1498,6 +1511,22 @@ nsContextMenu.prototype = {
|
|||
SocialMark.toggleURIMark(this.linkURI);
|
||||
},
|
||||
|
||||
shareLink: function CM_shareLink() {
|
||||
SocialShare.sharePage(null, { url: this.linkURI.spec });
|
||||
},
|
||||
|
||||
shareImage: function CM_shareImage() {
|
||||
SocialShare.sharePage(null, { url: this.imageURL, previews: [ this.mediaURL ] });
|
||||
},
|
||||
|
||||
shareVideo: function CM_shareVideo() {
|
||||
SocialShare.sharePage(null, { url: this.mediaURL, source: this.mediaURL });
|
||||
},
|
||||
|
||||
shareSelect: function CM_shareSelect(selection) {
|
||||
SocialShare.sharePage(null, { url: this.browser.currentURI.spec, text: selection });
|
||||
},
|
||||
|
||||
savePageAs: function CM_savePageAs() {
|
||||
saveDocument(this.browser.contentDocument);
|
||||
},
|
||||
|
|
|
@ -32,6 +32,7 @@ _BROWSER_FILES = \
|
|||
browser_social_window.js \
|
||||
social_activate.html \
|
||||
social_activate_iframe.html \
|
||||
browser_share.js \
|
||||
social_panel.html \
|
||||
social_mark_image.png \
|
||||
social_sidebar.html \
|
||||
|
@ -39,6 +40,7 @@ _BROWSER_FILES = \
|
|||
social_flyout.html \
|
||||
social_window.html \
|
||||
social_worker.js \
|
||||
share.html \
|
||||
$(NULL)
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
|
||||
let baseURL = "https://example.com/browser/browser/base/content/test/social/";
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
let manifest = { // normal provider
|
||||
name: "provider 1",
|
||||
origin: "https://example.com",
|
||||
sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html",
|
||||
workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
|
||||
iconURL: "https://example.com/browser/browser/base/content/test/moz.png",
|
||||
shareURL: "https://example.com/browser/browser/base/content/test/social/share.html"
|
||||
};
|
||||
runSocialTestWithProvider(manifest, function (finishcb) {
|
||||
runSocialTests(tests, undefined, undefined, finishcb);
|
||||
});
|
||||
}
|
||||
|
||||
let corpus = [
|
||||
{
|
||||
url: baseURL+"opengraph/opengraph.html",
|
||||
options: {
|
||||
// og:title
|
||||
title: ">This is my title<",
|
||||
// og:description
|
||||
description: "A test corpus file for open graph tags we care about",
|
||||
//medium: this.getPageMedium(),
|
||||
//source: this.getSourceURL(),
|
||||
// og:url
|
||||
url: "https://www.mozilla.org/",
|
||||
//shortUrl: this.getShortURL(),
|
||||
// og:image
|
||||
previews:["https://www.mozilla.org/favicon.png"],
|
||||
// og:site_name
|
||||
siteName: ">My simple test page<"
|
||||
}
|
||||
},
|
||||
{
|
||||
// tests that og:url doesn't override the page url if it is bad
|
||||
url: baseURL+"opengraph/og_invalid_url.html",
|
||||
options: {
|
||||
description: "A test corpus file for open graph tags passing a bad url",
|
||||
url: baseURL+"opengraph/og_invalid_url.html",
|
||||
previews: [],
|
||||
siteName: "Evil chrome delivering website"
|
||||
}
|
||||
},
|
||||
{
|
||||
url: baseURL+"opengraph/shorturl_link.html",
|
||||
options: {
|
||||
previews: ["http://example.com/1234/56789.jpg"],
|
||||
url: "http://www.example.com/photos/56789/",
|
||||
shortUrl: "http://imshort/p/abcde"
|
||||
}
|
||||
},
|
||||
{
|
||||
url: baseURL+"opengraph/shorturl_linkrel.html",
|
||||
options: {
|
||||
previews: ["http://example.com/1234/56789.jpg"],
|
||||
url: "http://www.example.com/photos/56789/",
|
||||
shortUrl: "http://imshort/p/abcde"
|
||||
}
|
||||
},
|
||||
{
|
||||
url: baseURL+"opengraph/shortlink_linkrel.html",
|
||||
options: {
|
||||
previews: ["http://example.com/1234/56789.jpg"],
|
||||
url: "http://www.example.com/photos/56789/",
|
||||
shortUrl: "http://imshort/p/abcde"
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
function loadURLInTab(url, callback) {
|
||||
info("Loading tab with "+url);
|
||||
let tab = gBrowser.selectedTab = gBrowser.addTab(url);
|
||||
tab.linkedBrowser.addEventListener("load", function listener() {
|
||||
is(tab.linkedBrowser.currentURI.spec, url, "tab loaded")
|
||||
tab.linkedBrowser.removeEventListener("load", listener, true);
|
||||
callback(tab);
|
||||
}, true);
|
||||
}
|
||||
|
||||
function hasoptions(testOptions, options) {
|
||||
let msg;
|
||||
for (let option in testOptions) {
|
||||
let data = testOptions[option];
|
||||
info("data: "+JSON.stringify(data));
|
||||
let message_data = options[option];
|
||||
info("message_data: "+JSON.stringify(message_data));
|
||||
if (Array.isArray(data)) {
|
||||
// the message may have more array elements than we are testing for, this
|
||||
// is ok since some of those are hard to test. So we just test that
|
||||
// anything in our test data IS in the message.
|
||||
ok(Array.every(data, function(item) { return message_data.indexOf(item) >= 0 }), "option "+option);
|
||||
} else {
|
||||
is(message_data, data, "option "+option);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var tests = {
|
||||
testSharePage: function(next) {
|
||||
let panel = document.getElementById("social-flyout-panel");
|
||||
let port = Social.provider.getWorkerPort();
|
||||
ok(port, "provider has a port");
|
||||
let testTab;
|
||||
let testIndex = 0;
|
||||
let testData = corpus[testIndex++];
|
||||
|
||||
function runOneTest() {
|
||||
loadURLInTab(testData.url, function(tab) {
|
||||
testTab = tab;
|
||||
SocialShare.sharePage();
|
||||
});
|
||||
}
|
||||
|
||||
port.onmessage = function (e) {
|
||||
let topic = e.data.topic;
|
||||
switch (topic) {
|
||||
case "got-sidebar-message":
|
||||
// open a tab with share data, then open the share panel
|
||||
runOneTest();
|
||||
break;
|
||||
case "got-share-data-message":
|
||||
gBrowser.removeTab(testTab);
|
||||
hasoptions(testData.options, e.data.result);
|
||||
testData = corpus[testIndex++];
|
||||
if (testData) {
|
||||
runOneTest();
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
port.postMessage({topic: "test-init"});
|
||||
}
|
||||
}
|
|
@ -4,3 +4,4 @@
|
|||
# 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/.
|
||||
|
||||
DIRS += ['opengraph']
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
# 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/.
|
||||
|
||||
DEPTH = @DEPTH@
|
||||
topsrcdir = @top_srcdir@
|
||||
srcdir = @srcdir@
|
||||
VPATH = @srcdir@
|
||||
relativesrcdir = @relativesrcdir@
|
||||
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
|
||||
_BROWSER_FILES = \
|
||||
opengraph.html \
|
||||
og_invalid_url.html \
|
||||
shortlink_linkrel.html \
|
||||
shorturl_link.html \
|
||||
shorturl_linkrel.html \
|
||||
$(NULL)
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
libs:: $(_BROWSER_FILES)
|
||||
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
|
|
@ -0,0 +1,4 @@
|
|||
# vim: set filetype=python:
|
||||
# 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/.
|
|
@ -0,0 +1,11 @@
|
|||
<html xmlns:og="http://ogp.me/ns#">
|
||||
<head>
|
||||
<meta property="og:url" content="chrome://browser/content/aboutDialog.xul"/>
|
||||
<meta property="og:site_name" content="Evil chrome delivering website"/>
|
||||
<meta property="og:description"
|
||||
content="A test corpus file for open graph tags passing a bad url"/>
|
||||
</head>
|
||||
<body>
|
||||
Open Graph Test Page
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,13 @@
|
|||
<html xmlns:og="http://ogp.me/ns#">
|
||||
<head>
|
||||
<meta property="og:title" content=">This is my title<"/>
|
||||
<meta property="og:url" content="https://www.mozilla.org"/>
|
||||
<meta property="og:image" content="https://www.mozilla.org/favicon.png"/>
|
||||
<meta property="og:site_name" content=">My simple test page<"/>
|
||||
<meta property="og:description"
|
||||
content="A test corpus file for open graph tags we care about"/>
|
||||
</head>
|
||||
<body>
|
||||
Open Graph Test Page
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,10 @@
|
|||
<html>
|
||||
<head>
|
||||
<link rel="image_src" href="http://example.com/1234/56789.jpg" id="image-src" />
|
||||
<link id="canonicalurl" rel="canonical" href="http://www.example.com/photos/56789/" />
|
||||
<link rel="shortlink" href="http://imshort/p/abcde" />
|
||||
</head>
|
||||
<body>
|
||||
link[rel='shortlink']
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,10 @@
|
|||
<html>
|
||||
<head>
|
||||
<link rel="image_src" href="http://example.com/1234/56789.jpg" id="image-src" />
|
||||
<link id="canonicalurl" rel="canonical" href="http://www.example.com/photos/56789/" />
|
||||
<link id="shorturl" rev="canonical" type="text/html" href="http://imshort/p/abcde" />
|
||||
</head>
|
||||
<body>
|
||||
link id="shorturl"
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,25 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Test Image</title>
|
||||
|
||||
<meta name="description" content="Iron man in a tutu" />
|
||||
<meta name="title" content="Test Image" />
|
||||
|
||||
<meta name="medium" content="image" />
|
||||
<link rel="image_src" href="http://example.com/1234/56789.jpg" id="image-src" />
|
||||
<link id="canonicalurl" rel="canonical" href="http://www.example.com/photos/56789/" />
|
||||
<link id="shorturl" href="http://imshort/p/abcde" />
|
||||
|
||||
<meta property="og:title" content="TestImage" />
|
||||
<meta property="og:type" content="photos:photo" />
|
||||
<meta property="og:url" content="http://www.example.com/photos/56789/" />
|
||||
<meta property="og:site_name" content="My Photo Site" />
|
||||
<meta property="og:description" content="Iron man in a tutu" />
|
||||
<meta property="og:image" content="http://example.com/1234/56789.jpg" />
|
||||
<meta property="og:image:width" content="480" />
|
||||
<meta property="og:image:height" content="640" />
|
||||
</head>
|
||||
<body>
|
||||
link[rel='shorturl']
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,18 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<script>
|
||||
var shareData;
|
||||
addEventListener("OpenGraphData", function(e) {
|
||||
shareData = JSON.parse(e.detail);
|
||||
var port = navigator.mozSocial.getWorker().port;
|
||||
port.postMessage({topic: "share-data-message", result: shareData});
|
||||
// share windows self-close
|
||||
window.close();
|
||||
})
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<p>This is a test social share window.</p>
|
||||
</body>
|
||||
</html>
|
|
@ -133,6 +133,10 @@ onconnect = function(e) {
|
|||
case "test-isVisible-response":
|
||||
testPort.postMessage({topic: "got-isVisible-response", result: event.data.result});
|
||||
break;
|
||||
case "share-data-message":
|
||||
if (testPort)
|
||||
testPort.postMessage({topic:"got-share-data-message", result: event.data.result});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -116,6 +116,17 @@ These should match what Safari and other Apple applications use on OS X Lion. --
|
|||
<!ENTITY bookmarkThisPageCmd.label "Bookmark This Page">
|
||||
<!ENTITY bookmarkThisPageCmd.commandkey "d">
|
||||
<!ENTITY markPageCmd.commandkey "l">
|
||||
<!ENTITY sharePageCmd.label "Share This Page">
|
||||
<!ENTITY sharePageCmd.commandkey "S">
|
||||
<!ENTITY sharePageCmd.accesskey "s">
|
||||
<!ENTITY shareLinkCmd.label "Share This Link">
|
||||
<!ENTITY shareLinkCmd.accesskey "s">
|
||||
<!ENTITY shareImageCmd.label "Share This Image">
|
||||
<!ENTITY shareImageCmd.accesskey "s">
|
||||
<!ENTITY shareSelectCmd.label "Share Selection">
|
||||
<!ENTITY shareSelectCmd.accesskey "s">
|
||||
<!ENTITY shareVideoCmd.label "Share This Video">
|
||||
<!ENTITY shareVideoCmd.accesskey "s">
|
||||
<!ENTITY subscribeToPageMenupopup.label "Subscribe to This Page">
|
||||
<!ENTITY subscribeToPageMenuitem.label "Subscribe to This Page…">
|
||||
<!ENTITY addCurPagesCmd.label "Bookmark All Tabs…">
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["Social"];
|
||||
this.EXPORTED_SYMBOLS = ["Social", "OpenGraphBuilder"];
|
||||
|
||||
const Ci = Components.interfaces;
|
||||
const Cc = Components.classes;
|
||||
|
@ -20,6 +20,10 @@ XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
|
|||
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
|
||||
"resource://gre/modules/commonjs/sdk/core/promise.js");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "unescapeService",
|
||||
"@mozilla.org/feed-unescapehtml;1",
|
||||
"nsIScriptableUnescapeHTML");
|
||||
|
||||
// Add a pref observer for the enabled state
|
||||
function prefObserver(subject, topic, data) {
|
||||
let enable = Services.prefs.getBoolPref("social.enabled");
|
||||
|
@ -434,3 +438,118 @@ SocialErrorListener.prototype = {
|
|||
onStatusChange: function SPL_onStatusChange() {},
|
||||
onSecurityChange: function SPL_onSecurityChange() {},
|
||||
};
|
||||
|
||||
|
||||
this.OpenGraphBuilder = {
|
||||
getData: function(browser) {
|
||||
let res = {
|
||||
url: this._validateURL(browser, browser.currentURI.spec),
|
||||
title: browser.contentDocument.title,
|
||||
previews: []
|
||||
};
|
||||
this._getMetaData(browser, res);
|
||||
this._getLinkData(browser, res);
|
||||
this._getPageData(browser, res);
|
||||
return res;
|
||||
},
|
||||
|
||||
_getMetaData: function(browser, o) {
|
||||
// query for standardized meta data
|
||||
let els = browser.contentDocument
|
||||
.querySelectorAll("head > meta[property], head > meta[name]");
|
||||
if (els.length < 1)
|
||||
return;
|
||||
let url;
|
||||
for (let el of els) {
|
||||
let value = el.getAttribute("content")
|
||||
if (!value)
|
||||
continue;
|
||||
value = unescapeService.unescape(value.trim());
|
||||
switch (el.getAttribute("property") || el.getAttribute("name")) {
|
||||
case "title":
|
||||
case "og:title":
|
||||
o.title = value;
|
||||
break;
|
||||
case "description":
|
||||
case "og:description":
|
||||
o.description = value;
|
||||
break;
|
||||
case "og:site_name":
|
||||
o.siteName = value;
|
||||
break;
|
||||
case "medium":
|
||||
case "og:type":
|
||||
o.medium = value;
|
||||
break;
|
||||
case "og:video":
|
||||
url = this._validateURL(browser, value);
|
||||
if (url)
|
||||
o.source = url;
|
||||
break;
|
||||
case "og:url":
|
||||
url = this._validateURL(browser, value);
|
||||
if (url)
|
||||
o.url = url;
|
||||
break;
|
||||
case "og:image":
|
||||
url = this._validateURL(browser, value);
|
||||
if (url)
|
||||
o.previews.push(url);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_getLinkData: function(browser, o) {
|
||||
let els = browser.contentDocument
|
||||
.querySelectorAll("head > link[rel], head > link[id]");
|
||||
for (let el of els) {
|
||||
let url = el.getAttribute("href");
|
||||
if (!url)
|
||||
continue;
|
||||
url = this._validateURL(browser, unescapeService.unescape(url.trim()));
|
||||
switch (el.getAttribute("rel") || el.getAttribute("id")) {
|
||||
case "shorturl":
|
||||
case "shortlink":
|
||||
o.shortUrl = url;
|
||||
break;
|
||||
case "canonicalurl":
|
||||
case "canonical":
|
||||
o.url = url;
|
||||
break;
|
||||
case "image_src":
|
||||
o.previews.push(url);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// scrape through the page for data we want
|
||||
_getPageData: function(browser, o) {
|
||||
if (o.previews.length < 1)
|
||||
o.previews = this._getImageUrls(browser);
|
||||
},
|
||||
|
||||
_validateURL: function(browser, url) {
|
||||
let uri = Services.io.newURI(browser.currentURI.resolve(url), null, null);
|
||||
if (["http", "https", "ftp", "ftps"].indexOf(uri.scheme) < 0)
|
||||
return null;
|
||||
uri.userPass = "";
|
||||
return uri.spec;
|
||||
},
|
||||
|
||||
_getImageUrls: function(browser) {
|
||||
let l = [];
|
||||
let els = browser.contentDocument.querySelectorAll("img");
|
||||
for (let el of els) {
|
||||
let content = el.getAttribute("src");
|
||||
if (content) {
|
||||
l.push(this._validateURL(browser, unescapeService.unescape(content)));
|
||||
// we don't want a billion images
|
||||
if (l.length > 5)
|
||||
break;
|
||||
}
|
||||
}
|
||||
return l;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1441,6 +1441,64 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
|
|||
list-style-image: url("chrome://browser/skin/Info.png");
|
||||
}
|
||||
|
||||
|
||||
/* social share panel */
|
||||
|
||||
.social-share-frame {
|
||||
background: linear-gradient(to bottom, rgba(242,242,242,.99), rgba(242,242,242,.95));
|
||||
border-left: 1px solid #f8f8f8;
|
||||
width: 330px;
|
||||
height: 150px;
|
||||
/* we resize our panels dynamically, make it look nice */
|
||||
transition: height 100ms ease-out, width 100ms ease-out;
|
||||
}
|
||||
|
||||
#social-share-button {
|
||||
list-style-image: url("chrome://browser/skin/social/share-button.png");
|
||||
}
|
||||
|
||||
#social-share-button[open],
|
||||
#social-share-button:hover:active {
|
||||
list-style-image: url("chrome://browser/skin/social/share-button-active.png");
|
||||
}
|
||||
|
||||
.social-share-toolbar {
|
||||
border-right: 1px solid #dedede;
|
||||
background: linear-gradient(to bottom, rgba(247,247,247,.99), rgba(247,247,247,.95));
|
||||
}
|
||||
|
||||
#social-share-provider-buttons {
|
||||
border-right: 1px solid #fbfbfb;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
#social-share-provider-buttons > .share-provider-button {
|
||||
padding: 6px;
|
||||
margin: 0;
|
||||
border: none;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
#social-share-provider-buttons > .share-provider-button[checked],
|
||||
#social-share-provider-buttons > .share-provider-button:active {
|
||||
padding: 5px;
|
||||
border: 1px solid #b5b5b8;
|
||||
box-shadow: inset 1px 1px 3px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
#social-share-provider-buttons > .share-provider-button[checked] {
|
||||
background: linear-gradient(to bottom, #d9d9d9, #e3e3e3);
|
||||
}
|
||||
|
||||
#social-share-provider-buttons > .share-provider-button > .toolbarbutton-text {
|
||||
display: none;
|
||||
}
|
||||
#social-share-provider-buttons > .share-provider-button > .toolbarbutton-icon {
|
||||
width: 16px;
|
||||
min-height: 16px;
|
||||
max-height: 16px;
|
||||
}
|
||||
|
||||
/* social recommending panel */
|
||||
|
||||
#social-mark-button {
|
||||
|
|
|
@ -105,6 +105,8 @@ browser.jar:
|
|||
skin/classic/browser/preferences/aboutPermissions.css (preferences/aboutPermissions.css)
|
||||
skin/classic/browser/social/services-16.png (social/services-16.png)
|
||||
skin/classic/browser/social/services-64.png (social/services-64.png)
|
||||
skin/classic/browser/social/share-button.png (social/share-button.png)
|
||||
skin/classic/browser/social/share-button-active.png (social/share-button-active.png)
|
||||
skin/classic/browser/social/chat-close.png (social/chat-close.png)
|
||||
skin/classic/browser/tabbrowser/connecting.png (tabbrowser/connecting.png)
|
||||
skin/classic/browser/tabbrowser/loading.png (tabbrowser/loading.png)
|
||||
|
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 1.3 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 1.3 KiB |
|
@ -1655,6 +1655,62 @@ window[tabsontop="false"] richlistitem[type~="action"][actiontype="switchtab"][s
|
|||
}
|
||||
}
|
||||
|
||||
/* social share panel */
|
||||
.social-share-frame {
|
||||
background: linear-gradient(to bottom, rgba(242,242,242,.99), rgba(242,242,242,.95));
|
||||
border-left: 1px solid #f8f8f8;
|
||||
width: 330px;
|
||||
height: 150px;
|
||||
/* we resize our panels dynamically, make it look nice */
|
||||
transition: height 100ms ease-out, width 100ms ease-out;
|
||||
}
|
||||
|
||||
#social-share-button {
|
||||
list-style-image: url("chrome://browser/skin/social/share-button.png");
|
||||
}
|
||||
|
||||
#social-share-button[open],
|
||||
#social-share-button:hover:active {
|
||||
list-style-image: url("chrome://browser/skin/social/share-button-active.png");
|
||||
}
|
||||
|
||||
.social-share-toolbar {
|
||||
border-right: 1px solid #dedede;
|
||||
background: linear-gradient(to bottom, rgba(247,247,247,.99), rgba(247,247,247,.95));
|
||||
}
|
||||
|
||||
#social-share-provider-buttons {
|
||||
border-right: 1px solid #fbfbfb;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
#social-share-provider-buttons > .share-provider-button {
|
||||
padding: 6px;
|
||||
margin: 0;
|
||||
border: none;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
#social-share-provider-buttons > .share-provider-button[checked],
|
||||
#social-share-provider-buttons > .share-provider-button:active {
|
||||
padding: 5px;
|
||||
border: 1px solid #b5b5b8;
|
||||
box-shadow: inset 1px 1px 3px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
#social-share-provider-buttons > .share-provider-button[checked] {
|
||||
background: linear-gradient(to bottom, #d9d9d9, #e3e3e3);
|
||||
}
|
||||
|
||||
#social-share-provider-buttons > .share-provider-button > .toolbarbutton-text {
|
||||
display: none;
|
||||
}
|
||||
#social-share-provider-buttons > .share-provider-button > .toolbarbutton-icon {
|
||||
width: 16px;
|
||||
min-height: 16px;
|
||||
max-height: 16px;
|
||||
}
|
||||
|
||||
/* social recommending panel */
|
||||
|
||||
#social-mark-button {
|
||||
|
@ -3628,6 +3684,10 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
|
|||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
|
||||
#social-toolbar-item > toolbaritem {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#social-provider-button {
|
||||
-moz-image-region: rect(0, 16px, 16px, 0);
|
||||
list-style-image: url(chrome://browser/skin/social/services-16.png);
|
||||
|
@ -3756,10 +3816,36 @@ toolbar[mode="icons"] > *|* > .toolbarbutton-badge[badge]:not([badge=""]):-moz-l
|
|||
padding: 0;
|
||||
}
|
||||
|
||||
.social-panel-frame {
|
||||
/* fixup rounded corners for osx panels */
|
||||
.social-panel > .social-panel-frame {
|
||||
border-radius: inherit;
|
||||
}
|
||||
|
||||
#social-share-panel {
|
||||
margin-top: 3px;
|
||||
max-height: 600px;
|
||||
min-height: 100px;
|
||||
max-width: 800px;
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
.social-share-frame {
|
||||
border-top-left-radius: none;
|
||||
border-bottom-left-radius: none;
|
||||
border-top-right-radius: inherit;
|
||||
border-bottom-right-radius: inherit;
|
||||
}
|
||||
|
||||
#social-share-panel > .social-share-toolbar {
|
||||
border-top-left-radius: inherit;
|
||||
border-bottom-left-radius: inherit;
|
||||
}
|
||||
|
||||
#social-share-provider-buttons {
|
||||
border-top-left-radius: inherit;
|
||||
border-bottom-left-radius: inherit;
|
||||
}
|
||||
|
||||
/* === end of social toolbar provider menu === */
|
||||
|
||||
%include ../shared/social/chat.inc.css
|
||||
|
|
|
@ -172,6 +172,8 @@ browser.jar:
|
|||
skin/classic/browser/social/services-16@2x.png (social/services-16@2x.png)
|
||||
skin/classic/browser/social/services-64.png (social/services-64.png)
|
||||
skin/classic/browser/social/services-64@2x.png (social/services-64@2x.png)
|
||||
skin/classic/browser/social/share-button.png (social/share-button.png)
|
||||
skin/classic/browser/social/share-button-active.png (social/share-button-active.png)
|
||||
skin/classic/browser/social/chat-close.png (social/chat-close.png)
|
||||
skin/classic/browser/tabbrowser/alltabs-box-bkgnd-icon.png (tabbrowser/alltabs-box-bkgnd-icon.png)
|
||||
skin/classic/browser/tabbrowser/newtab.png (tabbrowser/newtab.png)
|
||||
|
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 1.3 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 1.3 KiB |
|
@ -1,3 +1,3 @@
|
|||
%filter substitution
|
||||
|
||||
%define primaryToolbarButtons #back-button, #forward-button, #reload-button, #stop-button, #home-button, #print-button, #downloads-button, #downloads-indicator, #history-button, #bookmarks-button, #bookmarks-menu-button, #new-tab-button, #new-window-button, #cut-button, #copy-button, #paste-button, #fullscreen-button, #zoom-out-button, #zoom-in-button, #sync-button, #feed-button, #alltabs-button, #tabview-button, #webrtc-status-button
|
||||
%define primaryToolbarButtons #back-button, #forward-button, #reload-button, #stop-button, #home-button, #print-button, #downloads-button, #downloads-indicator, #history-button, #bookmarks-button, #bookmarks-menu-button, #new-tab-button, #new-window-button, #cut-button, #copy-button, #paste-button, #fullscreen-button, #zoom-out-button, #zoom-in-button, #sync-button, #feed-button, #alltabs-button, #tabview-button, #webrtc-status-button, #social-share-button
|
||||
|
|
|
@ -1713,6 +1713,93 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
|
|||
-moz-image-region: rect(0, 48px, 16px, 32px);
|
||||
}
|
||||
|
||||
|
||||
/* social share panel */
|
||||
|
||||
#social-share-panel > iframe {
|
||||
background: linear-gradient(to bottom, #f0f4f7, #fafbfc);
|
||||
width: 300px;
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
#social-share-button {
|
||||
list-style-image: url(chrome://browser/skin/social/share-button.png);
|
||||
-moz-image-region: rect(0, 16px, 16px, 0);
|
||||
}
|
||||
|
||||
.social-share-toolbar {
|
||||
border-right: 1px solid #e2e5e8;
|
||||
background: linear-gradient(to bottom, #ffffff, #f5f7fa);
|
||||
}
|
||||
|
||||
#social-share-provider-buttons {
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
#social-share-provider-buttons > .share-provider-button {
|
||||
-moz-appearance: none;
|
||||
padding: 5px;
|
||||
margin: 1px;
|
||||
border: none;
|
||||
background: none;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
#social-share-provider-buttons > .share-provider-button[checked="true"]:not([disabled="true"]),
|
||||
#social-share-provider-buttons > .share-provider-button:hover,
|
||||
#social-share-provider-buttons > .share-provider-button:active {
|
||||
padding: 4px;
|
||||
border: 1px solid #aeb8c1;
|
||||
box-shadow: inset 1px 1px 1px rgba(10, 31, 51, 0.1);
|
||||
}
|
||||
|
||||
#social-share-provider-buttons > .share-provider-button[checked="true"]:not([disabled="true"]) {
|
||||
background: linear-gradient(to bottom, rgba(230,232,234,.65), #d2d5d9);
|
||||
}
|
||||
|
||||
#social-share-provider-buttons > .share-provider-button > .toolbarbutton-text {
|
||||
display: none;
|
||||
}
|
||||
#social-share-provider-buttons > .share-provider-button > .toolbarbutton-icon {
|
||||
width: 16px;
|
||||
min-height: 16px;
|
||||
max-height: 16px;
|
||||
}
|
||||
|
||||
/* fixup corners for share panel */
|
||||
.social-panel > .social-panel-frame {
|
||||
border-radius: inherit;
|
||||
}
|
||||
|
||||
#social-share-panel {
|
||||
max-height: 600px;
|
||||
min-height: 100px;
|
||||
max-width: 800px;
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
.social-share-frame {
|
||||
background: linear-gradient(to bottom, #f0f4f7, #fafbfc);
|
||||
width: 330px;
|
||||
height: 150px;
|
||||
border-top-left-radius: none;
|
||||
border-bottom-left-radius: none;
|
||||
border-top-right-radius: inherit;
|
||||
border-bottom-right-radius: inherit;
|
||||
/* we resize our panels dynamically, make it look nice */
|
||||
transition: height 100ms ease-out, width 100ms ease-out;
|
||||
}
|
||||
|
||||
#social-share-panel > .social-share-toolbar {
|
||||
border-top-left-radius: inherit;
|
||||
border-bottom-left-radius: inherit;
|
||||
}
|
||||
|
||||
#social-share-provider-buttons {
|
||||
border-top-left-radius: inherit;
|
||||
border-bottom-left-radius: inherit;
|
||||
}
|
||||
|
||||
/* social recommending panel */
|
||||
|
||||
#social-mark-button {
|
||||
|
|
|
@ -125,6 +125,8 @@ browser.jar:
|
|||
skin/classic/browser/preferences/aboutPermissions.css (preferences/aboutPermissions.css)
|
||||
skin/classic/browser/social/services-16.png (social/services-16.png)
|
||||
skin/classic/browser/social/services-64.png (social/services-64.png)
|
||||
skin/classic/browser/social/share-button.png (social/share-button.png)
|
||||
skin/classic/browser/social/share-button-active.png (social/share-button-active.png)
|
||||
skin/classic/browser/social/chat-close.png (social/chat-close.png)
|
||||
skin/classic/browser/tabbrowser/newtab.png (tabbrowser/newtab.png)
|
||||
skin/classic/browser/tabbrowser/newtab-inverted.png (tabbrowser/newtab-inverted.png)
|
||||
|
@ -372,6 +374,8 @@ browser.jar:
|
|||
skin/classic/aero/browser/preferences/aboutPermissions.css (preferences/aboutPermissions.css)
|
||||
skin/classic/aero/browser/social/services-16.png (social/services-16.png)
|
||||
skin/classic/aero/browser/social/services-64.png (social/services-64.png)
|
||||
skin/classic/aero/browser/social/share-button.png (social/share-button.png)
|
||||
skin/classic/aero/browser/social/share-button-active.png (social/share-button-active.png)
|
||||
skin/classic/aero/browser/social/chat-close.png (social/chat-close.png)
|
||||
skin/classic/aero/browser/tabbrowser/newtab.png (tabbrowser/newtab.png)
|
||||
skin/classic/aero/browser/tabbrowser/newtab-inverted.png (tabbrowser/newtab-inverted.png)
|
||||
|
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 1.4 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 1.4 KiB |
|
@ -141,6 +141,32 @@ function attachToWindow(provider, targetWindow) {
|
|||
chromeWindow.SocialFlyout.panel.hidePopup();
|
||||
}
|
||||
},
|
||||
// allow a provider to share to other providers through the browser
|
||||
share: {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: function(data) {
|
||||
let chromeWindow = getChromeWindow(targetWindow);
|
||||
if (!chromeWindow.SocialShare || chromeWindow.SocialShare.shareButton.hidden)
|
||||
throw new Error("Share is unavailable");
|
||||
// ensure user action initates the share
|
||||
let dwu = chromeWindow.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
if (!dwu.isHandlingUserInput)
|
||||
throw new Error("Attempt to share without user input");
|
||||
|
||||
// limit to a few params we want to support for now
|
||||
let dataOut = {};
|
||||
for (let sub of ["url", "title", "description", "source"]) {
|
||||
dataOut[sub] = data[sub];
|
||||
}
|
||||
if (data.image)
|
||||
dataOut.previews = [data.image];
|
||||
|
||||
chromeWindow.SocialShare.sharePage(null, dataOut);
|
||||
}
|
||||
},
|
||||
getAttention: {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
|
|
|
@ -136,6 +136,7 @@ function migrateSettings() {
|
|||
for (let origin in ActiveProviders._providers) {
|
||||
let prefname;
|
||||
let manifest;
|
||||
let defaultManifest;
|
||||
try {
|
||||
prefname = getPrefnameFromOrigin(origin);
|
||||
manifest = JSON.parse(Services.prefs.getComplexValue(prefname, Ci.nsISupportsString).data);
|
||||
|
@ -148,14 +149,27 @@ function migrateSettings() {
|
|||
ActiveProviders.flush();
|
||||
continue;
|
||||
}
|
||||
if (!manifest.updateDate) {
|
||||
let needsUpdate = !manifest.updateDate;
|
||||
// fx23 may have built-ins with shareURL
|
||||
try {
|
||||
defaultManifest = Services.prefs.getDefaultBranch(null)
|
||||
.getComplexValue(prefname, Ci.nsISupportsString).data;
|
||||
defaultManifest = JSON.parse(defaultManifest);
|
||||
if (defaultManifest.shareURL && !manifest.shareURL) {
|
||||
manifest.shareURL = defaultManifest.shareURL;
|
||||
needsUpdate = true;
|
||||
}
|
||||
} catch(e) {
|
||||
// not a built-in, continue
|
||||
}
|
||||
if (needsUpdate) {
|
||||
// the provider was installed with an older build, so we will update the
|
||||
// timestamp and ensure the manifest is in user prefs
|
||||
delete manifest.builtin;
|
||||
if (!manifest.updateDate) {
|
||||
manifest.updateDate = Date.now();
|
||||
// we're potentially updating for share, so always mark the updateDate
|
||||
manifest.updateDate = Date.now();
|
||||
if (!manifest.installDate)
|
||||
manifest.installDate = 0; // we don't know when it was installed
|
||||
}
|
||||
|
||||
let string = Cc["@mozilla.org/supports-string;1"].
|
||||
createInstance(Ci.nsISupportsString);
|
||||
|
@ -404,7 +418,7 @@ this.SocialService = {
|
|||
},
|
||||
|
||||
_manifestFromData: function(type, data, principal) {
|
||||
let sameOriginRequired = ['workerURL', 'sidebarURL'];
|
||||
let sameOriginRequired = ['workerURL', 'sidebarURL', 'shareURL'];
|
||||
|
||||
if (type == 'directory') {
|
||||
// directory provided manifests must have origin in manifest, use that
|
||||
|
@ -422,7 +436,7 @@ this.SocialService = {
|
|||
// iconURL and name are required
|
||||
// iconURL may be a different origin (CDN or data url support) if this is
|
||||
// a whitelisted or directory listed provider
|
||||
if (!data['workerURL'] || !data['sidebarURL']) {
|
||||
if (!data['workerURL'] && !data['sidebarURL'] && !data['shareURL']) {
|
||||
Cu.reportError("SocialService.manifestFromData manifest missing required workerURL or sidebarURL.");
|
||||
return null;
|
||||
}
|
||||
|
@ -578,6 +592,7 @@ function SocialProvider(input) {
|
|||
this.icon64URL = input.icon64URL;
|
||||
this.workerURL = input.workerURL;
|
||||
this.sidebarURL = input.sidebarURL;
|
||||
this.shareURL = input.shareURL;
|
||||
this.origin = input.origin;
|
||||
let originUri = Services.io.newURI(input.origin, null, null);
|
||||
this.principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(originUri);
|
||||
|
|
Загрузка…
Ссылка в новой задаче