diff --git a/apps/files/js/breadcrumb.js b/apps/files/js/breadcrumb.js
index c017d710d6decad7830193b7a3d4bccc53bc4c02..7381fa8e432e5ee3e383198b2beea27777e466bf 100644
--- a/apps/files/js/breadcrumb.js
+++ b/apps/files/js/breadcrumb.js
@@ -41,8 +41,9 @@
 		$el: null,
 		dir: null,
 
-		lastWidth: 0,
-		hiddenBreadcrumbs: 0,
+		/**
+		 * Total width of all breadcrumbs
+		 */
 		totalWidth: 0,
 		breadcrumbs: [],
 		onClick: null,
@@ -116,7 +117,6 @@
 			}
 
 			this._updateTotalWidth();
-			this.resize($(window).width(), true);
 		},
 
 		/**
@@ -150,93 +150,93 @@
 			return crumbs;
 		},
 
+		/**
+		 * Calculate the total breadcrumb width when
+		 * all crumbs are expanded
+		 */
 		_updateTotalWidth: function () {
-			var self = this;
-
-			this.lastWidth = 0;
-
-			// initialize with some extra space
-			this.totalWidth = 64;
-			// FIXME: this class should not know about global elements
-			if ( $('#navigation').length ) {
-				this.totalWidth += $('#navigation').outerWidth();
+			this.totalWidth = 0;
+			for (var i = 0; i < this.breadcrumbs.length; i++ ) {
+				var $crumb = $(this.breadcrumbs[i]);
+				$crumb.data('real-width', $crumb.width());
+				this.totalWidth += $crumb.width();
 			}
+			this._resize();
+		},
 
-			if ( $('#app-navigation').length && !$('#app-navigation').hasClass('hidden')) {
-				this.totalWidth += $('#app-navigation').outerWidth();
+		/**
+		 * Show/hide breadcrumbs to fit the given width
+		 */
+		setMaxWidth: function (availableWidth) {
+			if (this.availableWidth !== availableWidth) {
+				this.availableWidth = availableWidth;
+				this._resize();
 			}
-			this.hiddenBreadcrumbs = 0;
+		},
 
-			for (var i = 0; i < this.breadcrumbs.length; i++ ) {
-				this.totalWidth += $(this.breadcrumbs[i]).get(0).offsetWidth;
+		_resize: function() {
+			var i, $crumb, $ellipsisCrumb;
+
+			if (!this.availableWidth) {
+				this.availableWidth = this.$el.width();
 			}
 
-			$.each($('#controls .actions'), function(index, action) {
-				self.totalWidth += $(action).outerWidth();
-			});
+			if (this.breadcrumbs.length <= 1) {
+				return;
+			}
 
-		},
+			// reset crumbs
+			this.$el.find('.crumb.ellipsized').remove();
 
-		/**
-		 * Show/hide breadcrumbs to fit the given width
-		 */
-		resize: function (width, firstRun) {
-			var i, $crumb;
+			// unhide all
+			this.$el.find('.crumb.hidden').removeClass('hidden');
 
-			if (width === this.lastWidth) {
+			if (this.totalWidth <= this.availableWidth) {
+				// no need to compute breadcrumbs, there is enough space
 				return;
 			}
 
-			// window was shrinked since last time or first run ?
-			if ((width < this.lastWidth || firstRun) && width < this.totalWidth) {
-				if (this.hiddenBreadcrumbs === 0 && this.breadcrumbs.length > 1) {
-					// start by hiding the first breadcrumb after home,
-					// that one will have extra three dots displayed
-					$crumb = this.breadcrumbs[1];
-					this.totalWidth -= $crumb.get(0).offsetWidth;
-					$crumb.find('a').addClass('hidden');
-					$crumb.append('<span class="ellipsis">...</span>');
-					this.totalWidth += $crumb.get(0).offsetWidth;
-					this.hiddenBreadcrumbs = 2;
-				}
-				i = this.hiddenBreadcrumbs;
-				// hide subsequent breadcrumbs if the space is still not enough
-				while (width < this.totalWidth && i > 1 && i < this.breadcrumbs.length - 1) {
-					$crumb = this.breadcrumbs[i];
-					this.totalWidth -= $crumb.get(0).offsetWidth;
+			// running width, considering the hidden crumbs
+			var currentTotalWidth = $(this.breadcrumbs[0]).data('real-width');
+			var firstHidden = true;
+
+			// insert ellipsis after root part (root part is always visible)
+			$ellipsisCrumb = $('<div class="crumb ellipsized svg"><span class="ellipsis">...</span></div>');
+			$(this.breadcrumbs[0]).after($ellipsisCrumb);
+			currentTotalWidth += $ellipsisCrumb.width();
+
+			i = this.breadcrumbs.length - 1;
+
+			// find the first section that would cause the overflow
+			// then hide everything in front of that
+			//
+			// this ensures that the last crumb section stays visible
+			// for most of the cases and is always the last one to be
+			// hidden when the screen becomes very narrow
+			while (i > 0) {
+				$crumb = $(this.breadcrumbs[i]);
+				// if the current breadcrumb would cause overflow
+				if (!firstHidden || currentTotalWidth + $crumb.data('real-width') > this.availableWidth) {
+					// hide it
 					$crumb.addClass('hidden');
-					this.hiddenBreadcrumbs = i;
-					i++;
-				}
-			// window is bigger than last time
-			} else if (width > this.lastWidth && this.hiddenBreadcrumbs > 0) {
-				i = this.hiddenBreadcrumbs;
-				while (width > this.totalWidth && i > 0) {
-					if (this.hiddenBreadcrumbs === 1) {
-						// special handling for last one as it has the three dots
-						$crumb = this.breadcrumbs[1];
-						if ($crumb) {
-							this.totalWidth -= $crumb.get(0).offsetWidth;
-							$crumb.find('.ellipsis').remove();
-							$crumb.find('a').removeClass('hidden');
-							this.totalWidth += $crumb.get(0).offsetWidth;
-						}
-					} else {
-						$crumb = this.breadcrumbs[i];
-						$crumb.removeClass('hidden');
-						this.totalWidth += $crumb.get(0).offsetWidth;
-						if (this.totalWidth > width) {
-							this.totalWidth -= $crumb.get(0).offsetWidth;
-							$crumb.addClass('hidden');
-							break;
-						}
+					if (firstHidden) {
+						// set the path of this one as title for the ellipsis
+						this.$el.find('.crumb.ellipsized')
+							.attr('title', $crumb.attr('data-dir'))
+							.tipsy();
 					}
-					i--;
-					this.hiddenBreadcrumbs = i;
+					// and all the previous ones (going backwards)
+					firstHidden = false;
+				} else {
+					// add to total width
+					currentTotalWidth += $crumb.data('real-width');
 				}
+				i--;
 			}
 
-			this.lastWidth = width;
+			if (!OC.Util.hasSVGSupport()) {
+				OC.Util.replaceSVG(this.$el);
+			}
 		}
 	};
 
diff --git a/apps/files/js/file-upload.js b/apps/files/js/file-upload.js
index da58e1c31b8c1690496605d7ea15390566f643a0..2637d13f9bacd88bb821721bf58e1924d534accc 100644
--- a/apps/files/js/file-upload.js
+++ b/apps/files/js/file-upload.js
@@ -179,9 +179,20 @@ OC.Upload = {
 		callbacks.onNoConflicts(selection);
 	},
 
+	_hideProgressBar: function() {
+		$('#uploadprogresswrapper input.stop').fadeOut();
+		$('#uploadprogressbar').fadeOut(function() {
+			$('#file_upload_start').trigger(new $.Event('resized'));
+		});
+	},
+
+	_showProgressBar: function() {
+		$('#uploadprogressbar').fadeIn();
+		$('#file_upload_start').trigger(new $.Event('resized'));
+	},
+
 	init: function() {
 		if ( $('#file_upload_start').exists() ) {
-
 			var file_upload_param = {
 				dropZone: $('#content'), // restrict dropZone to content div
 				autoUpload: false,
@@ -444,7 +455,7 @@ OC.Upload = {
 					OC.Upload.log('progress handle fileuploadstart', e, data);
 					$('#uploadprogresswrapper input.stop').show();
 					$('#uploadprogressbar').progressbar({value: 0});
-					$('#uploadprogressbar').fadeIn();
+					OC.Upload._showProgressBar();
 				});
 				fileupload.on('fileuploadprogress', function(e, data) {
 					OC.Upload.log('progress handle fileuploadprogress', e, data);
@@ -458,15 +469,13 @@ OC.Upload = {
 				fileupload.on('fileuploadstop', function(e, data) {
 					OC.Upload.log('progress handle fileuploadstop', e, data);
 
-					$('#uploadprogresswrapper input.stop').fadeOut();
-					$('#uploadprogressbar').fadeOut();
+					OC.Upload._hideProgressBar();
 				});
 				fileupload.on('fileuploadfail', function(e, data) {
 					OC.Upload.log('progress handle fileuploadfail', e, data);
 					//if user pressed cancel hide upload progress bar and cancel button
 					if (data.errorThrown === 'abort') {
-						$('#uploadprogresswrapper input.stop').fadeOut();
-						$('#uploadprogressbar').fadeOut();
+						OC.Upload._hideProgressBar();
 					}
 				});
 
@@ -649,7 +658,7 @@ OC.Upload = {
 							//IE < 10 does not fire the necessary events for the progress bar.
 							if ($('html.lte9').length === 0) {
 								$('#uploadprogressbar').progressbar({value: 0});
-								$('#uploadprogressbar').fadeIn();
+								OC.Upload._showProgressBar();
 							}
 
 							var eventSource = new OC.EventSource(
@@ -668,12 +677,12 @@ OC.Upload = {
 							});
 							eventSource.listen('success', function(data) {
 								var file = data;
-								$('#uploadprogressbar').fadeOut();
+								OC.Upload._hideProgressBar();
 
 								FileList.add(file, {hidden: hidden, animate: true});
 							});
 							eventSource.listen('error', function(error) {
-								$('#uploadprogressbar').fadeOut();
+								OC.Upload._hideProgressBar();
 								var message = (error && error.message) || t('core', 'Error fetching URL');
 								OC.Notification.show(message);
 								//hide notification after 10 sec
diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js
index 55afedb2065829e38cbf5b30ca06264a54e58802..31c4c1118328fa04e31b352e3f39d12774d48a79 100644
--- a/apps/files/js/filelist.js
+++ b/apps/files/js/filelist.js
@@ -150,11 +150,10 @@
 
 			this.$el.find('thead th .columntitle').click(_.bind(this._onClickHeader, this));
 
-			$(window).resize(function() {
-				// TODO: debounce this ?
-				var width = $(this).width();
-				self.breadcrumb.resize(width, false);
-			});
+			this._onResize = _.debounce(_.bind(this._onResize, this), 100);
+			$(window).resize(this._onResize);
+
+			this.$el.on('show', this._onResize);
 
 			this.$fileList.on('click','td.filename>a.name', _.bind(this._onClickFile, this));
 			this.$fileList.on('change', 'td.filename>input:checkbox', _.bind(this._onClickFileCheckbox, this));
@@ -176,6 +175,22 @@
 			}
 		},
 
+		/**
+		 * Event handler for when the window size changed
+		 */
+		_onResize: function() {
+			var containerWidth = this.$el.width();
+			var actionsWidth = 0;
+			$.each(this.$el.find('#controls .actions'), function(index, action) {
+				actionsWidth += $(action).outerWidth();
+			});
+
+			// substract app navigation toggle when visible
+			containerWidth -= $('#app-navigation-toggle').width();
+
+			this.breadcrumb.setMaxWidth(containerWidth - actionsWidth - 10);
+		},
+
 		/**
 		 * Event handler for when the URL changed
 		 */
@@ -1538,6 +1553,9 @@
 			// handle upload events
 			var fileUploadStart = this.$el.find('#file_upload_start');
 
+			// detect the progress bar resize
+			fileUploadStart.on('resized', this._onResize);
+
 			fileUploadStart.on('fileuploaddrop', function(e, data) {
 				OC.Upload.log('filelist handle fileuploaddrop', e, data);
 
diff --git a/apps/files/tests/js/breadcrumbSpec.js b/apps/files/tests/js/breadcrumbSpec.js
index e3d9c757a7c0a1e01031b2ed6b28f56aefd448b4..30784fd70ad636bcf312e634b7cd4d48903c8883 100644
--- a/apps/files/tests/js/breadcrumbSpec.js
+++ b/apps/files/tests/js/breadcrumbSpec.js
@@ -19,7 +19,6 @@
 *
 */
 
-/* global BreadCrumb */
 describe('OCA.Files.BreadCrumb tests', function() {
 	var BreadCrumb = OCA.Files.BreadCrumb;
 
@@ -131,48 +130,42 @@ describe('OCA.Files.BreadCrumb tests', function() {
 		});
 	});
 	describe('Resizing', function() {
-		var bc, widthStub, dummyDir,
-			oldUpdateTotalWidth;
+		var bc, dummyDir, widths, oldUpdateTotalWidth;
 
 		beforeEach(function() {
-			dummyDir = '/short name/longer name/looooooooooooonger/even longer long long long longer long/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/last one';
+			dummyDir = '/short name/longer name/looooooooooooonger/' +
+				'even longer long long long longer long/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/last one';
+
+			// using hard-coded widths (pre-measured) to avoid getting different
+			// results on different browsers due to font engine differences
+			widths = [41, 106, 112, 160, 257, 251, 91];
 
 			oldUpdateTotalWidth = BreadCrumb.prototype._updateTotalWidth;
 			BreadCrumb.prototype._updateTotalWidth = function() {
-				// need to set display:block for correct offsetWidth (no CSS loaded here)
-				$('div.crumb').css({
-					'display': 'block',
-					'float': 'left'
+				// pre-set a width to simulate consistent measurement
+				$('div.crumb').each(function(index){
+					$(this).css('width', widths[index]);
 				});
 
 				return oldUpdateTotalWidth.apply(this, arguments);
 			};
 
 			bc = new BreadCrumb();
-			widthStub = sinon.stub($.fn, 'width');
 			// append dummy navigation and controls
 			// as they are currently used for measurements
 			$('#testArea').append(
-				'<div id="navigation" style="width: 80px"></div>',
 				'<div id="controls"></div>'
 			);
-
-			// make sure we know the test screen width
-			$('#testArea').css('width', 1280);
-
-			// use test area as we need it for measurements
 			$('#controls').append(bc.$el);
-			$('#controls').append('<div class="actions"><div>Dummy action with a given width</div></div>');
 		});
 		afterEach(function() {
 			BreadCrumb.prototype._updateTotalWidth = oldUpdateTotalWidth;
-			widthStub.restore();
 			bc = null;
 		});
-		it('Hides breadcrumbs to fit window', function() {
+		it('Hides breadcrumbs to fit max allowed width', function() {
 			var $crumbs;
 
-			widthStub.returns(500);
+			bc.setMaxWidth(500);
 			// triggers resize implicitly
 			bc.setDirectory(dummyDir);
 			$crumbs = bc.$el.find('.crumb');
@@ -190,19 +183,23 @@ describe('OCA.Files.BreadCrumb tests', function() {
 			expect($crumbs.eq(4).hasClass('hidden')).toEqual(true);
 			expect($crumbs.eq(5).hasClass('hidden')).toEqual(true);
 			expect($crumbs.eq(6).hasClass('hidden')).toEqual(false);
+			expect($crumbs.eq(7).hasClass('hidden')).toEqual(false);
 		});
-		it('Updates ellipsis on window size increase', function() {
+		it('Updates the breadcrumbs when reducing max allowed width', function() {
 			var $crumbs;
 
-			widthStub.returns(500);
+			// enough space
+			bc.setMaxWidth(1800);
+
+			expect(bc.$el.find('.ellipsis').length).toEqual(0);
+
 			// triggers resize implicitly
 			bc.setDirectory(dummyDir);
-			$crumbs = bc.$el.find('.crumb');
 
 			// simulate increase
-			$('#testArea').css('width', 1800);
-			bc.resize(1800);
+			bc.setMaxWidth(950);
 
+			$crumbs = bc.$el.find('.crumb');
 			// first one is always visible
 			expect($crumbs.eq(0).hasClass('hidden')).toEqual(false);
 			// second one has ellipsis
@@ -213,37 +210,35 @@ describe('OCA.Files.BreadCrumb tests', function() {
 			// subsequent elements are hidden
 			expect($crumbs.eq(2).hasClass('hidden')).toEqual(true);
 			expect($crumbs.eq(3).hasClass('hidden')).toEqual(true);
-			expect($crumbs.eq(4).hasClass('hidden')).toEqual(true);
 			// the rest is visible
+			expect($crumbs.eq(4).hasClass('hidden')).toEqual(false);
 			expect($crumbs.eq(5).hasClass('hidden')).toEqual(false);
 			expect($crumbs.eq(6).hasClass('hidden')).toEqual(false);
 		});
-		it('Updates ellipsis on window size decrease', function() {
+		it('Removes the ellipsis when there is enough space', function() {
 			var $crumbs;
 
-			$('#testArea').css('width', 2000);
-			widthStub.returns(2000);
+			bc.setMaxWidth(500);
 			// triggers resize implicitly
 			bc.setDirectory(dummyDir);
 			$crumbs = bc.$el.find('.crumb');
 
-			// simulate decrease
-			bc.resize(500);
-			$('#testArea').css('width', 500);
+			// ellipsis
+			expect(bc.$el.find('.ellipsis').length).toEqual(1);
 
-			// first one is always visible
+			// simulate increase
+			bc.setMaxWidth(1800);
+
+			// no ellipsis
+			expect(bc.$el.find('.ellipsis').length).toEqual(0);
+
+			// all are visible
 			expect($crumbs.eq(0).hasClass('hidden')).toEqual(false);
-			// second one has ellipsis
 			expect($crumbs.eq(1).hasClass('hidden')).toEqual(false);
-			expect($crumbs.eq(1).find('.ellipsis').length).toEqual(1);
-			// there is only one ellipsis in total
-			expect($crumbs.find('.ellipsis').length).toEqual(1);
-			// subsequent elements are hidden
-			expect($crumbs.eq(2).hasClass('hidden')).toEqual(true);
-			expect($crumbs.eq(3).hasClass('hidden')).toEqual(true);
-			expect($crumbs.eq(4).hasClass('hidden')).toEqual(true);
-			// the rest is visible
-			expect($crumbs.eq(5).hasClass('hidden')).toEqual(true);
+			expect($crumbs.eq(2).hasClass('hidden')).toEqual(false);
+			expect($crumbs.eq(3).hasClass('hidden')).toEqual(false);
+			expect($crumbs.eq(4).hasClass('hidden')).toEqual(false);
+			expect($crumbs.eq(5).hasClass('hidden')).toEqual(false);
 			expect($crumbs.eq(6).hasClass('hidden')).toEqual(false);
 		});
 	});
diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js
index dea7c48e05eaa8633df81f3a54cf3b7744f38b48..318f66825a652c308b9e4279aebac4e9a5e8b755 100644
--- a/apps/files/tests/js/filelistSpec.js
+++ b/apps/files/tests/js/filelistSpec.js
@@ -21,6 +21,7 @@
 
 describe('OCA.Files.FileList tests', function() {
 	var testFiles, alertStub, notificationStub, fileList;
+	var bcResizeStub;
 
 	/**
 	 * Generate test file data
@@ -52,6 +53,9 @@ describe('OCA.Files.FileList tests', function() {
 	beforeEach(function() {
 		alertStub = sinon.stub(OC.dialogs, 'alert');
 		notificationStub = sinon.stub(OC.Notification, 'show');
+		// prevent resize algo to mess up breadcrumb order while
+		// testing
+		bcResizeStub = sinon.stub(OCA.Files.BreadCrumb.prototype, '_resize');
 
 		// init parameters and test table elements
 		$('#testArea').append(
@@ -125,6 +129,7 @@ describe('OCA.Files.FileList tests', function() {
 
 		notificationStub.restore();
 		alertStub.restore();
+		bcResizeStub.restore();
 	});
 	describe('Getters', function() {
 		it('Returns the current directory', function() {
diff --git a/core/css/mobile.css b/core/css/mobile.css
index d840cdafa2683ab45944c2e9fe4e503f2c877c10..2003e0a7f45485b3bfef25e168723f0fcd0f1585 100644
--- a/core/css/mobile.css
+++ b/core/css/mobile.css
@@ -110,7 +110,6 @@
 	min-width: initial !important;
 	left: 0 !important;
 	padding-left: 0;
-	padding-right: 0 !important;
 }
 /* position controls for apps with app-navigation */
 #app-navigation+#app-content #controls {
diff --git a/core/css/styles.css b/core/css/styles.css
index c1f42b457ed1d14d012e8c06fe98f60a43953665..66af01ae3c57fb0dfa69348d36901d1aecd5b021 100644
--- a/core/css/styles.css
+++ b/core/css/styles.css
@@ -261,11 +261,9 @@ input[type="submit"].enabled {
 /* position controls for apps with app-navigation */
 #app-navigation+#app-content #controls {
 	left: 250px;
-	padding-right: 250px;
 }
 .viewer-mode #app-navigation+#app-content #controls {
 	left: 0;
-	padding-right: 0;
 }
 
 #controls .button,
diff --git a/core/js/tests/specHelper.js b/core/js/tests/specHelper.js
index 2af3497051cd696bff24b495efb139a0c1498276..3d208d9ef3f4a6953af4208045e07f60612f6d26 100644
--- a/core/js/tests/specHelper.js
+++ b/core/js/tests/specHelper.js
@@ -85,7 +85,7 @@ window.Snap.prototype = {
 	beforeEach(function() {
 		// test area for elements that need absolute selector access or measure widths/heights
 		// which wouldn't work for detached or hidden elements
-		$testArea = $('<div id="testArea" style="position: absolute; width: 1280px; height: 800px; top: -3000px; left: -3000px;"></div>');
+		$testArea = $('<div id="testArea" style="position: absolute; width: 1280px; height: 800px; top: -3000px; left: -3000px; opacity: 0;"></div>');
 		$('body').append($testArea);
 		// enforce fake XHR, tests should not depend on the server and
 		// must use fake responses for expected calls
diff --git a/tests/karma.config.js b/tests/karma.config.js
index 290790686b04d9025d978c1372826a8ee38ad0fd..14a0d7e8464fd5b4683332e8b6e6c7ec1297bbf2 100644
--- a/tests/karma.config.js
+++ b/tests/karma.config.js
@@ -110,15 +110,16 @@ module.exports = function(config) {
 	// core mocks
 	files.push(corePath + 'tests/specHelper.js');
 
+	var srcFile, i;
 	// add core library files
-	for ( var i = 0; i < coreModule.libraries.length; i++ ) {
-		var srcFile = corePath + coreModule.libraries[i];
+	for ( i = 0; i < coreModule.libraries.length; i++ ) {
+		srcFile = corePath + coreModule.libraries[i];
 		files.push(srcFile);
 	}
 
 	// add core modules files
-	for ( var i = 0; i < coreModule.modules.length; i++ ) {
-		var srcFile = corePath + coreModule.modules[i];
+	for ( i = 0; i < coreModule.modules.length; i++ ) {
+		srcFile = corePath + coreModule.modules[i];
 		files.push(srcFile);
 		if (enableCoverage) {
 			preprocessors[srcFile] = 'coverage';
@@ -155,12 +156,15 @@ module.exports = function(config) {
 	}
 
 	// add source files for apps to test
-	for ( var i = 0; i < appsToTest.length; i++ ) {
+	for ( i = 0; i < appsToTest.length; i++ ) {
 		addApp(appsToTest[i]);
 	}
 
 	// serve images to avoid warnings
 	files.push({pattern: 'core/img/**/*', watched: false, included: false, served: true});
+	
+	// include core CSS
+	files.push({pattern: 'core/css/*.css', watched: true, included: true, served: true});
 
 	config.set({
 
@@ -180,7 +184,9 @@ module.exports = function(config) {
 
 		proxies: {
 			// prevent warnings for images
-			'/context.html//core/img/': 'http://localhost:9876/base/core/img/'
+			'/context.html//core/img/': 'http://localhost:9876/base/core/img/',
+			'/context.html//core/css/': 'http://localhost:9876/base/core/css/',
+			'/context.html//core/fonts/': 'http://localhost:9876/base/core/fonts/'
 		},
 
 		// test results reporter to use