Bug 1154277: Part 2 - support running Social API documents to run in a remote browser, i.e. the content process. f=mixedpuppy, r=Standard8

This commit is contained in:
Mike de Boer 2016-02-04 12:50:06 +01:00
parent 6c7fdd37b9
commit f72d46f1cc
12 changed files with 404 additions and 153 deletions

View File

@ -370,7 +370,7 @@ SocialFlyout = {
// the xbl bindings for the iframe probably don't exist yet, so we can't // the xbl bindings for the iframe probably don't exist yet, so we can't
// access iframe.messageManager directly - but can get at it with this dance. // access iframe.messageManager directly - but can get at it with this dance.
let mm = iframe.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader.messageManager; let mm = iframe.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader.messageManager;
mm.sendAsyncMessage("Social:SetErrorURL", null, mm.sendAsyncMessage("Social:SetErrorURL",
{ template: "about:socialerror?mode=compactInfo&origin=%{origin}" }); { template: "about:socialerror?mode=compactInfo&origin=%{origin}" });
}, },
@ -512,7 +512,7 @@ SocialShare = {
iframe.setAttribute("messagemanagergroup", "social"); iframe.setAttribute("messagemanagergroup", "social");
panel.lastChild.appendChild(iframe); panel.lastChild.appendChild(iframe);
let mm = iframe.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader.messageManager; let mm = iframe.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader.messageManager;
mm.sendAsyncMessage("Social:SetErrorURL", null, mm.sendAsyncMessage("Social:SetErrorURL",
{ template: "about:socialerror?mode=compactInfo&origin=%{origin}&url=%{url}" }); { template: "about:socialerror?mode=compactInfo&origin=%{origin}&url=%{url}" });
this.populateProviderMenu(); this.populateProviderMenu();

View File

@ -118,6 +118,10 @@ panelview:not([mainview]):not([current]) {
visibility: collapse; visibility: collapse;
} }
browser[frameType="social"][remote="true"] {
-moz-binding: url("chrome://global/content/bindings/remote-browser.xml#remote-browser");
}
tabbrowser { tabbrowser {
-moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser"); -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser");
} }

View File

