bug 891225 implement new provider status buttons, r=markh

This commit is contained in:
Shane Caraveo 2013-09-04 10:01:38 -07:00
parent b968953397
commit a3482498fe
11 changed files with 679 additions and 57 deletions

View File

@ -37,6 +37,10 @@ SocialUI = {
Services.obs.addObserver(this, "social:provider-set", false);
Services.obs.addObserver(this, "social:providers-changed", false);
Services.obs.addObserver(this, "social:provider-reload", false);
Services.obs.addObserver(this, "social:provider-installed", false);
Services.obs.addObserver(this, "social:provider-uninstalled", false);
Services.obs.addObserver(this, "social:provider-enabled", false);
Services.obs.addObserver(this, "social:provider-disabled", false);
Services.prefs.addObserver("social.sidebar.open", this, false);
Services.prefs.addObserver("social.toast-notifications.enabled", this, false);
@ -62,6 +66,10 @@ SocialUI = {
Services.obs.removeObserver(this, "social:provider-set");
Services.obs.removeObserver(this, "social:providers-changed");
Services.obs.removeObserver(this, "social:provider-reload");
Services.obs.removeObserver(this, "social:provider-installed");
Services.obs.removeObserver(this, "social:provider-uninstalled");
Services.obs.removeObserver(this, "social:provider-enabled");
Services.obs.removeObserver(this, "social:provider-disabled");
Services.prefs.removeObserver("social.sidebar.open", this);
Services.prefs.removeObserver("social.toast-notifications.enabled", this);
@ -76,6 +84,18 @@ SocialUI = {
// manually :(
try {
switch (topic) {
case "social:provider-installed":
SocialStatus.setPosition(data);
break;
case "social:provider-uninstalled":
SocialStatus.removePosition(data);
break;
case "social:provider-enabled":
SocialStatus.populateToolbarPalette();
break;
case "social:provider-disabled":
SocialStatus.removeProvider(data);
break;
case "social:provider-reload":
// if the reloaded provider is our current provider, fall through
// to social:provider-set so the ui will be reset
@ -98,6 +118,7 @@ SocialUI = {
SocialSidebar.update();
SocialMark.update();
SocialToolbar.update();
SocialStatus.populateToolbarPalette();
SocialMenu.populate();
break;
case "social:providers-changed":
@ -106,10 +127,12 @@ SocialUI = {
// and the multi-provider menu
SocialToolbar.populateProviderMenus();
SocialShare.populateProviderMenu();
SocialStatus.populateToolbarPalette();
break;
// Provider-specific notifications
case "social:ambient-notification-changed":
SocialStatus.updateNotification(data);
if (this._matchesCurrentProvider(data)) {
SocialToolbar.updateButton();
SocialMenu.populate();
@ -1056,6 +1079,15 @@ SocialToolbar = {
Services.prefs.clearUserPref(CACHE_PREF_NAME);
return;
}
// If the provider uses the new SocialStatus button, then they do not get
// to use the ambient icons in the old toolbar button. Since the status
// button depends on multiple workers, if not enabled we will ignore this
// limitation. That allows a provider to migrate to the new functionality
// once we enable multiple workers.
if (Social.provider.statusURL && Social.allowMultipleWorkers)
return;
let icons = Social.provider.ambientNotificationIcons;
let iconNames = Object.keys(icons);
@ -1154,9 +1186,8 @@ SocialToolbar = {
socialToolbarItem.insertBefore(toolbarButtons, SocialMark.button);
for (let frame of createdFrames) {
if (frame.socialErrorListener) {
if (frame.socialErrorListener)
frame.socialErrorListener.remove();
}
if (frame.docShell) {
frame.docShell.isActive = false;
Social.setErrorListener(frame, this.setPanelErrorMessage.bind(this));
@ -1201,11 +1232,10 @@ SocialToolbar = {
panel.addEventListener("popupshown", function onpopupshown() {
panel.removeEventListener("popupshown", onpopupshown);
// This attribute is needed on both the button and the
// containing toolbaritem since the buttons on OS X have
// moz-appearance:none, while their container gets
// moz-appearance:toolbarbutton due to the way that toolbar buttons
// get combined on OS X.
// The "open" attribute is needed on both the button and the containing
// toolbaritem since the buttons on OS X have moz-appearance:none, while
// their container gets moz-appearance:toolbarbutton due to the way that
// toolbar buttons get combined on OS X.
aToolbarButton.setAttribute("open", "true");
aToolbarButton.parentNode.setAttribute("open", "true");
notificationFrame.docShell.isActive = true;
@ -1392,4 +1422,353 @@ SocialSidebar = {
}
}
// this helper class is used by removable/customizable buttons to handle
// location persistence and insertion into palette and/or toolbars
// When a provider is installed we show all their UI so the user will see the
// functionality of what they installed. The user can later customize the UI,
// moving buttons around or off the toolbar.
//
// To make this happen, on install we add a button id to the navbar currentset.
// On enabling the provider (happens just after install) we insert the button
// into the toolbar as well. The button is then persisted on restart (assuming
// it was not removed).
//
// When a provider is disabled, we do not remove the buttons from currentset.
// That way, if the provider is re-enabled during the same session, the buttons
// will reappear where they were before. When a provider is uninstalled, we make
// sure that the id is removed from currentset.
//
// On startup, we insert the buttons of any enabled provider into either the
// apropriate toolbar or the palette.
function ToolbarHelper(type, createButtonFn) {
this._createButton = createButtonFn;
this._type = type;
}
ToolbarHelper.prototype = {
idFromOrgin: function(origin) {
return this._type + "-" + origin;
},
// find a button either in the document or the palette
_getExistingButton: function(id) {
let button = document.getElementById(id);
if (button)
return button;
let palette = document.getElementById("navigator-toolbox").palette;
let paletteItem = palette.firstChild;
while (paletteItem) {
if (paletteItem.id == id)
return paletteItem;
paletteItem = paletteItem.nextSibling;
}
return null;
},
setPersistentPosition: function(id) {
// called when a provider is installed. add provider buttons to nav-bar
let toolbar = document.getElementById("nav-bar");
// first startups will not have a currentset attribute, always rely on
// currentSet since it will be derived from the defaultset in that case.
let currentset = toolbar.currentSet;
if (currentset == "__empty")
currentset = []
else
currentset = currentset.split(",");
if (currentset.indexOf(id) >= 0)
return;
// we do not set toolbar.currentSet since that will try to add the button,
// and we have not added it yet (happens on provider being enabled)
currentset.push(id);
toolbar.setAttribute("currentset", currentset.join(","));
document.persist(toolbar.id, "currentset");
},
removeProviderButton: function(origin) {
// this will remove the button from the palette or the toolbar
let button = this._getExistingButton(this.idFromOrgin(origin));
if (button)
button.parentNode.removeChild(button);
},
removePersistence: function(id) {
let persisted = document.querySelectorAll("*[currentset]");
for (let pent of persisted) {
// the button will have been removed, but left in the currentset attribute
// in case the user re-enables (e.g. undo in addon manager). So we only
// check the attribute here.
let currentset = pent.getAttribute("currentset").split(",");
let pos = currentset.indexOf(id);
if (pos >= 0) {
currentset.splice(pos, 1);
pent.setAttribute("currentset", currentset.join(","));
document.persist(pent.id, "currentset");
return;
}
}
},
// if social is entirely disabled, we need to clear the palette, but leave
// the persisted id's in place
clearPalette: function() {
[this.removeProviderButton(p.origin) for (p of Social.providers)];
},
// should be called on startup of each window, otherwise the addon manager
// listener will handle new activations, or enable/disabling of a provider
// XXX we currently call more regularly, will fix during refactoring
populatePalette: function() {
if (!Social.enabled) {
this.clearPalette();
return;
}
let persisted = document.querySelectorAll("*[currentset]");
let persistedById = {};
for (let pent of persisted) {
let pset = pent.getAttribute("currentset").split(',');
for (let id of pset)
persistedById[id] = pent;
}
// create any buttons that do not exist yet if they have been persisted
// as a part of the UI (otherwise they belong in the palette).
for (let provider of Social.providers) {
let id = this.idFromOrgin(provider.origin);
if (this._getExistingButton(id))
return;
let button = this._createButton(provider);
if (button && persistedById.hasOwnProperty(id)) {
let parent = persistedById[id];
let pset = persistedById[id].getAttribute("currentset").split(',');
let pi = pset.indexOf(id) + 1;
let next = document.getElementById(pset[pi]);
parent.insertItem(id, next, null, false);
}
}
}
}
SocialStatus = {
populateToolbarPalette: function() {
if (!Social.allowMultipleWorkers)
return;
this._toolbarHelper.populatePalette();
},
setPosition: function(origin) {
if (!Social.allowMultipleWorkers)
return;
// this is called during install, before the provider is enabled so we have
// to use the manifest rather than the provider instance as we do elsewhere.
let manifest = Social.getManifestByOrigin(origin);
if (!manifest.statusURL)
return;
let tbh = this._toolbarHelper;
tbh.setPersistentPosition(tbh.idFromOrgin(origin));
},
removePosition: function(origin) {
if (!Social.allowMultipleWorkers)
return;
let tbh = this._toolbarHelper;
tbh.removePersistence(tbh.idFromOrgin(origin));
},
removeProvider: function(origin) {
if (!Social.allowMultipleWorkers)
return;
this._toolbarHelper.removeProviderButton(origin);
},
get _toolbarHelper() {
delete this._toolbarHelper;
this._toolbarHelper = new ToolbarHelper("social-status-button", this._createButton.bind(this));
return this._toolbarHelper;
},
get _dynamicResizer() {
delete this._dynamicResizer;
this._dynamicResizer = new DynamicResizeWatcher();
return this._dynamicResizer;
},
_createButton: function(provider) {
if (!provider.statusURL)
return null;
let palette = document.getElementById("navigator-toolbox").palette;
let button = document.createElement("toolbarbutton");
button.setAttribute("class", "toolbarbutton-1 social-status-button");
button.setAttribute("type", "badged");
button.setAttribute("removable", "true");
button.setAttribute("image", provider.iconURL);
button.setAttribute("label", provider.name);
button.setAttribute("tooltiptext", provider.name);
button.setAttribute("origin", provider.origin);
button.setAttribute("oncommand", "SocialStatus.showPopup(this);");
button.setAttribute("id", this._toolbarHelper.idFromOrgin(provider.origin));
palette.appendChild(button);
return button;
},
// status panels are one-per button per-process, we swap the docshells between
// windows when necessary
_attachNotificatonPanel: function(aButton, provider) {
let panel = document.getElementById("social-notification-panel");
panel.hidden = !SocialUI.enabled;
let notificationFrameId = "social-status-" + provider.origin;
let frame = document.getElementById(notificationFrameId);
if (!frame) {
frame = SharedFrame.createFrame(
notificationFrameId, /* frame name */
panel, /* parent */
{
"type": "content",
"mozbrowser": "true",
"class": "social-panel-frame",
"id": notificationFrameId,
"tooltip": "aHTMLTooltip",
// work around bug 793057 - by making the panel roughly the final size
// we are more likely to have the anchor in the correct position.
"style": "width: " + PANEL_MIN_WIDTH + "px;",
"origin": provider.origin,
"src": provider.statusURL
}
);
if (frame.socialErrorListener)
frame.socialErrorListener.remove();
if (frame.docShell) {
frame.docShell.isActive = false;
Social.setErrorListener(frame, this.setPanelErrorMessage.bind(this));
}
} else {
frame.setAttribute("origin", provider.origin);
SharedFrame.updateURL(notificationFrameId, provider.statusURL);
}
aButton.setAttribute("notificationFrameId", notificationFrameId);
},
updateNotification: function(origin) {
if (!Social.allowMultipleWorkers)
return;
let provider = Social._getProviderFromOrigin(origin);
let button = document.getElementById(this._toolbarHelper.idFromOrgin(provider.origin));
if (button) {
// we only grab the first notification, ignore all others
let icons = provider.ambientNotificationIcons;
let iconNames = Object.keys(icons);
let notif = icons[iconNames[0]];
if (!notif) {
button.setAttribute("badge", "");
button.setAttribute("aria-label", "");
button.setAttribute("tooltiptext", "");
return;
}
button.style.listStyleImage = "url(" + notif.iconURL || provider.iconURL + ")";
button.setAttribute("tooltiptext", notif.label);
let badge = notif.counter || "";
button.setAttribute("badge", badge);
let ariaLabel = notif.label;
// if there is a badge value, we must use a localizable string to insert it.
if (badge)
ariaLabel = gNavigatorBundle.getFormattedString("social.aria.toolbarButtonBadgeText",
[ariaLabel, badge]);
button.setAttribute("aria-label", ariaLabel);
}
},
showPopup: function(aToolbarButton) {
if (!Social.allowMultipleWorkers)
return;
// attach our notification panel if necessary
let origin = aToolbarButton.getAttribute("origin");
let provider = Social._getProviderFromOrigin(origin);
this._attachNotificatonPanel(aToolbarButton, provider);
let panel = document.getElementById("social-notification-panel");
let notificationFrameId = aToolbarButton.getAttribute("notificationFrameId");
let notificationFrame = document.getElementById(notificationFrameId);
let wasAlive = SharedFrame.isGroupAlive(notificationFrameId);
SharedFrame.setOwner(notificationFrameId, notificationFrame);
// Clear dimensions on all browsers so the panel size will
// only use the selected browser.
let frameIter = panel.firstElementChild;
while (frameIter) {
frameIter.collapsed = (frameIter != notificationFrame);
frameIter = frameIter.nextElementSibling;
}
function dispatchPanelEvent(name) {
let evt = notificationFrame.contentDocument.createEvent("CustomEvent");
evt.initCustomEvent(name, true, true, {});
notificationFrame.contentDocument.documentElement.dispatchEvent(evt);
}
let dynamicResizer = this._dynamicResizer;
panel.addEventListener("popuphidden", function onpopuphiding() {
panel.removeEventListener("popuphidden", onpopuphiding);
aToolbarButton.removeAttribute("open");
dynamicResizer.stop();
notificationFrame.docShell.isActive = false;
dispatchPanelEvent("socialFrameHide");
});
panel.addEventListener("popupshown", function onpopupshown() {
panel.removeEventListener("popupshown", onpopupshown);
// This attribute is needed on both the button and the
// containing toolbaritem since the buttons on OS X have
// moz-appearance:none, while their container gets
// moz-appearance:toolbarbutton due to the way that toolbar buttons
// get combined on OS X.
aToolbarButton.setAttribute("open", "true");
notificationFrame.docShell.isActive = true;
notificationFrame.docShell.isAppTab = true;
if (notificationFrame.contentDocument.readyState == "complete" && wasAlive) {
dynamicResizer.start(panel, notificationFrame);
dispatchPanelEvent("socialFrameShow");
} else {
// first time load, wait for load and dispatch after load
notificationFrame.addEventListener("load", function panelBrowserOnload(e) {
notificationFrame.removeEventListener("load", panelBrowserOnload, true);
dynamicResizer.start(panel, notificationFrame);
dispatchPanelEvent("socialFrameShow");
}, true);
}
});
let navBar = document.getElementById("nav-bar");
let anchor = navBar.getAttribute("mode") == "text" ?
document.getAnonymousElementByAttribute(aToolbarButton, "class", "toolbarbutton-text") :
document.getAnonymousElementByAttribute(aToolbarButton, "class", "toolbarbutton-badge-container");
// Bug 849216 - open the popup in a setTimeout so we avoid the auto-rollup
// handling from preventing it being opened in some cases.
setTimeout(function() {
panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, false);
}, 0);
},
setPanelErrorMessage: function(aNotificationFrame) {
if (!aNotificationFrame)
return;
let src = aNotificationFrame.getAttribute("src");
aNotificationFrame.removeAttribute("src");
aNotificationFrame.webNavigation.loadURI("about:socialerror?mode=tryAgainOnly&url=" +
encodeURIComponent(src),
null, null, null, null);
let panel = aNotificationFrame.parentNode;
sizeSocialPanelToContent(panel, aNotificationFrame);
},
};
})();

View File

@ -31,6 +31,7 @@ MOCHITEST_BROWSER_FILES = \
browser_social_multiprovider.js \
browser_social_multiworker.js \
browser_social_errorPage.js \
browser_social_status.js \
browser_social_window.js \
social_activate.html \
social_activate_iframe.html \

View File

@ -50,9 +50,10 @@ function installListener(next, aManifest) {
let expectEvent = "onInstalling";
let prefname = getManifestPrefname(aManifest);
// wait for the actual removal to call next
SocialService.registerProviderListener(function providerListener(topic, data) {
if (topic == "provider-removed") {
SocialService.registerProviderListener(function providerListener(topic, origin, providers) {
if (topic == "provider-disabled") {
SocialService.unregisterProviderListener(providerListener);
is(origin, aManifest.origin, "provider disabled");
executeSoon(next);
}
});
@ -295,14 +296,15 @@ var tests = {
Social.enabled = true;
// watch for the provider-update and test the new version
SocialService.registerProviderListener(function providerListener(topic, data) {
SocialService.registerProviderListener(function providerListener(topic, origin, providers) {
if (topic != "provider-update")
return;
is(origin, addonManifest.origin, "provider updated")
SocialService.unregisterProviderListener(providerListener);
Services.prefs.clearUserPref("social.whitelist");
let provider = Social._getProviderFromOrigin(addonManifest.origin);
let provider = Social._getProviderFromOrigin(origin);
is(provider.manifest.version, 2, "manifest version is 2");
Social.uninstallProvider(addonManifest.origin, function() {
Social.uninstallProvider(origin, function() {
gBrowser.removeTab(tab);
next();
});

View File

@ -155,14 +155,15 @@ var tests = {
setManifestPref("social.manifest.blocked", manifest_bad);
try {
SocialService.addProvider(manifest_bad, function(provider) {
// the act of blocking should cause a 'provider-removed' notification
// the act of blocking should cause a 'provider-disabled' notification
// from SocialService.
SocialService.registerProviderListener(function providerListener(topic) {
if (topic != "provider-removed")
SocialService.registerProviderListener(function providerListener(topic, origin, providers) {
if (topic != "provider-disabled")
return;
SocialService.unregisterProviderListener(providerListener);
is(origin, provider.origin, "provider disabled");
SocialService.getProvider(provider.origin, function(p) {
ok(p==null, "blocklisted provider removed");
ok(p == null, "blocklisted provider disabled");
Services.prefs.clearUserPref("social.manifest.blocked");
resetBlocklist(finish);
});

View File

@ -19,6 +19,13 @@ function test() {
var tests = {
testStatusIcons: function(next) {
let icon = {
name: "testIcon",
iconURL: "chrome://browser/skin/Info.png",
contentPanel: "https://example.com/browser/browser/base/content/test/social/social_panel.html",
counter: 1
};
let iconsReady = false;
let gotSidebarMessage = false;
@ -71,7 +78,7 @@ var tests = {
ok(true, "got sidebar message");
gotSidebarMessage = true;
// load a status panel
port.postMessage({topic: "test-ambient-notification"});
port.postMessage({topic: "test-ambient-notification", data: icon});
checkNext();
break;
}

View File

@ -0,0 +1,220 @@
/* 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;
let manifest = { // builtin provider
name: "provider example.com",
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"
};
let manifest2 = { // used for testing install
name: "provider test1",
origin: "https://test1.example.com",
workerURL: "https://test1.example.com/browser/browser/base/content/test/social/social_worker.js",
statusURL: "https://test1.example.com/browser/browser/base/content/test/social/social_panel.html",
iconURL: "https://test1.example.com/browser/browser/base/content/test/moz.png",
version: 1
};
let manifest3 = { // used for testing install
name: "provider test2",
origin: "https://test2.example.com",
sidebarURL: "https://test2.example.com/browser/browser/base/content/test/social/social_sidebar.html",
iconURL: "https://test2.example.com/browser/browser/base/content/test/moz.png",
version: 1
};
function openWindowAndWaitForInit(callback) {
let topic = "browser-delayed-startup-finished";
let w = OpenBrowserWindow();
Services.obs.addObserver(function providerSet(subject, topic, data) {
Services.obs.removeObserver(providerSet, topic);
executeSoon(() => callback(w));
}, topic, false);
}
function test() {
waitForExplicitFinish();
Services.prefs.setBoolPref("social.allowMultipleWorkers", true);
let toolbar = document.getElementById("nav-bar");
let currentsetAtStart = toolbar.currentSet;
info("tb0 "+currentsetAtStart);
runSocialTestWithProvider(manifest, function () {
runSocialTests(tests, undefined, undefined, function () {
Services.prefs.clearUserPref("social.remote-install.enabled");
// just in case the tests failed, clear these here as well
Services.prefs.clearUserPref("social.allowMultipleWorkers");
Services.prefs.clearUserPref("social.whitelist");
// This post-test test ensures that a new window maintains the same
// toolbar button set as when we started. That means our insert/removal of
// persistent id's is working correctly
is(currentsetAtStart, toolbar.currentSet, "toolbar currentset unchanged");
openWindowAndWaitForInit(function(w1) {
checkSocialUI(w1);
// Sometimes the new window adds other buttons to currentSet that are
// outside the scope of what we're checking. So we verify that all
// buttons from startup are in currentSet for a new window, and that the
// provider buttons are properly removed. (e.g on try, window-controls
// was not present in currentsetAtStart, but present on the second
// window)
let tb1 = w1.document.getElementById("nav-bar");
info("tb0 "+toolbar.currentSet);
info("tb1 "+tb1.currentSet);
let startupSet = Set(toolbar.currentSet.split(','));
let newSet = Set(tb1.currentSet.split(','));
let intersect = Set([x for (x of startupSet) if (newSet.has(x))]);
info("intersect "+intersect);
let difference = Set([x for (x of newSet) if (!startupSet.has(x))]);
info("difference "+difference);
is(startupSet.size, intersect.size, "new window toolbar same as old");
// verify that our provider buttons are not in difference
let id = SocialStatus._toolbarHelper.idFromOrgin(manifest2.origin);
ok(!difference.has(id), "status button not persisted at end");
w1.close();
finish();
});
});
});
}
var tests = {
testNoButtonOnInstall: function(next) {
// we expect the addon install dialog to appear, we need to accept the
// install from the dialog.
info("Waiting for install dialog");
let panel = document.getElementById("servicesInstall-notification");
PopupNotifications.panel.addEventListener("popupshown", function onpopupshown() {
PopupNotifications.panel.removeEventListener("popupshown", onpopupshown);
info("servicesInstall-notification panel opened");
panel.button.click();
})
let id = "social-status-button-" + manifest3.origin;
let toolbar = document.getElementById("nav-bar");
let currentset = toolbar.getAttribute("currentset").split(',');
ok(currentset.indexOf(id) < 0, "button is not part of currentset at start");
let activationURL = manifest3.origin + "/browser/browser/base/content/test/social/social_activate.html"
addTab(activationURL, function(tab) {
let doc = tab.linkedBrowser.contentDocument;
Social.installProvider(doc, manifest3, function(addonManifest) {
// enable the provider so we know the button would have appeared
SocialService.addBuiltinProvider(manifest3.origin, function(provider) {
ok(provider, "provider is installed");
currentset = toolbar.getAttribute("currentset").split(',');
ok(currentset.indexOf(id) < 0, "button was not added to currentset");
Social.uninstallProvider(manifest3.origin, function() {
gBrowser.removeTab(tab);
next();
});
});
});
});
},
testButtonOnInstall: function(next) {
// we expect the addon install dialog to appear, we need to accept the
// install from the dialog.
info("Waiting for install dialog");
let panel = document.getElementById("servicesInstall-notification");
PopupNotifications.panel.addEventListener("popupshown", function onpopupshown() {
PopupNotifications.panel.removeEventListener("popupshown", onpopupshown);
info("servicesInstall-notification panel opened");
panel.button.click();
})
let activationURL = manifest2.origin + "/browser/browser/base/content/test/social/social_activate.html"
addTab(activationURL, function(tab) {
let doc = tab.linkedBrowser.contentDocument;
Social.installProvider(doc, manifest2, function(addonManifest) {
// at this point, we should have a button id in the currentset for our provider
let id = "social-status-button-" + manifest2.origin;
let toolbar = document.getElementById("nav-bar");
waitForCondition(function() {
let currentset = toolbar.getAttribute("currentset").split(',');
return currentset.indexOf(id) >= 0;
},
function() {
// no longer need the tab
gBrowser.removeTab(tab);
next();
}, "status button added to currentset");
});
});
},
testButtonOnEnable: function(next) {
// enable the provider now
SocialService.addBuiltinProvider(manifest2.origin, function(provider) {
ok(provider, "provider is installed");
let id = "social-status-button-" + manifest2.origin;
waitForCondition(function() { return document.getElementById(id) },
next, "button exists after enabling social");
});
},
testStatusPanel: function(next) {
let icon = {
name: "testIcon",
iconURL: "chrome://browser/skin/Info.png",
counter: 1
};
// click on panel to open and wait for visibility
let provider = Social._getProviderFromOrigin(manifest2.origin);
let id = "social-status-button-" + provider.origin;
let btn = document.getElementById(id)
ok(btn, "got a status button");
let port = provider.getWorkerPort();
port.onmessage = function (e) {
let topic = e.data.topic;
switch (topic) {
case "test-init-done":
ok(true, "test-init-done received");
ok(provider.profile.userName, "profile was set by test worker");
btn.click();
break;
case "got-social-panel-visibility":
ok(true, "got the panel message " + e.data.result);
if (e.data.result == "shown") {
let panel = document.getElementById("social-notification-panel");
panel.hidePopup();
} else {
port.postMessage({topic: "test-ambient-notification", data: icon});
port.close();
waitForCondition(function() { return btn.getAttribute("badge"); },
function() {
is(btn.style.listStyleImage, "url(\"" + icon.iconURL + "\")", "notification icon updated");
next();
}, "button updated by notification");
}
break;
}
};
port.postMessage({topic: "test-init"});
},
testButtonOnDisable: function(next) {
// enable the provider now
let provider = Social._getProviderFromOrigin(manifest2.origin);
ok(provider, "provider is installed");
SocialService.removeProvider(manifest2.origin, function() {
let id = "social-status-button-" + manifest2.origin;
waitForCondition(function() { return !document.getElementById(id) },
next, "button does not exist after disabling the provider");
});
},
testButtonOnUninstall: function(next) {
Social.uninstallProvider(manifest2.origin, function() {
// test that the button is no longer persisted
let id = "social-status-button-" + manifest2.origin;
let toolbar = document.getElementById("nav-bar");
let currentset = toolbar.getAttribute("currentset").split(',');
is(currentset.indexOf(id), -1, "button no longer in currentset");
next();
});
}
}

View File

@ -177,7 +177,7 @@ function runSocialTests(tests, cbPreTest, cbPostTest, cbFinish) {
cbPostTest(runNextTest);
}
cbPreTest(function() {
is(providersAtStart, Social.providers.length, "pre-test: no new providers left enabled");
info("pre-test: starting with " + Social.providers.length + " providers");
info("sub-test " + name + " starting");
try {
func.call(tests, cleanupAndRunNextTest);

View File

@ -14,6 +14,7 @@ var data = {
// at least one of these must be defined
"sidebarURL": "/browser/browser/base/content/test/social/social_sidebar.html",
"workerURL": "/browser/browser/base/content/test/social/social_worker.js",
"statusURL": "/browser/browser/base/content/test/social/social_panel.html",
// should be available for display purposes
"description": "A short paragraph about this provider",

View File

@ -119,13 +119,7 @@ onconnect = function(e) {
});
break;
case "test-ambient-notification":
let icon = {
name: "testIcon",
iconURL: "chrome://browser/skin/Info.png",
contentPanel: "https://example.com/browser/browser/base/content/test/social/social_panel.html",
counter: 1
};
apiPort.postMessage({topic: "social.ambient-notification", data: icon});
apiPort.postMessage({topic: "social.ambient-notification", data: event.data.data});
break;
case "test-isVisible":
sidebarPort.postMessage({topic: "test-isVisible"});

View File

@ -169,24 +169,28 @@ this.Social = {
}
// Register an observer for changes to the provider list
SocialService.registerProviderListener(function providerListener(topic, data) {
SocialService.registerProviderListener(function providerListener(topic, origin, providers) {
// An engine change caused by adding/removing a provider should notify.
// any providers we receive are enabled in the AddonsManager
if (topic == "provider-added" || topic == "provider-removed") {
Social._updateProviderCache(data);
if (topic == "provider-installed" || topic == "provider-uninstalled") {
// installed/uninstalled do not send the providers param
Services.obs.notifyObservers(null, "social:" + topic, origin);
return;
}
if (topic == "provider-enabled" || topic == "provider-disabled") {
Social._updateProviderCache(providers);
Social._updateWorkerState(true);
Services.obs.notifyObservers(null, "social:providers-changed", null);
Services.obs.notifyObservers(null, "social:" + topic, origin);
return;
}
if (topic == "provider-update") {
// a provider has self-updated its manifest, we need to update our cache
// and reload the provider.
let provider = data;
SocialService.getOrderedProviderList(function(providers) {
Social._updateProviderCache(providers);
provider.reload();
Services.obs.notifyObservers(null, "social:providers-changed", null);
});
Social._updateProviderCache(providers);
let provider = Social._getProviderFromOrigin(origin);
provider.reload();
Services.obs.notifyObservers(null, "social:providers-changed", null);
}
});
},
@ -259,6 +263,10 @@ this.Social = {
return null;
},
getManifestByOrigin: function(origin) {
return SocialService.getManifestByOrigin(origin);
},
installProvider: function(doc, data, installCallback) {
SocialService.installProvider(doc, data, installCallback);
},

View File

@ -62,14 +62,6 @@ let SocialServiceInternal = {
}
}
},
getManifestByOrigin: function(origin) {
for (let manifest of SocialServiceInternal.manifests) {
if (origin == manifest.origin) {
return manifest;
}
}
return null;
},
getManifestPrefname: function(origin) {
// Retrieve the prefname for a given origin/manifest.
// If no existing pref, return a generated prefname.
@ -391,7 +383,7 @@ this.SocialService = {
});
return;
}
let manifest = SocialServiceInternal.getManifestByOrigin(origin);
let manifest = SocialService.getManifestByOrigin(origin);
if (manifest) {
let addon = new AddonWrapper(manifest);
AddonManagerPrivate.callAddonListeners("onEnabling", addon, false);
@ -416,7 +408,7 @@ this.SocialService = {
ActiveProviders.add(provider.origin);
this.getOrderedProviderList(function (providers) {
this._notifyProviderListeners("provider-added", providers);
this._notifyProviderListeners("provider-enabled", provider.origin, providers);
if (onDone)
onDone(provider);
}.bind(this));
@ -429,7 +421,7 @@ this.SocialService = {
throw new Error("SocialService.removeProvider: no provider with origin " + origin + " exists!");
let provider = SocialServiceInternal.providers[origin];
let manifest = SocialServiceInternal.getManifestByOrigin(origin);
let manifest = SocialService.getManifestByOrigin(origin);
let addon = manifest && new AddonWrapper(manifest);
if (addon) {
AddonManagerPrivate.callAddonListeners("onDisabling", addon, false);
@ -450,7 +442,7 @@ this.SocialService = {
}
this.getOrderedProviderList(function (providers) {
this._notifyProviderListeners("provider-removed", providers);
this._notifyProviderListeners("provider-disabled", origin, providers);
if (onDone)
onDone();
}.bind(this));
@ -471,6 +463,15 @@ this.SocialService = {
});
},
getManifestByOrigin: function(origin) {
for (let manifest of SocialServiceInternal.manifests) {
if (origin == manifest.origin) {
return manifest;
}
}
return null;
},
// Returns an array of installed providers, sorted by frecency
getOrderedProviderList: function(onDone) {
SocialServiceInternal.orderedProviders(onDone);
@ -488,10 +489,10 @@ this.SocialService = {
this._providerListeners.delete(listener);
},
_notifyProviderListeners: function (topic, data) {
_notifyProviderListeners: function (topic, origin, providers) {
for (let [listener, ] of this._providerListeners) {
try {
listener(topic, data);
listener(topic, origin, providers);
} catch (ex) {
Components.utils.reportError("SocialService: provider listener threw an exception: " + ex);
}
@ -499,7 +500,7 @@ this.SocialService = {
},
_manifestFromData: function(type, data, principal) {
let sameOriginRequired = ['workerURL', 'sidebarURL', 'shareURL'];
let sameOriginRequired = ['workerURL', 'sidebarURL', 'shareURL', 'statusURL'];
if (type == 'directory') {
// directory provided manifests must have origin in manifest, use that
@ -517,8 +518,9 @@ 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'] && !data['shareURL']) {
Cu.reportError("SocialService.manifestFromData manifest missing required workerURL or sidebarURL.");
let providerHasFeatures = [url for (url of sameOriginRequired) if (data[url])].length > 0;
if (!providerHasFeatures) {
Cu.reportError("SocialService.manifestFromData manifest missing required urls.");
return null;
}
if (!data['name'] || !data['iconURL']) {
@ -596,7 +598,10 @@ this.SocialService = {
aAddon.userDisabled = false;
}
schedule(function () {
this._installProvider(aDOMDocument, data, installCallback);
this._installProvider(aDOMDocument, data, aManifest => {
this._notifyProviderListeners("provider-installed", aManifest.origin);
installCallback(aManifest);
});
}.bind(this));
}.bind(this));
},
@ -661,7 +666,7 @@ this.SocialService = {
* have knowledge of the currently selected provider here, we will notify
* the front end to deal with any reload.
*/
updateProvider: function(aUpdateOrigin, aManifest, aCallback) {
updateProvider: function(aUpdateOrigin, aManifest) {
let originUri = Services.io.newURI(aUpdateOrigin, null, null);
let principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(originUri);
let installType = this.getOriginActivationType(aUpdateOrigin);
@ -682,13 +687,15 @@ this.SocialService = {
let provider = new SocialProvider(manifest);
SocialServiceInternal.providers[provider.origin] = provider;
// update the cache and ui, reload provider if necessary
this._notifyProviderListeners("provider-update", provider);
this.getOrderedProviderList(providers => {
this._notifyProviderListeners("provider-update", provider.origin, providers);
});
}
},
uninstallProvider: function(origin, aCallback) {
let manifest = SocialServiceInternal.getManifestByOrigin(origin);
let manifest = SocialService.getManifestByOrigin(origin);
let addon = new AddonWrapper(manifest);
addon.uninstall(aCallback);
}
@ -720,6 +727,7 @@ function SocialProvider(input) {
this.workerURL = input.workerURL;
this.sidebarURL = input.sidebarURL;
this.shareURL = input.shareURL;
this.statusURL = input.statusURL;
this.origin = input.origin;
let originUri = Services.io.newURI(input.origin, null, null);
this.principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(originUri);
@ -766,7 +774,7 @@ SocialProvider.prototype = {
},
get manifest() {
return SocialServiceInternal.getManifestByOrigin(this.origin);
return SocialService.getManifestByOrigin(this.origin);
},
// Reference to a workerAPI object for this provider. Null if the provider has
@ -1012,7 +1020,7 @@ function getPrefnameFromOrigin(origin) {
function AddonInstaller(sourceURI, aManifest, installCallback) {
aManifest.updateDate = Date.now();
// get the existing manifest for installDate
let manifest = SocialServiceInternal.getManifestByOrigin(aManifest.origin);
let manifest = SocialService.getManifestByOrigin(aManifest.origin);
let isNewInstall = !manifest;
if (manifest && manifest.installDate)
aManifest.installDate = manifest.installDate;
@ -1088,6 +1096,7 @@ var SocialAddonProvider = {
Services.prefs.clearUserPref(getPrefnameFromOrigin(aAddon.manifest.origin));
aAddon.pendingOperations -= AddonManager.PENDING_UNINSTALL;
AddonManagerPrivate.callAddonListeners("onUninstalled", aAddon);
SocialService._notifyProviderListeners("provider-uninstalled", aAddon.manifest.origin);
if (aCallback)
schedule(aCallback);
}