download-manager.js: fix bug resulting in un-killable special buffers
[conkeror.git] / modules / element.js
blob8b0ffe4da81c4781bc759d8d63c8c533245dc76f
1 /**
2  * (C) Copyright 2007-2008 John J. Foerch
3  * (C) Copyright 2007-2008 Jeremy Maitin-Shepard
4  *
5  * Portions of this file are derived from Vimperator,
6  * (C) Copyright 2006-2007 Martin Stubenschrott.
7  *
8  * Use, modification, and distribution are subject to the terms specified in the
9  * COPYING file.
10 **/
12 require("hints.js");
13 require("save.js");
14 require("mime-type-override.js");
15 require("minibuffer-read-mime-type.js");
17 var browser_object_classes = {};
19 /**
20  * handler is a coroutine called as: handler(buffer, prompt)
21  */
22 define_keywords("$doc", "$action", "$label", "$handler", "$xpath_expression");
23 function define_browser_object_class(name) {
24     keywords(arguments, $xpath_expression = undefined);
25     var handler = arguments.$handler;
26     let xpath_expression = arguments.$xpath_expression;
27     if (handler === undefined && xpath_expression != undefined) {
28         handler = function (buf, prompt) {
29             var result = yield buf.window.minibuffer.read_hinted_element(
30                 $buffer = buf,
31                 $prompt = prompt,
32                 $hint_xpath_expression = xpath_expression);
33             yield co_return(result);
34         };
35     }
36     var base_obj = browser_object_classes[name];
37     if (base_obj == null)
38         base_obj = browser_object_classes[name] = {};
39     var obj;
40     if (arguments.$action) {
41         name = name + "/" + arguments.$action;
42         obj = browser_object_classes[name];
43         if (obj == null)
44             obj = browser_object_classes[name] = {__proto__: base_obj};
45     } else
46         obj = base_obj;
47     if (arguments.$label !== undefined)
48         obj.label = arguments.$label;
49     if (arguments.$doc !== undefined)
50         obj.doc = arguments.$doc;
51     if (handler !== undefined)
52         obj.handler = handler;
53     interactive(
54         "browser-object-class-"+name,
55         "A prefix command to specify that the following command operate "+
56             "on objects of type: "+name+".",
57         function (ctx) { ctx._browser_object_class = name; },
58         $prefix = true);
61 define_browser_object_class("images",
62                             $label = "image",
63                             $xpath_expression = "//img | //xhtml:img");
65 define_browser_object_class("frames", $label = "frame", $handler = function (buf, prompt) {
66     check_buffer(buf, content_buffer);
67     var doc = buf.document;
68     if (doc.getElementsByTagName("frame").length == 0 &&
69         doc.getElementsByTagName("iframe").length == 0)
70     {
71         // only one frame (the top-level one), no need to use the hints system
72         yield co_return(buf.top_frame);
73     }
75     var result = yield buf.window.minibuffer.read_hinted_element(
76         $buffer = buf,
77         $prompt = prompt,
78         $hint_xpath_expression = "//iframe | //frame | //xhtml:iframe | //xhtml:frame");
79     yield co_return(result);
80 });
82 define_browser_object_class(
83     "links", $label = "link",
84     $xpath_expression =
85         "//*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or " +
86         "@role='link'] | " +
87         "//input[not(@type='hidden')] | //a | //area | //iframe | //textarea | //button | //select | //label | " +
88         "//xhtml:*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand] | " +
89         "//xhtml:input[not(@type='hidden')] | //xhtml:a | //xhtml:area | //xhtml:iframe | //xhtml:textarea | " +
90         "//xhtml:button | //xhtml:select");
92 define_browser_object_class("mathml", $label = "MathML element", $xpath_expression = "//m:math");
94 define_browser_object_class("top", $handler = function (buf, prompt) { yield co_return(buf.top_frame); });
96 define_browser_object_class("url", $handler = function (buf, prompt) {
97                                 check_buffer (buf, content_buffer);
98                                 var result = yield buf.window.minibuffer.read_url ($prompt = prompt);
99                                 yield co_return (result);
100                             });
102 define_variable(
103     "default_browser_object_classes",
104     {
105         follow: "links",
106         follow_top: "frames",
107         focus: "frames",
108         save: "links",
109         copy: "links",
110         view_source: "frames",
111         bookmark: "frames",
112         save_page: "frames",
113         save_page_complete: "top",
114         save_page_as_text: "frames",
115         default: "links"
116     },
117     "Specifies the default object class for each operation.\n" +
118         "This variable should be an object literal with string-valued properties that specify one of the defined browser object classes.  If a property named after the operation is not present, the \"default\" property is consulted instead.");
120 interactive_context.prototype.browser_object_class = function (action_name) {
121     var cls =
122         this._browser_object_class ||
123         this.get("default_browser_object_classes")[action_name] ||
124         this.get("default_browser_object_classes")["default"];
125     return cls;
128 function lookup_browser_object_class(class_name, action) {
129     var obj;
130     if (action != null) {
131         obj = browser_object_classes[class_name + "/" + action];
132         if (obj)
133             return obj;
134     }
135     return browser_object_classes[class_name];
138 interactive_context.prototype.read_browser_object = function(action, action_name, target)
140     var object_class_name = this.browser_object_class(action);
141     var object_class = lookup_browser_object_class(object_class_name, action);
143     var prompt = action_name;
144     var label = object_class.label || object_class_name;
145     if (target != null)
146         prompt += TARGET_PROMPTS[target];
147     prompt += " (select " + label + "):";
149     var result = yield object_class.handler.call(null, this.buffer, prompt);
150     yield co_return(result);
154 function is_dom_node_or_window(elem) {
155     if (elem instanceof Ci.nsIDOMNode)
156         return true;
157     if (elem instanceof Ci.nsIDOMWindow)
158         return true;
159     return false;
163  * This is a simple wrapper function that sets focus to elem, and
164  * bypasses the automatic focus prevention system, which might
165  * otherwise prevent this from happening.
166  */
167 function browser_set_element_focus(buffer, elem, prevent_scroll) {
168     if (!is_dom_node_or_window(elem))
169         return;
171     buffer.last_user_input_received = Date.now();
172     if (prevent_scroll)
173         set_focus_no_scroll(buffer.window, elem);
174     else
175         elem.focus();
178 function browser_element_focus(buffer, elem)
180     if (!is_dom_node_or_window(elem))
181         return;
183     if (elem instanceof Ci.nsIDOMXULTextBoxElement)  {
184         // Focus the input field instead
185         elem = elem.wrappedJSObject.inputField;
186     }
188     browser_set_element_focus(buffer, elem);
189     if (elem instanceof Ci.nsIDOMWindow) {
190         return;
191     }
192     // If it is not a window, it must be an HTML element
193     var x = 0;
194     var y = 0;
195     if (elem instanceof Ci.nsIDOMHTMLFrameElement || elem instanceof Ci.nsIDOMHTMLIFrameElement) {
196         elem.contentWindow.focus();
197         return;
198     }
199     if (elem instanceof Ci.nsIDOMHTMLAreaElement) {
200         var coords = elem.getAttribute("coords").split(",");
201         x = Number(coords[0]);
202         y = Number(coords[1]);
203     }
205     var doc = elem.ownerDocument;
206     var evt = doc.createEvent("MouseEvents");
207     var doc = elem.ownerDocument;
209     evt.initMouseEvent("mouseover", true, true, doc.defaultView, 1, x, y, 0, 0, 0, 0, 0, 0, 0, null);
210     elem.dispatchEvent(evt);
213 function browser_element_follow(buffer, target, elem)
215     browser_set_element_focus(buffer, elem, true /* no scroll */);
217     var no_click = (is_load_spec(elem) ||
218                     (elem instanceof Ci.nsIDOMWindow) ||
219                     (elem instanceof Ci.nsIDOMHTMLFrameElement) ||
220                     (elem instanceof Ci.nsIDOMHTMLIFrameElement) ||
221                     (elem instanceof Ci.nsIDOMHTMLLinkElement) ||
222                     (elem instanceof Ci.nsIDOMHTMLImageElement &&
223                      !elem.hasAttribute("onmousedown") && !elem.hasAttribute("onclick")));
225     if (target == FOLLOW_DEFAULT && !no_click) {
226         var x = 1, y = 1;
227         if (elem instanceof Ci.nsIDOMHTMLAreaElement) {
228             var coords = elem.getAttribute("coords").split(",");
229             if (coords.length >= 2) {
230                 x = Number(coords[0]) + 1;
231                 y = Number(coords[1]) + 1;
232             }
233         }
234         browser_follow_link_with_click(buffer, elem, x, y);
235         return;
236     }
238     var spec = element_get_load_spec(elem);
239     if (spec == null) {
240         throw interactive_error("Element has no associated URL");
241         return;
242     }
244     if (load_spec_uri_string(spec).match(/^\s*javascript:/)) {
245         // This URL won't work
246         throw interactive_error("Can't load javascript URL");
247     }
249     switch (target) {
250     case FOLLOW_CURRENT_FRAME:
251         var current_frame = load_spec_source_frame(spec);
252         if (current_frame && current_frame != buffer.top_frame) {
253             var target_obj = get_web_navigation_for_frame(current_frame);
254             apply_load_spec(target_obj, spec);
255             break;
256         }
257     case FOLLOW_DEFAULT:
258     case FOLLOW_TOP_FRAME:
259     case OPEN_CURRENT_BUFFER:
260         buffer.load(spec);
261         break;
262     case OPEN_NEW_WINDOW:
263     case OPEN_NEW_BUFFER:
264     case OPEN_NEW_BUFFER_BACKGROUND:
265         create_buffer(buffer.window,
266                       buffer_creator(content_buffer,
267                                      $load = spec,
268                                      $configuration = buffer.configuration),
269                       target);
270     }
274  * Follow a link-like element by generating fake mouse events.
275  */
276 function browser_follow_link_with_click(buffer, elem, x, y) {
277     var doc = elem.ownerDocument;
278     var view = doc.defaultView;
280     var evt = doc.createEvent("MouseEvents");
281     evt.initMouseEvent("mousedown", true, true, view, 1, x, y, 0, 0, /*ctrl*/ 0, /*event.altKey*/0,
282                        /*event.shiftKey*/ 0, /*event.metaKey*/ 0, 0, null);
283     elem.dispatchEvent(evt);
285     evt.initMouseEvent("click", true, true, view, 1, x, y, 0, 0, /*ctrl*/ 0, /*event.altKey*/0,
286                        /*event.shiftKey*/ 0, /*event.metaKey*/ 0, 0, null);
287     elem.dispatchEvent(evt);
290 function element_get_load_spec(elem) {
292     if (is_load_spec(elem))
293         return elem;
295     var spec = null;
297     if (elem instanceof Ci.nsIDOMWindow)
298         spec = load_spec({document: elem.document});
300     else if (elem instanceof Ci.nsIDOMHTMLFrameElement ||
301              elem instanceof Ci.nsIDOMHTMLIFrameElement)
302         spec = load_spec({document: elem.contentDocument});
304     else {
305         var url = null;
306         var title = null;
308         if (elem instanceof Ci.nsIDOMHTMLAnchorElement ||
309             elem instanceof Ci.nsIDOMHTMLAreaElement ||
310             elem instanceof Ci.nsIDOMHTMLLinkElement) {
311             if (!elem.hasAttribute("href"))
312                 return null; // nothing can be done, as no nesting within these elements is allowed
313             url = elem.href;
314             title = elem.title || elem.textContent;
315         }
316         else if (elem instanceof Ci.nsIDOMHTMLImageElement) {
317             url = elem.src;
318             title = elem.title || elem.alt;
319         }
320         else {
321             var node = elem;
322             while (node && !(node instanceof Ci.nsIDOMHTMLAnchorElement))
323                 node = node.parentNode;
324             if (node && !node.hasAttribute("href"))
325                 node = null;
326             else
327                 url = node.href;
328             if (!node) {
329                 // Try simple XLink
330                 node = elem;
331                 while (node) {
332                     if (node.nodeType == Ci.nsIDOMNode.ELEMENT_NODE) {
333                         url = linkNode.getAttributeNS(XLINK_NS, "href");
334                         break;
335                     }
336                     node = node.parentNode;
337                 }
338                 if (url)
339                     url = makeURLAbsolute(node.baseURI, url);
340                 title = node.title || node.textContent;
341             }
342         }
343         if (url && url.length > 0) {
344             if (title && title.length == 0)
345                 title = null;
346             spec = load_spec({uri: url, source_frame: elem.ownerDocument.defaultView, title: title});
347         }
348     }
349     return spec;
352 interactive("follow", function (I) {
353     var target = I.browse_target("follow");
354     var element = yield I.read_browser_object("follow", "Follow", target);
355     browser_element_follow(I.buffer, target, element);
358 interactive("follow-top", function (I) {
359     var target = I.browse_target("follow-top");
360     var element = yield I.read_browser_object("follow_top", "Follow", target);
361     browser_element_follow(I.buffer, target, element);
364 interactive("focus", function (I) {
365     var element = yield I.read_browser_object("focus", "Focus");
366     browser_element_focus(I.buffer, element);
369 function element_get_load_target_label(element) {
370     if (element instanceof Ci.nsIDOMWindow)
371         return "page";
372     if (element instanceof Ci.nsIDOMHTMLFrameElement)
373         return "frame";
374     if (element instanceof Ci.nsIDOMHTMLIFrameElement)
375         return "iframe";
376     return null;
379 function element_get_operation_label(element, op_name, suffix) {
380     var target_label = element_get_load_target_label(element);
381     if (target_label != null)
382         target_label = " " + target_label;
383     else
384         target_label = "";
386     if (suffix != null)
387         suffix = " " + suffix;
388     else
389         suffix = "";
391     return op_name + target_label + suffix + ":";
394 interactive("save", function (I) {
395     var element = yield I.read_browser_object("save", "Save");
397     var spec = element_get_load_spec(element);
398     if (spec == null)
399         throw interactive_error("Element has no associated URI");
401     var panel;
402     panel = create_info_panel(I.window, "download-panel",
403                               [["downloading",
404                                 element_get_operation_label(element, "Saving"),
405                                 load_spec_uri_string(spec)],
406                                ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
408     try {
409         var file = yield I.minibuffer.read_file_check_overwrite(
410             $prompt = "Save as:",
411             $initial_value = suggest_save_path_from_file_name(suggest_file_name(spec), I.buffer),
412             $history = "save");
414     } finally {
415         panel.destroy();
416     }
418     save_uri(spec, file,
419              $buffer = I.buffer,
420              $use_cache = false);
423 function browser_element_copy(buffer, elem)
425     var spec = element_get_load_spec(elem);
426     var text = null;
427     if (spec)
428         text = load_spec_uri_string(spec);
429     else  {
430         if (!(elem instanceof Ci.nsIDOMNode))
431             throw interactive_error("Element has no associated text to copy.");
432         switch (elem.localName) {
433         case "INPUT":
434         case "TEXTAREA":
435             text = elem.value;
436             break;
437         case "SELECT":
438             if (elem.selectedIndex >= 0)
439                 text = elem.item(elem.selectedIndex).text;
440             break;
441         default:
442             text = elem.textContent;
443             break;
444         }
445     }
446     writeToClipboard (text);
447     buffer.window.minibuffer.message ("Copied: " + text);
451 interactive("copy", function (I) {
452     var element = yield I.read_browser_object("copy", "Copy");
453     browser_element_copy(I.buffer, element);
456 var view_source_use_external_editor = false, view_source_function = null;
457 function browser_element_view_source(buffer, target, elem)
459     if (view_source_use_external_editor || view_source_function)
460     {
461         var spec = element_get_load_spec(elem);
462         if (spec == null) {
463             throw interactive_error("Element has no associated URL");
464             return;
465         }
467         let [file, temp] = yield download_as_temporary(spec,
468                                                        $buffer = buffer,
469                                                        $action = "View source");
470         if (view_source_use_external_editor)
471             yield open_file_with_external_editor(file, $temporary = temp);
472         else
473             yield view_source_function(file, $temporary = temp);
474         return;
475     }
477     var win = null;
478     var window = buffer.window;
479     if (elem.localName) {
480         switch (elem.localName.toLowerCase()) {
481         case "frame": case "iframe":
482             win = elem.contentWindow;
483             break;
484         case "math":
485             view_mathml_source (window, charset, elem);
486             return;
487         default:
488             throw new Error("Invalid browser element");
489         }
490     } else
491         win = elem;
492     win.focus();
494     var url_s = win.location.href;
495     if (url_s.substring (0,12) != "view-source:") {
496         try {
497             open_in_browser(buffer, target, "view-source:" + url_s);
498         } catch(e) { dump_error(e); }
499     } else {
500         window.minibuffer.message ("Already viewing source");
501     }
504 interactive("view-source", function (I) {
505     var target = I.browse_target("follow");
506     var element = yield I.read_browser_object("view_source", "View source", target);
507     yield browser_element_view_source(I.buffer, target, element);
510 interactive("shell-command-on-url", function (I) {
511     var cwd = I.cwd;
512     var element = yield I.read_browser_object("shell_command_url", "URL shell command");
513     var spec = element_get_load_spec(element);
514     if (spec == null)
515         throw interactive_error("Unable to obtain URI from element");
517     var uri = load_spec_uri_string(spec);
519     var panel;
520     panel = create_info_panel(I.window, "download-panel",
521                               [["downloading",
522                                 element_get_operation_label(element, "Running on", "URI"),
523                                 load_spec_uri_string(spec)],
524                                ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
526     try {
527         var cmd = yield I.minibuffer.read_shell_command(
528             $cwd = cwd,
529             $initial_value = load_spec_default_shell_command(spec));
530     } finally {
531         panel.destroy();
532     }
534     shell_command_with_argument_blind(cmd, uri, $cwd = cwd);
537 function browser_element_shell_command(buffer, elem, command) {
538     var spec = element_get_load_spec(elem);
539     if (spec == null) {
540         throw interactive_error("Element has no associated URL");
541         return;
542     }
543     yield download_as_temporary(spec,
544                                 $buffer = buffer,
545                                 $shell_command = command,
546                                 $shell_command_cwd = buffer.cwd);
549 interactive("shell-command-on-file", function (I) {
550     var cwd = I.cwd;
551     var element = yield I.read_browser_object("shell_command", "Shell command");
553     var spec = element_get_load_spec(element);
554     if (spec == null)
555         throw interactive_error("Unable to obtain URI from element");
557     var uri = load_spec_uri_string(spec);
559     var panel;
560     panel = create_info_panel(I.window, "download-panel",
561                               [["downloading",
562                                 element_get_operation_label(element, "Running on"),
563                                 load_spec_uri_string(spec)],
564                                ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
566     try {
568         var cmd = yield I.minibuffer.read_shell_command(
569             $cwd = cwd,
570             $initial_value = load_spec_default_shell_command(spec));
571     } finally {
572         panel.destroy();
573     }
575     /* FIXME: specify cwd as well */
576     yield browser_element_shell_command(I.buffer, element, cmd);
579 interactive("bookmark", function (I) {
580     var element = yield I.read_browser_object("bookmark", "Bookmark");
581     var spec = element_get_load_spec(element);
582     if (!spec)
583         throw interactive_error("Element has no associated URI");
584     var uri_string = load_spec_uri_string(spec);
585     var panel;
586     panel = create_info_panel(I.window, "bookmark-panel",
587                               [["bookmarking",
588                                 element_get_operation_label(element, "Bookmarking"),
589                                 uri_string]]);
590     try {
591         var title = yield I.minibuffer.read($prompt = "Bookmark with title:", $initial_value = load_spec_title(spec) || "");
592     } finally {
593         panel.destroy();
594     }
595     add_bookmark(uri_string, title);
596     I.minibuffer.message("Added bookmark: " + uri_string + " - " + title);
599 interactive("save-page", function (I) {
600     check_buffer(I.buffer, content_buffer);
601     var element = yield I.read_browser_object("save_page", "Save page");
602     var spec = element_get_load_spec(element);
603     if (!spec || !load_spec_document(spec))
604         throw interactive_error("Element is not associated with a document.");
605     var suggested_path = suggest_save_path_from_file_name(suggest_file_name(spec), I.buffer);
607     var panel;
608     panel = create_info_panel(I.window, "download-panel",
609                               [["downloading",
610                                 element_get_operation_label(element, "Saving"),
611                                 load_spec_uri_string(spec)],
612                                ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
614     try {
615         var file = yield I.minibuffer.read_file_check_overwrite(
616             $prompt = "Save page as:",
617             $history = "save",
618             $initial_value = suggested_path);
619     } finally {
620         panel.destroy();
621     }
623     save_uri(spec, file, $buffer = I.buffer);
626 interactive("save-page-as-text", function (I) {
627     check_buffer(I.buffer, content_buffer);
628     var element = yield I.read_browser_object("save_page_as_text", "Save page as text");
629     var spec = element_get_load_spec(element);
630     var doc;
631     if (!spec || !(doc = load_spec_document(spec)))
632         throw interactive_error("Element is not associated with a document.");
633     var suggested_path = suggest_save_path_from_file_name(suggest_file_name(spec, "txt"), I.buffer);
635     var panel;
636     panel = create_info_panel(I.window, "download-panel",
637                               [["downloading",
638                                 element_get_operation_label(element, "Saving", "as text"),
639                                 load_spec_uri_string(spec)],
640                                ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
642     try {
643         var file = yield I.minibuffer.read_file_check_overwrite(
644             $prompt = "Save page as text:",
645             $history = "save",
646             $initial_value = suggested_path);
647     } finally {
648         panel.destroy();
649     }
651     save_document_as_text(doc, file, $buffer = I.buffer);
654 interactive("save-page-complete", function (I) {
655     check_buffer(I.buffer, content_buffer);
656     var element = yield I.read_browser_object("save_page_complete", "Save page complete");
657     var spec = element_get_load_spec(element);
658     var doc;
659     if (!spec || !(doc = load_spec_document(spec)))
660         throw interactive_error("Element is not associated with a document.");
661     var suggested_path = suggest_save_path_from_file_name(suggest_file_name(spec), I.buffer);
663     var panel;
664     panel = create_info_panel(I.window, "download-panel",
665                               [["downloading",
666                                 element_get_operation_label(element, "Saving complete"),
667                                 load_spec_uri_string(spec)],
668                                ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
670     try {
671         var file = yield I.minibuffer.read_file_check_overwrite(
672             $prompt = "Save page complete:",
673             $history = "save",
674             $initial_value = suggested_path);
675         // FIXME: use proper read function
676         var dir = yield I.minibuffer.read_file(
677             $prompt = "Data Directory:",
678             $history = "save",
679             $initial_value = file.path + ".support");
680     } finally {
681         panel.destroy();
682     }
684     save_document_complete(doc, file, dir, $buffer = I.buffer);
687 default_browse_targets["view-as-mime-type"] = [FOLLOW_CURRENT_FRAME, OPEN_CURRENT_BUFFER,
688                                                OPEN_NEW_BUFFER, OPEN_NEW_WINDOW];
689 interactive("view-as-mime-type",
690             "Display a browser object in the browser using the specified MIME type.",
691             function (I) {
692                 var element = yield I.read_browser_object("view_as_mime_type", "View in browser as mime type");
693                 var spec = element_get_load_spec(element);
695                 var target = I.browse_target("view-as-mime-type");
697                 if (!spec)
698                     throw interactive_error("Element is not associated with a URI");
700                 if (!can_override_mime_type_for_uri(load_spec_uri(spec)))
701                     throw interactive_error("Overriding the MIME type is not currently supported for non-HTTP URLs.");
703                 var panel;
705                 var mime_type = load_spec_mime_type(spec);
706                 panel = create_info_panel(I.window, "download-panel",
707                                           [["downloading",
708                                             element_get_operation_label(element, "View in browser"),
709                                             load_spec_uri_string(spec)],
710                                            ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
713                 try {
714                     let suggested_type = mime_type;
715                     if (gecko_viewable_mime_type_list.indexOf(suggested_type) == -1)
716                         suggested_type = "text/plain";
717                     mime_type = yield I.minibuffer.read_gecko_viewable_mime_type(
718                         $prompt = "View internally as",
719                         $initial_value = suggested_type,
720                         $select);
721                     override_mime_type_for_next_load(load_spec_uri(spec), mime_type);
722                     browser_element_follow(I.buffer, target, spec);
723                 } finally {
724                     panel.destroy();
725                 }
726             });