diff --git a/pd/nw/pd_canvas.js b/pd/nw/pd_canvas.js
index ad1bf2fc89b9b3c33a74d89efd099a0fdeddc7cd..8a02095e61e327e2686011239de46a1f41785115 100644
--- a/pd/nw/pd_canvas.js
+++ b/pd/nw/pd_canvas.js
@@ -160,6 +160,8 @@ var canvas_events = (function() {
         last_draggable_y,       // last y
         previous_state = "none", /* last state, excluding explicit 'none' */
         match_words_state = false,
+        last_dropdown_menu_x,
+        last_dropdown_menu_y,
         last_search_term = "",
         svg_view = document.getElementById("patchsvg").viewBox.baseVal,
         textbox = function () {
@@ -208,21 +210,44 @@ var canvas_events = (function() {
             svg.setAttribute("height", h);
         },
         dropdown_index_to_pd = function(elem) {
-            pdgui.pdsend(elem.getAttribute("data-callback"),
-                elem.querySelector(".highlighted").getAttribute("data-index"));
+            var highlighted = elem.querySelector(".highlighted");
+            if (highlighted) {
+                pdgui.pdsend(elem.getAttribute("data-callback"),
+                    highlighted.getAttribute("data-index"));
+            }
         },
-        dropdown_highlight_elem = function(elem) {
+        dropdown_clear_highlight = function() {
             var container = document.querySelector("#dropdown_list"),
-                li_array;
-            if (!elem.classList.contains("highlighted")) {
                 li_array = container.querySelectorAll("li");
-                Array.prototype.forEach.call(li_array, function(e) {
-                    e.classList.remove("highlighted");
-                });
+            Array.prototype.forEach.call(li_array, function(e) {
+                e.classList.remove("highlighted");
+            });
+        },
+        dropdown_highlight_elem = function(elem, scroll) {
+            var container = document.querySelector("#dropdown_list");
+            if (!elem.classList.contains("highlighted")) {
+                dropdown_clear_highlight();
                 elem.classList.add("highlighted");
                 // Make sure the highlighted element is in view
-                container.scrollTop = elem.offsetTop + elem.offsetHeight
-                    - container.clientHeight;
+                if (scroll) {
+                    if (elem.offsetTop < container.scrollTop
+                        || elem.offsetTop + elem.offsetHeight >
+                            container.scrollTop + container.offsetHeight) {
+                            if (scroll === "up") {
+                                // we can't usse elem.scrollIntoView() here
+                                // because it may also change the scrollbar on
+                                // the document, which in turn could change the
+                                // pageX/pageY, for the mousemove event, which
+                                // in turn would make it impossible for us to
+                                // filter out unnecessary mousemove calls
+                                //    elem.scrollIntoView();
+                            container.scrollTop = elem.offsetTop;
+                        } else if (scroll === "down") {
+                            container.scrollTop = elem.offsetTop + elem.offsetHeight
+                                - container.clientHeight;
+                        }
+                    }
+                }
             }
         },
         events = {
@@ -494,13 +519,13 @@ var canvas_events = (function() {
             iemgui_label_mouseup: function(evt) {
                 //pdgui.post("lifting the mousebutton on an iemgui label");
                 // Set last state (none doesn't count as a state)
-                //pdgui.post("previous state is " + canvas_events.get_previous_state());
+                //pdgui.post("previous state is "
+                //    + canvas_events.get_previous_state());
                 canvas_events[canvas_events.get_previous_state()]();
             },
             dropdown_menu_keydown: function(evt) {
                 var select_elem = document.querySelector("#dropdown_list"),
                     li;
-                pdgui.post("keycode is " + evt.keyCode);
                 switch(evt.keyCode) {
                     case 13:
                     case 32:
@@ -510,34 +535,43 @@ var canvas_events = (function() {
                         break;
                     case 27: // escape
                         select_elem.style.setProperty("display", "none");
-                        pdgui.post("canceled thing");
                         canvas_events.normal();
                         break;
                     case 38: // up
                         li = select_elem.querySelector(".highlighted");
                         li = li.previousElementSibling ||
                              li.parentElement.lastElementChild;
-                        dropdown_highlight_elem(li);
+                        dropdown_highlight_elem(li, "up");
+                        evt.preventDefault();
                         break;
                     case 40: // down
                         li = select_elem.querySelector(".highlighted");
                         li = li.nextElementSibling ||
                              li.parentElement.firstElementChild;
-                        dropdown_highlight_elem(li);
+                        dropdown_highlight_elem(li, "down");
+                        evt.preventDefault();
+                        break;
                     default:
                 }
-
             },
             dropdown_menu_keypress: function(evt) {
                 var li_nodes = document.querySelectorAll("#dropdown_list li"),
                     string_array = [],
+                    highlighted,
                     highlighted_index,
                     match,
                     offset;
-                highlighted_index =
-                    +document.querySelector("#dropdown_list .highlighted")
-                        .getAttribute("data-index");
-                offset = highlighted_index + 1;
+                highlighted = document
+                    .querySelector("#dropdown_list .highlighted");
+                if (highlighted) {
+                    highlighted_index =
+                        +document.querySelector("#dropdown_list .highlighted")
+                            .getAttribute("data-index");
+                    offset = highlighted_index + 1;
+                } else {
+                    highlighted_index = 1;
+                    offset = 2;
+                }
                 Array.prototype.forEach.call(li_nodes, function(e, i, a) {
                     var s = a[(i + offset) % a.length];
                     string_array.push(s.textContent.trim());
@@ -550,23 +584,49 @@ var canvas_events = (function() {
                 if (match !== undefined) {
                     match = (match + offset) % li_nodes.length;
                     if (match !== highlighted_index) {
-                        dropdown_highlight_elem(li_nodes[match]);
+                        dropdown_highlight_elem(li_nodes[match],
+                            match < highlighted_index ? "up" : "down");
                     }
                 }
             },
             dropdown_menu_mousedown: function(evt) {
-                var select_elem = document.querySelector("#dropdown_list");
+                var select_elem = document.querySelector("#dropdown_list"),
+                    in_dropdown = evt.target;
+                while (in_dropdown) {
+                    if (in_dropdown.id === "dropdown_list") {
+                        break;
+                    }
+                    in_dropdown = in_dropdown.parentNode;
+                }
+                // Allow scrollbar click and drag without closing the menu
+                if (in_dropdown &&
+                        evt.pageX - select_elem.offsetLeft >
+                        select_elem.clientWidth) {
+                    return;
+                }
+                // Special case for OSX, where the scrollbar doesn't take
+                // up any extra space
+                if (nw.process.platform === "darwin"
+                    && (evt.target.id === "dropdown_list")) {
+                    return;
+                }
                 if (evt.target.parentNode
                     && evt.target.parentNode.parentNode
                     && evt.target.parentNode.parentNode.id === "dropdown_list") {
                     dropdown_highlight_elem(evt.target);
                 }
+                // This selects whatever item is highlighted even
+                // if we click outside the menu. Might be better to
+                // cancel in that case.
                 dropdown_index_to_pd(select_elem);
                 select_elem.style.setProperty("display", "none");
                 canvas_events.normal();
             },
             dropdown_menu_mouseup: function(evt) {
                 var i, select_elem;
+                // This can be triggered if the user keeps the mouse down
+                // to highlight an element and releases the mouse button to
+                // choose that element
                 if (evt.target.parentNode
                     && evt.target.parentNode.parentNode
                     && evt.target.parentNode.parentNode.id === "dropdown_list") {
@@ -576,14 +636,33 @@ var canvas_events = (function() {
                     canvas_events.normal();
                 }
             },
-            dropdown_menu_mouseover: function(evt) {
-                var li_array;
-                if (evt.target.parentNode
-                    && evt.target.parentNode.parentNode
-                    && evt.target.parentNode.parentNode.id === "dropdown_list") {
-                    dropdown_highlight_elem(evt.target);
+            dropdown_menu_wheel: function(evt) {
+                // Here we generate bogus mouse coords so that
+                // we can break through the filter below if we're
+                // using the mouse wheel to scroll in the list.
+                last_dropdown_menu_x = Number.MIN_VALUE;
+                last_dropdown_menu_y = Number.MIN_VALUE;
+            },
+            dropdown_menu_mousemove: function(evt) {
+                // For whatever reason, Chromium decides to trigger the
+                // mousemove/mouseenter/mouseover events if the element
+                // underneath it changes (or for mousemove, if the element
+                // moves at all). Unfortunately that means we have to track
+                // the mouse position the entire time to filter out the
+                // true mousemove events from the ones Chromium generates
+                // when a scroll event changes the element under the mouse.
+                if (evt.pageX !== last_dropdown_menu_x
+                    || evt.pageY !== last_dropdown_menu_y) {
+                    if (evt.target.parentNode
+                        && evt.target.parentNode.parentNode
+                        && evt.target.parentNode.parentNode.id === "dropdown_list") {
+                        dropdown_highlight_elem(evt.target);
+                    } else {
+                        dropdown_clear_highlight();
+                    }
                 }
-                // hide the dropdown menu <div> thingy
+                last_dropdown_menu_x = evt.pageX;
+                last_dropdown_menu_y = evt.pageY;
             }
         },
         utils = {
@@ -840,9 +919,11 @@ var canvas_events = (function() {
             this.none();
             document.addEventListener("mousedown", events.dropdown_menu_mousedown, false);
             document.addEventListener("mouseup", events.dropdown_menu_mouseup, false);
-            document.addEventListener("mouseover", events.dropdown_menu_mouseover, false);
+            document.addEventListener("mousemove", events.dropdown_menu_mousemove, false);
             document.addEventListener("keydown", events.dropdown_menu_keydown, false);
             document.addEventListener("keypress", events.dropdown_menu_keypress, false);
+            document.querySelector("#dropdown_list")
+                .addEventListener("wheel", events.dropdown_menu_wheel, false);
         },
         search: function() {
             this.none();
diff --git a/pd/nw/pdgui.js b/pd/nw/pdgui.js
index 9b074b430a619ba28202d13124e24c3783f809bb..aa9c5161b6fb40069bd3decf021dfb02da9b1ce8 100644
--- a/pd/nw/pdgui.js
+++ b/pd/nw/pdgui.js
@@ -4596,7 +4596,13 @@ function dropdown_populate(cid, label_array, current_index) {
 }
 
 function gui_dropdown_activate(cid, obj_tag, tag, current_index, font_size, state, label_array) {
-    var g, select_elem, svg_view;
+    var g, select_elem, svg_view, g_bbox,
+        doc_height,    // document height, excluding the scrollbar
+        menu_height, // height of the list of elements inside the div
+        div_y,       // position of div containing the dropdown menu
+        div_max,     // max height of the div
+        scroll_y,
+        offset_anchor; // top or bottom
     // Annoying: obj_tag is just the "x"-prepended hex value for the object,
     // and tag is the one from rtext_gettag that is used as our gobj id
     if (patchwin[cid]) {
@@ -4604,21 +4610,54 @@ function gui_dropdown_activate(cid, obj_tag, tag, current_index, font_size, stat
         if (state !== 0) {
             svg_view = patchwin[cid].window.document.getElementById("patchsvg")
                 .viewBox.baseVal;
-            dropdown_populate(cid, label_array, current_index);
             select_elem = patchwin[cid]
                 .window.document.querySelector("#dropdown_list");
+            dropdown_populate(cid, label_array, current_index);
             // stick the obj_tag in a data field
             select_elem.setAttribute("data-callback", obj_tag);
-            select_elem.style.setProperty("max-height",
-                (patchwin[cid].window.innerHeight -
-                    g.getBoundingClientRect().bottom - 5) + "px"
-            );
+            // display the menu so we can measure it
+
+            g_bbox = g.getBoundingClientRect();
+            // Measuring the document height is tricky-- the following
+            // method is the only reliable one I've found. And even here,
+            // if you display the select_elem as inline _before_ measuring
+            // the doc height, the result ends up being _smaller_. No idea.
+            doc_height =
+                patchwin[cid].window.document.documentElement
+                    .clientHeight;
+            // Now let's display the select_elem div so we can measure it
             select_elem.style.setProperty("display", "inline");
+            menu_height = select_elem.querySelector("ol")
+                .getBoundingClientRect().height;
+            scroll_y = patchwin[cid].window.scrollY;
+            // If the area below the object is smaller than 75px, then
+            // display the menu above the object.
+            // If the entire menu won't fit below the object but _will_
+            // fit above it, display it above the object.
+            // If the menu needs a scrollbar, display it below the object
+            if (doc_height - g_bbox.bottom <= 75
+                || (menu_height > doc_height - g_bbox.bottom
+                    && menu_height <= g_bbox.top)) {
+                // menu on top
+                offset_anchor = "bottom";
+                div_max = g_bbox.top - 2;
+                div_y = doc_height - (g_bbox.top + scroll_y);
+            }
+            else {
+                // menu on bottom (possibly with scrollbar)
+                offset_anchor = "top";
+                div_max = doc_height - g_bbox.bottom - 2;
+                div_y = g_bbox.bottom + scroll_y;
+            }
+            // set a max-height to force scrollbar if needed
+            select_elem.style.setProperty("max-height", div_max + "px");
             select_elem.style.setProperty("left",
                 (elem_get_coords(g).x - svg_view.x) + "px");
-            select_elem.style.setProperty("top",
-                (elem_get_coords(g).y + g.getBBox().height - svg_view.y)
-                    + "px");
+            // Remove "top" and "bottom" props to keep state clean
+            select_elem.style.removeProperty("top");
+            select_elem.style.removeProperty("bottom");
+            // Now position the div relative to either the "top" or "bottom"
+            select_elem.style.setProperty(offset_anchor, div_y + "px");
             select_elem.style.setProperty("font-size",
                 pd_fontsize_to_gui_fontsize(font_size) + "px");
             select_elem.style.setProperty("min-width", g.getBBox().width + "px");