Marco Trevisan (Treviño) has proposed merging 
~3v1n0/ubuntu/+source/gnome-shell:upstream/3.28.x into 
~ubuntu-desktop/ubuntu/+source/gnome-shell:upstream/3.28.x.

Requested reviews:
  Ubuntu Desktop (ubuntu-desktop)

For more details, see:
https://code.launchpad.net/~3v1n0/ubuntu/+source/gnome-shell/+git/gnome-shell/+merge/362054
-- 
Your team Ubuntu Desktop is requested to review the proposed merge of 
~3v1n0/ubuntu/+source/gnome-shell:upstream/3.28.x into 
~ubuntu-desktop/ubuntu/+source/gnome-shell:upstream/3.28.x.
diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js
index 481cd3a..a0a4a21 100644
--- a/js/gdm/authPrompt.js
+++ b/js/gdm/authPrompt.js
@@ -242,11 +242,11 @@ var AuthPrompt = new Lang.Class({
         this.emit('prompted');
     },
 
-    _onVerificationFailed() {
+    _onVerificationFailed(userVerifier, canRetry) {
         this._queryingService = null;
         this.clear();
 
-        this.updateSensitivity(true);
+        this.updateSensitivity(canRetry);
         this.setActorInDefaultButtonWell(null);
         this.verificationStatus = AuthPromptStatus.VERIFICATION_FAILED;
     },
@@ -439,6 +439,7 @@ var AuthPrompt = new Lang.Class({
         this.verificationStatus = AuthPromptStatus.NOT_VERIFYING;
         this.cancelButton.reactive = true;
         this.nextButton.label = _("Next");
+        this._preemptiveAnswer = null;
 
         if (this._userVerifier)
             this._userVerifier.cancel();
diff --git a/js/gdm/util.js b/js/gdm/util.js
index 261e1e4..105a320 100644
--- a/js/gdm/util.js
+++ b/js/gdm/util.js
@@ -534,12 +534,13 @@ var ShellUserVerifier = new Lang.Class({
     _verificationFailed(retry) {
         // For Not Listed / enterprise logins, immediately reset
         // the dialog
-        // Otherwise, we allow ALLOWED_FAILURES attempts. After that, we
-        // go back to the welcome screen.
+        // Otherwise, when in login mode we allow ALLOWED_FAILURES attempts.
+        // After that, we go back to the welcome screen.
 
         this._failCounter++;
         let canRetry = retry && this._userName &&
-            this._failCounter < this._settings.get_int(ALLOWED_FAILURES_KEY);
+            (this._reauthOnly ||
+             this._failCounter < this._settings.get_int(ALLOWED_FAILURES_KEY));
 
         if (canRetry) {
             if (!this.hasPendingMessages) {
@@ -562,7 +563,7 @@ var ShellUserVerifier = new Lang.Class({
             }
         }
 
-        this.emit('verification-failed');
+        this.emit('verification-failed', canRetry);
     },
 
     _onConversationStopped(client, serviceName) {
diff --git a/js/misc/objectManager.js b/js/misc/objectManager.js
index 1ce4f83..1442e3d 100644
--- a/js/misc/objectManager.js
+++ b/js/misc/objectManager.js
@@ -236,11 +236,12 @@ var ObjectManager = new Lang.Class({
     _onNameVanished() {
         let objectPaths = Object.keys(this._objects);
         for (let i = 0; i < objectPaths.length; i++) {
-            let object = this._objects[objectPaths];
+            let objectPath = objectPaths[i];
+            let object = this._objects[objectPath];
 
             let interfaceNames = Object.keys(object);
-            for (let j = 0; i < interfaceNames.length; i++) {
-                let interfaceName = interfaceNames[i];
+            for (let j = 0; j < interfaceNames.length; j++) {
+                let interfaceName = interfaceNames[j];
 
                 if (object[interfaceName])
                     this._removeInterface(objectPath, interfaceName);
diff --git a/js/ui/calendar.js b/js/ui/calendar.js
index 651aac6..fd133cc 100644
--- a/js/ui/calendar.js
+++ b/js/ui/calendar.js
@@ -802,6 +802,8 @@ var NotificationMessage = new Lang.Class({
     },
 
     _onDestroy() {
+        this.parent();
+
         if (this._updatedId)
             this.notification.disconnect(this._updatedId);
         this._updatedId = 0;
diff --git a/js/ui/components/automountManager.js b/js/ui/components/automountManager.js
index 2d8f3f8..a6cd857 100644
--- a/js/ui/components/automountManager.js
+++ b/js/ui/components/automountManager.js
@@ -210,6 +210,10 @@ var AutomountManager = new Lang.Class({
     },
 
     _onVolumeRemoved(monitor, volume) {
+        if (volume._allowAutorunExpireId && volume._allowAutorunExpireId > 0) {
+            Mainloop.source_remove(volume._allowAutorunExpireId);
+            delete volume._allowAutorunExpireId;
+        }
         this._volumeQueue = 
             this._volumeQueue.filter(element => (element != volume));
     },
@@ -234,8 +238,10 @@ var AutomountManager = new Lang.Class({
     _allowAutorunExpire(volume) {
         let id = Mainloop.timeout_add_seconds(AUTORUN_EXPIRE_TIMEOUT_SECS, () => {
             volume.allowAutorun = false;
+            delete volume._allowAutorunExpireId;
             return GLib.SOURCE_REMOVE;
         });
+        volume._allowAutorunExpireId = id;
         GLib.Source.set_name_by_id(id, '[gnome-shell] volume.allowAutorun');
     }
 });
diff --git a/js/ui/dash.js b/js/ui/dash.js
index 5ee2476..d75af65 100644
--- a/js/ui/dash.js
+++ b/js/ui/dash.js
@@ -52,6 +52,8 @@ var DashItemContainer = new Lang.Class({
         this.animatingOut = false;
 
         this.connect('destroy', () => {
+            if (this.child != null)
+                this.child.destroy();
             this.label.destroy();
         });
     },
diff --git a/js/ui/dnd.js b/js/ui/dnd.js
index a38607c..ec1ba1d 100644
--- a/js/ui/dnd.js
+++ b/js/ui/dnd.js
@@ -396,10 +396,15 @@ var _Draggable = new Lang.Class({
         return true;
     },
 
+    _pickTargetActor() {
+        return this._dragActor.get_stage().get_actor_at_pos(Clutter.PickMode.ALL,
+                                                            this._dragX, this._dragY);
+    },
+
     _updateDragHover() {
         this._updateHoverId = 0;
-        let target = this._dragActor.get_stage().get_actor_at_pos(Clutter.PickMode.ALL,
-                                                                  this._dragX, this._dragY);
+        let target = this._pickTargetActor();
+
         let dragEvent = {
             x: this._dragX,
             y: this._dragY,
@@ -407,6 +412,18 @@ var _Draggable = new Lang.Class({
             source: this.actor._delegate,
             targetActor: target
         };
+
+        let targetActorDestroyHandlerId;
+        let handleTargetActorDestroyClosure;
+        handleTargetActorDestroyClosure = () => {
+            target = this._pickTargetActor();
+            dragEvent.targetActor = target;
+            targetActorDestroyHandlerId =
+                target.connect('destroy', handleTargetActorDestroyClosure);
+        };
+        targetActorDestroyHandlerId =
+            target.connect('destroy', handleTargetActorDestroyClosure);
+
         for (let i = 0; i < dragMonitors.length; i++) {
             let motionFunc = dragMonitors[i].dragMotion;
             if (motionFunc) {
@@ -417,6 +434,7 @@ var _Draggable = new Lang.Class({
                 }
             }
         }
+        dragEvent.targetActor.disconnect(targetActorDestroyHandlerId);
 
         while (target) {
             if (target._delegate && target._delegate.handleDragOver) {
diff --git a/js/ui/endSessionDialog.js b/js/ui/endSessionDialog.js
index 7d18d0b..07c9541 100644
--- a/js/ui/endSessionDialog.js
+++ b/js/ui/endSessionDialog.js
@@ -760,7 +760,7 @@ var EndSessionDialog = new Lang.Class({
         let updatePrepared = this._pkOfflineProxy.UpdatePrepared;
         let updatesAllowed = this._updatesPermission && this._updatesPermission.allowed;
 
-        _setCheckBoxLabel(this._checkBox, dialogContent.checkBoxText);
+        _setCheckBoxLabel(this._checkBox, dialogContent.checkBoxText || '');
         this._checkBox.actor.visible = (dialogContent.checkBoxText && updatePrepared && updatesAllowed);
         this._checkBox.actor.checked = (updatePrepared && updateTriggered);
 
diff --git a/js/ui/extensionSystem.js b/js/ui/extensionSystem.js
index e35c01a..a7a3d20 100644
--- a/js/ui/extensionSystem.js
+++ b/js/ui/extensionSystem.js
@@ -76,6 +76,7 @@ function disableExtension(uuid) {
     if (extension.stylesheet) {
         let theme = St.ThemeContext.get_for_stage(global.stage).get_theme();
         theme.unload_stylesheet(extension.stylesheet);
+        delete extension.stylesheet;
     }
 
     try {
@@ -115,13 +116,18 @@ function enableExtension(uuid) {
     extensionOrder.push(uuid);
 
     let stylesheetNames = [global.session_mode + '.css', 'stylesheet.css'];
+    let theme = St.ThemeContext.get_for_stage(global.stage).get_theme();
     for (let i = 0; i < stylesheetNames.length; i++) {
-        let stylesheetFile = extension.dir.get_child(stylesheetNames[i]);
-        if (stylesheetFile.query_exists(null)) {
-            let theme = St.ThemeContext.get_for_stage(global.stage).get_theme();
+        try {
+            let stylesheetFile = extension.dir.get_child(stylesheetNames[i]);
             theme.load_stylesheet(stylesheetFile);
             extension.stylesheet = stylesheetFile;
             break;
+        } catch (e) {
+            if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND))
+                continue; // not an error
+            log(`Failed to load stylesheet for extension ${uuid}: ${e.message}`);
+            return;
         }
     }
 
@@ -131,6 +137,10 @@ function enableExtension(uuid) {
         _signals.emit('extension-state-changed', extension);
         return;
     } catch(e) {
+        if (extension.stylesheet) {
+            theme.unload_stylesheet(extension.stylesheet);
+            delete extension.stylesheet;
+        }
         logExtensionError(uuid, e);
         return;
     }
diff --git a/js/ui/iconGrid.js b/js/ui/iconGrid.js
index 60f2653..5389700 100644
--- a/js/ui/iconGrid.js
+++ b/js/ui/iconGrid.js
@@ -418,6 +418,11 @@ var IconGrid = new Lang.Class({
     },
 
     _animationDone() {
+        this._clonesAnimating.forEach(clone => {
+            clone.source.reactive = true;
+            clone.source.opacity = 255;
+            clone.destroy();
+        });
         this._clonesAnimating = [];
         this.emit('animation-done');
     },
@@ -538,10 +543,6 @@ var IconGrid = new Lang.Class({
                                    onComplete: () => {
                                        if (isLastItem)
                                            this._animationDone();
-
-                                       actor.opacity = 255;
-                                       actor.reactive = true;
-                                       actorClone.destroy();
                                    }};
                 fadeParams = { time: ANIMATION_FADE_IN_TIME_FOR_ITEM,
                                transition: 'easeInOutQuad',
@@ -562,12 +563,8 @@ var IconGrid = new Lang.Class({
                                    scale_x: scaleX,
                                    scale_y: scaleY,
                                    onComplete: () => {
-                                       if (isLastItem) {
+                                       if (isLastItem)
                                            this._animationDone();
-                                           this._restoreItemsOpacity();
-                                       }
-                                       actor.reactive = true;
-                                       actorClone.destroy();
                                    }};
                 fadeParams = { time: ANIMATION_FADE_IN_TIME_FOR_ITEM,
                                transition: 'easeInOutQuad',
@@ -581,12 +578,6 @@ var IconGrid = new Lang.Class({
         }
     },
 
-    _restoreItemsOpacity() {
-        for (let index = 0; index < this._items.length; index++) {
-            this._items[index].actor.opacity = 255;
-        }
-    },
-
     _getAllocatedChildSizeAndSpacing(child) {
         let [,, natWidth, natHeight] = child.get_preferred_size();
         let width = Math.min(this._getHItemSize(), natWidth);
diff --git a/js/ui/keyboard.js b/js/ui/keyboard.js
index 5fcdf98..4413846 100644
--- a/js/ui/keyboard.js
+++ b/js/ui/keyboard.js
@@ -533,17 +533,25 @@ var FocusTracker = new Lang.Class({
     },
 
     _setCurrentRect(rect) {
-        let frameRect = this._currentWindow.get_frame_rect();
-        rect.x -= frameRect.x;
-        rect.y -= frameRect.y;
+        if (this._currentWindow) {
+            let frameRect = this._currentWindow.get_frame_rect();
+            rect.x -= frameRect.x;
+            rect.y -= frameRect.y;
+        }
 
         this._rect = rect;
         this.emit('position-changed');
     },
 
     getCurrentRect() {
-        let frameRect = this._currentWindow.get_frame_rect();
-        let rect = { x: this._rect.x + frameRect.x, y: this._rect.y + frameRect.y, width: this._rect.width, height: this._rect.height };
+        let rect = { x: this._rect.x, y: this._rect.y,
+                     width: this._rect.width, height: this._rect.height };
+
+        if (this._currentWindow) {
+            let frameRect = this._currentWindow.get_frame_rect();
+            rect.x += frameRect.x;
+            rect.y += frameRect.y;
+        }
 
         return rect;
     }
@@ -916,9 +924,11 @@ var Keyboard = new Lang.Class({
     },
 
     _relayout() {
-        if (this.actor == null)
-            return;
         let monitor = Main.layoutManager.keyboardMonitor;
+
+        if (this.actor == null || monitor == null)
+            return;
+
         let maxHeight = monitor.height / 3;
         this.actor.width = monitor.width;
         this.actor.height = maxHeight;
diff --git a/js/ui/layout.js b/js/ui/layout.js
index 6f81039..e615e56 100644
--- a/js/ui/layout.js
+++ b/js/ui/layout.js
@@ -203,6 +203,7 @@ var LayoutManager = new Lang.Class({
 
         // Set up stage hierarchy to group all UI actors under one container.
         this.uiGroup = new Shell.GenericContainer({ name: 'uiGroup' });
+        this.uiGroup.set_flags(Clutter.ActorFlags.NO_LAYOUT);
         this.uiGroup.connect('allocate', (actor, box, flags) => {
             let children = actor.get_children();
             for (let i = 0; i < children.length; i++)
@@ -557,6 +558,8 @@ var LayoutManager = new Lang.Class({
     },
 
     get focusMonitor() {
+        if (this.focusIndex < 0)
+            return null;
         return this.monitors[this.focusIndex];
     },
 
diff --git a/js/ui/main.js b/js/ui/main.js
index d86cf9e..2c54bb6 100644
--- a/js/ui/main.js
+++ b/js/ui/main.js
@@ -343,6 +343,9 @@ function loadTheme() {
     let theme = new St.Theme ({ application_stylesheet: _cssStylesheet,
                                 default_stylesheet: _defaultCssStylesheet });
 
+    if (theme.default_stylesheet == null)
+        throw new Error("No valid stylesheet found for '%s'".format(sessionMode.stylesheetName));
+
     if (previousTheme) {
         let customStylesheets = previousTheme.get_custom_stylesheets();
 
diff --git a/js/ui/messageList.js b/js/ui/messageList.js
index aff201e..2d397c1 100644
--- a/js/ui/messageList.js
+++ b/js/ui/messageList.js
@@ -362,7 +362,8 @@ var Message = new Lang.Class({
         this.setBody(body);
 
         this._closeButton.connect('clicked', this.close.bind(this));
-        this.actor.connect('notify::hover', this._sync.bind(this));
+        let actorHoverId = this.actor.connect('notify::hover', this._sync.bind(this));
+        this._closeButton.connect('destroy', this.actor.disconnect.bind(this.actor, actorHoverId));
         this.actor.connect('clicked', this._onClicked.bind(this));
         this.actor.connect('destroy', this._onDestroy.bind(this));
         this._sync();
diff --git a/js/ui/messageTray.js b/js/ui/messageTray.js
index 9ce5b44..3fdaaac 100644
--- a/js/ui/messageTray.js
+++ b/js/ui/messageTray.js
@@ -1320,6 +1320,7 @@ var MessageTray = new Lang.Class({
         this._bannerBin.y = -this._banner.actor.height;
         this.actor.show();
 
+        Meta.disable_unredirect_for_display(global.display);
         this._updateShowingNotification();
 
         let [x, y, mods] = global.get_pointer();
@@ -1457,6 +1458,7 @@ var MessageTray = new Lang.Class({
 
         this._pointerInNotification = false;
         this._notificationRemoved = false;
+        Meta.enable_unredirect_for_display(global.display);
 
         this._banner.actor.destroy();
         this._banner = null;
diff --git a/js/ui/notificationDaemon.js b/js/ui/notificationDaemon.js
index 9f08562..dffb412 100644
--- a/js/ui/notificationDaemon.js
+++ b/js/ui/notificationDaemon.js
@@ -117,10 +117,8 @@ var FdoNotificationDaemon = new Lang.Class({
                  bitsPerSample, nChannels, data] = hints['image-data'];
             return Shell.util_create_pixbuf_from_data(data, GdkPixbuf.Colorspace.RGB, hasAlpha,
                                                       bitsPerSample, width, height, rowStride);
-        } else if (hints['image-path']) {
-            return new Gio.FileIcon({ file: Gio.File.new_for_path(hints['image-path']) });
         }
-        return null;
+        return this._iconForNotificationData(hints['image-path']);
     },
 
     _fallbackIconForNotificationData(hints) {
diff --git a/js/ui/osdWindow.js b/js/ui/osdWindow.js
index a739124..97e0498 100644
--- a/js/ui/osdWindow.js
+++ b/js/ui/osdWindow.js
@@ -108,15 +108,30 @@ var OsdWindow = new Lang.Class({
         this._hideTimeoutId = 0;
         this._reset();
 
-        Main.layoutManager.connect('monitors-changed',
-                                   this._relayout.bind(this));
+        this.actor.connect('destroy', this._onDestroy.bind(this));
+
+        this._monitorsChangedId =
+            Main.layoutManager.connect('monitors-changed',
+                                       this._relayout.bind(this));
         let themeContext = St.ThemeContext.get_for_stage(global.stage);
-        themeContext.connect('notify::scale-factor',
-                             this._relayout.bind(this));
+        this._scaleChangedId =
+            themeContext.connect('notify::scale-factor',
+                                 this._relayout.bind(this));
         this._relayout();
         Main.uiGroup.add_child(this.actor);
     },
 
+    _onDestroy() {
+        if (this._monitorsChangedId)
+            Main.layoutManager.disconnect(this._monitorsChangedId);
+        this._monitorsChangedId = 0;
+
+        let themeContext = St.ThemeContext.get_for_stage(global.stage);
+        if (this._scaleChangedId)
+            themeContext.disconnect(this._scaleChangedId);
+        this._scaleChangedId = 0;
+    },
+
     setIcon(icon) {
         this._icon.gicon = icon;
     },
diff --git a/js/ui/overview.js b/js/ui/overview.js
index 2240576..6a463c0 100644
--- a/js/ui/overview.js
+++ b/js/ui/overview.js
@@ -393,10 +393,8 @@ var Overview = new Lang.Class({
         if (!Main.layoutManager.primaryMonitor)
             return;
 
-        let workArea = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex);
-
-        this._coverPane.set_position(0, workArea.y);
-        this._coverPane.set_size(workArea.width, workArea.height);
+        this._coverPane.set_position(0, 0);
+        this._coverPane.set_size(global.screen_width, global.screen_height);
 
         this._updateBackgrounds();
     },
diff --git a/js/ui/overviewControls.js b/js/ui/overviewControls.js
index a2905ab..1b1c9cf 100644
--- a/js/ui/overviewControls.js
+++ b/js/ui/overviewControls.js
@@ -284,6 +284,11 @@ var ThumbnailsSlider = new Lang.Class({
         return child.get_theme_node().get_length('visible-width');
     },
 
+    _onDragEnd() {
+        this.actor.sync_hover();
+        this.parent();
+    },
+
     _getSlide() {
         if (!this._visible)
             return 0;
diff --git a/js/ui/panel.js b/js/ui/panel.js
index 2f59324..ef14ddf 100644
--- a/js/ui/panel.js
+++ b/js/ui/panel.js
@@ -796,6 +796,7 @@ var Panel = new Lang.Class({
         this.actor.connect('get-preferred-height', this._getPreferredHeight.bind(this));
         this.actor.connect('allocate', this._allocate.bind(this));
         this.actor.connect('button-press-event', this._onButtonPress.bind(this));
+        this.actor.connect('touch-event', this._onButtonPress.bind(this));
         this.actor.connect('key-press-event', this._onKeyPress.bind(this));
 
         Main.overview.connect('showing', () => {
@@ -939,8 +940,13 @@ var Panel = new Lang.Class({
         if (event.get_source() != actor)
             return Clutter.EVENT_PROPAGATE;
 
-        let button = event.get_button();
-        if (button != 1)
+        let type = event.type();
+        let isPress = type == Clutter.EventType.BUTTON_PRESS;
+        if (!isPress && type != Clutter.EventType.TOUCH_BEGIN)
+            return Clutter.EVENT_PROPAGATE;
+
+        let button = isPress ? event.get_button() : -1;
+        if (isPress && button != 1)
             return Clutter.EVENT_PROPAGATE;
 
         let focusWindow = global.display.focus_window;
@@ -1079,6 +1085,7 @@ var Panel = new Lang.Class({
         let windows = activeWorkspace.list_windows().filter(metaWindow => {
             return metaWindow.is_on_primary_monitor() &&
                    metaWindow.showing_on_its_workspace() &&
+                   !metaWindow.is_hidden() &&
                    metaWindow.get_window_type() != Meta.WindowType.DESKTOP;
         });
 
diff --git a/js/ui/popupMenu.js b/js/ui/popupMenu.js
index 83194d7..f449d6e 100644
--- a/js/ui/popupMenu.js
+++ b/js/ui/popupMenu.js
@@ -141,8 +141,17 @@ var PopupBaseMenuItem = new Lang.Class({
     },
 
     _onKeyPressEvent(actor, event) {
-        let symbol = event.get_key_symbol();
+        let state = event.get_state();
 
+        // if user has a modifier down (except capslock)
+        // then don't handle the key press here
+        state &= ~Clutter.ModifierType.LOCK_MASK;
+        state &= Clutter.ModifierType.MODIFIER_MASK;
+
+        if (state)
+            return Clutter.EVENT_PROPAGATE;
+
+        let symbol = event.get_key_symbol();
         if (symbol == Clutter.KEY_space || symbol == Clutter.KEY_Return) {
             this.activate(event);
             return Clutter.EVENT_STOP;
diff --git a/js/ui/runDialog.js b/js/ui/runDialog.js
index 82deab5..1b9e260 100644
--- a/js/ui/runDialog.js
+++ b/js/ui/runDialog.js
@@ -114,18 +114,16 @@ var RunDialog = new Lang.Class({
 
         this._history = new History.HistoryManager({ gsettingsKey: HISTORY_KEY,
                                                      entry: this._entryText });
+        this._entryText.connect('activate', (o) => {
+            this.popModal();
+            this._run(o.get_text(),
+                      Clutter.get_current_event().get_state() & Clutter.ModifierType.CONTROL_MASK);
+            if (!this._commandError ||
+                !this.pushModal())
+                this.close();
+        });
         this._entryText.connect('key-press-event', (o, e) => {
             let symbol = e.get_key_symbol();
-            if (symbol == Clutter.Return || symbol == Clutter.KP_Enter) {
-                this.popModal();
-                this._run(o.get_text(),
-                          e.get_state() & Clutter.ModifierType.CONTROL_MASK);
-                if (!this._commandError ||
-                    !this.pushModal())
-                    this.close();
-
-                return Clutter.EVENT_STOP;
-            }
             if (symbol == Clutter.Tab) {
                 let text = o.get_text();
                 let prefix;
diff --git a/js/ui/search.js b/js/ui/search.js
index 1fb54b4..3966741 100644
--- a/js/ui/search.js
+++ b/js/ui/search.js
@@ -192,6 +192,7 @@ var SearchResultsBase = new Lang.Class({
     },
 
     clear() {
+        this._cancellable.cancel();
         for (let resultId in this._resultDisplays)
             this._resultDisplays[resultId].actor.destroy();
         this._resultDisplays = {};
@@ -225,6 +226,12 @@ var SearchResultsBase = new Lang.Class({
             this._cancellable.reset();
 
             this.provider.getResultMetas(metasNeeded, metas => {
+                if (this._cancellable.is_cancelled()) {
+                    if (metas.length > 0)
+                        log(`Search provider ${this.provider.id} returned results after the request was canceled`);
+                    callback(false);
+                    return;
+                }
                 if (metas.length != metasNeeded.length) {
                     log('Wrong number of result metas returned by search provider ' + this.provider.id +
                         ': expected ' + metasNeeded.length + ' but got ' + metas.length);
diff --git a/js/ui/status/keyboard.js b/js/ui/status/keyboard.js
index 3dce2c9..b80566a 100644
--- a/js/ui/status/keyboard.js
+++ b/js/ui/status/keyboard.js
@@ -360,11 +360,14 @@ var InputSourceManager = new Lang.Class({
         this._settings.connect('per-window-changed', this._sourcesPerWindowChanged.bind(this));
         this._sourcesPerWindowChanged();
         this._disableIBus = false;
+        this._reloading = false;
     },
 
     reload() {
+        this._reloading = true;
         this._keyboardManager.setKeyboardOptions(this._settings.keyboardOptions);
         this._inputSourcesChanged();
+        this._reloading = false;
     },
 
     _ibusReadyCallback(im, ready) {
@@ -458,7 +461,15 @@ var InputSourceManager = new Lang.Class({
     },
 
     activateInputSource(is, interactive) {
-        KeyboardManager.holdKeyboard();
+        // The focus changes during holdKeyboard/releaseKeyboard may trick
+        // the client into hiding UI containing the currently focused entry.
+        // So holdKeyboard/releaseKeyboard are not called when
+        // 'set-content-type' signal is received.
+        // E.g. Focusing on a password entry in a popup in Xorg Firefox
+        // will emit 'set-content-type' signal.
+        // https://gitlab.gnome.org/GNOME/gnome-shell/issues/391
+        if (!this._reloading)
+            KeyboardManager.holdKeyboard();
         this._keyboardManager.apply(is.xkbId);
 
         // All the "xkb:..." IBus engines simply "echo" back symbols,
@@ -473,7 +484,10 @@ var InputSourceManager = new Lang.Class({
         else
             engine = 'xkb:us::eng';
 
-        this._ibusManager.setEngine(engine, KeyboardManager.releaseKeyboard);
+        if (!this._reloading)
+            this._ibusManager.setEngine(engine, KeyboardManager.releaseKeyboard);
+        else
+            this._ibusManager.setEngine(engine);
         this._currentInputSourceChanged(is);
 
         if (interactive)
diff --git a/js/ui/status/network.js b/js/ui/status/network.js
index a759936..79acef2 100644
--- a/js/ui/status/network.js
+++ b/js/ui/status/network.js
@@ -419,12 +419,14 @@ var NMConnectionDevice = new Lang.Class({
         this._deactivateItem.actor.visible = this._device.state > NM.DeviceState.DISCONNECTED;
 
         if (this._activeConnection == null) {
-            this._activeConnection = this._device.active_connection;
-
-            if (this._activeConnection) {
-                ensureActiveConnectionProps(this._activeConnection, this._client);
-                let item = this._connectionItems.get(this._activeConnection.connection.get_uuid());
-                item.setActiveConnection(this._activeConnection);
+            let activeConnection = this._device.active_connection;
+            if (activeConnection && activeConnection.connection) {
+                let item = this._connectionItems.get(activeConnection.connection.get_uuid());
+                if (item) {
+                    this._activeConnection = activeConnection;
+                    ensureActiveConnectionProps(this._activeConnection, this._client);
+                    item.setActiveConnection(this._activeConnection);
+                }
             }
         }
 
diff --git a/js/ui/status/system.js b/js/ui/status/system.js
index 68a0b4b..cd6f3d8 100644
--- a/js/ui/status/system.js
+++ b/js/ui/status/system.js
@@ -58,6 +58,9 @@ var AltSwitcher = new Lang.Class({
             childToShow = this._standard;
         } else if (this._alternate.visible) {
             childToShow = this._alternate;
+        } else {
+            this.actor.hide();
+            return;
         }
 
         let childShown = this.actor.get_child();
@@ -79,7 +82,7 @@ var AltSwitcher = new Lang.Class({
             global.sync_pointer();
         }
 
-        this.actor.visible = (childToShow != null);
+        this.actor.show();
     },
 
     _onDestroy() {
diff --git a/js/ui/viewSelector.js b/js/ui/viewSelector.js
index 91bc222..24fd5de 100644
--- a/js/ui/viewSelector.js
+++ b/js/ui/viewSelector.js
@@ -311,6 +311,7 @@ var ViewSelector = new Lang.Class({
     },
 
     hide() {
+        this.reset();
         this._workspacesDisplay.hide();
     },
 
@@ -459,7 +460,11 @@ var ViewSelector = new Lang.Class({
     },
 
     reset() {
-        global.stage.set_key_focus(null);
+        // Don't drop the key focus on Clutter's side if anything but the
+        // overview has pushed a modal (e.g. system modals when activated using
+        // the overview).
+        if (Main.modalCount <= 1)
+            global.stage.set_key_focus(null);
 
         this._entry.text = '';
 
diff --git a/js/ui/windowManager.js b/js/ui/windowManager.js
index 17576a0..8e7e7a6 100644
--- a/js/ui/windowManager.js
+++ b/js/ui/windowManager.js
@@ -627,8 +627,8 @@ var AppSwitchAction = new Lang.Class({
 
         if (this.get_n_current_points() == 3) {
             for (let i = 0; i < this.get_n_current_points(); i++) {
-                [startX, startY] = this.get_press_coords(i);
-                [x, y] = this.get_motion_coords(i);
+                let [startX, startY] = this.get_press_coords(i);
+                let [x, y] = this.get_motion_coords(i);
 
                 if (Math.abs(x - startX) > MOTION_THRESHOLD ||
                     Math.abs(y - startY) > MOTION_THRESHOLD)
@@ -1173,6 +1173,10 @@ var WindowManager = new Lang.Class({
                 yScale = geom.height / actor.height;
             } else {
                 let monitor = Main.layoutManager.monitors[actor.meta_window.get_monitor()];
+                if (!monitor) {
+                    this._minimizeWindowDone();
+                    return;
+                }
                 xDest = monitor.x;
                 yDest = monitor.y;
                 if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL)
@@ -1248,6 +1252,11 @@ var WindowManager = new Lang.Class({
                                 geom.height / actor.height);
             } else {
                 let monitor = Main.layoutManager.monitors[actor.meta_window.get_monitor()];
+                if (!monitor) {
+                    actor.show();
+                    this._unminimizeWindowDone();
+                    return;
+                }
                 actor.set_position(monitor.x, monitor.y);
                 if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL)
                     actor.x += monitor.width;
diff --git a/js/ui/windowMenu.js b/js/ui/windowMenu.js
index f0e564b..d4411d0 100644
--- a/js/ui/windowMenu.js
+++ b/js/ui/windowMenu.js
@@ -128,11 +128,10 @@ var WindowMenu = new Lang.Class({
 
         let screen = global.screen;
         let nMonitors = screen.get_n_monitors();
-        if (nMonitors > 1) {
+        let monitorIndex = window.get_monitor();
+        if (nMonitors > 1 && monitorIndex >= 0) {
             this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
 
-            let monitorIndex = window.get_monitor();
-
             let dir = Meta.ScreenDirection.UP;
             let upMonitorIndex =
                 screen.get_monitor_neighbor_index(monitorIndex, dir);
diff --git a/js/ui/workspace.js b/js/ui/workspace.js
index 8b56932..2bab2f8 100644
--- a/js/ui/workspace.js
+++ b/js/ui/workspace.js
@@ -137,8 +137,10 @@ var WindowClone = new Lang.Class({
         this._dragSlot = [0, 0, 0, 0];
         this._stackAbove = null;
 
-        this._windowClone._updateId = this.metaWindow.connect('size-changed',
-            this._onRealWindowSizeChanged.bind(this));
+        this._windowClone._sizeChangedId = this.metaWindow.connect('size-changed',
+            this._onMetaWindowSizeChanged.bind(this));
+        this._windowClone._posChangedId = this.metaWindow.connect('position-changed',
+            this._computeBoundingBox.bind(this));
         this._windowClone._destroyId =
             this.realWindow.connect('destroy', () => {
                 // First destroy the clone and then destroy everything
@@ -206,8 +208,7 @@ var WindowClone = new Lang.Class({
 
     addAttachedDialog(win) {
         this._doAddAttachedDialog(win, win.get_compositor_private());
-        this._computeBoundingBox();
-        this.emit('size-changed');
+        this._onMetaWindowSizeChanged();
     },
 
     hasAttachedDialogs() {
@@ -216,15 +217,14 @@ var WindowClone = new Lang.Class({
 
     _doAddAttachedDialog(metaWin, realWin) {
         let clone = new Clutter.Clone({ source: realWin });
-        clone._updateId = metaWin.connect('size-changed', () => {
-            this._computeBoundingBox();
-            this.emit('size-changed');
-        });
+        clone._sizeChangedId = metaWin.connect('size-changed',
+            this._onMetaWindowSizeChanged.bind(this));
+        clone._posChangedId = metaWin.connect('position-changed',
+            this._onMetaWindowSizeChanged.bind(this));
         clone._destroyId = realWin.connect('destroy', () => {
             clone.destroy();
 
-            this._computeBoundingBox();
-            this.emit('size-changed');
+            this._onMetaWindowSizeChanged();
         });
         this.actor.add_child(clone);
     },
@@ -321,12 +321,13 @@ var WindowClone = new Lang.Class({
             else
                 realWindow = child.source;
 
-            realWindow.meta_window.disconnect(child._updateId);
+            realWindow.meta_window.disconnect(child._sizeChangedId);
+            realWindow.meta_window.disconnect(child._posChangedId);
             realWindow.disconnect(child._destroyId);
         });
     },
 
-    _onRealWindowSizeChanged() {
+    _onMetaWindowSizeChanged() {
         this._computeBoundingBox();
         this.emit('size-changed');
     },
@@ -469,7 +470,6 @@ var WindowOverlay = new Lang.Class({
         this._windowAddedId = 0;
 
         button.hide();
-        title.hide();
 
         this.title = title;
         this.closeButton = button;
@@ -544,12 +544,10 @@ var WindowOverlay = new Lang.Class({
         let titleX = cloneX + (cloneWidth - title.width) / 2;
         let titleY = cloneY + cloneHeight - (title.height - this.borderSize) / 2;
 
-        if (animate) {
-            this._animateOverlayActor(title, Math.floor(titleX), Math.floor(titleY), title.width);
-        } else {
-            title.width = title.width;
+        if (animate)
+            this._animateOverlayActor(title, Math.floor(titleX), Math.floor(titleY));
+        else
             title.set_position(Math.floor(titleX), Math.floor(titleY));
-        }
 
         let borderX = cloneX - this.borderSize;
         let borderY = cloneY - this.borderSize;
@@ -568,10 +566,12 @@ var WindowOverlay = new Lang.Class({
     _animateOverlayActor(actor, x, y, width, height) {
         let params = { x: x,
                        y: y,
-                       width: width,
                        time: Overview.ANIMATION_TIME,
                        transition: 'easeOutQuad' };
 
+        if (width !== undefined)
+            params.width = width;
+
         if (height !== undefined)
             params.height = height;
 
@@ -1506,7 +1506,7 @@ var Workspace = new Lang.Class({
             if (metaWin.is_attached_dialog()) {
                 let parent = metaWin.get_transient_for();
                 while (parent.is_attached_dialog())
-                    parent = metaWin.get_transient_for();
+                    parent = parent.get_transient_for();
 
                 let idx = this._lookupIndex (parent);
                 if (idx < 0) {
diff --git a/js/ui/workspaceThumbnail.js b/js/ui/workspaceThumbnail.js
index 381169e..4aafcd3 100644
--- a/js/ui/workspaceThumbnail.js
+++ b/js/ui/workspaceThumbnail.js
@@ -68,7 +68,7 @@ var WindowClone = new Lang.Class({
         this.realWindow = realWindow;
         this.metaWindow = realWindow.meta_window;
 
-        this.clone._updateId = this.metaWindow.connect('position-changed',
+        this.clone._updateId = this.realWindow.connect('notify::position',
                                                        this._onPositionChanged.bind(this));
         this.clone._destroyId = this.realWindow.connect('destroy', () => {
             // First destroy the clone and then destroy everything
@@ -153,7 +153,7 @@ var WindowClone = new Lang.Class({
         let clone = new Clutter.Clone({ source: realDialog });
         this._updateDialogPosition(realDialog, clone);
 
-        clone._updateId = metaDialog.connect('position-changed', dialog => {
+        clone._updateId = realDialog.connect('notify::position', dialog => {
             this._updateDialogPosition(dialog, clone);
         });
         clone._destroyId = realDialog.connect('destroy', () => {
@@ -171,7 +171,6 @@ var WindowClone = new Lang.Class({
     },
 
     _onPositionChanged() {
-        let rect = this.metaWindow.get_frame_rect();
         this.actor.set_position(this.realWindow.x, this.realWindow.y);
     },
 
@@ -179,7 +178,7 @@ var WindowClone = new Lang.Class({
         this.actor.get_children().forEach(child => {
             let realWindow = child.source;
 
-            realWindow.meta_window.disconnect(child._updateId);
+            realWindow.disconnect(child._updateId);
             realWindow.disconnect(child._destroyId);
         });
     },
@@ -417,7 +416,7 @@ var WorkspaceThumbnail = new Lang.Class({
         } else if (metaWin.is_attached_dialog()) {
             let parent = metaWin.get_transient_for();
             while (parent.is_attached_dialog())
-                parent = metaWin.get_transient_for();
+                parent = parent.get_transient_for();
 
             let idx = this._lookupIndex (parent);
             if (idx < 0) {
@@ -677,7 +676,11 @@ var ThumbnailsBox = new Lang.Class({
         this._settings.connect('changed::dynamic-workspaces',
             this._updateSwitcherVisibility.bind(this));
 
-        Main.layoutManager.connect('monitors-changed', this._rebuildThumbnails.bind(this));
+        Main.layoutManager.connect('monitors-changed', () => {
+            this._destroyThumbnails();
+            if (Main.overview.visible)
+                this._createThumbnails();
+        });
     },
 
     _updateSwitcherVisibility() {
@@ -870,9 +873,6 @@ var ThumbnailsBox = new Lang.Class({
             Main.overview.connect('windows-restacked',
                                   this._syncStacking.bind(this));
 
-        this._workareasChangedId =
-            global.screen.connect('workareas-changed', this._rebuildThumbnails.bind(this));
-
         this._targetScale = 0;
         this._scale = 0;
         this._pendingScaleUpdate = false;
@@ -902,24 +902,12 @@ var ThumbnailsBox = new Lang.Class({
             this._syncStackingId = 0;
         }
 
-        if (this._workareasChangedId > 0) {
-            global.screen.disconnect(this._workareasChangedId);
-            this._workareasChangedId = 0;
-        }
-
         for (let w = 0; w < this._thumbnails.length; w++)
             this._thumbnails[w].destroy();
         this._thumbnails = [];
         this._porthole = null;
     },
 
-    _rebuildThumbnails() {
-        this._destroyThumbnails();
-
-        if (Main.overview.visible)
-            this._createThumbnails();
-    },
-
     _workspacesChanged() {
         let validThumbnails =
             this._thumbnails.filter(t => t.state <= ThumbnailState.NORMAL);
diff --git a/js/ui/workspacesView.js b/js/ui/workspacesView.js
index 563e43d..bb7835c 100644
--- a/js/ui/workspacesView.js
+++ b/js/ui/workspacesView.js
@@ -470,6 +470,7 @@ var WorkspacesDisplay = new Lang.Class({
         this._switchWorkspaceNotifyId = 0;
 
         this._notifyOpacityId = 0;
+        this._restackedNotifyId = 0;
         this._scrollEventId = 0;
         this._keyPressEventId = 0;
 
diff --git a/po/cs.po b/po/cs.po
index 9b7aa79..50e2ff0 100644
--- a/po/cs.po
+++ b/po/cs.po
@@ -1064,7 +1064,7 @@ msgstr "Načítá se…"
 #: js/ui/dateMenu.js:321
 #, javascript-format
 msgid "Feels like %s."
-msgstr "Pocitově jako %s."
+msgstr "Pocitová teplota %s."
 
 #: js/ui/dateMenu.js:324
 msgid "Go online for weather information"
diff --git a/src/st/st-bin.c b/src/st/st-bin.c
index f8b58da..21b3687 100644
--- a/src/st/st-bin.c
+++ b/src/st/st-bin.c
@@ -177,15 +177,15 @@ st_bin_get_preferred_height (ClutterActor *self,
 }
 
 static void
-st_bin_dispose (GObject *gobject)
+st_bin_destroy (ClutterActor *actor)
 {
-  StBinPrivate *priv = st_bin_get_instance_private (ST_BIN (gobject));
+  StBinPrivate *priv = st_bin_get_instance_private (ST_BIN (actor));
 
   if (priv->child)
     clutter_actor_destroy (priv->child);
   g_assert (priv->child == NULL);
 
-  G_OBJECT_CLASS (st_bin_parent_class)->dispose (gobject);
+  CLUTTER_ACTOR_CLASS (st_bin_parent_class)->destroy (actor);
 }
 
 static void
@@ -315,11 +315,11 @@ st_bin_class_init (StBinClass *klass)
 
   gobject_class->set_property = st_bin_set_property;
   gobject_class->get_property = st_bin_get_property;
-  gobject_class->dispose = st_bin_dispose;
 
   actor_class->get_preferred_width = st_bin_get_preferred_width;
   actor_class->get_preferred_height = st_bin_get_preferred_height;
   actor_class->allocate = st_bin_allocate;
+  actor_class->destroy = st_bin_destroy;
 
   widget_class->popup_menu = st_bin_popup_menu;
   widget_class->navigate_focus = st_bin_navigate_focus;
diff --git a/src/st/st-box-layout.c b/src/st/st-box-layout.c
index ffb7477..3acb15c 100644
--- a/src/st/st-box-layout.c
+++ b/src/st/st-box-layout.c
@@ -90,7 +90,7 @@ adjustment_value_notify_cb (StAdjustment *adjustment,
                             GParamSpec   *pspec,
                             StBoxLayout  *box)
 {
-  clutter_actor_queue_redraw (CLUTTER_ACTOR (box));
+  clutter_actor_queue_relayout (CLUTTER_ACTOR (box));
 }
 
 static void
@@ -490,7 +490,7 @@ st_box_layout_get_paint_volume (ClutterActor       *actor,
                                 ClutterPaintVolume *volume)
 {
   StBoxLayout *self = ST_BOX_LAYOUT (actor);
-  gdouble x, y;
+  gdouble x, y, lower, upper;
   StBoxLayoutPrivate *priv = self->priv;
   StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
   ClutterActorBox allocation_box;
@@ -505,13 +505,42 @@ st_box_layout_get_paint_volume (ClutterActor       *actor,
    * our paint volume on that. */
   if (priv->hadjustment || priv->vadjustment)
     {
+      gdouble width, height;
+
       clutter_actor_get_allocation_box (actor, &allocation_box);
       st_theme_node_get_content_box (theme_node, &allocation_box, &content_box);
       origin.x = content_box.x1 - allocation_box.x1;
       origin.y = content_box.y1 - allocation_box.y2;
       origin.z = 0.f;
-      clutter_paint_volume_set_width (volume, content_box.x2 - content_box.x1);
-      clutter_paint_volume_set_height (volume, content_box.y2 - content_box.y1);
+
+      if (priv->hadjustment)
+        {
+          g_object_get (priv->hadjustment,
+                        "lower", &lower,
+                        "upper", &upper,
+                        NULL);
+          width = upper - lower;
+        }
+      else
+        {
+          width = content_box.x2 - content_box.x1;
+        }
+
+      if (priv->vadjustment)
+        {
+          g_object_get (priv->vadjustment,
+                        "lower", &lower,
+                        "upper", &upper,
+                        NULL);
+          height = upper - lower;
+        }
+      else
+        {
+          height = content_box.y2 - content_box.y1;
+        }
+
+      clutter_paint_volume_set_width (volume, width);
+      clutter_paint_volume_set_height (volume, height);
     }
   else if (!CLUTTER_ACTOR_CLASS (st_box_layout_parent_class)->get_paint_volume (actor, volume))
     return FALSE;
diff --git a/src/st/st-button.c b/src/st/st-button.c
index 8f5c492..a3a7b24 100644
--- a/src/st/st-button.c
+++ b/src/st/st-button.c
@@ -248,14 +248,17 @@ st_button_touch_event (ClutterActor      *actor,
   if (event->type == CLUTTER_TOUCH_BEGIN && !priv->press_sequence)
     {
       clutter_input_device_sequence_grab (device, sequence, actor);
-      st_button_press (button, device, 0, sequence);
+      if (!clutter_event_is_pointer_emulated ((ClutterEvent*) event))
+        st_button_press (button, device, 0, sequence);
       return CLUTTER_EVENT_STOP;
     }
   else if (event->type == CLUTTER_TOUCH_END &&
            priv->device == device &&
            priv->press_sequence == sequence)
     {
-      st_button_release (button, device, mask, 0, sequence);
+      if (!clutter_event_is_pointer_emulated ((ClutterEvent*) event))
+        st_button_release (button, device, mask, 0, sequence);
+
       clutter_input_device_sequence_ungrab (device, sequence);
       return CLUTTER_EVENT_STOP;
     }
diff --git a/src/st/st-entry.c b/src/st/st-entry.c
index 74bdcdb..f5305c5 100644
--- a/src/st/st-entry.c
+++ b/src/st/st-entry.c
@@ -906,6 +906,13 @@ st_entry_unmap (ClutterActor *actor)
   CLUTTER_ACTOR_CLASS (st_entry_parent_class)->unmap (actor);
 }
 
+static gboolean
+st_entry_get_paint_volume (ClutterActor       *actor,
+                           ClutterPaintVolume *volume)
+{
+  return clutter_paint_volume_set_from_allocation (volume, actor);
+}
+
 static void
 st_entry_class_init (StEntryClass *klass)
 {
@@ -923,6 +930,7 @@ st_entry_class_init (StEntryClass *klass)
   actor_class->allocate = st_entry_allocate;
   actor_class->paint = st_entry_paint;
   actor_class->unmap = st_entry_unmap;
+  actor_class->get_paint_volume = st_entry_get_paint_volume;
 
   actor_class->key_press_event = st_entry_key_press_event;
   actor_class->key_focus_in = st_entry_key_focus_in;
@@ -1287,10 +1295,10 @@ st_entry_get_input_hints (StEntry *entry)
   return clutter_text_get_input_hints (CLUTTER_TEXT (priv->entry));
 }
 
-static gboolean
-_st_entry_icon_press_cb (ClutterActor       *actor,
-                         ClutterButtonEvent *event,
-                         StEntry            *entry)
+static void
+_st_entry_icon_clicked_cb (ClutterClickAction *action,
+                           ClutterActor       *actor,
+                           StEntry            *entry)
 {
   StEntryPrivate *priv = ST_ENTRY_PRIV (entry);
 
@@ -1298,8 +1306,6 @@ _st_entry_icon_press_cb (ClutterActor       *actor,
     g_signal_emit (entry, entry_signals[PRIMARY_ICON_CLICKED], 0);
   else
     g_signal_emit (entry, entry_signals[SECONDARY_ICON_CLICKED], 0);
-
-  return FALSE;
 }
 
 static void
@@ -1309,21 +1315,24 @@ _st_entry_set_icon (StEntry       *entry,
 {
   if (*icon)
     {
-      g_signal_handlers_disconnect_by_func (*icon,
-                                            _st_entry_icon_press_cb,
-                                            entry);
+      clutter_actor_remove_action_by_name (*icon, "entry-icon-action");
       clutter_actor_remove_child (CLUTTER_ACTOR (entry), *icon);
       *icon = NULL;
     }
 
   if (new_icon)
     {
+      ClutterAction *action;
+
       *icon = g_object_ref (new_icon);
 
       clutter_actor_set_reactive (*icon, TRUE);
       clutter_actor_add_child (CLUTTER_ACTOR (entry), *icon);
-      g_signal_connect (*icon, "button-release-event",
-                        G_CALLBACK (_st_entry_icon_press_cb), entry);
+
+      action = clutter_click_action_new ();
+      clutter_actor_add_action_with_name (*icon, "entry-icon-action", action);
+      g_signal_connect (action, "clicked",
+                        G_CALLBACK (_st_entry_icon_clicked_cb), entry);
     }
 
   clutter_actor_queue_relayout (CLUTTER_ACTOR (entry));
diff --git a/src/st/st-scroll-view.c b/src/st/st-scroll-view.c
index bb26f54..fc0db1c 100644
--- a/src/st/st-scroll-view.c
+++ b/src/st/st-scroll-view.c
@@ -304,6 +304,13 @@ st_scroll_view_pick (ClutterActor       *actor,
     clutter_actor_paint (priv->vscroll);
 }
 
+static gboolean
+st_scroll_view_get_paint_volume (ClutterActor       *actor,
+                                 ClutterPaintVolume *volume)
+{
+  return clutter_paint_volume_set_from_allocation (volume, actor);
+}
+
 static double
 get_scrollbar_width (StScrollView *scroll,
                      gfloat        for_height)
@@ -793,6 +800,7 @@ st_scroll_view_class_init (StScrollViewClass *klass)
 
   actor_class->paint = st_scroll_view_paint;
   actor_class->pick = st_scroll_view_pick;
+  actor_class->get_paint_volume = st_scroll_view_get_paint_volume;
   actor_class->get_preferred_width = st_scroll_view_get_preferred_width;
   actor_class->get_preferred_height = st_scroll_view_get_preferred_height;
   actor_class->allocate = st_scroll_view_allocate;
diff --git a/src/st/st-texture-cache.c b/src/st/st-texture-cache.c
index 0c794a3..6219071 100644
--- a/src/st/st-texture-cache.c
+++ b/src/st/st-texture-cache.c
@@ -780,13 +780,13 @@ st_texture_cache_load (StTextureCache       *cache,
   if (!texture)
     {
       texture = load (cache, key, data, error);
-      if (texture)
+      if (texture && policy == ST_TEXTURE_CACHE_POLICY_FOREVER)
         g_hash_table_insert (cache->priv->keyed_cache, g_strdup (key), texture);
-      else
-        return NULL;
     }
 
-  cogl_object_ref (texture);
+  if (texture && policy == ST_TEXTURE_CACHE_POLICY_FOREVER)
+    cogl_object_ref (texture);
+
   return texture;
 }
 
@@ -984,7 +984,7 @@ file_changed_cb (GFileMonitor      *monitor,
   char *key;
   guint file_hash;
 
-  if (event_type != G_FILE_MONITOR_EVENT_CHANGED)
+  if (event_type != G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT)
     return;
 
   file_hash = g_file_hash (file);
diff --git a/src/st/st-theme-node-drawing.c b/src/st/st-theme-node-drawing.c
index 8942966..0637d97 100644
--- a/src/st/st-theme-node-drawing.c
+++ b/src/st/st-theme-node-drawing.c
@@ -229,9 +229,9 @@ unpremultiply (ClutterColor *color)
 {
   if (color->alpha != 0)
     {
-      color->red = (color->red * 255 + 127) / color->alpha;
-      color->green = (color->green * 255 + 127) / color->alpha;
-      color->blue = (color->blue * 255 + 127) / color->alpha;
+      color->red = MIN((color->red * 255 + 127) / color->alpha, 255);
+      color->green = MIN((color->green * 255 + 127) / color->alpha, 255);
+      color->blue = MIN((color->blue * 255 + 127) / color->alpha, 255);
     }
 }
 
@@ -402,7 +402,7 @@ st_theme_node_lookup_corner (StThemeNode    *node,
     return COGL_INVALID_HANDLE;
 
   key = corner_to_string (&corner);
-  texture = st_texture_cache_load (cache, key, ST_TEXTURE_CACHE_POLICY_NONE, load_corner, &corner, NULL);
+  texture = st_texture_cache_load (cache, key, ST_TEXTURE_CACHE_POLICY_FOREVER, load_corner, &corner, NULL);
 
   if (texture)
     {
@@ -1414,6 +1414,32 @@ st_theme_node_load_background_image (StThemeNode *node)
   return node->background_texture != COGL_INVALID_HANDLE;
 }
 
+static gboolean
+st_theme_node_invalidate_resources_for_file (StThemeNode *node,
+                                             GFile       *file)
+{
+  StBorderImage *border_image;
+  gboolean changed = FALSE;
+  GFile *theme_file;
+
+  theme_file = st_theme_node_get_background_image (node);
+  if ((theme_file != NULL) && g_file_equal (theme_file, file))
+    {
+      st_theme_node_invalidate_background_image (node);
+      changed = TRUE;
+    }
+
+  border_image = st_theme_node_get_border_image (node);
+  theme_file = border_image ? st_border_image_get_file (border_image) : NULL;
+  if ((theme_file != NULL) && g_file_equal (theme_file, file))
+    {
+      st_theme_node_invalidate_border_image (node);
+      changed = TRUE;
+    }
+
+  return changed;
+}
+
 static void st_theme_node_prerender_shadow (StThemeNodePaintState *state);
 
 static void
@@ -2751,3 +2777,17 @@ st_theme_node_paint_state_invalidate (StThemeNodePaintState *state)
   state->alloc_width = 0;
   state->alloc_height = 0;
 }
+
+gboolean
+st_theme_node_paint_state_invalidate_for_file (StThemeNodePaintState *state,
+                                               GFile                 *file)
+{
+  if (state->node != NULL &&
+      st_theme_node_invalidate_resources_for_file (state->node, file))
+    {
+      st_theme_node_paint_state_invalidate (state);
+      return TRUE;
+    }
+
+  return FALSE;
+}
diff --git a/src/st/st-theme-node.h b/src/st/st-theme-node.h
index 940b97a..7597669 100644
--- a/src/st/st-theme-node.h
+++ b/src/st/st-theme-node.h
@@ -287,6 +287,9 @@ void st_theme_node_paint_state_free (StThemeNodePaintState *state);
 void st_theme_node_paint_state_copy (StThemeNodePaintState *state,
                                      StThemeNodePaintState *other);
 void st_theme_node_paint_state_invalidate (StThemeNodePaintState *state);
+gboolean st_theme_node_paint_state_invalidate_for_file (StThemeNodePaintState *state,
+                                                        GFile                 *file);
+
 void st_theme_node_paint_state_set_node (StThemeNodePaintState *state,
                                          StThemeNode           *node);
 
diff --git a/src/st/st-widget.c b/src/st/st-widget.c
index db984ac..7c39b35 100644
--- a/src/st/st-widget.c
+++ b/src/st/st-widget.c
@@ -289,44 +289,17 @@ st_widget_texture_cache_changed (StTextureCache *cache,
 {
   StWidget *actor = ST_WIDGET (user_data);
   StWidgetPrivate *priv = st_widget_get_instance_private (actor);
-  StThemeNode *node = priv->theme_node;
-  StBorderImage *border_image;
   gboolean changed = FALSE;
-  GFile *theme_file;
-
-  if (node == NULL)
-    return;
-
-  theme_file = st_theme_node_get_background_image (node);
-  if ((theme_file != NULL) && g_file_equal (theme_file, file))
-    {
-      st_theme_node_invalidate_background_image (node);
-      changed = TRUE;
-    }
+  int i;
 
-  border_image = st_theme_node_get_border_image (node);
-  theme_file = border_image ? st_border_image_get_file (border_image) : NULL;
-  if ((theme_file != NULL) && g_file_equal (theme_file, file))
+  for (i = 0; i < G_N_ELEMENTS (priv->paint_states); i++)
     {
-      st_theme_node_invalidate_border_image (node);
-      changed = TRUE;
+      StThemeNodePaintState *paint_state = &priv->paint_states[i];
+      changed |= st_theme_node_paint_state_invalidate_for_file (paint_state, file);
     }
 
-  if (changed)
-    {
-      /* If we prerender the background / border, we need to update
-       * the paint state. We should probably implement a method to
-       * the theme node to determine this, but for now, just wipe
-       * the entire paint state.
-       *
-       * Use the existing state instead of a new one because it's
-       * assumed the rest of the state will stay the same.
-       */
-      st_theme_node_paint_state_invalidate (current_paint_state (actor));
-
-      if (clutter_actor_is_mapped (CLUTTER_ACTOR (actor)))
-        clutter_actor_queue_redraw (CLUTTER_ACTOR (actor));
-    }
+  if (changed && clutter_actor_is_mapped (CLUTTER_ACTOR (actor)))
+    clutter_actor_queue_redraw (CLUTTER_ACTOR (actor));
 }
 
 static void
-- 
ubuntu-desktop mailing list
ubuntu-desktop@lists.ubuntu.com
https://lists.ubuntu.com/mailman/listinfo/ubuntu-desktop

Reply via email to