diff --git a/browser/base/content/browser-social.js b/browser/base/content/browser-social.js
index f1ad9efbf9aa..e714adfba894 100644
--- a/browser/base/content/browser-social.js
+++ b/browser/base/content/browser-social.js
@@ -40,6 +40,7 @@ let SocialUI = {
SocialShareButton.updateButtonHiddenState();
SocialToolbar.updateButtonHiddenState();
SocialSidebar.updateSidebar();
+ SocialChatBar.update();
} catch (e) {
Components.utils.reportError(e);
throw e;
@@ -157,6 +158,27 @@ let SocialUI = {
}
}
+let SocialChatBar = {
+ get chatbar() {
+ return document.getElementById("pinnedchats");
+ },
+ // Whether the chats can be shown for this window.
+ get canShow() {
+ let docElem = document.documentElement;
+ let chromeless = docElem.getAttribute("disablechrome") ||
+ docElem.getAttribute("chromehidden").indexOf("extrachrome") >= 0;
+ return Social.uiVisible && !chromeless;
+ },
+ newChat: function(aProvider, aURL, aCallback) {
+ if (this.canShow)
+ this.chatbar.newChat(aProvider, aURL, aCallback);
+ },
+ update: function() {
+ if (!this.canShow)
+ this.chatbar.removeAll();
+ }
+}
+
let SocialShareButton = {
// Called once, after window load, when the Social.provider object is initialized
init: function SSB_init() {
diff --git a/browser/base/content/browser.css b/browser/base/content/browser.css
index 7c222ea59bba..c0166b7d5d5c 100644
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -615,3 +615,12 @@ stack[anonid=browserStack][responsivemode] {
stack[anonid=browserStack][responsivemode][notransition] {
transition: none;
}
+
+chatbox {
+ -moz-binding: url("chrome://browser/content/socialchat.xml#chatbox");
+}
+
+chatbar {
+ -moz-binding: url("chrome://browser/content/socialchat.xml#chatbar");
+ height: 0;
+}
diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js
index e566b7935ce0..5f5f8c78c899 100644
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -3161,6 +3161,52 @@ const DOMLinkHandler = {
break;
}
},
+ getLinkIconURI: function(aLink) {
+ let targetDoc = aLink.ownerDocument;
+ var uri = makeURI(aLink.href, targetDoc.characterSet);
+
+ // Verify that the load of this icon is legal.
+ // Some error or special pages can load their favicon.
+ // To be on the safe side, only allow chrome:// favicons.
+ var isAllowedPage = [
+ /^about:neterror\?/,
+ /^about:blocked\?/,
+ /^about:certerror\?/,
+ /^about:home$/,
+ ].some(function (re) re.test(targetDoc.documentURI));
+
+ if (!isAllowedPage || !uri.schemeIs("chrome")) {
+ var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"].
+ getService(Ci.nsIScriptSecurityManager);
+ try {
+ ssm.checkLoadURIWithPrincipal(targetDoc.nodePrincipal, uri,
+ Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
+ } catch(e) {
+ return null;
+ }
+ }
+
+ try {
+ var contentPolicy = Cc["@mozilla.org/layout/content-policy;1"].
+ getService(Ci.nsIContentPolicy);
+ } catch(e) {
+ return null; // Refuse to load if we can't do a security check.
+ }
+
+ // Security says okay, now ask content policy
+ if (contentPolicy.shouldLoad(Ci.nsIContentPolicy.TYPE_IMAGE,
+ uri, targetDoc.documentURIObject,
+ aLink, aLink.type, null)
+ != Ci.nsIContentPolicy.ACCEPT)
+ return null;
+
+ try {
+ uri.userPass = "";
+ } catch(e) {
+ // some URIs are immutable
+ }
+ return uri;
+ },
onLinkAdded: function (event) {
var link = event.originalTarget;
var rel = link.rel && link.rel.toLowerCase();
@@ -3194,54 +3240,20 @@ const DOMLinkHandler = {
if (!gPrefService.getBoolPref("browser.chrome.site_icons"))
break;
- var targetDoc = link.ownerDocument;
- var uri = makeURI(link.href, targetDoc.characterSet);
+ var uri = this.getLinkIconURI(link);
+ if (!uri)
+ break;
if (gBrowser.isFailedIcon(uri))
break;
- // Verify that the load of this icon is legal.
- // Some error or special pages can load their favicon.
- // To be on the safe side, only allow chrome:// favicons.
- var isAllowedPage = [
- /^about:neterror\?/,
- /^about:blocked\?/,
- /^about:certerror\?/,
- /^about:home$/,
- ].some(function (re) re.test(targetDoc.documentURI));
-
- if (!isAllowedPage || !uri.schemeIs("chrome")) {
- var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"].
- getService(Ci.nsIScriptSecurityManager);
- try {
- ssm.checkLoadURIWithPrincipal(targetDoc.nodePrincipal, uri,
- Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
- } catch(e) {
- break;
- }
- }
-
- try {
- var contentPolicy = Cc["@mozilla.org/layout/content-policy;1"].
- getService(Ci.nsIContentPolicy);
- } catch(e) {
- break; // Refuse to load if we can't do a security check.
- }
-
- // Security says okay, now ask content policy
- if (contentPolicy.shouldLoad(Ci.nsIContentPolicy.TYPE_IMAGE,
- uri, targetDoc.documentURIObject,
- link, link.type, null)
- != Ci.nsIContentPolicy.ACCEPT)
- break;
-
- var browserIndex = gBrowser.getBrowserIndexForDocument(targetDoc);
+ var browserIndex = gBrowser.getBrowserIndexForDocument(link.ownerDocument);
// no browser? no favicon.
if (browserIndex == -1)
break;
let tab = gBrowser.tabs[browserIndex];
- gBrowser.setIcon(tab, link.href);
+ gBrowser.setIcon(tab, uri.spec);
iconAdded = true;
}
break;
diff --git a/browser/base/content/browser.xul b/browser/base/content/browser.xul
index 28d74bdc7f33..9573c0867801 100644
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -1051,6 +1051,7 @@
contentcontextmenu="contentAreaContextMenu"
autocompletepopup="PopupAutoComplete"
onclick="contentAreaClick(event, false);"/>
+
This is a test social chat window.
+ + diff --git a/browser/base/content/test/social_sidebar.html b/browser/base/content/test/social_sidebar.html index b23846ebf394..dbf928c2926a 100644 --- a/browser/base/content/test/social_sidebar.html +++ b/browser/base/content/test/social_sidebar.html @@ -8,6 +8,11 @@ port.onmessage = function(e) { var topic = e.data.topic; switch (topic) { + case "test-chatbox-open": + navigator.mozSocial.openChatWindow("social_chat.html", function(chatwin) { + port.postMessage({topic: "chatbox-opened", result: chatwin ? "ok" : "failed"}); + }); + break; case "test-service-window": win = navigator.mozSocial.openServiceWindow("social_window.html", "test-service-window", "width=300,height=300"); break; diff --git a/browser/base/content/test/social_worker.js b/browser/base/content/test/social_worker.js index 3dffa6e558ee..3cb3309ab38d 100644 --- a/browser/base/content/test/social_worker.js +++ b/browser/base/content/test/social_worker.js @@ -39,6 +39,15 @@ onconnect = function(e) { if (testPort && event.data.result == "ok") testPort.postMessage({topic:"got-panel-message"}); break; + case "test-chatbox-open": + sidebarPort.postMessage({topic:"test-chatbox-open"}); + break; + case "chatbox-message": + testPort.postMessage({topic:"got-chatbox-message", result: event.data.result}); + break; + case "chatbox-visibility": + testPort.postMessage({topic:"got-chatbox-visibility", result: event.data.result}); + break; case "social.initialize": // This is the workerAPI port, respond and set up a notification icon. port.postMessage({topic: "social.initialize-response"}); diff --git a/browser/base/jar.mn b/browser/base/jar.mn index 87e5cd1de27e..842ac8659152 100644 --- a/browser/base/jar.mn +++ b/browser/base/jar.mn @@ -102,6 +102,7 @@ browser.jar: content/browser/win6BrowserOverlay.xul (content/win6BrowserOverlay.xul) #endif content/browser/social-icon.png (content/social-icon.png) + content/browser/socialchat.xml (content/socialchat.xml) # the following files are browser-specific overrides * content/browser/license.html (/toolkit/content/license.html) % override chrome://global/content/license.html chrome://browser/content/license.html diff --git a/browser/themes/gnomestripe/browser.css b/browser/themes/gnomestripe/browser.css index 7c5d8116d786..c408fd6dec97 100644 --- a/browser/themes/gnomestripe/browser.css +++ b/browser/themes/gnomestripe/browser.css @@ -2748,3 +2748,126 @@ stack[anonid=browserStack][responsivemode] { max-width: 400px; } + +.chat-status-icon { + max-height: 16px; + max-width: 16px; + padding: 0; +} + +.chat-toolbarbutton { + -moz-appearance: none; + border: none; + padding: 0; + margin: 0; + background: none; +} + +.chat-toolbarbutton > .toolbarbutton-text { + display: none; +} + +.chat-close-button { + list-style-image: url("chrome://global/skin/icons/close.png"); + -moz-image-region: rect(0, 16px, 16px, 0); +} + +.chat-close-button:hover { + -moz-image-region: rect(0, 32px, 16px, 16px); +} + +.chat-close-button:hover:active { + -moz-image-region: rect(0, 48px, 16px, 32px); +} + + +.chat-toggle-button { + /* XXX get a real image for this */ + list-style-image: url("chrome://global/skin/icons/checkbox.png"); + -moz-image-region: rect(0, 32px, 16px, 16px); +} + +.chat-toggle-button:hover { + -moz-image-region: rect(0, 16px, 16px, 0); +} + +.chat-toggle-button[minimized="true"] { + -moz-image-region: rect(0, 16px, 16px, 0); +} + +.chat-toggle-button[minimized="true"]:hover { + -moz-image-region: rect(0, 32px, 16px, 16px); +} + +.chat-title { + font-weight: bold; + color: -moz-dialogtext; +} + +.chat-titlebar { + background-image: linear-gradient(white, #ddd); + height: 20px; + min-height: 20px; + width: 100%; + margin: 0; + padding: 2px; + border: none; + border-bottom: 1px solid gray; +} + +.chat-titlebar[minimized="true"] { + border-bottom: none; +} + +.chat-frame { + padding: 0; + margin: 0; + overflow: hidden; +} + +.chatbar-button { + /* XXX get a real image for this */ + list-style-image: url("chrome://browser/skin/social/social.png"); + border: none; + margin: 0; + padding: 2px; + height: 21px; + width: 21px; + border-top: 1px solid gray; + -moz-border-end: 1px solid gray; +} + +.chatbar-button[open="true"], +.chatbar-button:active:hover { + box-shadow: inset 0 2px 5px rgba(0,0,0,0.6), 0 1px rgba(255,255,255,0.2); +} + +/* toolbarbutton-icon */ +.chatbar-button > .toolbarbutton-text, +.chatbar-button > .toolbarbutton-menu-dropmarker { + display: none; +} + +.chatbar-innerbox { + background: transparent; + margin: -200px -1px 0 -1px; + overflow: hidden; +} + +chatbar > chatbox { + height: 200px; + width: 200px; + background-color: white; + border: 1px solid gray; + border-bottom: none; +} + +chatbar > chatbox[minimized="true"] { + width: 100px; + height: 20px; + border-bottom: none; +} + +chatbar > chatbox + chatbox { + -moz-margin-start: -1px; +} diff --git a/browser/themes/pinstripe/browser.css b/browser/themes/pinstripe/browser.css index 4c9886484d8c..844826f137be 100644 --- a/browser/themes/pinstripe/browser.css +++ b/browser/themes/pinstripe/browser.css @@ -3519,3 +3519,129 @@ stack[anonid=browserStack][responsivemode] { } /* === end of social toolbar panels === */ + + +.chat-status-icon { + max-height: 16px; + max-width: 16px; + padding: 0; +} + +.chat-toolbarbutton { + -moz-appearance: none; + border: none; + padding: 0; + margin: 0; + background: none; +} + +.chat-toolbarbutton > .toolbarbutton-text { + display: none; +} + +.chat-close-button { + list-style-image: url("chrome://global/skin/icons/close.png"); + -moz-image-region: rect(0, 16px, 16px, 0); +} + +.chat-close-button:hover { + -moz-image-region: rect(0, 32px, 16px, 16px); +} + +.chat-close-button:hover:active { + -moz-image-region: rect(0, 48px, 16px, 32px); +} + + +.chat-toggle-button { + /* XXX get a real image for this */ + list-style-image: url("chrome://global/skin/icons/checkbox.png"); + -moz-image-region: rect(0, 32px, 16px, 16px); +} + +.chat-toggle-button:hover { + -moz-image-region: rect(0, 16px, 16px, 0); +} + +.chat-toggle-button[minimized="true"] { + -moz-image-region: rect(0, 16px, 16px, 0); +} + +.chat-toggle-button[minimized="true"]:hover { + -moz-image-region: rect(0, 32px, 16px, 16px); +} + +.chat-title { + font-weight: bold; + color: -moz-dialogtext; +} + +.chat-titlebar { + background-image: linear-gradient(white, #ddd); + height: 20px; + min-height: 20px; + width: 100%; + margin: 0; + padding: 2px; + border: none; + border-bottom: 1px solid #404040; +} + +.chat-titlebar[minimized="true"] { + border-bottom: none; +} + +.chat-frame { + padding: 0; + margin: 0; + overflow: hidden; +} + +.chatbar-button { + /* XXX get a real image for this */ + background-image: linear-gradient(white, #ddd); + list-style-image: url("chrome://browser/skin/social/social.png"); + border: none; + margin: 0; + padding: 2px; + height: 21px; + width: 21px; + border-top: 1px solid #404040; + -moz-border-end: 1px solid #404040; +} + +.chatbar-button[open="true"], +.chatbar-button:active:hover { + box-shadow: inset 0 2px 5px rgba(0,0,0,0.6), 0 1px rgba(255,255,255,0.2); +} + +/* toolbarbutton-icon */ +.chatbar-button > .toolbarbutton-text, +.chatbar-button > .toolbarbutton-menu-dropmarker { + display: none; +} + +.chatbar-innerbox { + background: transparent; + margin: -200px -1px 0 -1px; + overflow: hidden; +} + +chatbar > chatbox { + height: 200px; + width: 200px; + background-color: white; + border: 1px solid #404040; + border-bottom: none; +} + +chatbar > chatbox[minimized="true"] { + width: 100px; + height: 20px; + border-bottom: none; +} + +chatbar > chatbox + chatbox { + -moz-margin-start: -1px; +} + diff --git a/browser/themes/winstripe/browser-aero.css b/browser/themes/winstripe/browser-aero.css index dba37a9f7817..cd60b1a7304a 100644 --- a/browser/themes/winstripe/browser-aero.css +++ b/browser/themes/winstripe/browser-aero.css @@ -110,6 +110,12 @@ .menu-iconic-accel { color: graytext; } + + .chatbar-button, + chatbar > chatbox { + border-color: #A9B7C9; + } + } @media (-moz-windows-compositor) { diff --git a/browser/themes/winstripe/browser.css b/browser/themes/winstripe/browser.css index bcee28402753..39b6fe7fc30b 100644 --- a/browser/themes/winstripe/browser.css +++ b/browser/themes/winstripe/browser.css @@ -3450,3 +3450,136 @@ stack[anonid=browserStack][responsivemode] { max-height: 600px; max-width: 400px; } + + +.chat-status-icon { + max-height: 16px; + max-width: 16px; + padding: 0; +} + +.chat-toolbarbutton { + -moz-appearance: none; + border: none; + padding: 0; + margin: 0; + background: none; +} + +.chat-toolbarbutton > .toolbarbutton-text { + display: none; +} + +.chat-close-button { + list-style-image: url("chrome://global/skin/icons/Close.gif"); + padding: 2px 4px; +} + +.chat-close-button:hover { + box-shadow: inset 0 2px 5px rgba(0,0,0,0.6), 0 1px rgba(255,255,255,0.2); +} + +.chat-close-button:hover:active { + box-shadow: inset 0 2px 5px rgba(0,0,0,0.6), 0 1px rgba(255,255,255,0.2); +} + + +.chat-toggle-button { + /* XXX get a real image for this */ + list-style-image: url("chrome://global/skin/icons/expand.png"); +} + +.chat-toggle-button:hover { + box-shadow: inset 0 2px 5px rgba(0,0,0,0.6), 0 1px rgba(255,255,255,0.2); +} + +.chat-toggle-button[minimized="true"] { + list-style-image: url("chrome://global/skin/icons/collapse.png"); +} + +.chat-toggle-button[minimized="true"]:hover { + box-shadow: inset 0 2px 5px rgba(0,0,0,0.6), 0 1px rgba(255,255,255,0.2); +} + +.chat-title { + font-weight: bold; + color: -moz-dialogtext; +} + +.chat-titlebar { + background-image: linear-gradient(white, #ddd); + height: 20px; + min-height: 20px; + width: 100%; + margin: 0; + padding: 2px; + border: none; + border-bottom: 1px solid gray; +} + +.chat-titlebar[minimized="true"] { + border-bottom: none; +} + +.chat-frame { + padding: 0; + margin: 0; + overflow: hidden; +} + +.chatbar-button { + /* XXX get a real image for this */ + -moz-appearance: none; + list-style-image: url("chrome://browser/skin/social/social.png"); + background-image: linear-gradient(white, #ddd); + border: none; + margin: 0; + padding: 2px; + height: 21px; + width: 21px; + border-top: 1px solid gray; + -moz-border-end: 1px solid gray; +} + +.chatbar-button > .button-box > .box-inherit > .button-icon { + max-height: 16px; + max-width: 16px; + padding: 2px; +} + +.chatbar-button[open="true"], +.chatbar-button:hover, +.chatbar-button:active:hover { + box-shadow: inset 0 2px 5px rgba(0,0,0,0.6), 0 1px rgba(255,255,255,0.2); +} + +/* toolbarbutton-icon */ +.chatbar-button > .toolbarbutton-text, +.chatbar-button > .toolbarbutton-menu-dropmarker { + display: none; +} + +.chatbar-innerbox { + background: transparent; + margin: -200px -1px 0 -1px; + overflow: hidden; +} + +chatbar > chatbox { + height: 200px; + width: 200px; + background-color: white; + border: 1px solid gray; + border-bottom: none; +} + +chatbar > chatbox[minimized="true"] { + width: 100px; + height: 20px; + border-bottom: none; +} + +chatbar > chatbox + chatbox { + -moz-margin-start: -1px; +} + diff --git a/toolkit/components/social/MozSocialAPI.jsm b/toolkit/components/social/MozSocialAPI.jsm index 46a254d5736a..c5fad292459e 100644 --- a/toolkit/components/social/MozSocialAPI.jsm +++ b/toolkit/components/social/MozSocialAPI.jsm @@ -99,7 +99,17 @@ function attachToWindow(provider, targetWindow) { configurable: true, writable: true, value: function(toURL, name, options) { - return openServiceWindow(provider, targetWindow, toURL, name, options); + let url = targetWindow.document.documentURIObject.resolve(toURL); + return openServiceWindow(provider, targetWindow, url, name, options); + } + }, + openChatWindow: { + enumerable: true, + configurable: true, + writable: true, + value: function(toURL, callback) { + let url = targetWindow.document.documentURIObject.resolve(toURL); + openChatWindow(getChromeWindow(targetWindow), provider, url, callback); } }, getAttention: { @@ -107,13 +117,7 @@ function attachToWindow(provider, targetWindow) { configurable: true, writable: true, value: 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(); + getChromeWindow(targetWindow).getAttention(); } }, isVisible: { @@ -152,12 +156,21 @@ function schedule(callback) { Services.tm.mainThread.dispatch(callback, Ci.nsIThread.DISPATCH_NORMAL); } -function openServiceWindow(provider, contentWindow, url, name, options) { +function getChromeWindow(contentWin) { + return contentWin.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShellTreeItem) + .rootTreeItem + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow); +} + +function ensureProviderOrigin(provider, url) { // resolve partial URLs and check prePath matches let uri; let fullURL; try { - fullURL = contentWindow.document.documentURIObject.resolve(url); + fullURL = Services.io.newURI(provider.origin, null, null).resolve(url); uri = Services.io.newURI(fullURL, null, null); } catch (ex) { Cu.reportError("openServiceWindow: failed to resolve window URL: " + url + "; " + ex); @@ -169,25 +182,33 @@ function openServiceWindow(provider, contentWindow, url, name, options) { provider.origin + " != " + uri.prePath); return null; } + return fullURL; +} - function getChromeWindow(contentWin) { - return contentWin.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebNavigation) - .QueryInterface(Ci.nsIDocShellTreeItem) - .rootTreeItem - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindow); +function openChatWindow(chromeWindow, provider, url, callback) { + if (!chromeWindow.SocialChatBar) + return; + let fullURL = ensureProviderOrigin(provider, url); + if (!fullURL) + return; + chromeWindow.SocialChatBar.newChat(provider, fullURL, callback); +} - } - let chromeWindow = Services.ww.getWindowByName("social-service-window-" + name, - getChromeWindow(contentWindow)); +function openServiceWindow(provider, contentWindow, url, name, options) { + // resolve partial URLs and check prePath matches + let fullURL = ensureProviderOrigin(provider, url); + if (!fullURL) + return null; + + let windowName = provider.origin + name; + let chromeWindow = Services.ww.getWindowByName(windowName, null); let tabbrowser = chromeWindow && chromeWindow.gBrowser; if (tabbrowser && tabbrowser.selectedBrowser.getAttribute("origin") == provider.origin) { return tabbrowser.contentWindow; } - let serviceWindow = contentWindow.openDialog(fullURL, name, + let serviceWindow = contentWindow.openDialog(fullURL, windowName, "chrome=no,dialog=no" + options); // Get the newly opened window's containing XUL window @@ -195,7 +216,7 @@ function openServiceWindow(provider, contentWindow, url, name, options) { // 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.name = windowName; chromeWindow.gBrowser.selectedBrowser.setAttribute("origin", provider.origin); // we dont want the default title the browser produces, we'll fixup whenever