|
1 <?xml version="1.0"?> |
|
2 |
|
3 <bindings id="socialMarkBindings" |
|
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"> |
|
7 |
|
8 |
|
9 <binding id="toolbarbutton-marks" display="xul:button" |
|
10 extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton"> |
|
11 <content disabled="true"> |
|
12 <xul:panel anonid="panel" hidden="true" type="arrow" class="social-panel"/> |
|
13 <xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label"/> |
|
14 <xul:label class="toolbarbutton-text" crop="right" flex="1" |
|
15 xbl:inherits="value=label,accesskey,crop,wrap"/> |
|
16 <xul:label class="toolbarbutton-multiline-text" flex="1" |
|
17 xbl:inherits="xbl:text=label,accesskey,wrap"/> |
|
18 </content> |
|
19 <implementation implements="nsIDOMEventListener, nsIObserver"> |
|
20 <field name="inMenuPanel">false</field> |
|
21 |
|
22 <property name="panel"> |
|
23 <getter> |
|
24 let widgetGroup = CustomizableUI.getWidget(this.getAttribute("id")); |
|
25 let widget = widgetGroup.forWindow(window); |
|
26 this.inMenuPanel = widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL; |
|
27 if (this.inMenuPanel) { |
|
28 widget.node.setAttribute("closemenu", "none"); |
|
29 return document.getElementById("PanelUI-socialapi"); |
|
30 } |
|
31 return document.getAnonymousElementByAttribute(this, "anonid", "panel"); |
|
32 </getter> |
|
33 </property> |
|
34 |
|
35 <property name="content"> |
|
36 <getter><![CDATA[ |
|
37 if (this._frame) |
|
38 return this._frame; |
|
39 let notificationFrameId = "social-mark-frame-" + this.getAttribute("origin"); |
|
40 this._frame = SharedFrame.createFrame( |
|
41 notificationFrameId, /* frame name */ |
|
42 this.panel, /* parent */ |
|
43 { |
|
44 "type": "content", |
|
45 "mozbrowser": "true", |
|
46 "class": "social-panel-frame", |
|
47 "id": notificationFrameId, |
|
48 "tooltip": "aHTMLTooltip", |
|
49 "flex": "1", |
|
50 "context": "contentAreaContextMenu", |
|
51 "origin": this.getAttribute("origin"), |
|
52 "src": "about:blank" |
|
53 } |
|
54 ); |
|
55 this._frame.addEventListener("DOMLinkAdded", this); |
|
56 this.setAttribute("notificationFrameId", notificationFrameId); |
|
57 return this._frame; |
|
58 ]]></getter> |
|
59 </property> |
|
60 |
|
61 <property name="contentWindow"> |
|
62 <getter> |
|
63 return this.content.contentWindow; |
|
64 </getter> |
|
65 </property> |
|
66 |
|
67 <property name="contentDocument"> |
|
68 <getter> |
|
69 return this.content.contentDocument; |
|
70 </getter> |
|
71 </property> |
|
72 |
|
73 <property name="provider"> |
|
74 <getter> |
|
75 return Social._getProviderFromOrigin(this.getAttribute("origin")); |
|
76 </getter> |
|
77 </property> |
|
78 |
|
79 <property name="isMarked"> |
|
80 <setter><![CDATA[ |
|
81 this._isMarked = val; |
|
82 let provider = this.provider; |
|
83 // we cannot size the image when we apply it via listStyleImage, so |
|
84 // use the toolbar image |
|
85 let place = CustomizableUI.getPlaceForItem(this); |
|
86 val = val && place != "palette"; |
|
87 let icon = val ? provider.markedIcon : provider.unmarkedIcon; |
|
88 let iconURL = icon || provider.icon32URL || provider.iconURL; |
|
89 this.setAttribute("image", iconURL); |
|
90 ]]></setter> |
|
91 <getter> |
|
92 return this._isMarked; |
|
93 </getter> |
|
94 </property> |
|
95 |
|
96 <method name="update"> |
|
97 <body><![CDATA[ |
|
98 // update the button for use with the current tab |
|
99 let provider = this.provider; |
|
100 if (this._dynamicResizer) { |
|
101 this._dynamicResizer.stop(); |
|
102 this._dynamicResizer = null; |
|
103 } |
|
104 this.content.setAttribute("src", "about:blank"); |
|
105 |
|
106 // do we have a savable page loaded? |
|
107 let aURI = gBrowser.currentURI; |
|
108 this.disabled = !aURI || !(aURI.schemeIs('http') || aURI.schemeIs('https')); |
|
109 if (this.disabled) { |
|
110 this.isMarked = false; |
|
111 } else { |
|
112 Social.isURIMarked(provider.origin, aURI, (isMarked) => { |
|
113 this.isMarked = isMarked; |
|
114 }); |
|
115 } |
|
116 |
|
117 this.content.setAttribute("origin", provider.origin); |
|
118 if (!this.inMenuPanel) { |
|
119 let panel = this.panel; |
|
120 // if customization is currently happening, we may not have a panel |
|
121 // that we can hide |
|
122 if (panel.hidePopup) { |
|
123 panel.hidePopup(); |
|
124 panel.hidden = true; |
|
125 } |
|
126 } |
|
127 this.pageData = null; |
|
128 ]]></body> |
|
129 </method> |
|
130 |
|
131 <method name="loadPanel"> |
|
132 <parameter name="pageData"/> |
|
133 <body><![CDATA[ |
|
134 let provider = this.provider; |
|
135 let panel = this.panel; |
|
136 panel.hidden = false; |
|
137 |
|
138 // reparent the iframe if we've been customized to a new location |
|
139 if (this.content.parentNode != panel) |
|
140 panel.appendChild(this.content); |
|
141 |
|
142 let URLTemplate = provider.markURL; |
|
143 this.pageData = pageData || OpenGraphBuilder.getData(gBrowser); |
|
144 let endpoint = OpenGraphBuilder.generateEndpointURL(URLTemplate, this.pageData); |
|
145 |
|
146 // setup listeners |
|
147 let DOMContentLoaded = (event) => { |
|
148 if (event.target != this.contentDocument) |
|
149 return; |
|
150 this._loading = false; |
|
151 this.content.removeEventListener("DOMContentLoaded", DOMContentLoaded, true); |
|
152 // add our resizer after the dom is ready |
|
153 if (!this.inMenuPanel) { |
|
154 let DynamicResizeWatcher = Cu.import("resource:///modules/Social.jsm", {}).DynamicResizeWatcher; |
|
155 this._dynamicResizer = new DynamicResizeWatcher(); |
|
156 this._dynamicResizer.start(this.panel, this.content); |
|
157 } else if (this._dynamicResizer) { |
|
158 this._dynamicResizer.stop(); |
|
159 this._dynamicResizer = null; |
|
160 } |
|
161 // send the opengraph data |
|
162 let evt = this.contentDocument.createEvent("CustomEvent"); |
|
163 evt.initCustomEvent("OpenGraphData", true, true, JSON.stringify(this.pageData)); |
|
164 this.contentDocument.documentElement.dispatchEvent(evt); |
|
165 |
|
166 let contentWindow = this.contentWindow; |
|
167 let markUpdate = function(event) { |
|
168 // update the annotation based on this event, then update the |
|
169 // icon as well |
|
170 this.isMarked = JSON.parse(event.detail).marked; |
|
171 let uri = Services.io.newURI(this.pageData.url, null, null); |
|
172 if (this.isMarked) { |
|
173 Social.markURI(provider.origin, uri); |
|
174 } else { |
|
175 Social.unmarkURI(provider.origin, uri, () => { |
|
176 this.update(); |
|
177 }); |
|
178 } |
|
179 }.bind(this); |
|
180 contentWindow.addEventListener("socialMarkUpdate", markUpdate); |
|
181 contentWindow.addEventListener("unload", function unload() { |
|
182 contentWindow.removeEventListener("unload", unload); |
|
183 contentWindow.removeEventListener("socialMarkUpdate", markUpdate); |
|
184 }); |
|
185 } |
|
186 this.content.addEventListener("DOMContentLoaded", DOMContentLoaded, true); |
|
187 this._loading = true; |
|
188 this.content.setAttribute("src", endpoint); |
|
189 ]]></body> |
|
190 </method> |
|
191 |
|
192 <method name="openPanel"> |
|
193 <parameter name="aResetOnClose"/> |
|
194 <body><![CDATA[ |
|
195 let panel = this.panel; |
|
196 let frameId = this.getAttribute("notificationFrameId"); |
|
197 |
|
198 let wasAlive = SharedFrame.isGroupAlive(frameId); |
|
199 SharedFrame.setOwner(frameId, this.content); |
|
200 |
|
201 // Clear dimensions on all browsers so the panel size will |
|
202 // only use the selected browser. |
|
203 let frameIter = panel.firstElementChild; |
|
204 while (frameIter) { |
|
205 frameIter.collapsed = (frameIter != this.content); |
|
206 frameIter = frameIter.nextElementSibling; |
|
207 } |
|
208 |
|
209 // if we're a slice in the hambuger, use that panel instead |
|
210 let widgetGroup = CustomizableUI.getWidget(this.getAttribute("id")); |
|
211 let widget = widgetGroup.forWindow(window); |
|
212 let inMenuPanel = widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL; |
|
213 if (inMenuPanel) { |
|
214 PanelUI.showSubView("PanelUI-socialapi", widget.node, |
|
215 CustomizableUI.AREA_PANEL); |
|
216 } else { |
|
217 let anchor = document.getAnonymousElementByAttribute(this, "class", "toolbarbutton-icon"); |
|
218 panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, false); |
|
219 this.setAttribute("open", "true"); |
|
220 } |
|
221 if (aResetOnClose) { |
|
222 let evName = inMenuPanel ? "ViewHiding": "popuphidden"; |
|
223 let _hidden = () => { |
|
224 panel.removeEventListener(evName, _hidden); |
|
225 this.update(); |
|
226 }; |
|
227 panel.addEventListener(evName, _hidden, false); |
|
228 } |
|
229 ]]></body> |
|
230 </method> |
|
231 |
|
232 <method name="markCurrentPage"> |
|
233 <parameter name="aOpenPanel"/> |
|
234 <body><![CDATA[ |
|
235 // we always set the src on click if it has not been set for this tab, |
|
236 // but we only want to open the panel if it was previously annotated. |
|
237 let openPanel = this.isMarked || aOpenPanel || |
|
238 this.inMenuPanel || !this.provider.haveLoggedInUser(); |
|
239 let src = this.content.getAttribute("src"); |
|
240 if (!src || src == "about:blank") { |
|
241 this.loadPanel(); |
|
242 } |
|
243 if (openPanel) |
|
244 this.openPanel(); |
|
245 ]]></body> |
|
246 </method> |
|
247 |
|
248 <method name="markLink"> |
|
249 <parameter name="aUrl"/> |
|
250 <body><![CDATA[ |
|
251 if (!aUrl) { |
|
252 this.markCurrentPage(true); |
|
253 return; |
|
254 } |
|
255 // initiated form an external source, such as gContextMenu, where |
|
256 // pageData is passed into us. In this case, we always load the iframe |
|
257 // and show it since the url may not be the browser tab, but an image, |
|
258 // link, etc. inside the page. We also "update" the iframe to the |
|
259 // previous url when it is closed. |
|
260 this.content.setAttribute("src", "about:blank"); |
|
261 this.loadPanel({ url: aUrl }); |
|
262 this.openPanel(true); |
|
263 ]]></body> |
|
264 </method> |
|
265 |
|
266 <method name="dispatchPanelEvent"> |
|
267 <parameter name="name"/> |
|
268 <body><![CDATA[ |
|
269 let evt = this.contentDocument.createEvent("CustomEvent"); |
|
270 evt.initCustomEvent(name, true, true, {}); |
|
271 this.contentDocument.documentElement.dispatchEvent(evt); |
|
272 ]]></body> |
|
273 </method> |
|
274 |
|
275 <method name="onShown"> |
|
276 <body><![CDATA[ |
|
277 // because the panel may be preloaded, we need to size the panel when |
|
278 // showing as well as after load |
|
279 let sizeSocialPanelToContent = Cu.import("resource:///modules/Social.jsm", {}).sizeSocialPanelToContent; |
|
280 if (!this._loading && this.contentDocument && |
|
281 this.contentDocument.readyState == "complete") { |
|
282 this.dispatchPanelEvent("socialFrameShow"); |
|
283 if (!this.inMenuPanel) |
|
284 sizeSocialPanelToContent(this.panel, this.content); |
|
285 } else { |
|
286 let panelBrowserOnload = (e) => { |
|
287 this.content.removeEventListener("load", panelBrowserOnload, true); |
|
288 this.dispatchPanelEvent("socialFrameShow"); |
|
289 if (!this.inMenuPanel) |
|
290 sizeSocialPanelToContent(this.panel, this.content); |
|
291 }; |
|
292 this.content.addEventListener("load", panelBrowserOnload, true); |
|
293 } |
|
294 ]]></body> |
|
295 </method> |
|
296 |
|
297 <method name="handleEvent"> |
|
298 <parameter name="aEvent"/> |
|
299 <body><![CDATA[ |
|
300 if (aEvent.eventPhase != aEvent.BUBBLING_PHASE) |
|
301 return; |
|
302 switch(aEvent.type) { |
|
303 case "DOMLinkAdded": { |
|
304 // much of this logic is from DOMLinkHandler in browser.js, this sets |
|
305 // the presence icon for a chat user, we simply use favicon style |
|
306 // updating |
|
307 let link = aEvent.originalTarget; |
|
308 let rel = link.rel && link.rel.toLowerCase(); |
|
309 if (!link || !link.ownerDocument || !rel || !link.href) |
|
310 return; |
|
311 if (link.rel.indexOf("icon") < 0) |
|
312 return; |
|
313 |
|
314 let ContentLinkHandler = Cu.import("resource:///modules/ContentLinkHandler.jsm", {}).ContentLinkHandler; |
|
315 let uri = ContentLinkHandler.getLinkIconURI(link); |
|
316 if (!uri) |
|
317 return; |
|
318 |
|
319 // we cannot size the image when we apply it via listStyleImage, so |
|
320 // use the toolbar image |
|
321 this.setAttribute("image", uri.spec); |
|
322 } |
|
323 break; |
|
324 case "ViewShowing": |
|
325 this.onShown(); |
|
326 break; |
|
327 case "ViewHiding": |
|
328 this.dispatchPanelEvent("socialFrameHide"); |
|
329 break; |
|
330 } |
|
331 ]]></body> |
|
332 </method> |
|
333 |
|
334 </implementation> |
|
335 <handlers> |
|
336 <handler event="popupshown"><![CDATA[ |
|
337 this.onShown(); |
|
338 ]]></handler> |
|
339 <handler event="popuphidden"><![CDATA[ |
|
340 this.dispatchPanelEvent("socialFrameHide"); |
|
341 this.removeAttribute("open"); |
|
342 ]]></handler> |
|
343 <handler event="command"><![CDATA[ |
|
344 this.markCurrentPage(); |
|
345 ]]></handler> |
|
346 </handlers> |
|
347 </binding> |
|
348 |
|
349 </bindings> |