Bug 770695: land support for Social "service window" - chromeless window opened by social provider, r=gavin

--HG--
extra : rebase_source : a3994283b352378358ad3c0a8de1fa7c3c984a2d
This commit is contained in:
Shane Caraveo 2012-07-24 14:29:46 -07:00
parent 7c29aed7d0
commit 447632fc11
8 changed files with 275 additions and 107 deletions

View File

@ -261,6 +261,7 @@ _BROWSER_FILES = \
browser_social_mozSocial_API.js \
social_panel.html \
social_sidebar.html \
social_window.html \
social_worker.js \
$(NULL)

View File

@ -10,7 +10,6 @@ function test() {
ok(true, "can't run social sidebar test in debug builds because they falsely report leaks");
return;
}
waitForExplicitFinish();
let manifest = { // normal provider
@ -20,68 +19,116 @@ function test() {
workerURL: "http://example.com/browser/browser/base/content/test/social_worker.js",
iconURL: "chrome://branding/content/icon48.png"
};
runSocialTestWithProvider(manifest, doTest);
runSocialTestWithProvider(manifest, function () {
runSocialTests(tests, undefined, undefined, function () {
SocialService.removeProvider(Social.provider.origin, finish);
});
});
}
function doTest() {
let iconsReady = false;
let gotSidebarMessage = false;
var tests = {
testStatusIcons: function(next) {
let iconsReady = false;
let gotSidebarMessage = false;
function checkNext() {
if (iconsReady && gotSidebarMessage)
triggerIconPanel();
}
function checkNext() {
if (iconsReady && gotSidebarMessage)
triggerIconPanel();
}
function triggerIconPanel() {
let statusIcons = document.getElementById("social-status-iconbox");
ok(!statusIcons.firstChild.collapsed, "status icon is visible");
// Click the button to trigger its contentPanel
let panel = document.getElementById("social-notification-panel");
EventUtils.synthesizeMouseAtCenter(statusIcons.firstChild, {});
}
function triggerIconPanel() {
let statusIcons = document.getElementById("social-status-iconbox");
ok(!statusIcons.firstChild.collapsed, "status icon is visible");
// Click the button to trigger its contentPanel
let panel = document.getElementById("social-notification-panel");
EventUtils.synthesizeMouseAtCenter(statusIcons.firstChild, {});
}
let port = Social.provider.port;
ok(port, "provider has a port");
port.postMessage({topic: "test-init"});
Social.provider.port.onmessage = function (e) {
let topic = e.data.topic;
switch (topic) {
case "got-panel-message":
ok(true, "got panel message");
// Wait for the panel to close before ending the test
let panel = document.getElementById("social-notification-panel");
panel.addEventListener("popuphidden", function hiddenListener() {
panel.removeEventListener("popuphidden", hiddenListener);
SocialService.removeProvider(Social.provider.origin, finish);
let port = Social.provider.port;
ok(port, "provider has a port");
port.postMessage({topic: "test-init"});
Social.provider.port.onmessage = function (e) {
let topic = e.data.topic;
switch (topic) {
case "got-panel-message":
ok(true, "got panel message");
// Wait for the panel to close before ending the test
let panel = document.getElementById("social-notification-panel");
panel.addEventListener("popuphidden", function hiddenListener() {
panel.removeEventListener("popuphidden", hiddenListener);
next();
});
panel.hidePopup();
break;
case "got-sidebar-message":
// The sidebar message will always come first, since it loads by default
ok(true, "got sidebar message");
gotSidebarMessage = true;
checkNext();
break;
}
}
// Our worker sets up ambient notification at the same time as it responds to
// the workerAPI initialization. If it's already initialized, we can
// immediately check the icons, otherwise wait for initialization by
// observing the topic sent out by the social service.
if (Social.provider.workerAPI.initialized) {
iconsReady = true;
checkNext();
} else {
Services.obs.addObserver(function obs() {
Services.obs.removeObserver(obs, "social:ambient-notification-changed");
// Let the other observers (like the one that updates the UI) run before
// checking the icons.
executeSoon(function () {
iconsReady = true;
checkNext();
});
panel.hidePopup();
break;
case "got-sidebar-message":
// The sidebar message will always come first, since it loads by default
ok(true, "got sidebar message");
info(topic);
gotSidebarMessage = true;
checkNext();
break;
}, "social:ambient-notification-changed", false);
}
},
testServiceWindow: function(next) {
// our test provider was initialized in the test above, we just
// initiate our specific test now.
let port = Social.provider.port;
ok(port, "provider has a port");
port.postMessage({topic: "test-service-window"});
port.onmessage = function (e) {
let topic = e.data.topic;
switch (topic) {
case "got-service-window-message":
// The sidebar message will always come first, since it loads by default
ok(true, "got service window message");
port.postMessage({topic: "test-close-service-window"});
break;
case "got-service-window-closed-message":
ok(true, "got service window closed message");
next();
break;
}
}
},
testServiceWindowTwice: function(next) {
let port = Social.provider.port;
port.postMessage({topic: "test-service-window-twice"});
Social.provider.port.onmessage = function (e) {
let topic = e.data.topic;
switch (topic) {
case "test-service-window-twice-result":
is(e.data.result, "ok", "only one window should open when name is reused");
break;
case "got-service-window-message":
ok(true, "got service window message");
port.postMessage({topic: "test-close-service-window"});
break;
case "got-service-window-closed-message":
ok(true, "got service window closed message");
next();
break;
}
}
}
// Our worker sets up ambient notification at the same time as it responds to
// the workerAPI initialization. If it's already initialized, we can
// immediately check the icons, otherwise wait for initialization by
// observing the topic sent out by the social service.
if (Social.provider.workerAPI.initialized) {
iconsReady = true;
checkNext();
} else {
Services.obs.addObserver(function obs() {
Services.obs.removeObserver(obs, "social:ambient-notification-changed");
// Let the other observers (like the one that updates the UI) run before
// checking the icons.
executeSoon(function () {
iconsReady = true;
checkNext();
});
}, "social:ambient-notification-changed", false);
}
}

View File

@ -14,7 +14,7 @@ function test() {
iconURL: "chrome://branding/content/icon48.png"
};
runSocialTestWithProvider(manifest, function () {
runTests(tests, undefined, undefined, function () {
runSocialTests(tests, undefined, undefined, function () {
SocialService.removeProvider(Social.provider.origin, finish);
});
});
@ -74,42 +74,3 @@ var tests = {
}
}
function runTests(tests, cbPreTest, cbPostTest, cbFinish) {
let testIter = Iterator(tests);
if (cbPreTest === undefined) {
cbPreTest = function(cb) {cb()};
}
if (cbPostTest === undefined) {
cbPostTest = function(cb) {cb()};
}
function runNextTest() {
let name, func;
try {
[name, func] = testIter.next();
} catch (err if err instanceof StopIteration) {
// out of items:
(cbFinish || finish)();
return;
}
// We run on a timeout as the frameworker also makes use of timeouts, so
// this helps keep the debug messages sane.
executeSoon(function() {
function cleanupAndRunNextTest() {
info("sub-test " + name + " complete");
cbPostTest(runNextTest);
}
cbPreTest(function() {
info("sub-test " + name + " starting");
try {
func.call(tests, cleanupAndRunNextTest);
} catch (ex) {
ok(false, "sub-test " + name + " failed: " + ex.toString() +"\n"+ex.stack);
cleanupAndRunNextTest();
}
})
});
}
runNextTest();
}

View File

@ -118,3 +118,44 @@ function runSocialTestWithProvider(manifest, callback) {
}
});
}
function runSocialTests(tests, cbPreTest, cbPostTest, cbFinish) {
let testIter = Iterator(tests);
if (cbPreTest === undefined) {
cbPreTest = function(cb) {cb()};
}
if (cbPostTest === undefined) {
cbPostTest = function(cb) {cb()};
}
function runNextTest() {
let name, func;
try {
[name, func] = testIter.next();
} catch (err if err instanceof StopIteration) {
// out of items:
(cbFinish || finish)();
return;
}
// We run on a timeout as the frameworker also makes use of timeouts, so
// this helps keep the debug messages sane.
executeSoon(function() {
function cleanupAndRunNextTest() {
info("sub-test " + name + " complete");
cbPostTest(runNextTest);
}
cbPreTest(function() {
info("sub-test " + name + " starting");
try {
func.call(tests, cleanupAndRunNextTest);
} catch (ex) {
ok(false, "sub-test " + name + " failed: " + ex.toString() +"\n"+ex.stack);
cleanupAndRunNextTest();
}
})
});
}
runNextTest();
}

View File

@ -2,8 +2,34 @@
<head>
<meta charset="utf-8">
<script>
var win;
function pingWorker() {
var port = navigator.mozSocial.getWorker().port;
port.onmessage = function(e) {
var topic = e.data.topic;
switch (topic) {
case "test-service-window":
win = navigator.mozSocial.openServiceWindow("social_window.html", "test-service-window", "width=300,height=300");
break;
case "test-service-window-twice":
win = navigator.mozSocial.openServiceWindow("social_window.html", "test-service-window", "width=300,height=300");
var win2 = navigator.mozSocial.openServiceWindow("social_window.html", "test-service-window", "");
var result;
if (win == win2)
result = "ok";
else
result = "not ok: " + win2 + " != " + win;
port.postMessage({topic: "test-service-window-twice-result", result: result});
break;
case "test-close-service-window":
win.addEventListener("unload", function watchClose() {
win.removeEventListener("unload", watchClose);
port.postMessage({topic: "service-window-closed-message", result: "ok"});
}, false)
win.close();
break;
}
}
port.postMessage({topic: "sidebar-message", result: "ok"});
}
</script>

View File

@ -0,0 +1,14 @@
<html>
<head>
<meta charset="utf-8">
<script>
function pingWorker() {
var port = navigator.mozSocial.getWorker().port;
port.postMessage({topic: "service-window-message", result: "ok"});
}
</script>
</head>
<body onload="pingWorker();">
<p>This is a test social service window.</p>
</body>
</html>

View File

@ -2,7 +2,7 @@
* 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 testPort;
let testPort, sidebarPort;
onconnect = function(e) {
let port = e.ports[0];
@ -13,9 +13,28 @@ onconnect = function(e) {
testPort = port;
break;
case "sidebar-message":
sidebarPort = port;
if (testPort && event.data.result == "ok")
testPort.postMessage({topic:"got-sidebar-message"});
break;
case "service-window-message":
testPort.postMessage({topic:"got-service-window-message"});
break;
case "service-window-closed-message":
testPort.postMessage({topic:"got-service-window-closed-message"});
break;
case "test-service-window":
sidebarPort.postMessage({topic:"test-service-window"});
break;
case "test-service-window-twice":
sidebarPort.postMessage({topic:"test-service-window-twice"});
break;
case "test-service-window-twice-result":
testPort.postMessage({topic: "test-service-window-twice-result", result: event.data.result })
break;
case "test-close-service-window":
sidebarPort.postMessage({topic:"test-close-service-window"});
break;
case "panel-message":
if (testPort && event.data.result == "ok")
testPort.postMessage({topic:"got-panel-message"});

View File

@ -41,15 +41,6 @@ function injectController(doc, topic, data) {
.QueryInterface(Ci.nsIDocShell)
.chromeEventHandler;
// If the containing browser isn't one of the social browsers, nothing to
// do here.
// XXX this is app-specific, might want some mechanism for consumers to
// whitelist other IDs/windowtypes
if (!(containingBrowser.id == "social-sidebar-browser" ||
containingBrowser.id == "social-notification-browser")) {
return;
}
let origin = containingBrowser.getAttribute("origin");
if (!origin) {
return;
@ -92,6 +83,18 @@ function attachToWindow(provider, targetWindow) {
},
hasBeenIdleFor: function () {
return false;
},
openServiceWindow: function(toURL, name, options) {
return openServiceWindow(provider, targetWindow, toURL, name, options);
},
getAttention: function() {
let mainWindow = targetWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIWebNavigation)
.QueryInterface(Components.interfaces.nsIDocShellTreeItem)
.rootTreeItem
.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindow);
mainWindow.getAttention();
}
};
@ -128,3 +131,59 @@ function attachToWindow(provider, targetWindow) {
function schedule(callback) {
Services.tm.mainThread.dispatch(callback, Ci.nsIThread.DISPATCH_NORMAL);
}
function openServiceWindow(provider, contentWindow, url, name, options) {
// resolve partial URLs and check prePath matches
let uri;
let fullURL;
try {
fullURL = contentWindow.document.documentURIObject.resolve(url);
uri = Services.io.newURI(fullURL, null, null);
} catch (ex) {
Cu.reportError("openServiceWindow: failed to resolve window URL: " + url + "; " + ex);
return null;
}
if (provider.origin != uri.prePath) {
Cu.reportError("openServiceWindow: unable to load new location, " +
provider.origin + " != " + uri.prePath);
return null;
}
function getChromeWindow(contentWin) {
return contentWin.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem)
.rootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
}
let chromeWindow = Services.ww.getWindowByName("social-service-window-" + name,
getChromeWindow(contentWindow));
let tabbrowser = chromeWindow && chromeWindow.gBrowser;
if (tabbrowser &&
tabbrowser.selectedBrowser.getAttribute("origin") == provider.origin) {
return tabbrowser.contentWindow;
}
let serviceWindow = contentWindow.openDialog(fullURL, name,
"chrome=no,dialog=no" + options);
// Get the newly opened window's containing XUL window
chromeWindow = getChromeWindow(serviceWindow);
// set the window's name and origin attribute on its browser, so that it can
// be found via getWindowByName
chromeWindow.name = "social-service-window-" + name;
chromeWindow.gBrowser.selectedBrowser.setAttribute("origin", provider.origin);
// we dont want the default title the browser produces, we'll fixup whenever
// it changes.
serviceWindow.addEventListener("DOMTitleChanged", function() {
let sep = xulWindow.document.documentElement.getAttribute("titlemenuseparator");
xulWindow.document.title = provider.name + sep + serviceWindow.document.title;
});
return serviceWindow;
}