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