bug 818675 implement new social share panel, r=felipe

This commit is contained in:
Shane Caraveo 2013-05-06 18:36:31 -07:00
Родитель dada7e49b3
Коммит dee9a7fd1e
37 изменённых файлов: 1076 добавлений и 14 удалений

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -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="&gt;This is my title&lt;"/>
<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="&#62;My simple test page&#60;"/>
<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)

Двоичные данные
browser/themes/linux/social/share-button-active.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.3 KiB

Двоичные данные
browser/themes/linux/social/share-button.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 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)

Двоичные данные
browser/themes/osx/social/share-button-active.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.3 KiB

Двоичные данные
browser/themes/osx/social/share-button.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 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)

Двоичные данные
browser/themes/windows/social/share-button-active.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.4 KiB

Двоичные данные
browser/themes/windows/social/share-button.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 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);