|
1 # This Source Code Form is subject to the terms of the Mozilla Public |
|
2 # License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
|
4 |
|
5 var gToolboxDocument = null; |
|
6 var gToolbox = null; |
|
7 var gCurrentDragOverItem = null; |
|
8 var gToolboxChanged = false; |
|
9 var gToolboxSheet = false; |
|
10 var gPaletteBox = null; |
|
11 |
|
12 Components.utils.import("resource://gre/modules/Services.jsm"); |
|
13 |
|
14 function onLoad() |
|
15 { |
|
16 if ("arguments" in window && window.arguments[0]) { |
|
17 InitWithToolbox(window.arguments[0]); |
|
18 repositionDialog(window); |
|
19 } |
|
20 else if (window.frameElement && |
|
21 "toolbox" in window.frameElement) { |
|
22 gToolboxSheet = true; |
|
23 InitWithToolbox(window.frameElement.toolbox); |
|
24 repositionDialog(window.frameElement.panel); |
|
25 } |
|
26 } |
|
27 |
|
28 function InitWithToolbox(aToolbox) |
|
29 { |
|
30 gToolbox = aToolbox; |
|
31 dispatchCustomizationEvent("beforecustomization"); |
|
32 gToolboxDocument = gToolbox.ownerDocument; |
|
33 gToolbox.customizing = true; |
|
34 forEachCustomizableToolbar(function (toolbar) { |
|
35 toolbar.setAttribute("customizing", "true"); |
|
36 }); |
|
37 gPaletteBox = document.getElementById("palette-box"); |
|
38 |
|
39 var elts = getRootElements(); |
|
40 for (let i=0; i < elts.length; i++) { |
|
41 elts[i].addEventListener("dragstart", onToolbarDragStart, true); |
|
42 elts[i].addEventListener("dragover", onToolbarDragOver, true); |
|
43 elts[i].addEventListener("dragexit", onToolbarDragExit, true); |
|
44 elts[i].addEventListener("drop", onToolbarDrop, true); |
|
45 } |
|
46 |
|
47 initDialog(); |
|
48 } |
|
49 |
|
50 function onClose() |
|
51 { |
|
52 if (!gToolboxSheet) |
|
53 window.close(); |
|
54 else |
|
55 finishToolbarCustomization(); |
|
56 } |
|
57 |
|
58 function onUnload() |
|
59 { |
|
60 if (!gToolboxSheet) |
|
61 finishToolbarCustomization(); |
|
62 } |
|
63 |
|
64 function finishToolbarCustomization() |
|
65 { |
|
66 removeToolboxListeners(); |
|
67 unwrapToolbarItems(); |
|
68 persistCurrentSets(); |
|
69 gToolbox.customizing = false; |
|
70 forEachCustomizableToolbar(function (toolbar) { |
|
71 toolbar.removeAttribute("customizing"); |
|
72 }); |
|
73 |
|
74 notifyParentComplete(); |
|
75 } |
|
76 |
|
77 function initDialog() |
|
78 { |
|
79 if (!gToolbox.toolbarset) { |
|
80 document.getElementById("newtoolbar").hidden = true; |
|
81 } |
|
82 |
|
83 var mode = gToolbox.getAttribute("mode"); |
|
84 document.getElementById("modelist").value = mode; |
|
85 var smallIconsCheckbox = document.getElementById("smallicons"); |
|
86 smallIconsCheckbox.checked = gToolbox.getAttribute("iconsize") == "small"; |
|
87 if (mode == "text") |
|
88 smallIconsCheckbox.disabled = true; |
|
89 |
|
90 // Build up the palette of other items. |
|
91 buildPalette(); |
|
92 |
|
93 // Wrap all the items on the toolbar in toolbarpaletteitems. |
|
94 wrapToolbarItems(); |
|
95 } |
|
96 |
|
97 function repositionDialog(aWindow) |
|
98 { |
|
99 // Position the dialog touching the bottom of the toolbox and centered with |
|
100 // it. |
|
101 if (!aWindow) |
|
102 return; |
|
103 |
|
104 var width; |
|
105 if (aWindow != window) |
|
106 width = aWindow.getBoundingClientRect().width; |
|
107 else if (document.documentElement.hasAttribute("width")) |
|
108 width = document.documentElement.getAttribute("width"); |
|
109 else |
|
110 width = parseInt(document.documentElement.style.width); |
|
111 var screenX = gToolbox.boxObject.screenX |
|
112 + ((gToolbox.boxObject.width - width) / 2); |
|
113 var screenY = gToolbox.boxObject.screenY + gToolbox.boxObject.height; |
|
114 |
|
115 aWindow.moveTo(screenX, screenY); |
|
116 } |
|
117 |
|
118 function removeToolboxListeners() |
|
119 { |
|
120 var elts = getRootElements(); |
|
121 for (let i=0; i < elts.length; i++) { |
|
122 elts[i].removeEventListener("dragstart", onToolbarDragStart, true); |
|
123 elts[i].removeEventListener("dragover", onToolbarDragOver, true); |
|
124 elts[i].removeEventListener("dragexit", onToolbarDragExit, true); |
|
125 elts[i].removeEventListener("drop", onToolbarDrop, true); |
|
126 } |
|
127 } |
|
128 |
|
129 /** |
|
130 * Invoke a callback on the toolbox to notify it that the dialog is done |
|
131 * and going away. |
|
132 */ |
|
133 function notifyParentComplete() |
|
134 { |
|
135 if ("customizeDone" in gToolbox) |
|
136 gToolbox.customizeDone(gToolboxChanged); |
|
137 dispatchCustomizationEvent("aftercustomization"); |
|
138 } |
|
139 |
|
140 function toolboxChanged(aType) |
|
141 { |
|
142 gToolboxChanged = true; |
|
143 if ("customizeChange" in gToolbox) |
|
144 gToolbox.customizeChange(aType); |
|
145 dispatchCustomizationEvent("customizationchange"); |
|
146 } |
|
147 |
|
148 function dispatchCustomizationEvent(aEventName) { |
|
149 var evt = document.createEvent("Events"); |
|
150 evt.initEvent(aEventName, true, true); |
|
151 gToolbox.dispatchEvent(evt); |
|
152 } |
|
153 |
|
154 /** |
|
155 * Persist the current set of buttons in all customizable toolbars to |
|
156 * localstore. |
|
157 */ |
|
158 function persistCurrentSets() |
|
159 { |
|
160 if (!gToolboxChanged || gToolboxDocument.defaultView.closed) |
|
161 return; |
|
162 |
|
163 var customCount = 0; |
|
164 forEachCustomizableToolbar(function (toolbar) { |
|
165 // Calculate currentset and store it in the attribute. |
|
166 var currentSet = toolbar.currentSet; |
|
167 toolbar.setAttribute("currentset", currentSet); |
|
168 |
|
169 var customIndex = toolbar.hasAttribute("customindex"); |
|
170 if (customIndex) { |
|
171 if (!toolbar.hasChildNodes()) { |
|
172 // Remove custom toolbars whose contents have been removed. |
|
173 gToolbox.removeChild(toolbar); |
|
174 } else if (gToolbox.toolbarset) { |
|
175 // Persist custom toolbar info on the <toolbarset/> |
|
176 gToolbox.toolbarset.setAttribute("toolbar"+(++customCount), |
|
177 toolbar.toolbarName + ":" + currentSet); |
|
178 gToolboxDocument.persist(gToolbox.toolbarset.id, "toolbar"+customCount); |
|
179 } |
|
180 } |
|
181 |
|
182 if (!customIndex) { |
|
183 // Persist the currentset attribute directly on hardcoded toolbars. |
|
184 gToolboxDocument.persist(toolbar.id, "currentset"); |
|
185 } |
|
186 }); |
|
187 |
|
188 // Remove toolbarX attributes for removed toolbars. |
|
189 while (gToolbox.toolbarset && gToolbox.toolbarset.hasAttribute("toolbar"+(++customCount))) { |
|
190 gToolbox.toolbarset.removeAttribute("toolbar"+customCount); |
|
191 gToolboxDocument.persist(gToolbox.toolbarset.id, "toolbar"+customCount); |
|
192 } |
|
193 } |
|
194 |
|
195 /** |
|
196 * Wraps all items in all customizable toolbars in a toolbox. |
|
197 */ |
|
198 function wrapToolbarItems() |
|
199 { |
|
200 forEachCustomizableToolbar(function (toolbar) { |
|
201 Array.forEach(toolbar.childNodes, function (item) { |
|
202 #ifdef XP_MACOSX |
|
203 if (item.firstChild && item.firstChild.localName == "menubar") |
|
204 return; |
|
205 #endif |
|
206 if (isToolbarItem(item)) { |
|
207 let wrapper = wrapToolbarItem(item); |
|
208 cleanupItemForToolbar(item, wrapper); |
|
209 } |
|
210 }); |
|
211 }); |
|
212 } |
|
213 |
|
214 function getRootElements() |
|
215 { |
|
216 return [gToolbox].concat(gToolbox.externalToolbars); |
|
217 } |
|
218 |
|
219 /** |
|
220 * Unwraps all items in all customizable toolbars in a toolbox. |
|
221 */ |
|
222 function unwrapToolbarItems() |
|
223 { |
|
224 let elts = getRootElements(); |
|
225 for (let i=0; i < elts.length; i++) { |
|
226 let paletteItems = elts[i].getElementsByTagName("toolbarpaletteitem"); |
|
227 let paletteItem; |
|
228 while ((paletteItem = paletteItems.item(0)) != null) { |
|
229 let toolbarItem = paletteItem.firstChild; |
|
230 restoreItemForToolbar(toolbarItem, paletteItem); |
|
231 paletteItem.parentNode.replaceChild(toolbarItem, paletteItem); |
|
232 } |
|
233 } |
|
234 } |
|
235 |
|
236 /** |
|
237 * Creates a wrapper that can be used to contain a toolbaritem and prevent |
|
238 * it from receiving UI events. |
|
239 */ |
|
240 function createWrapper(aId, aDocument) |
|
241 { |
|
242 var wrapper = aDocument.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", |
|
243 "toolbarpaletteitem"); |
|
244 |
|
245 wrapper.id = "wrapper-"+aId; |
|
246 return wrapper; |
|
247 } |
|
248 |
|
249 /** |
|
250 * Wraps an item that has been cloned from a template and adds |
|
251 * it to the end of the palette. |
|
252 */ |
|
253 function wrapPaletteItem(aPaletteItem) |
|
254 { |
|
255 var wrapper = createWrapper(aPaletteItem.id, document); |
|
256 |
|
257 wrapper.appendChild(aPaletteItem); |
|
258 |
|
259 // XXX We need to call this AFTER the palette item has been appended |
|
260 // to the wrapper or else we crash dropping certain buttons on the |
|
261 // palette due to removal of the command and disabled attributes - JRH |
|
262 cleanUpItemForPalette(aPaletteItem, wrapper); |
|
263 |
|
264 gPaletteBox.appendChild(wrapper); |
|
265 } |
|
266 |
|
267 /** |
|
268 * Wraps an item that is currently on a toolbar and replaces the item |
|
269 * with the wrapper. This is not used when dropping items from the palette, |
|
270 * only when first starting the dialog and wrapping everything on the toolbars. |
|
271 */ |
|
272 function wrapToolbarItem(aToolbarItem) |
|
273 { |
|
274 var wrapper = createWrapper(aToolbarItem.id, gToolboxDocument); |
|
275 |
|
276 wrapper.flex = aToolbarItem.flex; |
|
277 |
|
278 aToolbarItem.parentNode.replaceChild(wrapper, aToolbarItem); |
|
279 |
|
280 wrapper.appendChild(aToolbarItem); |
|
281 |
|
282 return wrapper; |
|
283 } |
|
284 |
|
285 /** |
|
286 * Get the list of ids for the current set of items on each toolbar. |
|
287 */ |
|
288 function getCurrentItemIds() |
|
289 { |
|
290 var currentItems = {}; |
|
291 forEachCustomizableToolbar(function (toolbar) { |
|
292 var child = toolbar.firstChild; |
|
293 while (child) { |
|
294 if (isToolbarItem(child)) |
|
295 currentItems[child.id] = 1; |
|
296 child = child.nextSibling; |
|
297 } |
|
298 }); |
|
299 return currentItems; |
|
300 } |
|
301 |
|
302 /** |
|
303 * Builds the palette of draggable items that are not yet in a toolbar. |
|
304 */ |
|
305 function buildPalette() |
|
306 { |
|
307 // Empty the palette first. |
|
308 while (gPaletteBox.lastChild) |
|
309 gPaletteBox.removeChild(gPaletteBox.lastChild); |
|
310 |
|
311 // Add the toolbar separator item. |
|
312 var templateNode = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", |
|
313 "toolbarseparator"); |
|
314 templateNode.id = "separator"; |
|
315 wrapPaletteItem(templateNode); |
|
316 |
|
317 // Add the toolbar spring item. |
|
318 templateNode = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", |
|
319 "toolbarspring"); |
|
320 templateNode.id = "spring"; |
|
321 templateNode.flex = 1; |
|
322 wrapPaletteItem(templateNode); |
|
323 |
|
324 // Add the toolbar spacer item. |
|
325 templateNode = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", |
|
326 "toolbarspacer"); |
|
327 templateNode.id = "spacer"; |
|
328 templateNode.flex = 1; |
|
329 wrapPaletteItem(templateNode); |
|
330 |
|
331 var currentItems = getCurrentItemIds(); |
|
332 templateNode = gToolbox.palette.firstChild; |
|
333 while (templateNode) { |
|
334 // Check if the item is already in a toolbar before adding it to the palette. |
|
335 if (!(templateNode.id in currentItems)) { |
|
336 var paletteItem = document.importNode(templateNode, true); |
|
337 wrapPaletteItem(paletteItem); |
|
338 } |
|
339 |
|
340 templateNode = templateNode.nextSibling; |
|
341 } |
|
342 } |
|
343 |
|
344 /** |
|
345 * Makes sure that an item that has been cloned from a template |
|
346 * is stripped of any attributes that may adversely affect its |
|
347 * appearance in the palette. |
|
348 */ |
|
349 function cleanUpItemForPalette(aItem, aWrapper) |
|
350 { |
|
351 aWrapper.setAttribute("place", "palette"); |
|
352 setWrapperType(aItem, aWrapper); |
|
353 |
|
354 if (aItem.hasAttribute("title")) |
|
355 aWrapper.setAttribute("title", aItem.getAttribute("title")); |
|
356 else if (aItem.hasAttribute("label")) |
|
357 aWrapper.setAttribute("title", aItem.getAttribute("label")); |
|
358 else if (isSpecialItem(aItem)) { |
|
359 var stringBundle = document.getElementById("stringBundle"); |
|
360 // Remove the common "toolbar" prefix to generate the string name. |
|
361 var title = stringBundle.getString(aItem.localName.slice(7) + "Title"); |
|
362 aWrapper.setAttribute("title", title); |
|
363 } |
|
364 aWrapper.setAttribute("tooltiptext", aWrapper.getAttribute("title")); |
|
365 |
|
366 // Remove attributes that screw up our appearance. |
|
367 aItem.removeAttribute("command"); |
|
368 aItem.removeAttribute("observes"); |
|
369 aItem.removeAttribute("type"); |
|
370 aItem.removeAttribute("width"); |
|
371 |
|
372 Array.forEach(aWrapper.querySelectorAll("[disabled]"), function(aNode) { |
|
373 aNode.removeAttribute("disabled"); |
|
374 }); |
|
375 } |
|
376 |
|
377 /** |
|
378 * Makes sure that an item that has been cloned from a template |
|
379 * is stripped of all properties that may adversely affect its |
|
380 * appearance in the toolbar. Store critical properties on the |
|
381 * wrapper so they can be put back on the item when we're done. |
|
382 */ |
|
383 function cleanupItemForToolbar(aItem, aWrapper) |
|
384 { |
|
385 setWrapperType(aItem, aWrapper); |
|
386 aWrapper.setAttribute("place", "toolbar"); |
|
387 |
|
388 if (aItem.hasAttribute("command")) { |
|
389 aWrapper.setAttribute("itemcommand", aItem.getAttribute("command")); |
|
390 aItem.removeAttribute("command"); |
|
391 } |
|
392 |
|
393 if (aItem.checked) { |
|
394 aWrapper.setAttribute("itemchecked", "true"); |
|
395 aItem.checked = false; |
|
396 } |
|
397 |
|
398 if (aItem.disabled) { |
|
399 aWrapper.setAttribute("itemdisabled", "true"); |
|
400 aItem.disabled = false; |
|
401 } |
|
402 } |
|
403 |
|
404 /** |
|
405 * Restore all the properties that we stripped off above. |
|
406 */ |
|
407 function restoreItemForToolbar(aItem, aWrapper) |
|
408 { |
|
409 if (aWrapper.hasAttribute("itemdisabled")) |
|
410 aItem.disabled = true; |
|
411 |
|
412 if (aWrapper.hasAttribute("itemchecked")) |
|
413 aItem.checked = true; |
|
414 |
|
415 if (aWrapper.hasAttribute("itemcommand")) { |
|
416 let commandID = aWrapper.getAttribute("itemcommand"); |
|
417 aItem.setAttribute("command", commandID); |
|
418 |
|
419 //XXX Bug 309953 - toolbarbuttons aren't in sync with their commands after customizing |
|
420 let command = gToolboxDocument.getElementById(commandID); |
|
421 if (command && command.hasAttribute("disabled")) |
|
422 aItem.setAttribute("disabled", command.getAttribute("disabled")); |
|
423 } |
|
424 } |
|
425 |
|
426 function setWrapperType(aItem, aWrapper) |
|
427 { |
|
428 if (aItem.localName == "toolbarseparator") { |
|
429 aWrapper.setAttribute("type", "separator"); |
|
430 } else if (aItem.localName == "toolbarspring") { |
|
431 aWrapper.setAttribute("type", "spring"); |
|
432 } else if (aItem.localName == "toolbarspacer") { |
|
433 aWrapper.setAttribute("type", "spacer"); |
|
434 } else if (aItem.localName == "toolbaritem" && aItem.firstChild) { |
|
435 aWrapper.setAttribute("type", aItem.firstChild.localName); |
|
436 } |
|
437 } |
|
438 |
|
439 function setDragActive(aItem, aValue) |
|
440 { |
|
441 var node = aItem; |
|
442 var direction = window.getComputedStyle(aItem, null).direction; |
|
443 var value = direction == "ltr"? "left" : "right"; |
|
444 if (aItem.localName == "toolbar") { |
|
445 node = aItem.lastChild; |
|
446 value = direction == "ltr"? "right" : "left"; |
|
447 } |
|
448 |
|
449 if (!node) |
|
450 return; |
|
451 |
|
452 if (aValue) { |
|
453 if (!node.hasAttribute("dragover")) |
|
454 node.setAttribute("dragover", value); |
|
455 } else { |
|
456 node.removeAttribute("dragover"); |
|
457 } |
|
458 } |
|
459 |
|
460 function addNewToolbar() |
|
461 { |
|
462 var promptService = Services.prompt; |
|
463 var stringBundle = document.getElementById("stringBundle"); |
|
464 var message = stringBundle.getString("enterToolbarName"); |
|
465 var title = stringBundle.getString("enterToolbarTitle"); |
|
466 |
|
467 var name = {}; |
|
468 |
|
469 // Quitting from the toolbar dialog while the new toolbar prompt is up |
|
470 // can cause things to become unresponsive on the Mac. Until dialog modality |
|
471 // is fixed (395465), disable the "Done" button explicitly. |
|
472 var doneButton = document.getElementById("donebutton"); |
|
473 doneButton.disabled = true; |
|
474 |
|
475 while (true) { |
|
476 |
|
477 if (!promptService.prompt(window, title, message, name, null, {})) { |
|
478 doneButton.disabled = false; |
|
479 return; |
|
480 } |
|
481 |
|
482 if (!name.value) { |
|
483 message = stringBundle.getFormattedString("enterToolbarBlank", [name.value]); |
|
484 continue; |
|
485 } |
|
486 |
|
487 var dupeFound = false; |
|
488 |
|
489 // Check for an existing toolbar with the same display name |
|
490 for (let i = 0; i < gToolbox.childNodes.length; ++i) { |
|
491 var toolbar = gToolbox.childNodes[i]; |
|
492 var toolbarName = toolbar.getAttribute("toolbarname"); |
|
493 |
|
494 if (toolbarName == name.value && |
|
495 toolbar.getAttribute("type") != "menubar" && |
|
496 toolbar.nodeName == 'toolbar') { |
|
497 dupeFound = true; |
|
498 break; |
|
499 } |
|
500 } |
|
501 |
|
502 if (!dupeFound) |
|
503 break; |
|
504 |
|
505 message = stringBundle.getFormattedString("enterToolbarDup", [name.value]); |
|
506 } |
|
507 |
|
508 gToolbox.appendCustomToolbar(name.value, ""); |
|
509 |
|
510 toolboxChanged(); |
|
511 |
|
512 doneButton.disabled = false; |
|
513 } |
|
514 |
|
515 /** |
|
516 * Restore the default set of buttons to fixed toolbars, |
|
517 * remove all custom toolbars, and rebuild the palette. |
|
518 */ |
|
519 function restoreDefaultSet() |
|
520 { |
|
521 // Unwrap the items on the toolbar. |
|
522 unwrapToolbarItems(); |
|
523 |
|
524 // Remove all of the customized toolbars. |
|
525 var child = gToolbox.lastChild; |
|
526 while (child) { |
|
527 if (child.hasAttribute("customindex")) { |
|
528 var thisChild = child; |
|
529 child = child.previousSibling; |
|
530 thisChild.currentSet = "__empty"; |
|
531 gToolbox.removeChild(thisChild); |
|
532 } else { |
|
533 child = child.previousSibling; |
|
534 } |
|
535 } |
|
536 |
|
537 // Restore the defaultset for fixed toolbars. |
|
538 forEachCustomizableToolbar(function (toolbar) { |
|
539 var defaultSet = toolbar.getAttribute("defaultset"); |
|
540 if (defaultSet) |
|
541 toolbar.currentSet = defaultSet; |
|
542 }); |
|
543 |
|
544 // Restore the default icon size and mode. |
|
545 document.getElementById("smallicons").checked = (updateIconSize() == "small"); |
|
546 document.getElementById("modelist").value = updateToolbarMode(); |
|
547 |
|
548 // Now rebuild the palette. |
|
549 buildPalette(); |
|
550 |
|
551 // Now re-wrap the items on the toolbar. |
|
552 wrapToolbarItems(); |
|
553 |
|
554 toolboxChanged("reset"); |
|
555 } |
|
556 |
|
557 function updateIconSize(aSize) { |
|
558 return updateToolboxProperty("iconsize", aSize, "large"); |
|
559 } |
|
560 |
|
561 function updateToolbarMode(aModeValue) { |
|
562 var mode = updateToolboxProperty("mode", aModeValue, "icons"); |
|
563 |
|
564 var iconSizeCheckbox = document.getElementById("smallicons"); |
|
565 iconSizeCheckbox.disabled = mode == "text"; |
|
566 |
|
567 return mode; |
|
568 } |
|
569 |
|
570 function updateToolboxProperty(aProp, aValue, aToolkitDefault) { |
|
571 var toolboxDefault = gToolbox.getAttribute("default" + aProp) || |
|
572 aToolkitDefault; |
|
573 |
|
574 gToolbox.setAttribute(aProp, aValue || toolboxDefault); |
|
575 gToolboxDocument.persist(gToolbox.id, aProp); |
|
576 |
|
577 forEachCustomizableToolbar(function (toolbar) { |
|
578 var toolbarDefault = toolbar.getAttribute("default" + aProp) || |
|
579 toolboxDefault; |
|
580 if (toolbar.getAttribute("lock" + aProp) == "true" && |
|
581 toolbar.getAttribute(aProp) == toolbarDefault) |
|
582 return; |
|
583 |
|
584 toolbar.setAttribute(aProp, aValue || toolbarDefault); |
|
585 gToolboxDocument.persist(toolbar.id, aProp); |
|
586 }); |
|
587 |
|
588 toolboxChanged(aProp); |
|
589 |
|
590 return aValue || toolboxDefault; |
|
591 } |
|
592 |
|
593 function forEachCustomizableToolbar(callback) { |
|
594 Array.filter(gToolbox.childNodes, isCustomizableToolbar).forEach(callback); |
|
595 Array.filter(gToolbox.externalToolbars, isCustomizableToolbar).forEach(callback); |
|
596 } |
|
597 |
|
598 function isCustomizableToolbar(aElt) |
|
599 { |
|
600 return aElt.localName == "toolbar" && |
|
601 aElt.getAttribute("customizable") == "true"; |
|
602 } |
|
603 |
|
604 function isSpecialItem(aElt) |
|
605 { |
|
606 return aElt.localName == "toolbarseparator" || |
|
607 aElt.localName == "toolbarspring" || |
|
608 aElt.localName == "toolbarspacer"; |
|
609 } |
|
610 |
|
611 function isToolbarItem(aElt) |
|
612 { |
|
613 return aElt.localName == "toolbarbutton" || |
|
614 aElt.localName == "toolbaritem" || |
|
615 aElt.localName == "toolbarseparator" || |
|
616 aElt.localName == "toolbarspring" || |
|
617 aElt.localName == "toolbarspacer"; |
|
618 } |
|
619 |
|
620 /////////////////////////////////////////////////////////////////////////// |
|
621 //// Drag and Drop observers |
|
622 |
|
623 function onToolbarDragExit(aEvent) |
|
624 { |
|
625 if (isUnwantedDragEvent(aEvent)) { |
|
626 return; |
|
627 } |
|
628 |
|
629 if (gCurrentDragOverItem) |
|
630 setDragActive(gCurrentDragOverItem, false); |
|
631 } |
|
632 |
|
633 function onToolbarDragStart(aEvent) |
|
634 { |
|
635 var item = aEvent.target; |
|
636 while (item && item.localName != "toolbarpaletteitem") { |
|
637 if (item.localName == "toolbar") |
|
638 return; |
|
639 item = item.parentNode; |
|
640 } |
|
641 |
|
642 item.setAttribute("dragactive", "true"); |
|
643 |
|
644 var dt = aEvent.dataTransfer; |
|
645 var documentId = gToolboxDocument.documentElement.id; |
|
646 dt.setData("text/toolbarwrapper-id/" + documentId, item.firstChild.id); |
|
647 dt.effectAllowed = "move"; |
|
648 } |
|
649 |
|
650 function onToolbarDragOver(aEvent) |
|
651 { |
|
652 if (isUnwantedDragEvent(aEvent)) { |
|
653 return; |
|
654 } |
|
655 |
|
656 var documentId = gToolboxDocument.documentElement.id; |
|
657 if (!aEvent.dataTransfer.types.contains("text/toolbarwrapper-id/" + documentId.toLowerCase())) |
|
658 return; |
|
659 |
|
660 var toolbar = aEvent.target; |
|
661 var dropTarget = aEvent.target; |
|
662 while (toolbar && toolbar.localName != "toolbar") { |
|
663 dropTarget = toolbar; |
|
664 toolbar = toolbar.parentNode; |
|
665 } |
|
666 |
|
667 // Make sure we are dragging over a customizable toolbar. |
|
668 if (!toolbar || !isCustomizableToolbar(toolbar)) { |
|
669 gCurrentDragOverItem = null; |
|
670 return; |
|
671 } |
|
672 |
|
673 var previousDragItem = gCurrentDragOverItem; |
|
674 |
|
675 if (dropTarget.localName == "toolbar") { |
|
676 gCurrentDragOverItem = dropTarget; |
|
677 } else { |
|
678 gCurrentDragOverItem = null; |
|
679 |
|
680 var direction = window.getComputedStyle(dropTarget.parentNode, null).direction; |
|
681 var dropTargetCenter = dropTarget.boxObject.x + (dropTarget.boxObject.width / 2); |
|
682 var dragAfter; |
|
683 if (direction == "ltr") |
|
684 dragAfter = aEvent.clientX > dropTargetCenter; |
|
685 else |
|
686 dragAfter = aEvent.clientX < dropTargetCenter; |
|
687 |
|
688 if (dragAfter) { |
|
689 gCurrentDragOverItem = dropTarget.nextSibling; |
|
690 if (!gCurrentDragOverItem) |
|
691 gCurrentDragOverItem = toolbar; |
|
692 } else |
|
693 gCurrentDragOverItem = dropTarget; |
|
694 } |
|
695 |
|
696 if (previousDragItem && gCurrentDragOverItem != previousDragItem) { |
|
697 setDragActive(previousDragItem, false); |
|
698 } |
|
699 |
|
700 setDragActive(gCurrentDragOverItem, true); |
|
701 |
|
702 aEvent.preventDefault(); |
|
703 aEvent.stopPropagation(); |
|
704 } |
|
705 |
|
706 function onToolbarDrop(aEvent) |
|
707 { |
|
708 if (isUnwantedDragEvent(aEvent)) { |
|
709 return; |
|
710 } |
|
711 |
|
712 if (!gCurrentDragOverItem) |
|
713 return; |
|
714 |
|
715 setDragActive(gCurrentDragOverItem, false); |
|
716 |
|
717 var documentId = gToolboxDocument.documentElement.id; |
|
718 var draggedItemId = aEvent.dataTransfer.getData("text/toolbarwrapper-id/" + documentId); |
|
719 if (gCurrentDragOverItem.id == draggedItemId) |
|
720 return; |
|
721 |
|
722 var toolbar = aEvent.target; |
|
723 while (toolbar.localName != "toolbar") |
|
724 toolbar = toolbar.parentNode; |
|
725 |
|
726 var draggedPaletteWrapper = document.getElementById("wrapper-"+draggedItemId); |
|
727 if (!draggedPaletteWrapper) { |
|
728 // The wrapper has been dragged from the toolbar. |
|
729 // Get the wrapper from the toolbar document and make sure that |
|
730 // it isn't being dropped on itself. |
|
731 var wrapper = gToolboxDocument.getElementById("wrapper-"+draggedItemId); |
|
732 if (wrapper == gCurrentDragOverItem) |
|
733 return; |
|
734 |
|
735 // Don't allow non-removable kids (e.g., the menubar) to move. |
|
736 if (wrapper.firstChild.getAttribute("removable") != "true") |
|
737 return; |
|
738 |
|
739 // Remove the item from its place in the toolbar. |
|
740 wrapper.parentNode.removeChild(wrapper); |
|
741 |
|
742 // Determine which toolbar we are dropping on. |
|
743 var dropToolbar = null; |
|
744 if (gCurrentDragOverItem.localName == "toolbar") |
|
745 dropToolbar = gCurrentDragOverItem; |
|
746 else |
|
747 dropToolbar = gCurrentDragOverItem.parentNode; |
|
748 |
|
749 // Insert the item into the toolbar. |
|
750 if (gCurrentDragOverItem != dropToolbar) |
|
751 dropToolbar.insertBefore(wrapper, gCurrentDragOverItem); |
|
752 else |
|
753 dropToolbar.appendChild(wrapper); |
|
754 } else { |
|
755 // The item has been dragged from the palette |
|
756 |
|
757 // Create a new wrapper for the item. We don't know the id yet. |
|
758 var wrapper = createWrapper("", gToolboxDocument); |
|
759 |
|
760 // Ask the toolbar to clone the item's template, place it inside the wrapper, and insert it in the toolbar. |
|
761 var newItem = toolbar.insertItem(draggedItemId, gCurrentDragOverItem == toolbar ? null : gCurrentDragOverItem, wrapper); |
|
762 |
|
763 // Prepare the item and wrapper to look good on the toolbar. |
|
764 cleanupItemForToolbar(newItem, wrapper); |
|
765 wrapper.id = "wrapper-"+newItem.id; |
|
766 wrapper.flex = newItem.flex; |
|
767 |
|
768 // Remove the wrapper from the palette. |
|
769 if (draggedItemId != "separator" && |
|
770 draggedItemId != "spring" && |
|
771 draggedItemId != "spacer") |
|
772 gPaletteBox.removeChild(draggedPaletteWrapper); |
|
773 } |
|
774 |
|
775 gCurrentDragOverItem = null; |
|
776 |
|
777 toolboxChanged(); |
|
778 }; |
|
779 |
|
780 function onPaletteDragOver(aEvent) |
|
781 { |
|
782 if (isUnwantedDragEvent(aEvent)) { |
|
783 return; |
|
784 } |
|
785 var documentId = gToolboxDocument.documentElement.id; |
|
786 if (aEvent.dataTransfer.types.contains("text/toolbarwrapper-id/" + documentId.toLowerCase())) |
|
787 aEvent.preventDefault(); |
|
788 } |
|
789 |
|
790 function onPaletteDrop(aEvent) |
|
791 { |
|
792 if (isUnwantedDragEvent(aEvent)) { |
|
793 return; |
|
794 } |
|
795 var documentId = gToolboxDocument.documentElement.id; |
|
796 var itemId = aEvent.dataTransfer.getData("text/toolbarwrapper-id/" + documentId); |
|
797 |
|
798 var wrapper = gToolboxDocument.getElementById("wrapper-"+itemId); |
|
799 if (wrapper) { |
|
800 // Don't allow non-removable kids (e.g., the menubar) to move. |
|
801 if (wrapper.firstChild.getAttribute("removable") != "true") |
|
802 return; |
|
803 |
|
804 var wrapperType = wrapper.getAttribute("type"); |
|
805 if (wrapperType != "separator" && |
|
806 wrapperType != "spacer" && |
|
807 wrapperType != "spring") { |
|
808 restoreItemForToolbar(wrapper.firstChild, wrapper); |
|
809 wrapPaletteItem(document.importNode(wrapper.firstChild, true)); |
|
810 gToolbox.palette.appendChild(wrapper.firstChild); |
|
811 } |
|
812 |
|
813 // The item was dragged out of the toolbar. |
|
814 wrapper.parentNode.removeChild(wrapper); |
|
815 } |
|
816 |
|
817 toolboxChanged(); |
|
818 } |
|
819 |
|
820 |
|
821 function isUnwantedDragEvent(aEvent) { |
|
822 try { |
|
823 if (Services.prefs.getBoolPref("toolkit.customization.unsafe_drag_events")) { |
|
824 return false; |
|
825 } |
|
826 } catch (ex) {} |
|
827 |
|
828 /* Discard drag events that originated from a separate window to |
|
829 prevent content->chrome privilege escalations. */ |
|
830 let mozSourceNode = aEvent.dataTransfer.mozSourceNode; |
|
831 // mozSourceNode is null in the dragStart event handler or if |
|
832 // the drag event originated in an external application. |
|
833 if (!mozSourceNode) { |
|
834 return true; |
|
835 } |
|
836 let sourceWindow = mozSourceNode.ownerDocument.defaultView; |
|
837 return sourceWindow != window && sourceWindow != gToolboxDocument.defaultView; |
|
838 } |
|
839 |