browser/base/content/socialchat.xml

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 <?xml version="1.0"?>
michael@0 2
michael@0 3 <bindings id="socialChatBindings"
michael@0 4 xmlns="http://www.mozilla.org/xbl"
michael@0 5 xmlns:xbl="http://www.mozilla.org/xbl"
michael@0 6 xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
michael@0 7
michael@0 8 <binding id="chatbox">
michael@0 9 <content orient="vertical" mousethrough="never">
michael@0 10 <xul:hbox class="chat-titlebar" xbl:inherits="minimized,selected,activity" align="baseline">
michael@0 11 <xul:hbox flex="1" onclick="document.getBindingParent(this).onTitlebarClick(event);">
michael@0 12 <xul:image class="chat-status-icon" xbl:inherits="src=image"/>
michael@0 13 <xul:label class="chat-title" flex="1" xbl:inherits="value=label" crop="center"/>
michael@0 14 </xul:hbox>
michael@0 15 <xul:toolbarbutton anonid="notification-icon" class="notification-anchor-icon chat-toolbarbutton"
michael@0 16 oncommand="document.getBindingParent(this).showNotifications(); event.stopPropagation();"/>
michael@0 17 <xul:toolbarbutton anonid="minimize" class="chat-minimize-button chat-toolbarbutton"
michael@0 18 oncommand="document.getBindingParent(this).toggle();"/>
michael@0 19 <xul:toolbarbutton anonid="swap" class="chat-swap-button chat-toolbarbutton"
michael@0 20 oncommand="document.getBindingParent(this).swapWindows();"/>
michael@0 21 <xul:toolbarbutton anonid="close" class="chat-close-button chat-toolbarbutton"
michael@0 22 oncommand="document.getBindingParent(this).close();"/>
michael@0 23 </xul:hbox>
michael@0 24 <xul:browser anonid="content" class="chat-frame" flex="1"
michael@0 25 context="contentAreaContextMenu"
michael@0 26 disableglobalhistory="true"
michael@0 27 tooltip="aHTMLTooltip"
michael@0 28 xbl:inherits="src,origin" type="content"/>
michael@0 29 </content>
michael@0 30
michael@0 31 <implementation implements="nsIDOMEventListener">
michael@0 32 <constructor><![CDATA[
michael@0 33 let Social = Components.utils.import("resource:///modules/Social.jsm", {}).Social;
michael@0 34 this.content.__defineGetter__("popupnotificationanchor",
michael@0 35 () => document.getAnonymousElementByAttribute(this, "anonid", "notification-icon"));
michael@0 36 Social.setErrorListener(this.content, function(aBrowser) {
michael@0 37 aBrowser.webNavigation.loadURI("about:socialerror?mode=compactInfo&origin=" +
michael@0 38 encodeURIComponent(aBrowser.getAttribute("origin")),
michael@0 39 null, null, null, null);
michael@0 40 });
michael@0 41 if (!this.chatbar) {
michael@0 42 document.getAnonymousElementByAttribute(this, "anonid", "minimize").hidden = true;
michael@0 43 document.getAnonymousElementByAttribute(this, "anonid", "close").hidden = true;
michael@0 44 }
michael@0 45 let contentWindow = this.contentWindow;
michael@0 46 this.addEventListener("DOMContentLoaded", function DOMContentLoaded(event) {
michael@0 47 if (event.target != this.contentDocument)
michael@0 48 return;
michael@0 49 this.removeEventListener("DOMContentLoaded", DOMContentLoaded, true);
michael@0 50 this.isActive = !this.minimized;
michael@0 51 // process this._callbacks, then set to null so the chatbox creator
michael@0 52 // knows to make new callbacks immediately.
michael@0 53 if (this._callbacks) {
michael@0 54 for (let callback of this._callbacks) {
michael@0 55 if (callback)
michael@0 56 callback(contentWindow);
michael@0 57 }
michael@0 58 this._callbacks = null;
michael@0 59 }
michael@0 60
michael@0 61 // content can send a socialChatActivity event to have the UI update.
michael@0 62 let chatActivity = function() {
michael@0 63 this.setAttribute("activity", true);
michael@0 64 if (this.chatbar)
michael@0 65 this.chatbar.updateTitlebar(this);
michael@0 66 }.bind(this);
michael@0 67 contentWindow.addEventListener("socialChatActivity", chatActivity);
michael@0 68 contentWindow.addEventListener("unload", function unload() {
michael@0 69 contentWindow.removeEventListener("unload", unload);
michael@0 70 contentWindow.removeEventListener("socialChatActivity", chatActivity);
michael@0 71 });
michael@0 72 }, true);
michael@0 73 if (this.src)
michael@0 74 this.setAttribute("src", this.src);
michael@0 75 ]]></constructor>
michael@0 76
michael@0 77 <field name="content" readonly="true">
michael@0 78 document.getAnonymousElementByAttribute(this, "anonid", "content");
michael@0 79 </field>
michael@0 80
michael@0 81 <property name="contentWindow">
michael@0 82 <getter>
michael@0 83 return this.content.contentWindow;
michael@0 84 </getter>
michael@0 85 </property>
michael@0 86
michael@0 87 <property name="contentDocument">
michael@0 88 <getter>
michael@0 89 return this.content.contentDocument;
michael@0 90 </getter>
michael@0 91 </property>
michael@0 92
michael@0 93 <property name="minimized">
michael@0 94 <getter>
michael@0 95 return this.getAttribute("minimized") == "true";
michael@0 96 </getter>
michael@0 97 <setter><![CDATA[
michael@0 98 // Note that this.isActive is set via our transitionend handler so
michael@0 99 // the content doesn't see intermediate values.
michael@0 100 let parent = this.chatbar;
michael@0 101 if (val) {
michael@0 102 this.setAttribute("minimized", "true");
michael@0 103 // If this chat is the selected one a new one needs to be selected.
michael@0 104 if (parent && parent.selectedChat == this)
michael@0 105 parent._selectAnotherChat();
michael@0 106 } else {
michael@0 107 this.removeAttribute("minimized");
michael@0 108 // this chat gets selected.
michael@0 109 if (parent)
michael@0 110 parent.selectedChat = this;
michael@0 111 }
michael@0 112 ]]></setter>
michael@0 113 </property>
michael@0 114
michael@0 115 <property name="chatbar">
michael@0 116 <getter>
michael@0 117 if (this.parentNode.nodeName == "chatbar")
michael@0 118 return this.parentNode;
michael@0 119 return null;
michael@0 120 </getter>
michael@0 121 </property>
michael@0 122
michael@0 123 <property name="isActive">
michael@0 124 <getter>
michael@0 125 return this.content.docShell.isActive;
michael@0 126 </getter>
michael@0 127 <setter>
michael@0 128 this.content.docShell.isActive = !!val;
michael@0 129
michael@0 130 // let the chat frame know if it is being shown or hidden
michael@0 131 let evt = this.contentDocument.createEvent("CustomEvent");
michael@0 132 evt.initCustomEvent(val ? "socialFrameShow" : "socialFrameHide", true, true, {});
michael@0 133 this.contentDocument.documentElement.dispatchEvent(evt);
michael@0 134 </setter>
michael@0 135 </property>
michael@0 136
michael@0 137 <method name="showNotifications">
michael@0 138 <body><![CDATA[
michael@0 139 PopupNotifications._reshowNotifications(this.content.popupnotificationanchor,
michael@0 140 this.content);
michael@0 141 ]]></body>
michael@0 142 </method>
michael@0 143
michael@0 144 <method name="swapDocShells">
michael@0 145 <parameter name="aTarget"/>
michael@0 146 <body><![CDATA[
michael@0 147 aTarget.setAttribute('label', this.contentDocument.title);
michael@0 148 aTarget.src = this.src;
michael@0 149 aTarget.content.setAttribute("origin", this.content.getAttribute("origin"));
michael@0 150 aTarget.content.popupnotificationanchor.className = this.content.popupnotificationanchor.className;
michael@0 151 this.content.socialErrorListener.remove();
michael@0 152 aTarget.content.socialErrorListener.remove();
michael@0 153 this.content.swapDocShells(aTarget.content);
michael@0 154 Social.setErrorListener(this.content, function(aBrowser) {}); // 'this' will be destroyed soon.
michael@0 155 Social.setErrorListener(aTarget.content, function(aBrowser) {
michael@0 156 aBrowser.webNavigation.loadURI("about:socialerror?mode=compactInfo&origin=" +
michael@0 157 encodeURIComponent(aBrowser.getAttribute("origin")),
michael@0 158 null, null, null, null);
michael@0 159 });
michael@0 160 ]]></body>
michael@0 161 </method>
michael@0 162
michael@0 163 <method name="onTitlebarClick">
michael@0 164 <parameter name="aEvent"/>
michael@0 165 <body><![CDATA[
michael@0 166 if (!this.chatbar)
michael@0 167 return;
michael@0 168 if (aEvent.button == 0) { // left-click: toggle minimized.
michael@0 169 this.toggle();
michael@0 170 // if we restored it, we want to focus it.
michael@0 171 if (!this.minimized)
michael@0 172 this.chatbar.focus();
michael@0 173 } else if (aEvent.button == 1) // middle-click: close chat
michael@0 174 this.close();
michael@0 175 ]]></body>
michael@0 176 </method>
michael@0 177
michael@0 178 <method name="close">
michael@0 179 <body><![CDATA[
michael@0 180 if (this.chatbar)
michael@0 181 this.chatbar.remove(this);
michael@0 182 else
michael@0 183 window.close();
michael@0 184 ]]></body>
michael@0 185 </method>
michael@0 186
michael@0 187 <method name="swapWindows">
michael@0 188 <body><![CDATA[
michael@0 189 let provider = Social._getProviderFromOrigin(this.content.getAttribute("origin"));
michael@0 190 if (this.chatbar) {
michael@0 191 this.chatbar.detachChatbox(this, { "centerscreen": "yes" }, win => {
michael@0 192 win.document.title = provider.name;
michael@0 193 });
michael@0 194 } else {
michael@0 195 // attach this chatbox to the topmost browser window
michael@0 196 let findChromeWindowForChats = Cu.import("resource://gre/modules/MozSocialAPI.jsm").findChromeWindowForChats;
michael@0 197 let win = findChromeWindowForChats();
michael@0 198 let chatbar = win.SocialChatBar.chatbar;
michael@0 199 chatbar.openChat(provider, "about:blank", win => {
michael@0 200 let cb = chatbar.selectedChat;
michael@0 201 this.swapDocShells(cb);
michael@0 202
michael@0 203 // chatboxForURL is a map of URL -> chatbox used to avoid opening
michael@0 204 // duplicate chat windows. Ensure reattached chat windows aren't
michael@0 205 // registered with about:blank as their URL, otherwise reattaching
michael@0 206 // more than one chat window isn't possible.
michael@0 207 chatbar.chatboxForURL.delete("about:blank");
michael@0 208 chatbar.chatboxForURL.set(this.src, Cu.getWeakReference(cb));
michael@0 209
michael@0 210 chatbar.focus();
michael@0 211 this.close();
michael@0 212 });
michael@0 213 }
michael@0 214 ]]></body>
michael@0 215 </method>
michael@0 216
michael@0 217 <method name="toggle">
michael@0 218 <body><![CDATA[
michael@0 219 this.minimized = !this.minimized;
michael@0 220 ]]></body>
michael@0 221 </method>
michael@0 222 </implementation>
michael@0 223
michael@0 224 <handlers>
michael@0 225 <handler event="focus" phase="capturing">
michael@0 226 if (this.chatbar)
michael@0 227 this.chatbar.selectedChat = this;
michael@0 228 </handler>
michael@0 229 <handler event="DOMTitleChanged"><![CDATA[
michael@0 230 this.setAttribute('label', this.contentDocument.title);
michael@0 231 if (this.chatbar)
michael@0 232 this.chatbar.updateTitlebar(this);
michael@0 233 ]]></handler>
michael@0 234 <handler event="DOMLinkAdded"><![CDATA[
michael@0 235 // much of this logic is from DOMLinkHandler in browser.js
michael@0 236 // this sets the presence icon for a chat user, we simply use favicon style updating
michael@0 237 let link = event.originalTarget;
michael@0 238 let rel = link.rel && link.rel.toLowerCase();
michael@0 239 if (!link || !link.ownerDocument || !rel || !link.href)
michael@0 240 return;
michael@0 241 if (link.rel.indexOf("icon") < 0)
michael@0 242 return;
michael@0 243
michael@0 244 let ContentLinkHandler = Cu.import("resource:///modules/ContentLinkHandler.jsm", {}).ContentLinkHandler;
michael@0 245 let uri = ContentLinkHandler.getLinkIconURI(link);
michael@0 246 if (!uri)
michael@0 247 return;
michael@0 248
michael@0 249 // we made it this far, use it
michael@0 250 this.setAttribute('image', uri.spec);
michael@0 251 if (this.chatbar)
michael@0 252 this.chatbar.updateTitlebar(this);
michael@0 253 ]]></handler>
michael@0 254 <handler event="transitionend">
michael@0 255 if (this.isActive == this.minimized)
michael@0 256 this.isActive = !this.minimized;
michael@0 257 </handler>
michael@0 258 </handlers>
michael@0 259 </binding>
michael@0 260
michael@0 261 <binding id="chatbar">
michael@0 262 <content>
michael@0 263 <xul:hbox align="end" pack="end" anonid="innerbox" class="chatbar-innerbox" mousethrough="always" flex="1">
michael@0 264 <xul:spacer flex="1" anonid="spacer" class="chatbar-overflow-spacer"/>
michael@0 265 <xul:toolbarbutton anonid="nub" class="chatbar-button" type="menu" collapsed="true" mousethrough="never">
michael@0 266 <xul:menupopup anonid="nubMenu" oncommand="document.getBindingParent(this).showChat(event.target.chat)"/>
michael@0 267 </xul:toolbarbutton>
michael@0 268 <children/>
michael@0 269 </xul:hbox>
michael@0 270 </content>
michael@0 271
michael@0 272 <implementation implements="nsIDOMEventListener">
michael@0 273 <constructor>
michael@0 274 // to avoid reflows we cache the width of the nub.
michael@0 275 this.cachedWidthNub = 0;
michael@0 276 this._selectedChat = null;
michael@0 277 </constructor>
michael@0 278
michael@0 279 <field name="innerbox" readonly="true">
michael@0 280 document.getAnonymousElementByAttribute(this, "anonid", "innerbox");
michael@0 281 </field>
michael@0 282
michael@0 283 <field name="menupopup" readonly="true">
michael@0 284 document.getAnonymousElementByAttribute(this, "anonid", "nubMenu");
michael@0 285 </field>
michael@0 286
michael@0 287 <field name="nub" readonly="true">
michael@0 288 document.getAnonymousElementByAttribute(this, "anonid", "nub");
michael@0 289 </field>
michael@0 290
michael@0 291 <method name="focus">
michael@0 292 <body><![CDATA[
michael@0 293 if (!this.selectedChat)
michael@0 294 return;
michael@0 295 Services.focus.focusedWindow = this.selectedChat.contentWindow;
michael@0 296 ]]></body>
michael@0 297 </method>
michael@0 298
michael@0 299 <method name="_isChatFocused">
michael@0 300 <parameter name="aChatbox"/>
michael@0 301 <body><![CDATA[
michael@0 302 // If there are no XBL bindings for the chat it can't be focused.
michael@0 303 if (!aChatbox.content)
michael@0 304 return false;
michael@0 305 let fw = Services.focus.focusedWindow;
michael@0 306 if (!fw)
michael@0 307 return false;
michael@0 308 // We want to see if the focused window is in the subtree below our browser...
michael@0 309 let containingBrowser = fw.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 310 .getInterface(Ci.nsIWebNavigation)
michael@0 311 .QueryInterface(Ci.nsIDocShell)
michael@0 312 .chromeEventHandler;
michael@0 313 return containingBrowser == aChatbox.content;
michael@0 314 ]]></body>
michael@0 315 </method>
michael@0 316
michael@0 317 <property name="selectedChat">
michael@0 318 <getter><![CDATA[
michael@0 319 return this._selectedChat;
michael@0 320 ]]></getter>
michael@0 321 <setter><![CDATA[
michael@0 322 // this is pretty horrible, but we:
michael@0 323 // * want to avoid doing touching 'selected' attribute when the
michael@0 324 // specified chat is already selected.
michael@0 325 // * remove 'activity' attribute on newly selected tab *even if*
michael@0 326 // newly selected is already selected.
michael@0 327 // * need to handle either current or new being null.
michael@0 328 if (this._selectedChat != val) {
michael@0 329 if (this._selectedChat) {
michael@0 330 this._selectedChat.removeAttribute("selected");
michael@0 331 }
michael@0 332 this._selectedChat = val;
michael@0 333 if (val) {
michael@0 334 this._selectedChat.setAttribute("selected", "true");
michael@0 335 }
michael@0 336 }
michael@0 337 if (val) {
michael@0 338 this._selectedChat.removeAttribute("activity");
michael@0 339 }
michael@0 340 ]]></setter>
michael@0 341 </property>
michael@0 342
michael@0 343 <field name="menuitemMap">new WeakMap()</field>
michael@0 344 <field name="chatboxForURL">new Map();</field>
michael@0 345
michael@0 346 <property name="hasCollapsedChildren">
michael@0 347 <getter><![CDATA[
michael@0 348 return !!this.querySelector("[collapsed]");
michael@0 349 ]]></getter>
michael@0 350 </property>
michael@0 351
michael@0 352 <property name="collapsedChildren">
michael@0 353 <getter><![CDATA[
michael@0 354 // A generator yielding all collapsed chatboxes, in the order in
michael@0 355 // which they should be restored.
michael@0 356 let child = this.lastElementChild;
michael@0 357 while (child) {
michael@0 358 if (child.collapsed)
michael@0 359 yield child;
michael@0 360 child = child.previousElementSibling;
michael@0 361 }
michael@0 362 ]]></getter>
michael@0 363 </property>
michael@0 364
michael@0 365 <property name="visibleChildren">
michael@0 366 <getter><![CDATA[
michael@0 367 // A generator yielding all non-collapsed chatboxes.
michael@0 368 let child = this.firstElementChild;
michael@0 369 while (child) {
michael@0 370 if (!child.collapsed)
michael@0 371 yield child;
michael@0 372 child = child.nextElementSibling;
michael@0 373 }
michael@0 374 ]]></getter>
michael@0 375 </property>
michael@0 376
michael@0 377 <property name="collapsibleChildren">
michael@0 378 <getter><![CDATA[
michael@0 379 // A generator yielding all children which are able to be collapsed
michael@0 380 // in the order in which they should be collapsed.
michael@0 381 // (currently this is all visible ones other than the selected one.)
michael@0 382 for (let child of this.visibleChildren)
michael@0 383 if (child != this.selectedChat)
michael@0 384 yield child;
michael@0 385 ]]></getter>
michael@0 386 </property>
michael@0 387
michael@0 388 <method name="_selectAnotherChat">
michael@0 389 <body><![CDATA[
michael@0 390 // Select a different chat (as the currently selected one is no
michael@0 391 // longer suitable as the selection - maybe it is being minimized or
michael@0 392 // closed.) We only select non-minimized and non-collapsed chats,
michael@0 393 // and if none are found, set the selectedChat to null.
michael@0 394 // It's possible in the future we will track most-recently-selected
michael@0 395 // chats or similar to find the "best" candidate - for now though
michael@0 396 // the choice is somewhat arbitrary.
michael@0 397 let moveFocus = this.selectedChat && this._isChatFocused(this.selectedChat);
michael@0 398 for (let other of this.children) {
michael@0 399 if (other != this.selectedChat && !other.minimized && !other.collapsed) {
michael@0 400 this.selectedChat = other;
michael@0 401 if (moveFocus)
michael@0 402 this.focus();
michael@0 403 return;
michael@0 404 }
michael@0 405 }
michael@0 406 // can't find another - so set no chat as selected.
michael@0 407 this.selectedChat = null;
michael@0 408 ]]></body>
michael@0 409 </method>
michael@0 410
michael@0 411 <method name="updateTitlebar">
michael@0 412 <parameter name="aChatbox"/>
michael@0 413 <body><![CDATA[
michael@0 414 if (aChatbox.collapsed) {
michael@0 415 let menuitem = this.menuitemMap.get(aChatbox);
michael@0 416 if (aChatbox.getAttribute("activity")) {
michael@0 417 menuitem.setAttribute("activity", true);
michael@0 418 this.nub.setAttribute("activity", true);
michael@0 419 }
michael@0 420 menuitem.setAttribute("label", aChatbox.getAttribute("label"));
michael@0 421 menuitem.setAttribute("image", aChatbox.getAttribute("image"));
michael@0 422 }
michael@0 423 ]]></body>
michael@0 424 </method>
michael@0 425
michael@0 426 <method name="calcTotalWidthOf">
michael@0 427 <parameter name="aElement"/>
michael@0 428 <body><![CDATA[
michael@0 429 let cs = document.defaultView.getComputedStyle(aElement);
michael@0 430 let margins = parseInt(cs.marginLeft) + parseInt(cs.marginRight);
michael@0 431 return aElement.getBoundingClientRect().width + margins;
michael@0 432 ]]></body>
michael@0 433 </method>
michael@0 434
michael@0 435 <method name="getTotalChildWidth">
michael@0 436 <parameter name="aChatbox"/>
michael@0 437 <body><![CDATA[
michael@0 438 // These are from the CSS for the chatbox and must be kept in sync.
michael@0 439 // We can't use calcTotalWidthOf due to the transitions...
michael@0 440 const CHAT_WIDTH_OPEN = 260;
michael@0 441 const CHAT_WIDTH_MINIMIZED = 160;
michael@0 442 return aChatbox.minimized ? CHAT_WIDTH_MINIMIZED : CHAT_WIDTH_OPEN;
michael@0 443 ]]></body>
michael@0 444 </method>
michael@0 445
michael@0 446 <method name="collapseChat">
michael@0 447 <parameter name="aChatbox"/>
michael@0 448 <body><![CDATA[
michael@0 449 // we ensure that the cached width for a child of this type is
michael@0 450 // up-to-date so we can use it when resizing.
michael@0 451 this.getTotalChildWidth(aChatbox);
michael@0 452 aChatbox.collapsed = true;
michael@0 453 aChatbox.isActive = false;
michael@0 454 let menu = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "menuitem");
michael@0 455 menu.setAttribute("class", "menuitem-iconic");
michael@0 456 menu.setAttribute("label", aChatbox.contentDocument.title);
michael@0 457 menu.setAttribute("image", aChatbox.getAttribute("image"));
michael@0 458 menu.chat = aChatbox;
michael@0 459 this.menuitemMap.set(aChatbox, menu);
michael@0 460 this.menupopup.appendChild(menu);
michael@0 461 this.nub.collapsed = false;
michael@0 462 ]]></body>
michael@0 463 </method>
michael@0 464
michael@0 465 <method name="showChat">
michael@0 466 <parameter name="aChatbox"/>
michael@0 467 <parameter name="aMode"/>
michael@0 468 <body><![CDATA[
michael@0 469 if ((aMode != "minimized") && aChatbox.minimized)
michael@0 470 aChatbox.minimized = false;
michael@0 471 if (this.selectedChat != aChatbox)
michael@0 472 this.selectedChat = aChatbox;
michael@0 473 if (!aChatbox.collapsed)
michael@0 474 return; // already showing - no more to do.
michael@0 475 this._showChat(aChatbox);
michael@0 476 // showing a collapsed chat might mean another needs to be collapsed
michael@0 477 // to make room...
michael@0 478 this.resize();
michael@0 479 ]]></body>
michael@0 480 </method>
michael@0 481
michael@0 482 <method name="_showChat">
michael@0 483 <parameter name="aChatbox"/>
michael@0 484 <body><![CDATA[
michael@0 485 // the actual implementation - doesn't check for overflow, assumes
michael@0 486 // collapsed, etc.
michael@0 487 let menuitem = this.menuitemMap.get(aChatbox);
michael@0 488 this.menuitemMap.delete(aChatbox);
michael@0 489 this.menupopup.removeChild(menuitem);
michael@0 490 aChatbox.collapsed = false;
michael@0 491 aChatbox.isActive = !aChatbox.minimized;
michael@0 492 ]]></body>
michael@0 493 </method>
michael@0 494
michael@0 495 <method name="remove">
michael@0 496 <parameter name="aChatbox"/>
michael@0 497 <body><![CDATA[
michael@0 498 this._remove(aChatbox);
michael@0 499 // The removal of a chat may mean a collapsed one can spring up,
michael@0 500 // or that the popup should be hidden. We also defer the selection
michael@0 501 // of another chat until after a resize, as a new candidate may
michael@0 502 // become uncollapsed after the resize.
michael@0 503 this.resize();
michael@0 504 if (this.selectedChat == aChatbox) {
michael@0 505 this._selectAnotherChat();
michael@0 506 }
michael@0 507 ]]></body>
michael@0 508 </method>
michael@0 509
michael@0 510 <method name="_remove">
michael@0 511 <parameter name="aChatbox"/>
michael@0 512 <body><![CDATA[
michael@0 513 aChatbox.content.socialErrorListener.remove();
michael@0 514 this.removeChild(aChatbox);
michael@0 515 // child might have been collapsed.
michael@0 516 let menuitem = this.menuitemMap.get(aChatbox);
michael@0 517 if (menuitem) {
michael@0 518 this.menuitemMap.delete(aChatbox);
michael@0 519 this.menupopup.removeChild(menuitem);
michael@0 520 }
michael@0 521 this.chatboxForURL.delete(aChatbox.src);
michael@0 522 ]]></body>
michael@0 523 </method>
michael@0 524
michael@0 525 <method name="removeAll">
michael@0 526 <body><![CDATA[
michael@0 527 this.selectedChat = null;
michael@0 528 while (this.firstElementChild) {
michael@0 529 this._remove(this.firstElementChild);
michael@0 530 }
michael@0 531 // and the nub/popup must also die.
michael@0 532 this.nub.collapsed = true;
michael@0 533 ]]></body>
michael@0 534 </method>
michael@0 535
michael@0 536 <method name="openChat">
michael@0 537 <parameter name="aProvider"/>
michael@0 538 <parameter name="aURL"/>
michael@0 539 <parameter name="aCallback"/>
michael@0 540 <parameter name="aMode"/>
michael@0 541 <body><![CDATA[
michael@0 542 let cb = this.chatboxForURL.get(aURL);
michael@0 543 if (cb) {
michael@0 544 cb = cb.get();
michael@0 545 if (cb.parentNode) {
michael@0 546 this.showChat(cb, aMode);
michael@0 547 if (aCallback) {
michael@0 548 if (cb._callbacks == null) {
michael@0 549 // DOMContentLoaded has already fired, so callback now.
michael@0 550 aCallback(cb.contentWindow);
michael@0 551 } else {
michael@0 552 // DOMContentLoaded for this chat is yet to fire...
michael@0 553 cb._callbacks.push(aCallback);
michael@0 554 }
michael@0 555 }
michael@0 556 return;
michael@0 557 }
michael@0 558 this.chatboxForURL.delete(aURL);
michael@0 559 }
michael@0 560 cb = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "chatbox");
michael@0 561 // _callbacks is a javascript property instead of a <field> as it
michael@0 562 // must exist before the (possibly delayed) bindings are created.
michael@0 563 cb._callbacks = [aCallback];
michael@0 564 // src also a javascript property; the src attribute is set in the ctor.
michael@0 565 cb.src = aURL;
michael@0 566 if (aMode == "minimized")
michael@0 567 cb.setAttribute("minimized", "true");
michael@0 568 cb.setAttribute("origin", aProvider.origin);
michael@0 569 this.insertBefore(cb, this.firstChild);
michael@0 570 this.selectedChat = cb;
michael@0 571 this.chatboxForURL.set(aURL, Cu.getWeakReference(cb));
michael@0 572 this.resize();
michael@0 573 ]]></body>
michael@0 574 </method>
michael@0 575
michael@0 576 <method name="resize">
michael@0 577 <body><![CDATA[
michael@0 578 // Checks the current size against the collapsed state of children
michael@0 579 // and collapses or expands as necessary such that as many as possible
michael@0 580 // are shown.
michael@0 581 // So 2 basic strategies:
michael@0 582 // * Collapse/Expand one at a time until we can't collapse/expand any
michael@0 583 // more - but this is one reflow per change.
michael@0 584 // * Calculate the dimensions ourself and choose how many to collapse
michael@0 585 // or expand based on this, then do them all in one go. This is one
michael@0 586 // reflow regardless of how many we change.
michael@0 587 // So we go the more complicated but more efficient second option...
michael@0 588 let availWidth = this.getBoundingClientRect().width;
michael@0 589 let currentWidth = 0;
michael@0 590 if (!this.nub.collapsed) { // the nub is visible.
michael@0 591 if (!this.cachedWidthNub)
michael@0 592 this.cachedWidthNub = this.calcTotalWidthOf(this.nub);
michael@0 593 currentWidth += this.cachedWidthNub;
michael@0 594 }
michael@0 595 for (let child of this.visibleChildren) {
michael@0 596 currentWidth += this.getTotalChildWidth(child);
michael@0 597 }
michael@0 598
michael@0 599 if (currentWidth > availWidth) {
michael@0 600 // we need to collapse some.
michael@0 601 let toCollapse = [];
michael@0 602 for (let child of this.collapsibleChildren) {
michael@0 603 if (currentWidth <= availWidth)
michael@0 604 break;
michael@0 605 toCollapse.push(child);
michael@0 606 currentWidth -= this.getTotalChildWidth(child);
michael@0 607 }
michael@0 608 if (toCollapse.length) {
michael@0 609 for (let child of toCollapse)
michael@0 610 this.collapseChat(child);
michael@0 611 }
michael@0 612 } else if (currentWidth < availWidth) {
michael@0 613 // we *might* be able to expand some - see how many.
michael@0 614 // XXX - if this was clever, it could know when removing the nub
michael@0 615 // leaves enough space to show all collapsed
michael@0 616 let toShow = [];
michael@0 617 for (let child of this.collapsedChildren) {
michael@0 618 currentWidth += this.getTotalChildWidth(child);
michael@0 619 if (currentWidth > availWidth)
michael@0 620 break;
michael@0 621 toShow.push(child);
michael@0 622 }
michael@0 623 for (let child of toShow)
michael@0 624 this._showChat(child);
michael@0 625
michael@0 626 // If none remain collapsed remove the nub.
michael@0 627 if (!this.hasCollapsedChildren) {
michael@0 628 this.nub.collapsed = true;
michael@0 629 }
michael@0 630 }
michael@0 631 // else: achievement unlocked - we are pixel-perfect!
michael@0 632 ]]></body>
michael@0 633 </method>
michael@0 634
michael@0 635 <method name="handleEvent">
michael@0 636 <parameter name="aEvent"/>
michael@0 637 <body><![CDATA[
michael@0 638 if (aEvent.type == "resize") {
michael@0 639 this.resize();
michael@0 640 }
michael@0 641 ]]></body>
michael@0 642 </method>
michael@0 643
michael@0 644 <method name="_getDragTarget">
michael@0 645 <parameter name="event"/>
michael@0 646 <body><![CDATA[
michael@0 647 return event.target.localName == "chatbox" ? event.target : null;
michael@0 648 ]]></body>
michael@0 649 </method>
michael@0 650
michael@0 651 <!-- Moves a chatbox to a new window. -->
michael@0 652 <method name="detachChatbox">
michael@0 653 <parameter name="aChatbox"/>
michael@0 654 <parameter name="aOptions"/>
michael@0 655 <parameter name="aCallback"/>
michael@0 656 <body><![CDATA[
michael@0 657 let options = "";
michael@0 658 for (let name in aOptions)
michael@0 659 options += "," + name + "=" + aOptions[name];
michael@0 660
michael@0 661 let otherWin = window.openDialog("chrome://browser/content/chatWindow.xul",
michael@0 662 "_blank", "chrome,all,dialog=no" + options);
michael@0 663
michael@0 664 otherWin.addEventListener("load", function _chatLoad(event) {
michael@0 665 if (event.target != otherWin.document)
michael@0 666 return;
michael@0 667
michael@0 668 otherWin.removeEventListener("load", _chatLoad, true);
michael@0 669 let otherChatbox = otherWin.document.getElementById("chatter");
michael@0 670 aChatbox.swapDocShells(otherChatbox);
michael@0 671 aChatbox.close();
michael@0 672 if (aCallback)
michael@0 673 aCallback(otherWin);
michael@0 674 }, true);
michael@0 675 ]]></body>
michael@0 676 </method>
michael@0 677
michael@0 678 </implementation>
michael@0 679
michael@0 680 <handlers>
michael@0 681 <handler event="popupshown"><![CDATA[
michael@0 682 this.nub.removeAttribute("activity");
michael@0 683 ]]></handler>
michael@0 684 <handler event="load"><![CDATA[
michael@0 685 window.addEventListener("resize", this, true);
michael@0 686 ]]></handler>
michael@0 687 <handler event="unload"><![CDATA[
michael@0 688 window.removeEventListener("resize", this, true);
michael@0 689 ]]></handler>
michael@0 690
michael@0 691 <handler event="dragstart"><![CDATA[
michael@0 692 // chat window dragging is essentially duplicated from tabbrowser.xml
michael@0 693 // to acheive the same visual experience
michael@0 694 let chatbox = this._getDragTarget(event);
michael@0 695 if (!chatbox) {
michael@0 696 return;
michael@0 697 }
michael@0 698
michael@0 699 let dt = event.dataTransfer;
michael@0 700 // we do not set a url in the drag data to prevent moving to tabbrowser
michael@0 701 // or otherwise having unexpected drop handlers do something with our
michael@0 702 // chatbox
michael@0 703 dt.mozSetDataAt("application/x-moz-chatbox", chatbox, 0);
michael@0 704
michael@0 705 // Set the cursor to an arrow during tab drags.
michael@0 706 dt.mozCursor = "default";
michael@0 707
michael@0 708 // Create a canvas to which we capture the current tab.
michael@0 709 // Until canvas is HiDPI-aware (bug 780362), we need to scale the desired
michael@0 710 // canvas size (in CSS pixels) to the window's backing resolution in order
michael@0 711 // to get a full-resolution drag image for use on HiDPI displays.
michael@0 712 let windowUtils = window.getInterface(Ci.nsIDOMWindowUtils);
michael@0 713 let scale = windowUtils.screenPixelsPerCSSPixel / windowUtils.fullZoom;
michael@0 714 let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
michael@0 715 canvas.mozOpaque = true;
michael@0 716 canvas.width = 160 * scale;
michael@0 717 canvas.height = 90 * scale;
michael@0 718 PageThumbs.captureToCanvas(chatbox.contentWindow, canvas);
michael@0 719 dt.setDragImage(canvas, -16 * scale, -16 * scale);
michael@0 720
michael@0 721 event.stopPropagation();
michael@0 722 ]]></handler>
michael@0 723
michael@0 724 <handler event="dragend"><![CDATA[
michael@0 725 let dt = event.dataTransfer;
michael@0 726 let draggedChat = dt.mozGetDataAt("application/x-moz-chatbox", 0);
michael@0 727 if (dt.mozUserCancelled || dt.dropEffect != "none") {
michael@0 728 return;
michael@0 729 }
michael@0 730
michael@0 731 let eX = event.screenX;
michael@0 732 let eY = event.screenY;
michael@0 733 // screen.availLeft et. al. only check the screen that this window is on,
michael@0 734 // but we want to look at the screen the tab is being dropped onto.
michael@0 735 let sX = {}, sY = {}, sWidth = {}, sHeight = {};
michael@0 736 Cc["@mozilla.org/gfx/screenmanager;1"]
michael@0 737 .getService(Ci.nsIScreenManager)
michael@0 738 .screenForRect(eX, eY, 1, 1)
michael@0 739 .GetAvailRect(sX, sY, sWidth, sHeight);
michael@0 740 // default size for the chat window as used in chatWindow.xul, use them
michael@0 741 // here to attempt to keep the window fully within the screen when
michael@0 742 // opening at the drop point. If the user has resized the window to
michael@0 743 // something larger (which gets persisted), at least a good portion of
michael@0 744 // the window should still be within the screen.
michael@0 745 let winWidth = 400;
michael@0 746 let winHeight = 420;
michael@0 747 // ensure new window entirely within screen
michael@0 748 let left = Math.min(Math.max(eX, sX.value),
michael@0 749 sX.value + sWidth.value - winWidth);
michael@0 750 let top = Math.min(Math.max(eY, sY.value),
michael@0 751 sY.value + sHeight.value - winHeight);
michael@0 752
michael@0 753 let provider = Social._getProviderFromOrigin(draggedChat.content.getAttribute("origin"));
michael@0 754 this.detachChatbox(draggedChat, { screenX: left, screenY: top }, win => {
michael@0 755 win.document.title = provider.name;
michael@0 756 });
michael@0 757
michael@0 758 event.stopPropagation();
michael@0 759 ]]></handler>
michael@0 760 </handlers>
michael@0 761 </binding>
michael@0 762
michael@0 763 </bindings>

mercurial