@ -14,10 +14,26 @@ Cu.import("resource://gre/modules/Services.jsm");
// social frames are always treated as app tabs // social frames are always treated as app tabs
docShell.isAppTab = true; docShell.isAppTab = true;
var gDOMContentLoaded = false;
addEventListener("DOMContentLoaded", function() {
gDOMContentLoaded = true;
sendAsyncMessage("DOMContentLoaded");
});
var gDOMTitleChangedByUs = false;
addEventListener("DOMTitleChanged", function(e) {
if (!gDOMTitleChangedByUs) {
sendAsyncMessage("DOMTitleChanged", {
title: e.target.title
});
gDOMTitleChangedByUs = false;
}
});
// Error handling class used to listen for network errors in the social frames // Error handling class used to listen for network errors in the social frames
// and replace them with a social-specific error page // and replace them with a social-specific error page
SocialErrorListener = { SocialErrorListener = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener, QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMEventListener,
Ci.nsIWebProgressListener,
Ci.nsISupportsWeakReference, Ci.nsISupportsWeakReference,
Ci.nsISupports]), Ci.nsISupports]),
@ -25,21 +41,132 @@ SocialErrorListener = {
urlTemplate: null, urlTemplate: null,
init() { init() {
addMessageListener("Loop:MonitorPeerConnectionLifecycle", this);
addMessageListener("Loop:GetAllWebrtcStats", this);
addMessageListener("Social:CustomEvent", this);
addMessageListener("Social:EnsureFocus", this);
addMessageListener("Social:EnsureFocusElement", this);
addMessageListener("Social:HookWindowCloseForPanelClose", this);
addMessageListener("Social:ListenForEvents", this);
addMessageListener("Social:SetDocumentTitle", this);
addMessageListener("Social:SetErrorURL", this); addMessageListener("Social:SetErrorURL", this);
addMessageListener("Social:WaitForDocumentVisible", this);
addMessageListener("WaitForDOMContentLoaded", this);
let webProgress = docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor) let webProgress = docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIWebProgress); .getInterface(Components.interfaces.nsIWebProgress);
webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_REQUEST | webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_REQUEST |
Ci.nsIWebProgress.NOTIFY_LOCATION); Ci.nsIWebProgress.NOTIFY_LOCATION);
}, },
receiveMessage(message) { receiveMessage(message) {
switch(message.name) { let document = content.document;
case "Social:SetErrorURL": {
// either a url or null to reset to default template switch (message.name) {
this.urlTemplate = message.objects.template; case "Loop:GetAllWebrtcStats":
} content.WebrtcGlobalInformation.getAllStats(allStats => {
content.WebrtcGlobalInformation.getLogging("", logs => {
sendAsyncMessage("Loop:GetAllWebrtcStats", {
allStats: allStats,
logs: logs
});
});
}, message.data.peerConnectionID);
break;
case "Loop:MonitorPeerConnectionLifecycle":
let ourID = content.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
let onPCLifecycleChange = (pc, winID, type) => {
if (winID != ourID) {
return;
}
sendAsyncMessage("Loop:PeerConnectionLifecycleChange", {
iceConnectionState: pc.iceConnectionState,
locationHash: content.location.hash,
peerConnectionID: pc.id,
type: type
});
};
let pc_static = new content.RTCPeerConnectionStatic();
pc_static.registerPeerConnectionLifecycleCallback(onPCLifecycleChange);
break;
case "Social:CustomEvent":
let ev = new content.CustomEvent(message.data.name, message.data.detail ?
{ detail: message.data.detail } : null);
content.dispatchEvent(ev);
break;
case "Social:EnsureFocus":
Services.focus.focusedWindow = content;
break;
case "Social:EnsureFocusElement":
let fm = Services.focus;
fm.moveFocus(document.defaultView, null, fm.MOVEFOCUS_FIRST, fm.FLAG_NOSCROLL);
break;
case "Social:HookWindowCloseForPanelClose":
// We allow window.close() to close the panel, so add an event handler for
// this, then cancel the event (so the window itself doesn't die) and
// close the panel instead.
// However, this is typically affected by the dom.allow_scripts_to_close_windows
// preference, but we can avoid that check by setting a flag on the window.
let dwu = content.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
dwu.allowScriptsToClose();
content.addEventListener("DOMWindowClose", function _mozSocialDOMWindowClose(evt) {
sendAsyncMessage("DOMWindowClose");
// preventDefault stops the default window.close() function being called,
// which doesn't actually close anything but causes things to get into
// a bad state (an internal 'closed' flag is set and debug builds start
// asserting as the window is used.).
// None of the windows we inject this API into are suitable for this
// default close behaviour, so even if we took no action above, we avoid
// the default close from doing anything.
evt.preventDefault();
}, true);
break;
case "Social:ListenForEvents":
for (let eventName of message.data.eventNames) {
content.addEventListener(eventName, this);
}
break;
case "Social:SetDocumentTitle":
let title = message.data.title;
if (title && (title = title.trim())) {
gDOMTitleChangedByUs = true;
document.title = title;
}
break;
case "Social:SetErrorURL":
// Either a url or null to reset to default template.
this.urlTemplate = message.data.template;
break;
case "Social:WaitForDocumentVisible":
if (!document.hidden) {
sendAsyncMessage("Social:DocumentVisible");
break;
}
document.addEventListener("visibilitychange", function onVisibilityChanged() {
document.removeEventListener("visibilitychange", onVisibilityChanged);
sendAsyncMessage("Social:DocumentVisible");
});
break;
case "WaitForDOMContentLoaded":
if (gDOMContentLoaded) {
sendAsyncMessage("DOMContentLoaded");
}
break;
} }
}, },
handleEvent: function(event) {
sendAsyncMessage("Social:CustomEvent", {
name: event.type
});
},
setErrorPage() { setErrorPage() {
// if this is about:providerdirectory, use the directory iframe // if this is about:providerdirectory, use the directory iframe
let frame = docShell.chromeEventHandler; let frame = docShell.chromeEventHandler;

View File

@ -27,33 +27,51 @@
<xul:toolbarbutton anonid="close" class="chat-close-button chat-toolbarbutton" <xul:toolbarbutton anonid="close" class="chat-close-button chat-toolbarbutton"
oncommand="document.getBindingParent(this).close();"/> oncommand="document.getBindingParent(this).close();"/>
</xul:hbox> </xul:hbox>
<xul:browser anonid="remote-content" class="chat-frame" flex="1"
context="contentAreaContextMenu"
disableglobalhistory="true"
frameType="social"
message="true"
messagemanagergroup="social"
tooltip="aHTMLTooltip"
remote="true"
xbl:inherits="src,origin"
type="content"/>
<xul:browser anonid="content" class="chat-frame" flex="1" <xul:browser anonid="content" class="chat-frame" flex="1"
context="contentAreaContextMenu" context="contentAreaContextMenu"
disableglobalhistory="true" disableglobalhistory="true"
message="true" message="true"
messagemanagergroup="social" messagemanagergroup="social"
tooltip="aHTMLTooltip" tooltip="aHTMLTooltip"
xbl:inherits="src,origin" type="content"/> xbl:inherits="src,origin"
type="content"/>
</content> </content>
<implementation implements="nsIDOMEventListener"> <implementation implements="nsIDOMEventListener, nsIMessageListener">
<constructor><![CDATA[ <constructor><![CDATA[
const kAnchorMap = new Map([ const kAnchorMap = new Map([
["", "notification-"], ["", "notification-"],
["webRTC-shareScreen-", ""], ["webRTC-shareScreen-", ""],
["webRTC-sharingScreen-", ""] ["webRTC-sharingScreen-", ""]
]); ]);
for (let [getterPrefix, idPrefix] of kAnchorMap) { const kBrowsers = [
let getter = getterPrefix + "popupnotificationanchor"; document.getAnonymousElementByAttribute(this, "anonid", "content"),
let anonid = (idPrefix || getterPrefix) + "icon"; document.getAnonymousElementByAttribute(this, "anonid", "remote-content")
this.content.__defineGetter__(getter, () => { ];
delete this.content[getter]; for (let content of kBrowsers) {
return this.content[getter] = document.getAnonymousElementByAttribute( for (let [getterPrefix, idPrefix] of kAnchorMap) {
this, "anonid", anonid); let getter = getterPrefix + "popupnotificationanchor";
}); let anonid = (idPrefix || getterPrefix) + "icon";
content.__defineGetter__(getter, () => {
delete content[getter];
return content[getter] = document.getAnonymousElementByAttribute(
this, "anonid", anonid);
});
}
} }
let contentWindow = this.contentWindow; let mm = this.content.messageManager;
// process this._callbacks, then set to null so the chatbox creator // process this._callbacks, then set to null so the chatbox creator
// knows to make new callbacks immediately. // knows to make new callbacks immediately.
if (this._callbacks) { if (this._callbacks) {
@ -62,17 +80,18 @@
} }
this._callbacks = null; this._callbacks = null;
} }
this.addEventListener("DOMContentLoaded", function DOMContentLoaded(event) {
if (event.target != this.contentDocument) mm.addMessageListener("DOMTitleChanged", this);
return;
this.removeEventListener("DOMContentLoaded", DOMContentLoaded, true); mm.sendAsyncMessage("WaitForDOMContentLoaded");
mm.addMessageListener("DOMContentLoaded", function DOMContentLoaded(event) {
mm.removeMessageListener("DOMContentLoaded", DOMContentLoaded);
this.isActive = !this.minimized; this.isActive = !this.minimized;
this._chat.loadButtonSet(this, this.getAttribute("buttonSet")); this._chat.loadButtonSet(this, this.getAttribute("buttonSet"));
this._deferredChatLoaded.resolve(this); this._deferredChatLoaded.resolve(this);
}, true); }.bind(this));
if (this.src) this.setActiveBrowser();
this.setAttribute("src", this.src);
]]></constructor> ]]></constructor>
<field name="_deferredChatLoaded" readonly="true"> <field name="_deferredChatLoaded" readonly="true">
@ -85,26 +104,17 @@
</getter> </getter>
</property> </property>
<field name="content" readonly="true"> <property name="content">
document.getAnonymousElementByAttribute(this, "anonid", "content"); <getter>
</field> return document.getAnonymousElementByAttribute(this, "anonid",
(this.remote ? "remote-" : "") + "content");
</getter>
</property>
<field name="_chat" readonly="true"> <field name="_chat" readonly="true">
Cu.import("resource:///modules/Chat.jsm", {}).Chat; Cu.import("resource:///modules/Chat.jsm", {}).Chat;
</field> </field>
<property name="contentWindow">
<getter>
return this.content.contentWindow;
</getter>
</property>
<property name="contentDocument">
<getter>
return this.content.contentDocument;
</getter>
</property>
<property name="minimized"> <property name="minimized">
<getter> <getter>
return this.getAttribute("minimized") == "true"; return this.getAttribute("minimized") == "true";
@ -143,12 +153,41 @@
this.content.docShellIsActive = !!val; this.content.docShellIsActive = !!val;
// let the chat frame know if it is being shown or hidden // let the chat frame know if it is being shown or hidden
let evt = this.contentDocument.createEvent("CustomEvent"); this.content.messageManager.sendAsyncMessage("Social:CustomEvent", {
evt.initCustomEvent(val ? "socialFrameShow" : "socialFrameHide", true, true, {}); name: val ? "socialFrameShow" : "socialFrameHide"
this.contentDocument.documentElement.dispatchEvent(evt); });
</setter> </setter>
</property> </property>
<field name="_remote">false</field>
<property name="remote" onget="return this._remote;">
<setter><![CDATA[
this._remote = !!val;
this.setActiveBrowser();
]]></setter>
</property>
<method name="setActiveBrowser">
<body><![CDATA[
// Make sure we only show one browser element at a time.
let content = document.getAnonymousElementByAttribute(this, "anonid", "content");
let remoteContent = document.getAnonymousElementByAttribute(this, "anonid", "remote-content");
remoteContent.setAttribute("hidden", !this.remote);
content.setAttribute("hidden", this.remote);
remoteContent.removeAttribute("src");
content.removeAttribute("src");
if (this.src) {
this.setAttribute("src", this.src);
// Stop loading of the document - that is set before this method was
// called - in the now hidden browser.
(this.remote ? content : remoteContent).setAttribute("src", "about:blank");
}
]]></body>
</method>
<method name="showNotifications"> <method name="showNotifications">
<parameter name="aAnchor"/> <parameter name="aAnchor"/>
<body><![CDATA[ <body><![CDATA[
@ -160,12 +199,14 @@
<method name="swapDocShells"> <method name="swapDocShells">
<parameter name="aTarget"/> <parameter name="aTarget"/>
<body><![CDATA[ <body><![CDATA[
aTarget.setAttribute("label", this.contentDocument.title); aTarget.setAttribute("label", this.content.contentTitle);
aTarget.remote = this.remote;
aTarget.src = this.src; aTarget.src = this.src;
aTarget.content.setAttribute("origin", this.content.getAttribute("origin")); let content = aTarget.content;
aTarget.content.popupnotificationanchor.className = this.content.popupnotificationanchor.className; content.setAttribute("origin", this.content.getAttribute("origin"));
aTarget.content.swapDocShells(this.content); content.popupnotificationanchor.className = this.content.popupnotificationanchor.className;
content.swapDocShells(this.content);
]]></body> ]]></body>
</method> </method>
@ -209,7 +250,9 @@
if (this.chatbar) { if (this.chatbar) {
this.chatbar.detachChatbox(this, { "centerscreen": "yes" }).then( this.chatbar.detachChatbox(this, { "centerscreen": "yes" }).then(
chatbox => { chatbox => {
chatbox.contentWindow.document.title = title; chatbox.content.messageManager.sendAsyncMessage("Social:SetDocumentTitle", {
title: title
});
deferred.resolve(chatbox); deferred.resolve(chatbox);
} }
); );
@ -219,11 +262,16 @@
let win = Chat.findChromeWindowForChats(); let win = Chat.findChromeWindowForChats();
let chatbar = win.document.getElementById("pinnedchats"); let chatbar = win.document.getElementById("pinnedchats");
let origin = this.content.getAttribute("origin"); let origin = this.content.getAttribute("origin");
let cb = chatbar.openChat(origin, title, "about:blank"); let cb = chatbar.openChat({
this.setDecorationAttributes(cb); origin: origin,
title: title,
url: "about:blank"
});
cb.promiseChatLoaded.then( cb.promiseChatLoaded.then(
() => { () => {
this.setDecorationAttributes(cb);
this.swapDocShells(cb); this.swapDocShells(cb);
chatbar.focus(); chatbar.focus();
@ -236,11 +284,9 @@
chatbar.chatboxForURL.delete("about:blank"); chatbar.chatboxForURL.delete("about:blank");
chatbar.chatboxForURL.set(this.src, Cu.getWeakReference(cb)); chatbar.chatboxForURL.set(this.src, Cu.getWeakReference(cb));
let attachEvent = new cb.contentWindow.CustomEvent("socialFrameAttached", { cb.content.messageManager.sendAsyncMessage("Social:CustomEvent", {
bubbles: true, name: "socialFrameAttached"
cancelable: true,
}); });
cb.contentDocument.dispatchEvent(attachEvent);
deferred.resolve(cb); deferred.resolve(cb);
} }
@ -255,6 +301,27 @@
this.minimized = !this.minimized; this.minimized = !this.minimized;
]]></body> ]]></body>
</method> </method>
<method name="setTitle">
<body><![CDATA[
try {
this.setAttribute("label", this.content.contentTitle);
} catch (ex) {}
if (this.chatbar)
this.chatbar.updateTitlebar(this);
]]></body>
</method>
<method name="receiveMessage">
<parameter name="aMessage" />
<body><![CDATA[
switch (aMessage.name) {
case "DOMTitleChanged":
this.setTitle();
break;
}
]]></body>
</method>
</implementation> </implementation>
<handlers> <handlers>
@ -262,14 +329,13 @@
if (this.chatbar) if (this.chatbar)
this.chatbar.selectedChat = this; this.chatbar.selectedChat = this;
</handler> </handler>
<handler event="DOMTitleChanged"><![CDATA[ <handler event="DOMTitleChanged">
this.setAttribute('label', this.contentDocument.title); this.setTitle();
if (this.chatbar) </handler>
this.chatbar.updateTitlebar(this);
]]></handler>
<handler event="DOMLinkAdded"><![CDATA[ <handler event="DOMLinkAdded"><![CDATA[
// much of this logic is from DOMLinkHandler in browser.js // Much of this logic is from DOMLinkHandler in browser.js.
// this sets the presence icon for a chat user, we simply use favicon style updating // This sets the presence icon for a chat user, we simply use favicon
// style updating.
let link = event.originalTarget; let link = event.originalTarget;
let rel = link.rel && link.rel.toLowerCase(); let rel = link.rel && link.rel.toLowerCase();
if (!link || !link.ownerDocument || !rel || !link.href) if (!link || !link.ownerDocument || !rel || !link.href)
@ -277,15 +343,17 @@
if (link.rel.indexOf("icon") < 0) if (link.rel.indexOf("icon") < 0)
return; return;
let ContentLinkHandler = Cu.import("resource:///modules/ContentLinkHandler.jsm", {}).ContentLinkHandler; let ContentLinkHandler = Cu.import("resource:///modules/ContentLinkHandler.jsm", {})
.ContentLinkHandler;
let uri = ContentLinkHandler.getLinkIconURI(link); let uri = ContentLinkHandler.getLinkIconURI(link);
if (!uri) if (!uri)
return; return;
// we made it this far, use it // We made it this far, use it.
this.setAttribute('image', uri.spec); this.setAttribute("image", uri.spec);
if (this.chatbar) if (this.chatbar)
this.chatbar.updateTitlebar(this); this.chatbar.updateTitlebar(this);
break;
]]></handler> ]]></handler>
<handler event="transitionend"> <handler event="transitionend">
if (this.isActive == this.minimized) if (this.isActive == this.minimized)
@ -328,7 +396,7 @@
<body><![CDATA[ <body><![CDATA[
if (!this.selectedChat) if (!this.selectedChat)
return; return;
Services.focus.focusedWindow = this.selectedChat.contentWindow; this.selectedChat.content.messageManager.sendAsyncMessage("Social:EnsureFocus");
]]></body> ]]></body>
</method> </method>
@ -499,7 +567,7 @@
aChatbox.isActive = false; aChatbox.isActive = false;
let menu = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "menuitem"); let menu = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "menuitem");
menu.setAttribute("class", "menuitem-iconic"); menu.setAttribute("class", "menuitem-iconic");
menu.setAttribute("label", aChatbox.contentDocument.title); menu.setAttribute("label", aChatbox.content.contentTitle);
menu.setAttribute("image", aChatbox.getAttribute("image")); menu.setAttribute("image", aChatbox.getAttribute("image"));
menu.chat = aChatbox; menu.chat = aChatbox;
this.menuitemMap.set(aChatbox, menu); this.menuitemMap.set(aChatbox, menu);
@ -568,18 +636,16 @@
</method> </method>
<method name="openChat"> <method name="openChat">
<parameter name="aOrigin"/> <parameter name="aOptions"/>
<parameter name="aTitle"/>
<parameter name="aURL"/>
<parameter name="aMode"/>
<parameter name="aCallback"/> <parameter name="aCallback"/>
<body><![CDATA[ <body><![CDATA[
let cb = this.chatboxForURL.get(aURL); let {origin, title, url, mode} = aOptions;
let cb = this.chatboxForURL.get(url);
if (cb && (cb = cb.get())) { if (cb && (cb = cb.get())) {
// A chatbox is still alive to us when it's parented and still has // A chatbox is still alive to us when it's parented and still has
// content. // content.
if (cb.parentNode && cb.contentWindow) { if (cb.parentNode) {
this.showChat(cb, aMode); this.showChat(cb, mode);
if (aCallback) { if (aCallback) {
if (cb._callbacks == null) { if (cb._callbacks == null) {
// Chatbox has already been created, so callback now. // Chatbox has already been created, so callback now.
@ -591,7 +657,7 @@
} }
return cb; return cb;
} }
this.chatboxForURL.delete(aURL); this.chatboxForURL.delete(url);
} }
cb = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "chatbox"); cb = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "chatbox");
cb._callbacks = []; cb._callbacks = [];
@ -600,15 +666,17 @@
// must exist before the (possibly delayed) bindings are created. // must exist before the (possibly delayed) bindings are created.
cb._callbacks.push(aCallback); cb._callbacks.push(aCallback);
} }
cb.remote = !!aOptions.remote;
// src also a javascript property; the src attribute is set in the ctor. // src also a javascript property; the src attribute is set in the ctor.
cb.src = aURL; cb.src = url;
if (aMode == "minimized") if (mode == "minimized")
cb.setAttribute("minimized", "true"); cb.setAttribute("minimized", "true");
cb.setAttribute("origin", aOrigin); cb.setAttribute("origin", origin);
cb.setAttribute("label", aTitle); cb.setAttribute("label", title);
this.insertBefore(cb, this.firstChild); this.insertBefore(cb, this.firstChild);
this.selectedChat = cb; this.selectedChat = cb;
this.chatboxForURL.set(aURL, Cu.getWeakReference(cb)); this.chatboxForURL.set(url, Cu.getWeakReference(cb));
this.resize(); this.resize();
return cb; return cb;
]]></body> ]]></body>
@ -714,21 +782,18 @@
setAttribute("customSize", aChatbox.getAttribute("customSize")); setAttribute("customSize", aChatbox.getAttribute("customSize"));
} }
let document = aChatbox.contentDocument;
let detachEvent = new aChatbox.contentWindow.CustomEvent("socialFrameDetached", {
bubbles: true,
cancelable: true,
});
otherWin.removeEventListener("load", _chatLoad, true); otherWin.removeEventListener("load", _chatLoad, true);
let otherChatbox = otherWin.document.getElementById("chatter"); let otherChatbox = otherWin.document.getElementById("chatter");
aChatbox.setDecorationAttributes(otherChatbox); aChatbox.setDecorationAttributes(otherChatbox);
aChatbox.swapDocShells(otherChatbox); aChatbox.swapDocShells(otherChatbox);
aChatbox.close(); aChatbox.close();
chatbar.chatboxForURL.set(aChatbox.src, Cu.getWeakReference(otherChatbox)); chatbar.chatboxForURL.set(aChatbox.src, Cu.getWeakReference(otherChatbox));
// All processing is done, now we can fire the event. // All processing is done, now we can fire the event.
document.dispatchEvent(detachEvent); otherChatbox.content.messageManager.sendAsyncMessage("Social:CustomEvent", {
name: "socialFrameDetached"
});
deferred.resolve(otherChatbox); deferred.resolve(otherChatbox);
}, true); }, true);

