browser/base/content/socialchat.xml

Wed, 31 Dec 2014 13:27:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 13:27:57 +0100
branch
TOR_BUG_3246
changeset 6
8bccb770b82d
permissions
-rw-r--r--

Ignore runtime configuration files generated during quality assurance.

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

mercurial