Bug 755136: implement social sidebar, r=gavin

--HG--
extra : rebase_source : 5545f8120d9fc3cea168007bbedde7ba30efdaa9
This commit is contained in:
Shane Caraveo 2012-07-18 11:40:05 -07:00
parent 5845c4644a
commit 14a82646a5
11 changed files with 284 additions and 125 deletions

View File

@ -1184,4 +1184,5 @@ pref("pdfjs.previousHandler.alwaysAskBeforeHandling", false);
pref("image.mem.max_decoded_image_kb", 256000);
// Example social provider
pref("social.manifest.motown", "{\"origin\":\"https://motown-dev.mozillalabs.com\",\"name\":\"MoTown\",\"workerURL\":\"https://motown-dev.mozillalabs.com/social/worker.js\",\"iconURL\":\"https://motown-dev.mozillalabs.com/images/motown-icon.png\"}");
pref("social.manifest.motown", "{\"origin\":\"https://motown-dev.mozillalabs.com\",\"name\":\"MoTown\",\"workerURL\":\"https://motown-dev.mozillalabs.com/social/worker.js\",\"iconURL\":\"https://motown-dev.mozillalabs.com/images/motown-icon.png\",\"sidebarURL\":\"https://motown-dev.mozillalabs.com/social/sidebar\"}");
pref("social.sidebar.open", true);

View File

@ -107,6 +107,7 @@
<command id="Browser:ToggleAddonBar" oncommand="toggleAddonBar();"/>
<command id="Social:SharePage" oncommand="SocialShareButton.sharePage();"/>
<command id="Social:UnsharePage" oncommand="SocialShareButton.unsharePage();"/>
<command id="Social:ToggleSidebar" oncommand="Social.toggleSidebar();"/>
</commandset>
<commandset id="placesCommands">
@ -181,6 +182,7 @@
<broadcaster id="sync-syncnow-state"/>
#endif
<broadcaster id="workOfflineMenuitemState"/>
<broadcaster id="socialSidebarBroadcaster" hidden="true"/>
</broadcasterset>
<keyset id="mainKeyset">

View File

