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
// access iframe.messageManager directly - but can get at it with this dance.
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}" });
},
@ -512,7 +512,7 @@ SocialShare = {
iframe.setAttribute("messagemanagergroup", "social");
panel.lastChild.appendChild(iframe);
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}" });
this.populateProviderMenu();

View File

@ -118,6 +118,10 @@ panelview:not([mainview]):not([current]) {
visibility: collapse;
}
browser[frameType="social"][remote="true"] {
-moz-binding: url("chrome://global/content/bindings/remote-browser.xml#remote-browser");
}
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
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
// and replace them with a social-specific error page
SocialErrorListener = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMEventListener,
Ci.nsIWebProgressListener,
Ci.nsISupportsWeakReference,
Ci.nsISupports]),
@ -25,21 +41,132 @@ SocialErrorListener = {
urlTemplate: null,
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:WaitForDocumentVisible", this);
addMessageListener("WaitForDOMContentLoaded", this);
let webProgress = docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIWebProgress);
webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_REQUEST |
Ci.nsIWebProgress.NOTIFY_LOCATION);
},
receiveMessage(message) {
switch(message.name) {
case "Social:SetErrorURL": {
// either a url or null to reset to default template
this.urlTemplate = message.objects.template;
}
let document = content.document;
switch (message.name) {
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() {
// if this is about:providerdirectory, use the directory iframe
let frame = docShell.chromeEventHandler;

View File

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

View File

@ -28,7 +28,13 @@ function promiseOpenChat(url, mode, focus, buttonSet = null) {
deferred.resolve(chatbox);
}, 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) {
chatbox.setAttribute("buttonSet", buttonSet);
}
@ -42,7 +48,12 @@ function promiseOpenChatCallback(url, mode) {
let title = origin;
let deferred = Promise.defer();
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;
}

View File

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

View File

@ -92,27 +92,35 @@ var Chat = {
*
* @param contentWindow [optional]
* 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
* to help identify all chats from the same provider.
* @param title
* - title
* 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
* chatbox exists with the same URL, it will be reused and returned.
* @param mode [optional]
* - mode [optional]
* May be undefined or 'minimized'
* @param focus [optional]
* - focus [optional]
* Indicates if the chatbox should be focused. If undefined the chat
* 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)
* - 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
* can be used to determine when the chatbox is being created and
* has loaded. Will return null if no chat can be created (Which
* 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);
if (!chromeWindow) {
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");
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
// we can call it unconditionally.
chromeWindow.getAttention();
// If focus is undefined we want automatic focus handling, and only focus
// if a direct result of user action.
if (focus === undefined) {
if (!("focus" in options)) {
let dwu = chromeWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
focus = dwu.isHandlingUserInput;
options.focus = dwu.isHandlingUserInput;
}
if (focus) {
if (options.focus) {
chatbar.focus();
}
return chatbox;

View File

@ -81,6 +81,11 @@ var PanelFrameInternal = {
attrs["message"] = "true";
attrs["messagemanagergroup"] = aType;
}
if (aType == "loop") {
attrs.message = true;
attrs.messagemanagergroup = "social";
attrs.autocompletepopup = "PopupAutoComplete";
}
for (let [k, v] of Iterator(attrs)) {
frame.setAttribute(k, v);
}
@ -127,6 +132,9 @@ var PanelFrame = {
let notificationFrameId = aToolbarButton.getAttribute("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
// only use the selected browser.
@ -137,9 +145,7 @@ var PanelFrame = {
}
function dispatchPanelEvent(name) {
let evt = notificationFrame.contentDocument.createEvent("CustomEvent");
evt.initCustomEvent(name, true, true, {});
notificationFrame.contentDocument.documentElement.dispatchEvent(evt);
mm.sendAsyncMessage("Social:CustomEvent", { name: name });
}
// we only use a dynamic resizer when we're located the toolbar.
@ -152,35 +158,28 @@ var PanelFrame = {
anchorBtn.removeAttribute("open");
if (dynamicResizer)
dynamicResizer.stop();
notificationFrame.docShell.isActive = false;
notificationFrame.docShellIsActive = false;
dispatchPanelEvent(aType + "FrameHide");
});
panel.addEventListener("popupshown", function 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
// 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.
anchorBtn.setAttribute("open", "true");
if (notificationFrame.contentDocument &&
notificationFrame.contentDocument.readyState == "complete") {
initFrameShow();
} else {
// first time load, wait for load and dispatch after load
notificationFrame.addEventListener("load", function panelBrowserOnload(e) {
notificationFrame.removeEventListener("load", panelBrowserOnload, true);
initFrameShow();
}, true);
}
mm.sendAsyncMessage("WaitForDOMContentLoaded");
mm.addMessageListener("DOMContentLoaded", function onloaded() {
mm.removeMessageListener("DOMContentLoaded", onloaded);
mm = notificationFrame.messageManager;
notificationFrame.docShellIsActive = true;
if (dynamicResizer)
dynamicResizer.start(panel, notificationFrame);
dispatchPanelEvent(aType + "FrameShow");
});
});
let anchor = aWindow.document.getAnonymousElementByAttribute(anchorBtn, "class", "toolbarbutton-icon");

View File

@ -242,6 +242,17 @@ function attachToWindow(provider, 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
// this, then cancel the event (so the window itself doesn't die) and
// close the panel instead.
@ -251,21 +262,12 @@ function hookWindowCloseForPanelClose(targetWindow) {
.getInterface(Ci.nsIDOMWindowUtils);
dwu.allowScriptsToClose();
targetWindow.addEventListener("DOMWindowClose", function _mozSocialDOMWindowClose(evt) {
targetWindow.addEventListener("DOMWindowClose", _mozSocialDOMWindowClose = function(evt) {
let elt = targetWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell)
.chromeEventHandler;
while (elt) {
if (elt.localName == "panel") {
elt.hidePopup();
break;
} else if (elt.localName == "chatbox") {
elt.close();
break;
}
elt = elt.parentNode;
}
closePanel(elt);
// 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
@ -277,6 +279,19 @@ function hookWindowCloseForPanelClose(targetWindow) {
}, 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) {
Services.tm.mainThread.dispatch(callback, Ci.nsIThread.DISPATCH_NORMAL);
}
@ -298,11 +313,15 @@ this.openChatWindow =
return;
}
let chatbox = Chat.open(contentWindow, provider.origin, provider.name,
fullURI.spec, mode);
let chatbox = Chat.open(contentWindow, {
origin: provider.origin,
title: provider.name,
url: fullURI.spec,
mode: mode
});
if (callback) {
chatbox.promiseChatLoaded.then(() => {
callback(chatbox.contentWindow);
callback(chatbox);
});
}
}

View File

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

View File

@ -1207,8 +1207,13 @@
if (window.PopupNotifications)
PopupNotifications._swapBrowserNotifications(aOtherBrowser, this);
this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner)
.swapFrameLoaders(aOtherBrowser);
try {
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
// form fill controller from those docShells.
@ -1237,8 +1242,10 @@
this._remoteWebNavigationImpl.swapBrowser(this);
aOtherBrowser._remoteWebNavigationImpl.swapBrowser(aOtherBrowser);
this._remoteWebProgressManager.swapBrowser(this);
aOtherBrowser._remoteWebProgressManager.swapBrowser(aOtherBrowser);
if (this._remoteWebProgressManager && aOtherBrowser._remoteWebProgressManager) {
this._remoteWebProgressManager.swapBrowser(this);
aOtherBrowser._remoteWebProgressManager.swapBrowser(aOtherBrowser);
}
if (this._remoteFinder)
this._remoteFinder.swapBrowser(this);

View File

@ -390,7 +390,12 @@
return;
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")) {
try {