From ea68151257a69e105ad3281a3400165215b64ab0 Mon Sep 17 00:00:00 2001 From: Marcus Cavanaugh Date: Wed, 30 Sep 2015 15:02:16 -0700 Subject: [PATCH] Bug 1209402 - [TaskManager] Implement New Sheet and New Private Sheet. r=etienne --- .../sharedtest/test/unit/event_safety_test.js | 82 +++++----- apps/system/index.html | 12 +- apps/system/js/app_transition_controller.js | 2 +- apps/system/js/card.js | 12 +- apps/system/js/task_manager.js | 105 ++++++++---- apps/system/style/cards_view/cards_view.css | 92 ++++++++--- .../style/task-manager/images/tm_new_tab.png | Bin 0 -> 210 bytes .../task-manager/images/tm_new_tab@1.5x.png | Bin 0 -> 233 bytes .../task-manager/images/tm_new_tab@2.25x.png | Bin 0 -> 218 bytes .../task-manager/images/tm_new_tab@2x.png | Bin 0 -> 214 bytes .../task-manager/images/tm_new_tab_press.png | Bin 0 -> 16922 bytes .../images/tm_new_tab_press@1.5x.png | Bin 0 -> 19262 bytes .../images/tm_new_tab_press@2.25x.png | Bin 0 -> 25905 bytes .../images/tm_new_tab_press@2x.png | Bin 0 -> 23874 bytes .../style/task-manager/images/tm_pb_tab.png | Bin 0 -> 798 bytes .../task-manager/images/tm_pb_tab@1.5x.png | Bin 0 -> 1009 bytes .../task-manager/images/tm_pb_tab@2.25x.png | Bin 0 -> 1365 bytes .../task-manager/images/tm_pb_tab@2x.png | Bin 0 -> 1243 bytes .../task-manager/images/tm_pb_tab_press.png | Bin 0 -> 17128 bytes .../images/tm_pb_tab_press@1.5x.png | Bin 0 -> 19422 bytes .../images/tm_pb_tab_press@2.25x.png | Bin 0 -> 27603 bytes .../images/tm_pb_tab_press@2x.png | Bin 0 -> 24721 bytes apps/system/style/window.css | 11 ++ apps/system/test/marionette/lib/system.js | 2 +- .../test/marionette/lib/task_manager.js | 15 +- .../test/marionette/task_manager_test.js | 20 +++ apps/system/test/unit/card_test.js | 11 ++ apps/system/test/unit/task_manager_test.js | 150 +++++++++++++----- shared/js/event_safety.js | 6 +- 29 files changed, 380 insertions(+), 140 deletions(-) create mode 100755 apps/system/style/task-manager/images/tm_new_tab.png create mode 100755 apps/system/style/task-manager/images/tm_new_tab@1.5x.png create mode 100755 apps/system/style/task-manager/images/tm_new_tab@2.25x.png create mode 100755 apps/system/style/task-manager/images/tm_new_tab@2x.png create mode 100755 apps/system/style/task-manager/images/tm_new_tab_press.png create mode 100755 apps/system/style/task-manager/images/tm_new_tab_press@1.5x.png create mode 100755 apps/system/style/task-manager/images/tm_new_tab_press@2.25x.png create mode 100755 apps/system/style/task-manager/images/tm_new_tab_press@2x.png create mode 100755 apps/system/style/task-manager/images/tm_pb_tab.png create mode 100755 apps/system/style/task-manager/images/tm_pb_tab@1.5x.png create mode 100755 apps/system/style/task-manager/images/tm_pb_tab@2.25x.png create mode 100755 apps/system/style/task-manager/images/tm_pb_tab@2x.png create mode 100755 apps/system/style/task-manager/images/tm_pb_tab_press.png create mode 100755 apps/system/style/task-manager/images/tm_pb_tab_press@1.5x.png create mode 100755 apps/system/style/task-manager/images/tm_pb_tab_press@2.25x.png create mode 100755 apps/system/style/task-manager/images/tm_pb_tab_press@2x.png diff --git a/apps/sharedtest/test/unit/event_safety_test.js b/apps/sharedtest/test/unit/event_safety_test.js index 88d1fbad604c..772fd96ddd05 100644 --- a/apps/sharedtest/test/unit/event_safety_test.js +++ b/apps/sharedtest/test/unit/event_safety_test.js @@ -40,24 +40,29 @@ suite('eventSafety: callback variant >', function() { window.dispatchEvent(new CustomEvent('foobar')); }); - test('filters mismatched elements for transitionend events', function() { - this.sinon.useFakeTimers(); - var called = false; - var timeoutSpy = this.sinon.spy(window, 'setTimeout'); - eventSafety(window, 'foobar', function() { - called = true; - }, 1000); - - // Get the private callback and execute with arguments. - timeoutSpy.getCall(0).args[0]({ - type: 'transitionend', - target: document.body // (Not what we called the eventSafety method with) + function testBubblingEvent(eventName) { + test(`filters mismatched elements for ${eventName} events`, function() { + this.sinon.useFakeTimers(); + var called = false; + var timeoutSpy = this.sinon.spy(window, 'setTimeout'); + eventSafety(window, 'foobar', function() { + called = true; + }, 1000); + + // Get the private callback and execute with arguments. + timeoutSpy.getCall(0).args[0]({ + type: eventName, + target: document.body // (Not what we called `eventSafety` with) + }); + + assert.equal(called, false, 'should not call the callback'); + this.sinon.clock.tick(1000); + assert.equal(called, true, 'called after timeout'); }); + } - assert.equal(called, false, 'should not call the callback'); - this.sinon.clock.tick(1000); - assert.equal(called, true, 'called after timeout'); - }); + testBubblingEvent('transitionend'); + testBubblingEvent('animationend'); }); @@ -99,27 +104,32 @@ suite('eventSafety: promise variant >', function() { window.dispatchEvent(new CustomEvent('foobar')); }); - test('filters mismatched elements for transitionend events', function(done) { - this.sinon.useFakeTimers(); - var called = false; - var timeoutSpy = this.sinon.spy(window, 'setTimeout'); - eventSafety(window, 'foobar', 1000).then(function() { - called = true; - }); - - // Get the private callback and execute with arguments. - timeoutSpy.getCall(0).args[0]({ - type: 'transitionend', - target: document.body // (Not what we called the eventSafety method with) + function testBubblingEvent(eventName) { + test(`filters mismatched elements for ${eventName} events`, function(done) { + this.sinon.useFakeTimers(); + var called = false; + var timeoutSpy = this.sinon.spy(window, 'setTimeout'); + eventSafety(window, 'foobar', 1000).then(function() { + called = true; + }); + + // Get the private callback and execute with arguments. + timeoutSpy.getCall(0).args[0]({ + type: eventName, + target: document.body // (Not what we called `eventSafety` with) + }); + + assert.equal(called, false, 'should not call the callback'); + this.sinon.clock.tick(1000); + // NOTE: Executing in the next Promise tick so that our Promise callback + // has a chance to fire. + Promise.resolve().then(() => { + assert.equal(called, true, 'called after timeout'); + }).then(() => { done(); }, done); }); + } - assert.equal(called, false, 'should not call the callback'); - this.sinon.clock.tick(1000); - // NOTE: Executing in the next Promise tick so that our Promise callback - // has a chance to fire. - Promise.resolve().then(() => { - assert.equal(called, true, 'called after timeout'); - }).then(() => { done(); }, done); - }); + testBubblingEvent('transitionend'); + testBubblingEvent('animationend'); }); diff --git a/apps/system/index.html b/apps/system/index.html index d39977dd2a1b..d374457d03ae 100644 --- a/apps/system/index.html +++ b/apps/system/index.html @@ -664,9 +664,15 @@

-
- - +
+
+
    + +
    +
    + + +
    diff --git a/apps/system/js/app_transition_controller.js b/apps/system/js/app_transition_controller.js index 06df0163d85b..94d73a9a2aa6 100644 --- a/apps/system/js/app_transition_controller.js +++ b/apps/system/js/app_transition_controller.js @@ -342,7 +342,7 @@ 'will-become-active', 'will-become-inactive', 'slide-to-top', 'slide-from-top', 'slide-to-bottom', 'slide-from-bottom', - 'home-from-cardview', 'home-to-cardview']; + 'home-from-cardview', 'home-to-cardview', 'from-new-card']; classes.forEach(function iterator(cls) { this.app.element.classList.remove(cls); diff --git a/apps/system/js/card.js b/apps/system/js/card.js index 923fb88b1e1f..21c31ae266c4 100644 --- a/apps/system/js/card.js +++ b/apps/system/js/card.js @@ -12,16 +12,24 @@ * also included in this file. * * @param {AppWindow} app - * @param {boolean} disableScreenshots + * @param {boolean} opts.disableScreenshots * Legacy option. If true, we will use a small icon preview on the card * rather than showing a full screenshot and/or moz-element of the app. * Originally introduced on 128MB Tarako devices to save memory. + * @param {boolean} opts.stayInvisible + * If true (because we're just using this card as a placeholder since we're + * immediately launching the app), don't actually render the card. */ -function Card(app, disableScreenshots) { +function Card(app, { disableScreenshots, stayInvisible } = {}) { var el = document.createElement('li'); this.app = app; this.element = el; + if (stayInvisible) { + this.element.style.display = 'none'; + return; + } + this.title = (app.isBrowser() && app.title) ? app.title : app.name; this.subTitle = TaskManagerUtils.getDisplayUrlForApp(app); this.titleId = 'card-title-' + app.instanceID; diff --git a/apps/system/js/task_manager.js b/apps/system/js/task_manager.js index e2cdef963974..53a07e0dcc96 100644 --- a/apps/system/js/task_manager.js +++ b/apps/system/js/task_manager.js @@ -1,5 +1,5 @@ /* global Card, TaskManagerUtils, LazyLoader, Service, StackManager, - eventSafety, SettingsListener */ + eventSafety, SettingsListener, AppWindow, BrowserConfigHelper */ 'use strict'; (function(exports) { @@ -49,10 +49,15 @@ TaskManager.prototype = { * @return {Promise} */ start() { - this.element = document.getElementById('cards-view'), + this.element = document.getElementById('task-manager'), + this.scrollElement = document.getElementById('cards-view'), this.cardsList = document.getElementById('cards-list'); this.screenElement = document.getElementById('screen'); this.noRecentWindowsEl = document.getElementById('cards-no-recent-windows'); + this.newSheetButton = + document.getElementById('task-manager-new-sheet-button'); + this.newPrivateSheetButton = + document.getElementById('task-manager-new-private-sheet-button'); window.addEventListener('taskmanagershow', this); Service.request('registerHierarchy', this); @@ -94,11 +99,16 @@ TaskManager.prototype = { * Create a new Card representing the given app, and add it to the DOM. * (This does not deal with stack order.) */ - _addApp(app) { - var card = new Card(app, this.disableScreenshots); + _addApp(app, { stayInvisible }) { + var card = new Card(app, { + disableScreenshots: this.disableScreenshots, + stayInvisible: stayInvisible + }); + this.cardsList.appendChild(card.element); this.elementToCardMap.set(card.element, card); this.appToCardMap.set(app, card); + app.enterTaskManager(); return card; }, @@ -112,6 +122,7 @@ TaskManager.prototype = { this.appToCardMap.delete(app); this.elementToCardMap.delete(card.element); this.cardsList.removeChild(card.element); + app.leaveTaskManager(); } }, @@ -120,7 +131,7 @@ TaskManager.prototype = { * Call this whenever we think the stack has changed; this function will * take care of updating the layout and card positions. */ - updateStack() { + updateStack({ currentlyLaunchingApp } = {}) { this.stack = StackManager.snapshot(); if (this._browserOnly) { @@ -134,6 +145,7 @@ TaskManager.prototype = { this._browserOnly ? 'no-recent-browser-windows' : 'no-recent-app-windows'); this.element.classList.toggle('filtered', !!this._browserOnly); + this.element.classList.toggle('empty', this.stack.length === 0); var latestAppSet = new Set(this.stack); @@ -146,8 +158,9 @@ TaskManager.prototype = { this.stack.forEach((app) => { var card = this.appToCardMap.get(app); if (!card) { - card = this._addApp(app); - app.enterTaskManager(); + card = this._addApp(app, { + stayInvisible: (app === currentlyLaunchingApp) + }); } }); @@ -210,7 +223,7 @@ TaskManager.prototype = { getCurrentIndex() { return Math.min( this.stack.length - 1, - Math.floor(this.element.scrollLeft / this.cardWidth)); + Math.floor(this.scrollElement.scrollLeft / this.cardWidth)); }, /** @@ -244,9 +257,9 @@ TaskManager.prototype = { window.addEventListener('wheel', this); window.addEventListener('resize', this); this.element.addEventListener('click', this); - this.element.addEventListener('scroll', this); this.element.addEventListener('card-will-drag', this); this.element.addEventListener('card-dropped', this); + this.scrollElement.addEventListener('scroll', this); this.updateStack(); this.panToApp(StackManager.getCurrent(), true); @@ -272,7 +285,7 @@ TaskManager.prototype = { * Hide the Task Manager and return to the AppWindow specified, or the * homescreen otherwise. */ - hide(newApp) { + hide(newApp, animation) { if (!this.isShown() || this._isTransitioning) { return Promise.resolve(); } @@ -286,9 +299,9 @@ TaskManager.prototype = { window.removeEventListener('wheel', this); window.removeEventListener('resize', this); this.element.removeEventListener('click', this); - this.element.removeEventListener('scroll', this); this.element.removeEventListener('card-will-drag', this); this.element.removeEventListener('card-dropped', this); + this.scrollElement.removeEventListener('scroll', this); newApp = newApp || Service.query('AppWindowManager.getActiveWindow') || @@ -313,19 +326,18 @@ TaskManager.prototype = { this.element.classList.add('to-home'); newApp.open('home-from-cardview'); } else { - newApp.open('from-cardview'); + newApp.open(animation || 'from-cardview'); } // ... and when the transition has finished, clean up. - return eventSafety(newApp.element, '_opened', () => { + return eventSafety(newApp.element, 'animationend', (e) => { this.setActive(false); this.element.classList.remove('to-home'); this.element.classList.remove('filtered'); this.stack.forEach((app) => { this._removeApp(app); - app.leaveTaskManager(); }); - }, 400); + }, 2000); }, /** @@ -400,13 +412,15 @@ TaskManager.prototype = { if (idx === -1) { idx = this.stack.length - 1; } - var currentPosition = this.element.scrollLeft; + + var currentPosition = this.scrollElement.scrollLeft; var desiredPosition = this.indexToOffset(idx); + return new Promise((resolve, reject) => { if (currentPosition === desiredPosition) { resolve(); } else { - this.element.scrollTo({ + this.scrollElement.scrollTo({ left: desiredPosition, top: 0, behavior: immediately ? 'auto' : 'smooth' @@ -421,14 +435,17 @@ TaskManager.prototype = { switch (evt.type) { case 'click': - if (this.element.classList.contains('empty')) { + if (evt.target === this.newSheetButton) { + this.openNewSheet({ isPrivate: false }); + } else if (evt.target === this.newPrivateSheetButton) { + this.openNewSheet({ isPrivate: true }); + } else if (this.element.classList.contains('empty')) { this.hide(Service.query('getHomescreen', true)); - return; - } - - card = this.elementToCardMap.get(evt.target.closest('.card')); - if (card) { - this.cardAction(card, evt.target.dataset.buttonAction || 'select'); + } else { + card = this.elementToCardMap.get(evt.target.closest('.card')); + if (card) { + this.cardAction(card, evt.target.dataset.buttonAction || 'select'); + } } break; @@ -437,7 +454,7 @@ TaskManager.prototype = { break; case 'scroll': - if (this.element.style.overflowX === 'hidden') { + if (this.scrollElement.style.overflowX === 'hidden') { // Believe it or not, you will receive scroll events even when // overflow is hidden. We don't care about those, because we're // dragging a card; the user won't see scroll events. @@ -457,7 +474,7 @@ TaskManager.prototype = { // If we're not going to prevent the drag, we should disable scrolling // while the card is dragging; we'll reenable it on "card-dropped". else { - this.element.style.overflowX = 'hidden'; + this.scrollElement.style.overflowX = 'hidden'; } break; @@ -465,7 +482,7 @@ TaskManager.prototype = { // The user has stopped touching the card; it will either bounce back // into position, or we'll need to kill the app if they swiped upward. // Regardless, reenable scrolling: - this.element.style.overflowX = 'scroll'; + this.scrollElement.style.overflowX = 'scroll'; // And kill the app after the swipe-up transition has finished. if (evt.detail.willKill) { card = this.elementToCardMap.get(evt.target); @@ -562,9 +579,41 @@ TaskManager.prototype = { this.publish('taskmanager-deactivated'); } this.element.classList.toggle('active', active); - this.element.classList.toggle('empty', active && this.stack.length === 0); }, + /** + * Launch a new browser sheet at the end of the stack, pan to its position, + * and exit into the new sheet. + */ + openNewSheet({ isPrivate } = {}) { + var config; + + if (isPrivate) { + config = new BrowserConfigHelper({ + manifestURL: 'app://search.gaiamobile.org/manifest.webapp', + url: 'app://search.gaiamobile.org/newtab.html?private=1', + }); + config.isPrivate = true; + config.oop = true; + } else { + config = new BrowserConfigHelper({ + manifestURL: 'app://search.gaiamobile.org/manifest.webapp', + url: 'app://search.gaiamobile.org/newtab.html' + }); + } + + var appWindow = new AppWindow(config); + + this.updateStack({ currentlyLaunchingApp: appWindow }); + + // NOTE: These two actions (panToApp and hide) are triggered simultaneously, + // to attempt to give the appWindow some time to load before animating + // onscreen. The 'from-new-card' transition includes a bit of dead time + // at the beginning of the animation to allow 'panToApp' to complete. + this.panToApp(appWindow); + this.hide(appWindow, 'from-new-card'); + } + }; exports.TaskManager = TaskManager; diff --git a/apps/system/style/cards_view/cards_view.css b/apps/system/style/cards_view/cards_view.css index de062e3332a3..83de9e1dfc51 100644 --- a/apps/system/style/cards_view/cards_view.css +++ b/apps/system/style/cards_view/cards_view.css @@ -1,14 +1,22 @@ -#cards-view { +#task-manager { visibility: hidden; position: absolute; top: 0; left: 0; width: 100%; - overflow-x: auto; height: 100%; - overflow-y: hidden; -moz-user-select: none; background-color: rgb(51, 51, 51); +} + +#cards-view { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + overflow-x: auto; + overflow-y: hidden; /* NOTE: We can't use scroll-snap-destination, even though it's what we want, because it thinks every element is positioned at 0px since everything is positioned using CSS transforms. */ @@ -20,7 +28,7 @@ } @media not all and (-moz-physical-home-button) { - #screen:not(.software-button-disabled) #cards-view { + #screen:not(.software-button-disabled) #task-manager { height: calc(100% - var(--software-home-button-height)); bottom: var(--software-home-button-height); } @@ -31,7 +39,7 @@ opacity: 0; } -#screen.software-button-enabled #cards-view { +#screen.software-button-enabled #task-manager { /* clip the horizontal scrollbar */ height: calc(100% - var(--software-home-button-height)); bottom: var(--software-home-button-height); @@ -39,7 +47,7 @@ @media (orientation:landscape) { /* not currently in use, see bug 1174325 */ - #screen.software-button-enabled #cards-view { + #screen.software-button-enabled #task-manager { height: 100%; width: calc(100% - var(--software-home-button-height)); bottom: 0; @@ -47,11 +55,11 @@ } } -#cards-view.active { +#task-manager.active { visibility: inherit; } -#cards-view .no-recent-apps { +#task-manager .no-recent-apps { display: none; position: absolute; top: calc(50% - 1rem); @@ -65,11 +73,11 @@ html[dir="ltr"] #cards-view .no-recent-apps { left: 3rem; } html[dir="rtl"] #cards-view .no-recent-apps { right: 3rem; } -#cards-view.empty .no-recent-apps { +#task-manager.empty .no-recent-apps { display: block; } -#cards-view.empty { +#task-manager.empty.active { background-color: rgba(51, 51, 51, .85); } @@ -100,7 +108,7 @@ html[dir="rtl"] #cards-view .no-recent-apps { right: 3rem; } left: 0; /* BiDi proof */ } -#cards-view.active .card { +#task-manager.active .card { transition: transform var(--transition-duration) cubic-bezier(.27,.02,.74,1.14); } @@ -191,11 +199,11 @@ html[dir="rtl"] #cards-view .screenshotView.maximized { opacity: 1; } -#cards-view.filtered { +#task-manager.filtered { background-color: rgba(255, 255, 255, 1); } -#cards-view.filtered .card .screenshotView { +#task-manager.filtered .card .screenshotView { outline: solid 1px rgba(133, 133, 133, 0.8); } @@ -215,11 +223,11 @@ html[dir="rtl"] #cards-view .screenshotView.maximized { background-color: rgb(95, 95, 95); } -#cards-view.filtered .card-tray { +#task-manager.filtered .card-tray { background-color: rgb(244, 244, 244); } -#cards-view.filtered .private .card-tray { +#task-manager.filtered .private .card-tray { background-color: rgb(95, 95, 95); } @@ -246,10 +254,11 @@ html[dir="rtl"] #cards-view .screenshotView.maximized { #cards-view .card-tray .close-button { background-image: url(../task-manager/light/close.png); } + html[dir="ltr"] #cards-view .card-tray .close-button { left: 0; } html[dir="rtl"] #cards-view .card-tray .close-button { right: 0; } -#cards-view.filtered .card-tray .close-button { +#task-manager.filtered .card-tray .close-button { background-image: url(../task-manager/dark/close.png); } @@ -257,10 +266,11 @@ html[dir="rtl"] #cards-view .card-tray .close-button { right: 0; } background-image: url(../task-manager/light/favorite.png); display: none; } + html[dir="ltr"] #cards-view .card-tray .favorite-button { right: 0; } html[dir="rtl"] #cards-view .card-tray .favorite-button { left: 0; } -#cards-view.filtered .card-tray .favorite-button { +#task-manager.filtered .card-tray .favorite-button { background-image: url(../task-manager/dark/favorite.png); } @@ -292,7 +302,7 @@ html[dir="rtl"] #cards-view .card-tray > button.appIcon { right: calc(50% - 2rem } #cards-view .private .close-button, -#cards-view.filtered .private .close-button { +#task-manager.filtered .private .close-button { background-image: url(../task-manager/light/close.png); } @@ -322,7 +332,7 @@ html[dir="rtl"] #cards-view .card-tray > button.appIcon { right: calc(50% - 2rem pointer-events: auto; } -#cards-view.filtered .card .titles h1 { +#task-manager.filtered .card .titles h1 { color: #4d4d4d; } @@ -345,7 +355,7 @@ html[dir="rtl"] #cards-view .card-tray > button.appIcon { right: calc(50% - 2rem font-style: italic; } -#cards-view.filtered .card p.subtitle { +#task-manager.filtered .card p.subtitle { color: #858585; } @@ -381,7 +391,7 @@ html[dir="rtl"] #cards-view .card.show-subtitle[data-ssl="broken"] p.subtitle { background-image: url("../chrome/images/dark/ssl_broken.png"); } -#cards-view.filtered .card.show-subtitle[data-ssl="secure"] p.subtitle { +#task-manager.filtered .card.show-subtitle[data-ssl="secure"] p.subtitle { background-image: url("../chrome/images/light/ssl.png"); } #cards-view .card.show-subtitle[data-ssl="broken"] p.subtitle { @@ -390,11 +400,11 @@ html[dir="rtl"] #cards-view .card.show-subtitle[data-ssl="broken"] p.subtitle { /* Animations */ -#cards-view.from-home { +#task-manager.from-home { animation: cardview-from-home 0.3s forwards cubic-bezier(0.7, 0.0, 1.0, 1.0); } -#cards-view.to-home { +#task-manager.to-home { animation: cardview-to-home 0.3s forwards cubic-bezier(0.7, 0.0, 1.0, 1.0); animation-fill-mode: forwards; } @@ -408,3 +418,39 @@ html[dir="rtl"] #cards-view .card.show-subtitle[data-ssl="broken"] p.subtitle { 0% { opacity: 1; } 100% { opacity: 0; } } + + + + +#task-manager-buttons { + position: absolute; + left: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: space-between; +} + +#task-manager-new-private-sheet-button { + width: 7.5rem; + height: 4.8rem; + border: none; + background: url("../task-manager/images/tm_pb_tab.png") no-repeat center; + background-size: 3.5rem 1.8rem; +} + +#task-manager-new-sheet-button { + width: 6.4rem; + height: 4.8rem; + border: none; + background: url("../task-manager/images/tm_new_tab.png") no-repeat center; + background-size: 2.4rem 2.4rem; +} + +#task-manager-new-private-sheet-button:active { + background-image: url("../task-manager/images/tm_pb_tab_press.png"); +} + +#task-manager-new-sheet-button:active { + background-image: url("../task-manager/images/tm_new_tab_press.png"); +} diff --git a/apps/system/style/task-manager/images/tm_new_tab.png b/apps/system/style/task-manager/images/tm_new_tab.png new file mode 100755 index 0000000000000000000000000000000000000000..75da38b33b1d120eb155c0bb6a39b7b2729fe547 GIT binary patch literal 210 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaP3?%1DUd;wlEa{HEjtmSN`?>!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8U)v6XFV_|Ns9FB#VzV z7Xm4!k|4iehRrI!_4$B2#w2fd7lsa2Sq~tGv%n*=n1Mm%B?vPHs677x6qN9EaSY+O ro}7@7z?;Cp*tH_S;DBG?Q8tE?YJ78@%MH4LY8X6S{an^LB{Ts5{0}pD literal 0 HcmV?d00001 diff --git a/apps/system/style/task-manager/images/tm_new_tab@1.5x.png b/apps/system/style/task-manager/images/tm_new_tab@1.5x.png new file mode 100755 index 0000000000000000000000000000000000000000..e63732d45d15569424326c3ec0b53d86fabb9be8 GIT binary patch literal 233 zcmeAS@N?(olHy`uVBq!ia0vp^N+8U_3?xrvihTr9Ea{HEjtmSN`?>!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBC?OQ!6XFV_|04k)*X~WX z29RPc3GxeOn7e$%=F|fVK;|$edAqwXbg;^L06Clm9+AZi3@R@{m@z=*`46C=vZsq< zh{pNkga!shRadr!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8U)v6XFV_|Ns9FB#VzV z7Xm4!k|4iehRrI!_4$B2#w2fd7lsa2Sq~tGv%n*=n1Mm%B?vPHs677x6jbnZaSYKo zpPazJcFt2X_s%7wW^>bP0l+XkK7SlGw literal 0 HcmV?d00001 diff --git a/apps/system/style/task-manager/images/tm_new_tab@2x.png b/apps/system/style/task-manager/images/tm_new_tab@2x.png new file mode 100755 index 0000000000000000000000000000000000000000..a8ef406414898a6da4a2a521227c2ac983f602de GIT binary patch literal 214 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 literal 0 HcmV?d00001 diff --git a/apps/system/style/task-manager/images/tm_new_tab_press@2.25x.png b/apps/system/style/task-manager/images/tm_new_tab_press@2.25x.png new file mode 100755 index 0000000000000000000000000000000000000000..1129a681dda835925ea7d019a5c6abeea348276b GIT binary patch literal 25905 zcmeI3O>g5w7{{k8fuLwl2q8FG*+l|&6FV}bz^CA{h!xUG2q8d%GULR_W5?;Fp^ZxPMC$tS%=0qy z`_D|=YJG40#>?jypIsz`oL^h5ZLq7#e;#|3kcCfv{OCTrdE8mOl6Xa2LWe(|S2 z2w8s7+}!fF>aS>q-OlI@`wq=?+Ya+4q{H2 z&I;WzR}Wlz^Wes&ajv zC~ei(r4`$ylA0-|4JBKUmP(ndT3jkEU6yj}URIXnoRU^bnwr;?oYeoMs*B7jyN#x{ zQM=wR$L=br9p85}S?+W?nNB`qyW4WMR4U0zPR`}h%p>jXTfW{+Ti%6Uk}yw=dWLH{ zzG+($&#T|D_xwsK#S0BS{du*WK_ScQvttzHuI|X$j3OrmHH<-wv*)&gp*0MdwrHDL zzQ^LSiE+-3?c3gtJ*MQKdu(B*NxeR(ojBfhJF&FqU%Sf?^pPe;dYk(Wl{cto@3{uO zc9%`%LelJgvpEzel8N_>9XsusW5Re(FDzIJJ%!3_uG)%Ab>DV3ZM#+Nt+MqHp|r9R ztSagGx^9>j=lVifjt31DALgSq-KSNyB2~5))5`K@wxB6REnm2#WHm(rNqU~K9Cf>4 zHupzz6g3u|Uxqn=XFLbnK@Hv4C&~hSL7s-8HEp-8`&F~8Z&TT^w#)Lc5qJ*Pm9}EH zY?lp-R`X>!sTo8CYfW3TJYTmAx>l>QhKy-8G(DHks<}cw-7Msb>4M%Yrk9NpO=p*K zG^eUdg+f*hAlB8EZSGc^bwN&Tv8va?Qa0Utj4DY-{3U6N#BaLh8-<4IF%gT^(fzSl~lOt2FKXol;SbL-qm!2pJ!ErZwcBc%aPXM zum+L+FmqY~d-*~5_!^xZY8|zqdZ_}h@ku

    34LOUfpJEVzS-d%Nq@h7bwg88=z%I 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 literal 0 HcmV?d00001 diff --git a/apps/system/style/task-manager/images/tm_pb_tab@1.5x.png b/apps/system/style/task-manager/images/tm_pb_tab@1.5x.png new file mode 100755 index 0000000000000000000000000000000000000000..30510381519339390d114df4a8d866240902d0e2 GIT binary patch literal 1009 zcmeAS@N?(olHy`uVBq!ia0vp^20$#y!3-oF>SMiu6id3JuOkD)#(wTUiL5}rLb6AY zF9SoB8UsT^3j@P1pisjL28L3g##adp3|2E37{m+a>JNs1yRg zz;qK24Fpp8B|(0{3{1>=vCVT8k6sgTpeTlv~^97rQCxFNs#%)UzCDHDi*uy9+}HtE>l*!&%@FS>04iwqba4!^I6XBaJo=D>Kn%wWfeB(>Tt}NU76q9mFm6<43Udg_NpSxAzkgEQ ztM868+Een&p6P9`7B^^MQ%*DTf6&sp<(Ar8KJAw(F?pLgjg^F$1LQ2f{Q6_Euqkni z#P`i{kxv#jaI-iwJY4xwGKpi(=82XK0R~9Qq>j?hwD$IVm4UJ zQ{?l0Ea?#;cYXsCr|GfDAzE{Pu0F9OI3(xlb*Fpp zFoad)=CxjZChp-Ie<$qarM?|LEN_~o-gfOfJ+De@Ms15;r>r%Dg~&bkRF$crJZUW~ zUI~5dj(%rCCz$$)IE&8QuV&QeIAi%zsjL2#mX*sS&ZRc(U*TPPNWb;r)JfY-X06R$ z6T2g@Gkv=I^3Lna#r&uGsFqw-|Mo!rmfbfqF8UhJ-2R<^&HdkJRQE|Vm5DFD`F`KBUyM#O W>~3f-@7)88BL+`bKbLh*2~7a#KvKK_ literal 0 HcmV?d00001 diff --git a/apps/system/style/task-manager/images/tm_pb_tab@2.25x.png b/apps/system/style/task-manager/images/tm_pb_tab@2.25x.png new file mode 100755 index 0000000000000000000000000000000000000000..d6995454a81034f6599114086e203e43b295736d GIT binary patch literal 1365 zcmeAS@N?(olHy`uVBq!ia0vp^{y?nB!3-pw-CI5bDVB6cUq=Rpjs4tz5?O(Kg=CK) zUj~LMH3o);76yi2K%s^g3=E|P3=FRl7#OT(FffQ0%-I!a!@$5eDZnSh6{z6S&$ z0E~{CFJ7zwQmrLHe!&b9Ht{Vp7O&sDY02dBAU(DOSyrRWd8?gESIy#fs^nR*E`){4 zv2Jd-Qedm{n&m5J_fKkWFU!ad>#1+-ZEA?HOVeSJ>gx26WVTnC;LT|;QEhFGU_l|j z!$x+-s*YIRxX5T#?fIe`x`n)o@^x$SQl|vCnk-yoo~~tUtvSIh+L^k;M!QDlb8pF+k<{4+aLNeoq(25Rc<)!+tvpISR<>KYdrB z=3gl{Q9)qB1i=+krm(6;rHQPJP|<3gnGrZKb-ld}3twLTbdh)a=9i`K`}rWPJWZ^j zNAT697c-jDB9?lsz4&UD*xaziVVaR`VLiQZ%iMk)IcSl0NxDaktuZop=7vRTlP@k% zUU_rNilgreX3gwA_-4zF3khp^ExwETm)DnGdcAK!g!Go{iSOO2P4f6G+CKXJoLwLL zo}cAq;}NS#j%T-~?Ko%uGp zeY8(J;*ECMf5&Acb{n*mK?7!1BFS3*&71#chA0M|8p&hfA7_#w;fh}N@-UOOGC87_O^%Fq%8a$ z{jI;Jq~;ZG*^14VzP>tiq;8r{x{J1&R-m8is%wRr#WN1itBrROdQ!$)aq|kd^J&q2 zlXkfzZ&)7BU&7q+!|cv&=Z(Cz8=lEJ>4i4_%b3HnB5m=5{U?Gv-?&QL3pxInUAi-L zW3yjSq=opp_e=lO=xkf@sjz3~oK=4xs84wJ<@x&iXD{>2S4rA<^UwVKdNx1gFM9Uu UF{^tl228IEp00i_>zopr0Dx*SPyhe` literal 0 HcmV?d00001 diff --git a/apps/system/style/task-manager/images/tm_pb_tab@2x.png b/apps/system/style/task-manager/images/tm_pb_tab@2x.png new file mode 100755 index 0000000000000000000000000000000000000000..6ef06c2d8ddb820b84ddb2d85fd576419b3e2b55 GIT binary patch literal 1243 zcmeAS@N?(olHy`uVBq!ia0vp^Za}QU!3-ps&+K^)q*&4&eH|GXHuiJ>Nn{1`6_P!I zd>I(3)EF2VS{N990fib~Fff!FFfhDIU|_JC!N4G1FlSew4FdyXUVu-CD^S7zQ9zdv z00wolR1z@c87fPH{DK*zU9;NftlYeQ*|fSiQ#Rv>8a)<+)LBKk?(8<@94nOg9oAIT zZk%2ipHMJi)5NJ87B89I*HoIawtv++@73AjE{1tN{@ltcyaBzP=7OwKl8iL!nPxEZ zq|Nl5@8RjZ$Uaw&(`(`6wn+=LwGw+oL=}WOx@0q&=PtK1V{#J#I)X9D+uensgH_f8 z$l)yTh%5%Ct(PFo7@+d}2Ll6>tEY=&h{y4@Q{Tp43J^KozhbsD<04_B${;~`hYksU z*QCVG76C1(<_`-G1bLrk|MhuxPI7tqW&Xo+!@tk@Y<>T3^}E9iCOIceY>d1nB#Y$M zrEO%2$bDQkOG>%(;>5(u6T(=168@ z$X~my@ttKxvW;5+M@1%!8du+PXOB;!M|ut%X)t9v>~x32M@A?@MbfYJa{tsNFGc=J zb*nz$eU*04uO$C&)U>NM{)%S1%jR!ix8Hlw$L|MD_-SodjNi`hXsY@n@Lc}0?N9U# zHz-=Q`nx`TdjH#Fk^kF*HfCyfOnzb?FVvQH)%(e^sUo-5PMXo`(Md!VG zd{s5Nc(p!1?pkr{X6VUp0jK6^?cJ@sOlwatuZQ>j^?Ov*F$~bg| zcdkryk^4rDSwGay^54Gs?pSU9*0;+uo?N+?_t#_9)YW^}{`}c8-K0cq{nwfTAIaR` zM;A=dJ^Jk5Beul_`==g%P_)3l<++gkz}*sy7m2h;|q~5?ri1S ip7Uo}VQ=_f_TxVaZoFLl`V26YF?hQAxvXY2)R zcQsu#J3aekkQW6b*$1Of@<50QfrK9ujSou#_yZCnA%Jd_@Do5Z5?LV#vh8>3r~6Dz z&t7H&;pLv>t2+1GbI!f@JNMr1m-_zv+_gK0FC7*_?3|q`AH;h%aPfto6k_P^cW(SG z8lP^>9QK6R^LN6~{GM0e5u)%_XJOG_tbEh3+(y={x=VJp(`dn4h~nN(%e0Q#erm~H zcA8V^H-3FGopP#E=^OJEz0#VtSDcySZTryixdrR^QERfA-dh?jb_^tF*uI(SH0n*y z=uD-9yaseKYw1+r;vbz#@1p@zi!SaDTyj_asdSnK>PfV&Myoea(~GzRi(1EQ zX}PSf4GOASy_nW&yBMJ*ZBuXsP7T{eBYgdLg3 zUd-r)h1{5-j~k=;tM#0r>nus=naELbt4?iwQ;u;1(K(&xV4lewI6+m@H$PAo?#uF2 zEu-eP8>U}!8s@UCwVKOCt#4$Wy?td&yLGpXY1yUGqBh9PqJq6<%r-sWY+CkgxdcPj zajHhXkjtB+leLU)gTJv{VJuS^x2hSdX4mw>m^Cqxs|6%hDm;8s>auIC(s_weSGSp( zg>OoIecQoZXV#ey&3?N>3`nzSs2Rt@4qazE7yCE3%?{Z&Z?wi8btkx$T4vj`>C8=~ z<3rcG#(K9D#RO;9FfF>zN){anyIRyXSo_oJMMh!HxfU?Y&%(DJV{OH5p6x(|sRFO9 zlXz~;Uo+eGzGdu*4|dwayc+|P0~9s-1*kh4OA%T(Rw&-Hn=4wRi_O4pz|72`3b~@T z$<`k^^k1Z^)>i}nkujY_%h5B6<^9Rn!;)CupN#E?-q3XX(nvI1!px%R{td5&L-?y; zm&Wt?(R@)$nv)UpPRFhf#LS(1UC-cBbpbXdm zgMaO{LGtzmk(E|U5(iul{R}|%9pv;Z5O?BJPH`b{7qAlu=%OVY1708lgd9i$ z2fV`(_6(gm6Hn}QSZR#U1KSbQK9F5BufGEH@o6l?7(PQitHeILBCqF@KUj)e zp=(k5X}s52-{+wJjM}tk;zZQGA0Se;BWI9=s28Gf-ih~lwm8dPC=0;l0ImI=sJ#d8 z7lC^L+6sXnTd*~$aDN8A5}LR54C3N-oJ#^4Er^Sg@Zp1F#|;VnbfPV&=CRB}q4OAv z2j%N=1Mf$GAFwt}fT|LdKfxiH>v*khJ9di7^d)qh0AhIp z3SF91^&1-t;f z0?=;|T_OJgo&pxc}6@fBeT~IEmT)bLIQKapac& E1J3nvJ^%m! literal 0 HcmV?d00001 diff --git a/apps/system/style/task-manager/images/tm_pb_tab_press@1.5x.png b/apps/system/style/task-manager/images/tm_pb_tab_press@1.5x.png new file mode 100755 index 0000000000000000000000000000000000000000..148b50b5102245cd6c0edadf81091faf33263216 GIT binary patch literal 19422 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 Z^5-XBek6Tn+TD!MIe%ftPv+ge{(r_Yi(dc$ literal 0 HcmV?d00001 diff --git a/apps/system/style/task-manager/images/tm_pb_tab_press@2x.png b/apps/system/style/task-manager/images/tm_pb_tab_press@2x.png new file mode 100755 index 0000000000000000000000000000000000000000..5217011372d44a23f3391403b41176eeb3557f05 GIT binary patch literal 24721 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 literal 0 HcmV?d00001 diff --git a/apps/system/style/window.css b/apps/system/style/window.css index bce6f8b0a3f2..ceb950a133e7 100644 --- a/apps/system/style/window.css +++ b/apps/system/style/window.css @@ -79,6 +79,17 @@ animation: openAppFromCardView 300ms forwards cubic-bezier(.35,.9,.6,1); } +@keyframes openAppFromNewCard { + 0% { transform: scale(0.5) translateY(200%); } + 20% { transform: scale(0.5) translateY(200%); } + 65% { transform: scale(0.5) translateY(0); } + 100% { transform: scale(1) translateY(0); } +} + +.appWindow.from-new-card { + animation: openAppFromNewCard 1200ms forwards ease-in-out; +} + /* Opacity increases start up time then we don't use it anymore for opening */ @keyframes openApp { 0% { transform: scale(0.2);} diff --git a/apps/system/test/marionette/lib/system.js b/apps/system/test/marionette/lib/system.js index d7cb4ae6d9ed..d433240dc22b 100644 --- a/apps/system/test/marionette/lib/system.js +++ b/apps/system/test/marionette/lib/system.js @@ -520,7 +520,7 @@ System.prototype = { }, waitForBrowser: function waitForBrowser(url) { - this.client.helper.waitForElement( + return this.client.helper.waitForElement( 'div[transition-state="opened"] iframe[src="' + url + '"]'); }, diff --git a/apps/system/test/marionette/lib/task_manager.js b/apps/system/test/marionette/lib/task_manager.js index 7c9ddb4f1cb3..7c83ba2293b1 100644 --- a/apps/system/test/marionette/lib/task_manager.js +++ b/apps/system/test/marionette/lib/task_manager.js @@ -7,7 +7,10 @@ TaskManager.prototype = { selectors: { - element: '#cards-view', + element: '#task-manager', + scrollElement: '#cards-view', + newSheetButton: '#task-manager-new-sheet-button', + newPrivateSheetButton: '#task-manager-new-private-sheet-button', cards: '#cards-view li', screenshot: '.screenshotView', icon: '.appIcon' @@ -20,6 +23,15 @@ return this.client.findElements(this.selectors.cards); }, + get newSheetButton() { + return this.client.helper.waitForElement(this.selectors.newSheetButton); + }, + + get newPrivateSheetButton() { + return this.client.helper.waitForElement( + this.selectors.newPrivateSheetButton); + }, + show: function() { this.client.switchToFrame(); this.client.executeAsyncScript(function() { @@ -43,6 +55,7 @@ win.dispatchEvent(new CustomEvent('home')); }); }, + getIconForCard: function(idx) { var card = this.cards[idx]; this.client.waitFor(card.displayed.bind(card)); diff --git a/apps/system/test/marionette/task_manager_test.js b/apps/system/test/marionette/task_manager_test.js index c167f5da2148..eb9ac949b397 100644 --- a/apps/system/test/marionette/task_manager_test.js +++ b/apps/system/test/marionette/task_manager_test.js @@ -179,6 +179,26 @@ marionette('Task Manager', function() { }); }); + suite('new sheet buttons', function() { + setup(function() { + taskManager.show(); + }); + + test('should open a new sheet', function() { + taskManager.newSheetButton.tap(); + system.waitForBrowser('app://search.gaiamobile.org/newtab.html'); + }); + + test('should open a new private sheet', function() { + taskManager.newPrivateSheetButton.tap(); + var appWindow = system.waitForBrowser( + 'app://search.gaiamobile.org/newtab.html?private=1'); + client.waitFor(function() { + return appWindow.getAttribute('mozprivatebrowsing') === 'true'; + }); + }); + }); + test('swiping then taping should switch app', function() { taskManager.show(); diff --git a/apps/system/test/unit/card_test.js b/apps/system/test/unit/card_test.js index 1c3c22c1bc71..0528dd70f828 100644 --- a/apps/system/test/unit/card_test.js +++ b/apps/system/test/unit/card_test.js @@ -76,6 +76,17 @@ suite('system/Card', function() { }); suite('render > ', function() { + + test('does nothing when stayInvisible is set', function() { + var card = new Card(makeApp({ + name: 'anyapp', + title: 'sometitle' + }), { stayInvisible: true }); + + assert.equal(card.element.style.display, 'none'); + assert.equal(card.title, undefined); + }); + test('adds browser class for browser windows', function(){ var card = new Card(makeApp({ name: 'browserapp', diff --git a/apps/system/test/unit/task_manager_test.js b/apps/system/test/unit/task_manager_test.js index 3035ec912368..d79519eab7f0 100644 --- a/apps/system/test/unit/task_manager_test.js +++ b/apps/system/test/unit/task_manager_test.js @@ -1,5 +1,5 @@ /* global MockStackManager, MockService, - TaskManager, AppWindow, WheelEvent, + TaskManager, AppWindow, WheelEvent, MockAppWindow, HomescreenWindow, MockSettingsListener, MocksHelper, MockL10n */ 'use strict'; @@ -22,6 +22,9 @@ var mocksForTaskManager = new MocksHelper([ 'SettingsListener' ]).init(); + +var TICK_SHOW_HIDE_MS = 2000; + suite('system/TaskManager >', function() { suiteSetup(mocksForTaskManager.suiteSetup); @@ -57,9 +60,16 @@ suite('system/TaskManager >', function() { document.body.innerHTML = `

    -
    -
      - +
      +
      +
        + +
        +
        + + +
        `; @@ -111,7 +121,7 @@ suite('system/TaskManager >', function() { assert.isTrue( MockService.request.calledWith('unregisterHierarchy', tm)); }).then(done, done); - clock.tick(1000); + clock.tick(TICK_SHOW_HIDE_MS); }); function spyEvent(obj, name) { @@ -148,12 +158,12 @@ suite('system/TaskManager >', function() { tm = new TaskManager(); tm.start().then(() => tm.show()).then(() => { done(); }, done); - clock.tick(1000); + clock.tick(TICK_SHOW_HIDE_MS); }); teardown(function(done) { tm.hide().then(() => tm.stop()).then(() => { done(); }, done); - clock.tick(1000); + clock.tick(TICK_SHOW_HIDE_MS); }); test('Proper state after show and hide', (done) => { @@ -162,7 +172,7 @@ suite('system/TaskManager >', function() { assert.isTrue( document.querySelector('#screen').classList.contains('cards-view')); assert.isTrue(isActivated); - assert.ok(document.querySelector('#cards-view.empty.active')); + assert.ok(document.querySelector('#task-manager.empty.active')); // We're pretending to be in fullscreen mode. assert.isTrue(document.mozCancelFullScreen.calledOnce); @@ -176,7 +186,7 @@ suite('system/TaskManager >', function() { assert.equal(document.querySelectorAll('.card').length, 0); done(); }); - clock.tick(1000); + clock.tick(TICK_SHOW_HIDE_MS); }); test('Should emit "cardviewclosed" after hiding', (done) => { @@ -184,7 +194,7 @@ suite('system/TaskManager >', function() { tm.hide().then(() => { assert.isTrue(spyCardViewClosed.called); }).then(done, done); - clock.tick(1000); + clock.tick(TICK_SHOW_HIDE_MS); }); test('Should emit "cardviewbeforeshow" and "cardviewshown"', (done) => { @@ -193,12 +203,12 @@ suite('system/TaskManager >', function() { tm.hide().then(() => { var show = tm.show(); assert.isTrue(spyCardViewBeforeShow.called); - clock.tick(1000); + clock.tick(TICK_SHOW_HIDE_MS); return show; }).then(() => { assert.isTrue(spyCardViewShown.called); }).then(done, done); - clock.tick(1000); + clock.tick(TICK_SHOW_HIDE_MS); }); test('Should not show if already showing', (done) => { @@ -269,7 +279,7 @@ suite('system/TaskManager >', function() { test('click when empty takes you home', () => { sinon.spy(tm, 'hide'); tm.element.dispatchEvent(new CustomEvent('click')); - clock.tick(1000); + clock.tick(TICK_SHOW_HIDE_MS); assert.ok(tm.hide.calledOnce); }); }); @@ -322,17 +332,17 @@ suite('system/TaskManager >', function() { tm = new TaskManager(); tm.start().then(() => tm.show()).then(() => { done(); }, done); - clock.tick(1000); + clock.tick(TICK_SHOW_HIDE_MS); }); teardown(function(done) { tm.hide().then(() => tm.stop()).then(done, done); - clock.tick(1000); + clock.tick(TICK_SHOW_HIDE_MS); }); test('Proper state', () => { assert.isTrue(tm.isShown()); - assert.ok(document.querySelector('#cards-view.active:not(.empty)')); + assert.ok(document.querySelector('#task-manager.active:not(.empty)')); assert.equal( MockStackManager.getCurrent(), @@ -417,21 +427,21 @@ suite('system/TaskManager >', function() { assert.ok(spy.called); }); }).then(done, done); - clock.tick(1000); + clock.tick(TICK_SHOW_HIDE_MS); }); test('close and reopen', function(done) { tm.hide().then(() => { MockStackManager.mCurrent = 0; var promise = tm.show(); - clock.tick(1000); + clock.tick(TICK_SHOW_HIDE_MS); return promise; }).then(() => { assert.equal( MockStackManager.getCurrent(), tm.currentCard.app); }).then(done, done); - clock.tick(1000); + clock.tick(TICK_SHOW_HIDE_MS); }); suite('settings > ', function() { @@ -461,12 +471,12 @@ suite('system/TaskManager >', function() { tm.disableScreenshots = true; MockStackManager.mCurrent = 0; var promise = tm.show(); - clock.tick(1000); + clock.tick(TICK_SHOW_HIDE_MS); return promise; }).then(() => { assert.ok(tm.currentCard.element.classList.contains('appIconPreview')); }).then(done, done); - clock.tick(1000); + clock.tick(TICK_SHOW_HIDE_MS); }); test('disableScreenshots = false', function(done) { @@ -474,12 +484,12 @@ suite('system/TaskManager >', function() { tm.disableScreenshots = false; MockStackManager.mCurrent = 0; var promise = tm.show(); - clock.tick(1000); + clock.tick(TICK_SHOW_HIDE_MS); return promise; }).then(() => { assert.ok(!tm.currentCard.element.classList.contains('appIconPreview')); }).then(done, done); - clock.tick(1000); + clock.tick(TICK_SHOW_HIDE_MS); }); function getExpectedCardPlacement(element, position) { @@ -511,7 +521,7 @@ suite('system/TaskManager >', function() { MockStackManager.mStack.length = 3; MockStackManager.mCurrent = 2; var promise = tm.show(); - clock.tick(1000); + clock.tick(TICK_SHOW_HIDE_MS); return promise; }).then(() => { // current card should be the last in the stack @@ -519,13 +529,13 @@ suite('system/TaskManager >', function() { MockStackManager.getCurrent(), tm.currentCard.app); var promise = tm.hide(outOfStackApp); - clock.tick(1000); + clock.tick(TICK_SHOW_HIDE_MS); return promise; }).then(() => { assert.ok(outOfStackApp.open.calledOnce); assert.ok(MockStackManager.position, -1); }).then(done, done); - clock.tick(1000); + clock.tick(TICK_SHOW_HIDE_MS); }); suite('center apps', function() { @@ -535,7 +545,7 @@ suite('system/TaskManager >', function() { MockStackManager.mCurrent = MockStackManager.mStack.length - 1; return tm.show(); }).then(() => { done(); }, done); - clock.tick(1000); + clock.tick(TICK_SHOW_HIDE_MS); }); test('initial centering', function() { @@ -570,7 +580,7 @@ suite('system/TaskManager >', function() { assert.equal(tm.currentCard.element.style.transform, `translate(${expectedLeft}px, calc(50% + 0px))`); }).then(done, done); - clock.tick(1000); + clock.tick(TICK_SHOW_HIDE_MS); }); }); @@ -578,7 +588,7 @@ suite('system/TaskManager >', function() { MockStackManager.mStack.length = 0; sinon.spy(tm, 'hide'); window.dispatchEvent(new CustomEvent('appterminated')); - clock.tick(1000); + clock.tick(TICK_SHOW_HIDE_MS); assert.ok(tm.hide.calledOnce); done(); }); @@ -586,7 +596,7 @@ suite('system/TaskManager >', function() { test('kill cards and update in response to StackManager', function() { MockStackManager.mStack.length = 2; window.dispatchEvent(new CustomEvent('appterminated')); - clock.tick(1000); + clock.tick(TICK_SHOW_HIDE_MS); assert.equal( document.querySelectorAll('.card').length, MockStackManager.mStack.length @@ -643,7 +653,7 @@ suite('system/TaskManager >', function() { done(); }; card.element.querySelector('.close-button').click(); - clock.tick(1000); + clock.tick(TICK_SHOW_HIDE_MS); }); // These events should trigger an exit: @@ -655,7 +665,7 @@ suite('system/TaskManager >', function() { test(`${NAME} should trigger hide`, function() { this.sinon.spy(tm, 'hide'); window.dispatchEvent(new CustomEvent(NAME)); - clock.tick(1000); + clock.tick(TICK_SHOW_HIDE_MS); assert.ok(tm.hide.called); }); }); @@ -691,12 +701,12 @@ suite('system/TaskManager >', function() { assert.isFalse(tm.handleEvent.called, 'handleEvent not called'); }).then(function() { done(); }, done); tm.element.dispatchEvent(new CustomEvent('touchstart')); - clock.tick(1000); + clock.tick(TICK_SHOW_HIDE_MS); }); suite('card-will-drag / scroll axis lock', function() { test('handling card-will-drag (prevent scrolling)', function() { - tm.element.style.overflowX = 'scroll'; + tm.scrollElement.style.overflowX = 'scroll'; // In this test, they did not scroll, so the card-will-drag event // should be passed through as-is, and we should set 'overflow: hidden'. var willDragEvent = new CustomEvent('card-will-drag', { @@ -707,14 +717,14 @@ suite('system/TaskManager >', function() { tm.currentCard.element.dispatchEvent(willDragEvent); assert.isFalse(willDragEvent.defaultPrevented); - assert.equal(tm.element.style.overflowX, 'hidden'); + assert.equal(tm.scrollElement.style.overflowX, 'hidden'); }); test('handling card-will-drag (cancel event)', function() { - tm.element.style.overflowX = 'scroll'; + tm.scrollElement.style.overflowX = 'scroll'; // In this test, they scrolled AFTER the first touch, meaning we // should prevent the card-will-drag event. - tm.element.dispatchEvent(new CustomEvent('scroll')); + tm.scrollElement.dispatchEvent(new CustomEvent('scroll')); var willDragEvent = new CustomEvent('card-will-drag', { detail: { firstTouchTimestamp: Date.now() - 1000 }, @@ -724,23 +734,23 @@ suite('system/TaskManager >', function() { tm.currentCard.element.dispatchEvent(willDragEvent); assert.isTrue(willDragEvent.defaultPrevented); - assert.equal(tm.element.style.overflowX, 'scroll'); + assert.equal(tm.scrollElement.style.overflowX, 'scroll'); }); }); test('card-dropped, not killed', function() { - tm.element.style.overflowX = 'hidden'; + tm.scrollElement.style.overflowX = 'hidden'; var dropEvent = new CustomEvent('card-dropped', { detail: { willKill: false }, bubbles: true }); tm.currentCard.element.dispatchEvent(dropEvent); - assert.equal(tm.element.style.overflowX, 'scroll'); + assert.equal(tm.scrollElement.style.overflowX, 'scroll'); }); test('card-dropped, kill the app', function(done) { - tm.element.style.overflowX = 'hidden'; + tm.scrollElement.style.overflowX = 'hidden'; var dropEvent = new CustomEvent('card-dropped', { detail: { willKill: true }, bubbles: true @@ -751,8 +761,8 @@ suite('system/TaskManager >', function() { }; tm.currentCard.element.dispatchEvent(dropEvent); - assert.equal(tm.element.style.overflowX, 'scroll'); - clock.tick(1000); + assert.equal(tm.scrollElement.style.overflowX, 'scroll'); + clock.tick(TICK_SHOW_HIDE_MS); }); test('browser-only filtering', function(done) { @@ -778,6 +788,60 @@ suite('system/TaskManager >', function() { }).then(done, done); }); + suite('new sheet buttons >', function() { + var NEW_SHEET_SELECTOR = '#task-manager-new-sheet-button'; + var NEW_PRIVATE_SHEET_SELECTOR = '#task-manager-new-private-sheet-button'; + setup(function() { + window.AppWindow = function(config) { + var app = new MockAppWindow(config); + MockStackManager.mStack.push(app); + return app; + }; + window.BrowserConfigHelper = function(config) { + return config; + }; + }); + + function testSheetButton(name, { selector, isPrivate }) { + test(name, function() { + this.sinon.spy(tm, 'hide'); + var originalStack = tm.stack.slice(); + + tm.element.querySelector(selector).click(); + + var newStack = tm.stack.slice(); + var lastApp = newStack[newStack.length - 1]; + + assert.equal(newStack.length, originalStack.length + 1); + assert.equal(lastApp.isPrivate ? true : false, isPrivate); + assert.equal(tm.currentCard.app, lastApp); + assert.ok(tm.hide.calledWith(lastApp, 'from-new-card')); + }); + } + + testSheetButton('open new sheet', { + selector: NEW_SHEET_SELECTOR, + isPrivate: false + }); + + testSheetButton('open new private sheet', { + selector: NEW_PRIVATE_SHEET_SELECTOR, + isPrivate: true + }); + + test('only opens one sheet, even if called multiple times', function() { + this.sinon.spy(tm, 'hide'); + + var button = tm.element.querySelector(NEW_SHEET_SELECTOR); + button.click(); + button.click(); + button.click(); + + assert.ok(tm.hide.calledOnce); + }); + + }); + }); }); diff --git a/shared/js/event_safety.js b/shared/js/event_safety.js index c280863b126e..59fa367b3e34 100644 --- a/shared/js/event_safety.js +++ b/shared/js/event_safety.js @@ -38,8 +38,10 @@ function eventSafety(obj, event, callback, timeout) { return new Promise((resolve) => { var finishTimeout; function done(e) { - // transitionend events bubble by default, so we filter them by element. - if (e && e.type === 'transitionend' && e.target !== obj) { + // Both "transitionend" and "animationend" events bubble by default; + // ignore them here if they're not targeted on the element we care about. + if (e && e.target !== obj && + (e.type === 'transitionend' || e.type === 'animationend')) { return; }