@ -9,6 +9,8 @@ let SocialUI = {
Services.obs.addObserver(this, "social:ambient-notification-changed", false);
Services.obs.addObserver(this, "social:profile-changed", false);
Services.prefs.addObserver("social.sidebar.open", this, false);
Social.init(this._providerReady.bind(this));
},
@ -17,6 +19,8 @@ let SocialUI = {
Services.obs.removeObserver(this, "social:pref-changed");
Services.obs.removeObserver(this, "social:ambient-notification-changed");
Services.obs.removeObserver(this, "social:profile-changed");
Services.prefs.removeObserver("social.sidebar.open", this);
},
showProfile: function SocialUI_showProfile() {
@ -29,6 +33,7 @@ let SocialUI = {
case "social:pref-changed":
SocialShareButton.updateButtonHiddenState();
SocialToolbar.updateButtonHiddenState();
SocialSidebar.updateSidebar();
break;
case "social:ambient-notification-changed":
SocialToolbar.updateButton();
@ -36,6 +41,8 @@ let SocialUI = {
case "social:profile-changed":
SocialToolbar.updateProfile();
break;
case "nsPref:changed":
SocialSidebar.updateSidebar();
}
},
@ -43,12 +50,13 @@ let SocialUI = {
_providerReady: function SocialUI_providerReady() {
SocialToolbar.init();
SocialShareButton.init();
SocialSidebar.init();
}
}
let SocialShareButton = {
// Called once, after window load, when the Social.provider object is initialized
init: function SSB_init() {
this.sharePopup.hidden = false;
this.updateButtonHiddenState();
let profileRow = document.getElementById("editSharePopupHeader");
@ -98,6 +106,8 @@ let SocialShareButton = {
},
sharePage: function SSB_sharePage() {
this.sharePopup.hidden = false;
let uri = gBrowser.currentURI;
if (!Social.isPageShared(uri)) {
Social.sharePage(uri);
@ -248,9 +258,60 @@ var SocialToolbar = {
notifBrowser.setAttribute("src", "about:blank");
});
notifBrowser.service = Social.provider;
notifBrowser.setAttribute("origin", Social.provider.origin);
notifBrowser.setAttribute("src", iconImage.getAttribute("contentPanel"));
document.getElementById("social-toolbar-button").setAttribute("open", "true");
panel.openPopup(iconImage, "bottomcenter topleft", 0, 0, false, false);
}
}
var SocialSidebar = {
// Called once, after window load, when the Social.provider object is initialized
init: function SocialSidebar_init() {
this.updateSidebar();
},
// Whether the sidebar can be shown for this window.
get canShow() {
return Social.uiVisible && Social.provider.sidebarURL && !this.chromeless;
},
// Whether this is a "chromeless window" (e.g. popup window). We don't show
// the sidebar in these windows.
get chromeless() {
let docElem = document.documentElement;
return docElem.getAttribute('disablechrome') ||
docElem.getAttribute('chromehidden').indexOf("extrachrome") >= 0;
},
// Whether the user has toggled the sidebar on (for windows where it can appear)
get enabled() {
return Services.prefs.getBoolPref("social.sidebar.open");
},
updateSidebar: function SocialSidebar_updateSidebar() {
// Hide the toggle menu item if the sidebar cannot appear
let command = document.getElementById("Social:ToggleSidebar");
command.hidden = !this.canShow;
// Hide the sidebar if it cannot appear, or has been toggled off.
// Also set the command "checked" state accordingly.
let hideSidebar = !this.canShow || !this.enabled;
let broadcaster = document.getElementById("socialSidebarBroadcaster");
broadcaster.hidden = hideSidebar;
command.setAttribute("checked", !hideSidebar);
// If the sidebar is hidden, unload its document
// XXX this results in a poor UX, we should revisit
let sbrowser = document.getElementById("social-sidebar-browser");
if (broadcaster.hidden) {
sbrowser.removeAttribute("origin");
sbrowser.setAttribute("src", "about:blank");
return;
}
// Load the sidebar document
sbrowser.setAttribute("origin", Social.provider.origin);
sbrowser.setAttribute("src", Social.provider.sidebarURL);
}
}

View File

@ -638,6 +638,12 @@
oncommand="SocialUI.showProfile(); document.getElementById('social-statusarea-popup').hidePopup();"/>
</vbox>
</hbox>
<menuitem id="social-toggle-sidebar-menuitem"
type="checkbox"
autocheck="false"
command="Social:ToggleSidebar"
label="&social.toggleSidebar.label;"
accesskey="&social.toggleSidebar.accesskey;"/>
</menupopup>
</button>
<hbox id="social-status-iconbox" flex="1">
@ -1024,6 +1030,17 @@
onclick="contentAreaClick(event, false);"/>
<statuspanel id="statusbar-display" inactive="true"/>
</vbox>
<splitter id="social-sidebar-splitter"
class="chromeclass-extrachrome"
observes="socialSidebarBroadcaster"/>
<vbox id="social-sidebar-box"
class="chromeclass-extrachrome"
observes="socialSidebarBroadcaster">
<browser id="social-sidebar-browser"
type="content"
flex="1"
style="min-width: 14em; width: 18em; max-width: 36em;"/>
</vbox>
<splitter id="devtools-side-splitter" hidden="true"/>
<vbox id="devtools-sidebar-box" hidden="true"
style="min-width: 18em; width: 22em; max-width: 42em;" persist="width">

View File

@ -257,6 +257,7 @@ _BROWSER_FILES = \
browser_lastAccessedTab.js \
browser_bug734076.js \
browser_social_toolbar.js \
browser_social_sidebar.js \
$(NULL)
ifneq (cocoa,$(MOZ_WIDGET_TOOLKIT))

View File

@ -51,7 +51,6 @@ function testInitial() {
sharePopup = SocialShareButton.sharePopup;
ok(shareButton, "share button exists");
ok(sharePopup, "share popup exists");
ok(!sharePopup.hidden, "share popup is not hidden");
okButton = document.getElementById("editSharePopupOkButton");
undoButton = document.getElementById("editSharePopupUndoButton");

View File

@ -0,0 +1,74 @@
/* 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/. */
let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
function test() {
waitForExplicitFinish();
let oldProvider;
function saveOldProviderAndStartTestWith(provider) {
oldProvider = Social.provider;
registerCleanupFunction(function () {
Social.provider = oldProvider;
});
Social.provider = provider;
// Now that we've set the UI's provider, enable the social functionality
Services.prefs.setBoolPref("social.enabled", true);
registerCleanupFunction(function () {
Services.prefs.clearUserPref("social.enabled");
});
doTest();
}
let manifest = { // normal provider
name: "provider 1",
origin: "https://example1.com",
sidebarURL: "https://example1.com/sidebar.html",
workerURL: "https://example1.com/worker.js",
iconURL: "chrome://branding/content/icon48.png"
};
SocialService.addProvider(manifest, function(provider) {
// If the UI is already active, run the test immediately, otherwise wait
// for initialization.
if (Social.provider) {
saveOldProviderAndStartTestWith(provider);
} else {
Services.obs.addObserver(function obs() {
Services.obs.removeObserver(obs, "test-social-ui-ready");
saveOldProviderAndStartTestWith(provider);
}, "test-social-ui-ready", false);
}
});
}
function doTest() {
ok(SocialSidebar.canShow, "social sidebar should be able to be shown");
ok(SocialSidebar.enabled, "social sidebar should be on by default");
let command = document.getElementById("Social:ToggleSidebar");
let sidebar = document.getElementById("social-sidebar-box");
// Check the the sidebar is initially visible, and loaded
ok(!command.hidden, "sidebar toggle command should be visible");
is(command.getAttribute("checked"), "true", "sidebar toggle command should be checked");
ok(!sidebar.hidden, "sidebar itself should be visible");
ok(Services.prefs.getBoolPref("social.sidebar.open"), "sidebar open pref should be true");
is(sidebar.firstChild.getAttribute('src'), "https://example1.com/sidebar.html", "sidebar url should be set");
// Now toggle it!
info("Toggling sidebar");
Social.toggleSidebar();
is(command.getAttribute("checked"), "false", "sidebar toggle command should not be checked");
ok(sidebar.hidden, "sidebar itself should not be visible");
ok(!Services.prefs.getBoolPref("social.sidebar.open"), "sidebar open pref should be false");
is(sidebar.firstChild.getAttribute('src'), "about:blank", "sidebar url should not be set");
// Remove the test provider
SocialService.removeProvider(Social.provider.origin, finish);
}
// XXX test sidebar in popup

View File

@ -8,11 +8,6 @@ let gProvider;
function test() {
waitForExplicitFinish();
Services.prefs.setBoolPref("social.enabled", true);
registerCleanupFunction(function () {
Services.prefs.clearUserPref("social.enabled");
});
let oldProvider;
function saveOldProviderAndStartTestWith(provider) {
oldProvider = Social.provider;
@ -20,6 +15,12 @@ function test() {
Social.provider = oldProvider;
});
Social.provider = gProvider = provider;
Services.prefs.setBoolPref("social.enabled", true);
registerCleanupFunction(function () {
Services.prefs.clearUserPref("social.enabled");
});
runTests(tests, undefined, undefined, function () {
SocialService.removeProvider(provider.origin, finish);
});

View File

@ -662,3 +662,5 @@ toolbar button -->
<!ENTITY social.sharePopup.ok.accesskey "O">
<!ENTITY social.sharePopup.shared.label "You shared this page.">
<!ENTITY social.sharePopup.portrait.arialabel "User profile picture">
<!ENTITY social.toggleSidebar.label "Show sidebar">
<!ENTITY social.toggleSidebar.accesskey "s">

View File

@ -34,14 +34,15 @@ let Social = {
}.bind(this));
},
get enabled() {
return SocialService.enabled;
},
get uiVisible() {
return this.provider && this.provider.enabled && this.provider.port;
},
toggleSidebar: function SocialSidebar_toggle() {
let prefValue = Services.prefs.getBoolPref("social.sidebar.open");
Services.prefs.setBoolPref("social.sidebar.open", !prefValue);
},
sendWorkerMessage: function Social_sendWorkerMessage(message) {
// Responses aren't handled yet because there is no actions to perform
// based on the response from the provider at this point.

View File

@ -12,6 +12,117 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "getFrameWorkerHandle", "resource://gre/modules/FrameWorker.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WorkerAPI", "resource://gre/modules/WorkerAPI.jsm");
/**
* The SocialService is the public API to social providers - it tracks which
* providers are installed and enabled, and is the entry-point for access to
* the provider itself.
*/
// Internal helper methods and state
let SocialServiceInternal = {
enabled: Services.prefs.getBoolPref("social.enabled"),
get providerArray() {
return [p for ([, p] of Iterator(this.providers))];
}
};
XPCOMUtils.defineLazyGetter(SocialServiceInternal, "providers", function () {
// Initialize the service (add a pref observer)
function prefObserver(subject, topic, data) {
SocialService._setEnabled(Services.prefs.getBoolPref(data));
}
Services.prefs.addObserver("social.enabled", prefObserver, false);
Services.obs.addObserver(function xpcomShutdown() {
Services.obs.removeObserver(xpcomShutdown, "xpcom-shutdown");
Services.prefs.removeObserver("social.enabled", prefObserver);
}, "xpcom-shutdown", false);
// Now retrieve the providers
let providers = {};
let MANIFEST_PREFS = Services.prefs.getBranch("social.manifest.");
let prefs = MANIFEST_PREFS.getChildList("", {});
prefs.forEach(function (pref) {
try {
var manifest = JSON.parse(MANIFEST_PREFS.getCharPref(pref));
if (manifest && typeof(manifest) == "object") {
let provider = new SocialProvider(manifest, SocialServiceInternal.enabled);
providers[provider.origin] = provider;
}
} catch (err) {
Cu.reportError("SocialService: failed to load provider: " + pref +
", exception: " + err);
}
});
return providers;
});
function schedule(callback) {
Services.tm.mainThread.dispatch(callback, Ci.nsIThread.DISPATCH_NORMAL);
}
// Public API
const SocialService = {
get enabled() {
return SocialServiceInternal.enabled;
},
set enabled(val) {
let enable = !!val;
if (enable == SocialServiceInternal.enabled)
return;
Services.prefs.setBoolPref("social.enabled", enable);
this._setEnabled(enable);
},
_setEnabled: function _setEnabled(enable) {
SocialServiceInternal.providerArray.forEach(function (p) p.enabled = enable);
SocialServiceInternal.enabled = enable;
Services.obs.notifyObservers(null, "social:pref-changed", enable ? "enabled" : "disabled");
},
// Adds a provider given a manifest, and returns the added provider.
addProvider: function addProvider(manifest, onDone) {
if (SocialServiceInternal.providers[manifest.origin])
throw new Error("SocialService.addProvider: provider with this origin already exists");
let provider = new SocialProvider(manifest, SocialServiceInternal.enabled);
SocialServiceInternal.providers[provider.origin] = provider;
schedule(function () {
onDone(provider);
});
},
// Removes a provider with the given origin, and notifies when the removal is
// complete.
removeProvider: function removeProvider(origin, onDone) {
if (!(origin in SocialServiceInternal.providers))
throw new Error("SocialService.removeProvider: no provider with this origin exists!");
let provider = SocialServiceInternal.providers[origin];
provider.enabled = false;
delete SocialServiceInternal.providers[origin];
if (onDone)
schedule(onDone);
},
// Returns a single provider object with the specified origin.
getProvider: function getProvider(origin, onDone) {
schedule((function () {
onDone(SocialServiceInternal.providers[origin] || null);
}).bind(this));
},
// Returns an array of installed provider origins.
getProviderList: function getProviderList(onDone) {
schedule(function () {
onDone(SocialServiceInternal.providerArray);
});
}
};
/**
* The SocialProvider object represents a social provider, and allows
* access to its FrameWorker (if it has one).
@ -29,6 +140,7 @@ function SocialProvider(input, enabled) {
this.name = input.name;
this.iconURL = input.iconURL;
this.workerURL = input.workerURL;
this.sidebarURL = input.sidebarURL;
this.origin = input.origin;
this.ambientNotificationIcons = {};
@ -153,115 +265,3 @@ SocialProvider.prototype = {
}
}
}
/**
* The SocialService is the public API to social providers - it tracks which
* providers are installed and enabled, and is the entry-point for access to
* the provider itself.
*/
// Internal helper methods and state
let SocialServiceInternal = {
enabled: Services.prefs.getBoolPref("social.enabled"),
get providerArray() {
return [p for ([, p] of Iterator(this.providers))];
}
};
XPCOMUtils.defineLazyGetter(SocialServiceInternal, "providers", function () {
// Initialize the service (add a pref observer)
function prefObserver(subject, topic, data) {
SocialService._setEnabled(Services.prefs.getBoolPref(data));
}
Services.prefs.addObserver("social.enabled", prefObserver, false);
Services.obs.addObserver(function xpcomShutdown() {
Services.obs.removeObserver(xpcomShutdown, "xpcom-shutdown");
Services.prefs.removeObserver("social.enabled", prefObserver);
}, "xpcom-shutdown", false);
// Now retrieve the providers
let providers = {};
let MANIFEST_PREFS = Services.prefs.getBranch("social.manifest.");
let prefs = MANIFEST_PREFS.getChildList("", {});
prefs.forEach(function (pref) {
try {
var manifest = JSON.parse(MANIFEST_PREFS.getCharPref(pref));
if (manifest && typeof(manifest) == "object") {
let provider = new SocialProvider(manifest, SocialServiceInternal.enabled);
providers[provider.origin] = provider;
}
} catch (err) {
Cu.reportError("SocialService: failed to load provider: " + pref +
", exception: " + err);
}
});
return providers;
});
function schedule(callback) {
Services.tm.mainThread.dispatch(callback, Ci.nsIThread.DISPATCH_NORMAL);
}
// Public API
const SocialService = {
get enabled() {
return SocialServiceInternal.enabled;
},
set enabled(val) {
let enable = !!val;
if (enable == SocialServiceInternal.enabled)
return;
Services.prefs.setBoolPref("social.enabled", enable);
this._setEnabled(enable);
},
_setEnabled: function _setEnabled(enable) {
SocialServiceInternal.providerArray.forEach(function (p) p.enabled = enable);
SocialServiceInternal.enabled = enable;
Services.obs.notifyObservers(null, "social:pref-changed", enable ? "enabled" : "disabled");
},
// Adds a provider given a manifest, and returns the added provider.
addProvider: function addProvider(manifest, onDone) {
if (SocialServiceInternal.providers[manifest.origin])
throw new Error("SocialService.addProvider: provider with this origin already exists");
let provider = new SocialProvider(manifest, SocialServiceInternal.enabled);
SocialServiceInternal.providers[provider.origin] = provider;
schedule(function () {
onDone(provider);
});
},
// Removes a provider with the given origin, and notifies when the removal is
// complete.
removeProvider: function removeProvider(origin, onDone) {
if (!(origin in SocialServiceInternal.providers))
throw new Error("SocialService.removeProvider: no provider with this origin exists!");
let provider = SocialServiceInternal.providers[origin];
provider.enabled = false;
delete SocialServiceInternal.providers[origin];
if (onDone)
schedule(onDone);
},
// Returns a single provider object with the specified origin.
getProvider: function getProvider(origin, onDone) {
schedule((function () {
onDone(SocialServiceInternal.providers[origin] || null);
}).bind(this));
},
// Returns an array of installed provider origins.
getProviderList: function getProviderList(onDone) {
schedule(function () {
onDone(SocialServiceInternal.providerArray);
});
}
};