View File

@ -28,7 +28,13 @@ function promiseOpenChat(url, mode, focus, buttonSet = null) {
deferred.resolve(chatbox); deferred.resolve(chatbox);
}, true); }, true);
} }
let chatbox = Chat.open(null, origin, title, url, mode, focus, callback); let chatbox = Chat.open(null, {
origin: origin,
title: title,
url: url,
mode: mode,
focus: focus
}, callback);
if (buttonSet) { if (buttonSet) {
chatbox.setAttribute("buttonSet", buttonSet); chatbox.setAttribute("buttonSet", buttonSet);
} }
@ -42,7 +48,12 @@ function promiseOpenChatCallback(url, mode) {
let title = origin; let title = origin;
let deferred = Promise.defer(); let deferred = Promise.defer();
let callback = deferred.resolve; let callback = deferred.resolve;
Chat.open(null, origin, title, url, mode, undefined, callback); Chat.open(null, {
origin: origin,
title: title,
url: url,
mode: mode
}, callback);
return deferred.promise; return deferred.promise;
} }

View File

@ -40,13 +40,12 @@ function openChatViaWorkerMessage(port, data, callback) {
// so the child has been added, but we don't know if it // so the child has been added, but we don't know if it
// has been intialized - re-request it and the callback // has been intialized - re-request it and the callback
// means it's done. Minimized, same as the worker. // means it's done. Minimized, same as the worker.
chatbar.openChat(SocialSidebar.provider.origin, chatbar.openChat({
SocialSidebar.provider.name, origin: SocialSidebar.provider.origin,
data, title: SocialSidebar.provider.name,
"minimized", url: data,
function() { mode: "minimized"
callback(); }, function() { callback(); });
});
}, },
"No new chat appeared"); "No new chat appeared");
} }

View File

@ -92,27 +92,35 @@ var Chat = {
* *
* @param contentWindow [optional] * @param contentWindow [optional]
* The content window that requested this chat. May be null. * The content window that requested this chat. May be null.
* @param origin * @param options
* Object that may contain the following properties:
* - origin
* The origin for the chat. This is primarily used as an identifier * The origin for the chat. This is primarily used as an identifier
* to help identify all chats from the same provider. * to help identify all chats from the same provider.
* @param title * - title
* The title to be used if a new chat window is created. * The title to be used if a new chat window is created.
* @param url * - url
* The URL for the that. Should be under the origin. If an existing * The URL for the that. Should be under the origin. If an existing
* chatbox exists with the same URL, it will be reused and returned. * chatbox exists with the same URL, it will be reused and returned.
* @param mode [optional] * - mode [optional]
* May be undefined or 'minimized' * May be undefined or 'minimized'
* @param focus [optional] * - focus [optional]
* Indicates if the chatbox should be focused. If undefined the chat * Indicates if the chatbox should be focused. If undefined the chat
* will be focused if the window is currently handling user input (ie, * will be focused if the window is currently handling user input (ie,
* if the chat is being opened as a direct result of user input) * if the chat is being opened as a direct result of user input)
* - remote [optional]
* Indicates if the chatbox browser should use the remote bindings
* to run in the content process when TRUE.
* @param callback
* Function to be invoked once the chat constructed. The chatbox binding
* is passed as the first argument.
*
* @return A chatbox binding. This binding has a number of promises which * @return A chatbox binding. This binding has a number of promises which
* can be used to determine when the chatbox is being created and * can be used to determine when the chatbox is being created and
* has loaded. Will return null if no chat can be created (Which * has loaded. Will return null if no chat can be created (Which
* should only happen in edge-cases) * should only happen in edge-cases)
*/ */
open: function(contentWindow, origin, title, url, mode, focus, callback) { open: function(contentWindow, options, callback) {
let chromeWindow = this.findChromeWindowForChats(contentWindow); let chromeWindow = this.findChromeWindowForChats(contentWindow);
if (!chromeWindow) { if (!chromeWindow) {
Cu.reportError("Failed to open a chat window - no host window could be found."); Cu.reportError("Failed to open a chat window - no host window could be found.");
@ -121,18 +129,25 @@ var Chat = {
let chatbar = chromeWindow.document.getElementById("pinnedchats"); let chatbar = chromeWindow.document.getElementById("pinnedchats");
chatbar.hidden = false; chatbar.hidden = false;
let chatbox = chatbar.openChat(origin, title, url, mode, callback); if (options.remote) {
// Double check that current window can handle remote browser elements.
let browser = chromeWindow.gBrowser && chromeWindow.gBrowser.selectedBrowser;
if (!browser || browser.getAttribute("remote") != "true") {
options.remote = false;
}
}
let chatbox = chatbar.openChat(options, callback);
// getAttention is ignored if the target window is already foreground, so // getAttention is ignored if the target window is already foreground, so
// we can call it unconditionally. // we can call it unconditionally.
chromeWindow.getAttention(); chromeWindow.getAttention();
// If focus is undefined we want automatic focus handling, and only focus // If focus is undefined we want automatic focus handling, and only focus
// if a direct result of user action. // if a direct result of user action.
if (focus === undefined) { if (!("focus" in options)) {
let dwu = chromeWindow.QueryInterface(Ci.nsIInterfaceRequestor) let dwu = chromeWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils); .getInterface(Ci.nsIDOMWindowUtils);
focus = dwu.isHandlingUserInput; options.focus = dwu.isHandlingUserInput;
} }
if (focus) { if (options.focus) {
chatbar.focus(); chatbar.focus();
} }
return chatbox; return chatbox;

View File

@ -81,6 +81,11 @@ var PanelFrameInternal = {
attrs["message"] = "true"; attrs["message"] = "true";
attrs["messagemanagergroup"] = aType; attrs["messagemanagergroup"] = aType;
} }
if (aType == "loop") {
attrs.message = true;
attrs.messagemanagergroup = "social";
attrs.autocompletepopup = "PopupAutoComplete";
}
for (let [k, v] of Iterator(attrs)) { for (let [k, v] of Iterator(attrs)) {
frame.setAttribute(k, v); frame.setAttribute(k, v);
} }
@ -127,6 +132,9 @@ var PanelFrame = {
let notificationFrameId = aToolbarButton.getAttribute("notificationFrameId"); let notificationFrameId = aToolbarButton.getAttribute("notificationFrameId");
let notificationFrame = aWindow.document.getElementById(notificationFrameId); let notificationFrame = aWindow.document.getElementById(notificationFrameId);
// the xbl bindings for the iframe probably don't exist yet, so we can't
// access iframe.messageManager directly - but can get at it with this dance.
let mm = notificationFrame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager;
// Clear dimensions on all browsers so the panel size will // Clear dimensions on all browsers so the panel size will
// only use the selected browser. // only use the selected browser.
@ -137,9 +145,7 @@ var PanelFrame = {
} }
function dispatchPanelEvent(name) { function dispatchPanelEvent(name) {
let evt = notificationFrame.contentDocument.createEvent("CustomEvent"); mm.sendAsyncMessage("Social:CustomEvent", { name: name });
evt.initCustomEvent(name, true, true, {});
notificationFrame.contentDocument.documentElement.dispatchEvent(evt);
} }
// we only use a dynamic resizer when we're located the toolbar. // we only use a dynamic resizer when we're located the toolbar.
@ -152,35 +158,28 @@ var PanelFrame = {
anchorBtn.removeAttribute("open"); anchorBtn.removeAttribute("open");
if (dynamicResizer) if (dynamicResizer)
dynamicResizer.stop(); dynamicResizer.stop();
notificationFrame.docShell.isActive = false; notificationFrame.docShellIsActive = false;
dispatchPanelEvent(aType + "FrameHide"); dispatchPanelEvent(aType + "FrameHide");
}); });
panel.addEventListener("popupshown", function onpopupshown() { panel.addEventListener("popupshown", function onpopupshown() {
panel.removeEventListener("popupshown", onpopupshown); panel.removeEventListener("popupshown", onpopupshown);
let initFrameShow = () => {
notificationFrame.docShell.isActive = true;
notificationFrame.docShell.isAppTab = true;
if (dynamicResizer)
dynamicResizer.start(panel, notificationFrame);
dispatchPanelEvent(aType + "FrameShow");
};
// This attribute is needed on both the button and the // This attribute is needed on both the button and the
// containing toolbaritem since the buttons on OS X have // containing toolbaritem since the buttons on OS X have
// moz-appearance:none, while their container gets // moz-appearance:none, while their container gets
// moz-appearance:toolbarbutton due to the way that toolbar buttons // moz-appearance:toolbarbutton due to the way that toolbar buttons
// get combined on OS X. // get combined on OS X.
anchorBtn.setAttribute("open", "true"); anchorBtn.setAttribute("open", "true");
if (notificationFrame.contentDocument &&
notificationFrame.contentDocument.readyState == "complete") { mm.sendAsyncMessage("WaitForDOMContentLoaded");
initFrameShow(); mm.addMessageListener("DOMContentLoaded", function onloaded() {
} else { mm.removeMessageListener("DOMContentLoaded", onloaded);
// first time load, wait for load and dispatch after load mm = notificationFrame.messageManager;
notificationFrame.addEventListener("load", function panelBrowserOnload(e) { notificationFrame.docShellIsActive = true;
notificationFrame.removeEventListener("load", panelBrowserOnload, true); if (dynamicResizer)
initFrameShow(); dynamicResizer.start(panel, notificationFrame);
}, true); dispatchPanelEvent(aType + "FrameShow");
} });
}); });
let anchor = aWindow.document.getAnonymousElementByAttribute(anchorBtn, "class", "toolbarbutton-icon"); let anchor = aWindow.document.getAnonymousElementByAttribute(anchorBtn, "class", "toolbarbutton-icon");

View File

@ -242,6 +242,17 @@ function attachToWindow(provider, targetWindow) {
} }
function hookWindowCloseForPanelClose(targetWindow) { function hookWindowCloseForPanelClose(targetWindow) {
let _mozSocialDOMWindowClose;
if ("messageManager" in targetWindow) {
let mm = targetWindow.messageManager;
mm.sendAsyncMessage("Social:HookWindowCloseForPanelClose");
mm.addMessageListener("DOMWindowClose", _mozSocialDOMWindowClose = function() {
closePanel(targetWindow);
});
return;
}
// We allow window.close() to close the panel, so add an event handler for // We allow window.close() to close the panel, so add an event handler for
// this, then cancel the event (so the window itself doesn't die) and // this, then cancel the event (so the window itself doesn't die) and
// close the panel instead. // close the panel instead.
@ -251,21 +262,12 @@ function hookWindowCloseForPanelClose(targetWindow) {
.getInterface(Ci.nsIDOMWindowUtils); .getInterface(Ci.nsIDOMWindowUtils);
dwu.allowScriptsToClose(); dwu.allowScriptsToClose();
targetWindow.addEventListener("DOMWindowClose", function _mozSocialDOMWindowClose(evt) { targetWindow.addEventListener("DOMWindowClose", _mozSocialDOMWindowClose = function(evt) {
let elt = targetWindow.QueryInterface(Ci.nsIInterfaceRequestor) let elt = targetWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation) .getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell) .QueryInterface(Ci.nsIDocShell)
.chromeEventHandler; .chromeEventHandler;
while (elt) { closePanel(elt);
if (elt.localName == "panel") {
elt.hidePopup();
break;
} else if (elt.localName == "chatbox") {
elt.close();
break;
}
elt = elt.parentNode;
}
// preventDefault stops the default window.close() function being called, // preventDefault stops the default window.close() function being called,
// which doesn't actually close anything but causes things to get into // which doesn't actually close anything but causes things to get into
// a bad state (an internal 'closed' flag is set and debug builds start // a bad state (an internal 'closed' flag is set and debug builds start
@ -277,6 +279,19 @@ function hookWindowCloseForPanelClose(targetWindow) {
}, true); }, true);
} }
function closePanel(elt) {
while (elt) {
if (elt.localName == "panel") {
elt.hidePopup();
break;
} else if (elt.localName == "chatbox") {
elt.close();
break;
}
elt = elt.parentNode;
}
}
function schedule(callback) { function schedule(callback) {
Services.tm.mainThread.dispatch(callback, Ci.nsIThread.DISPATCH_NORMAL); Services.tm.mainThread.dispatch(callback, Ci.nsIThread.DISPATCH_NORMAL);
} }
@ -298,11 +313,15 @@ this.openChatWindow =
return; return;
} }
let chatbox = Chat.open(contentWindow, provider.origin, provider.name, let chatbox = Chat.open(contentWindow, {
fullURI.spec, mode); origin: provider.origin,
title: provider.name,
url: fullURI.spec,
mode: mode
});
if (callback) { if (callback) {
chatbox.promiseChatLoaded.then(() => { chatbox.promiseChatLoaded.then(() => {
callback(chatbox.contentWindow); callback(chatbox);
}); });
} }
} }

View File

@ -599,7 +599,7 @@ var outerWindowID = content.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils) .getInterface(Ci.nsIDOMWindowUtils)
.outerWindowID; .outerWindowID;
var initData = sendSyncMessage("Browser:Init", {outerWindowID: outerWindowID}); var initData = sendSyncMessage("Browser:Init", {outerWindowID: outerWindowID});
if (initData.length) { if (initData.length && initData[0]) {
docShell.useGlobalHistory = initData[0].useGlobalHistory; docShell.useGlobalHistory = initData[0].useGlobalHistory;
if (initData[0].initPopup) { if (initData[0].initPopup) {
setTimeout(() => AutoCompletePopup.init(), 0); setTimeout(() => AutoCompletePopup.init(), 0);

View File

@ -1207,8 +1207,13 @@
if (window.PopupNotifications) if (window.PopupNotifications)
PopupNotifications._swapBrowserNotifications(aOtherBrowser, this); PopupNotifications._swapBrowserNotifications(aOtherBrowser, this);
this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner) try {
.swapFrameLoaders(aOtherBrowser); this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner)
.swapFrameLoaders(aOtherBrowser);
} catch(ex) {
// This may not be implemented for browser elements that are not
// attached to a BrowserDOMWindow.
}
// Before we swap the actual docShell property we need to detach the // Before we swap the actual docShell property we need to detach the
// form fill controller from those docShells. // form fill controller from those docShells.
@ -1237,8 +1242,10 @@
this._remoteWebNavigationImpl.swapBrowser(this); this._remoteWebNavigationImpl.swapBrowser(this);
aOtherBrowser._remoteWebNavigationImpl.swapBrowser(aOtherBrowser); aOtherBrowser._remoteWebNavigationImpl.swapBrowser(aOtherBrowser);
this._remoteWebProgressManager.swapBrowser(this); if (this._remoteWebProgressManager && aOtherBrowser._remoteWebProgressManager) {
aOtherBrowser._remoteWebProgressManager.swapBrowser(aOtherBrowser); this._remoteWebProgressManager.swapBrowser(this);
aOtherBrowser._remoteWebProgressManager.swapBrowser(aOtherBrowser);
}
if (this._remoteFinder) if (this._remoteFinder)
this._remoteFinder.swapBrowser(this); this._remoteFinder.swapBrowser(this);

View File

@ -390,7 +390,12 @@
return; return;
this.mDestroyed = true; this.mDestroyed = true;
this.controllers.removeController(this._controller); try {
this.controllers.removeController(this._controller);
} catch (ex) {
// This can fail when this browser element is not attached to a
// BrowserDOMWindow.
}
if (!this.hasAttribute("disablehistory")) { if (!this.hasAttribute("disablehistory")) {
try { try {