author | Tim Taubert <tim.taubert@gmx.de> |
Sat, 19 Feb 2011 19:22:49 +0100 | |
changeset 62866 | 4ae4b97f392bafd1fb8e7d6651f1c00da902db4d |
parent 62865 | 97625b507157c24adef2f2ac31692c0a2a5bb718 |
child 62867 | 61c6c43d9630a4496d65e03d0f83f24c231b70c5 |
push id | unknown |
push user | unknown |
push date | unknown |
reviewers | ian, sdwilsh, blocking2.0 |
bugs | 604699 |
milestone | 2.0b12pre |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/browser/base/content/tabview/storage.js +++ b/browser/base/content/tabview/storage.js @@ -42,30 +42,48 @@ // ########## // Class: Storage // Singleton for permanent storage of TabView data. let Storage = { GROUP_DATA_IDENTIFIER: "tabview-group", GROUPS_DATA_IDENTIFIER: "tabview-groups", TAB_DATA_IDENTIFIER: "tabview-tab", UI_DATA_IDENTIFIER: "tabview-ui", + CACHE_CLIENT_IDENTIFIER: "tabview-cache", + CACHE_PREFIX: "moz-panorama:", // ---------- // Function: init // Sets up the object. init: function Storage_init() { this._sessionStore = Cc["@mozilla.org/browser/sessionstore;1"]. getService(Ci.nsISessionStore); + + // Create stream-based cache session for tabview + let cacheService = + Cc["@mozilla.org/network/cache-service;1"]. + getService(Ci.nsICacheService); + this._cacheSession = cacheService.createSession( + this.CACHE_CLIENT_IDENTIFIER, Ci.nsICache.STORE_ON_DISK, true); + this.StringInputStream = Components.Constructor( + "@mozilla.org/io/string-input-stream;1", "nsIStringInputStream", + "setData"); + this.StorageStream = Components.Constructor( + "@mozilla.org/storagestream;1", "nsIStorageStream", + "init"); }, // ---------- // Function: uninit uninit: function Storage_uninit () { this._sessionStore = null; + this._cacheSession = null; + this.StringInputStream = null; + this.StorageStream = null; }, // ---------- // Function: wipe // Cleans out all the stored data, leaving empty objects. wipe: function Storage_wipe() { try { var self = this; @@ -85,46 +103,205 @@ let Storage = { this._sessionStore.setWindowValue(gWindow, this.GROUP_DATA_IDENTIFIER, JSON.stringify({})); } catch (e) { Utils.log("Error in wipe: "+e); } }, // ---------- + // Function: _openCacheEntry + // Opens a cache entry for the given <url> and requests access <access>. + // Calls <successCallback>(entry) when the entry was successfully opened with + // requested access rights. Otherwise calls <errorCallback>(). + _openCacheEntry: function Storage__openCacheEntry(url, access, successCallback, errorCallback) { + let onCacheEntryAvailable = function (entry, accessGranted, status) { + if (entry && access == accessGranted && Components.isSuccessCode(status)) { + successCallback(entry); + } else { + entry && entry.close(); + errorCallback(); + } + } + + let key = this.CACHE_PREFIX + url; + + // switch to synchronous mode if parent window is about to close + if (UI.isDOMWindowClosing) { + let entry = this._cacheSession.openCacheEntry(key, access, true); + let status = Components.results.NS_OK; + onCacheEntryAvailable(entry, entry.accessGranted, status); + } else { + let listener = new CacheListener(onCacheEntryAvailable); + this._cacheSession.asyncOpenCacheEntry(key, access, listener); + } + }, + + // ---------- + // Function: saveThumbnail + // Saves the <imageData> to the cache using the given <url> as key. + // Calls <callback>(status) when finished (passing true or false indicating + // whether the operation succeeded). + saveThumbnail: function Storage_saveThumbnail(url, imageData, callback) { + Utils.assert(url, "url"); + Utils.assert(imageData, "imageData"); + Utils.assert(typeof callback == "function", "callback arg must be a function"); + + let self = this; + let StringInputStream = this.StringInputStream; + + let onCacheEntryAvailable = function (entry) { + let outputStream = entry.openOutputStream(0); + + let cleanup = function () { + outputStream.close(); + entry.close(); + } + + // switch to synchronous mode if parent window is about to close + if (UI.isDOMWindowClosing) { + outputStream.write(imageData, imageData.length); + cleanup(); + callback(true); + return; + } + + // asynchronous mode + let inputStream = new StringInputStream(imageData, imageData.length); + gNetUtil.asyncCopy(inputStream, outputStream, function (result) { + cleanup(); + inputStream.close(); + callback(Components.isSuccessCode(result)); + }); + } + + let onCacheEntryUnavailable = function () { + callback(false); + } + + this._openCacheEntry(url, Ci.nsICache.ACCESS_WRITE, + onCacheEntryAvailable, onCacheEntryUnavailable); + }, + + // ---------- + // Function: loadThumbnail + // Asynchrously loads image data from the cache using the given <url> as key. + // Calls <callback>(status, data) when finished, passing true or false + // (indicating whether the operation succeeded) and the retrieved image data. + loadThumbnail: function Storage_loadThumbnail(url, callback) { + Utils.assert(url, "url"); + Utils.assert(typeof callback == "function", "callback arg must be a function"); + + let self = this; + + let onCacheEntryAvailable = function (entry) { + let imageChunks = []; + let nativeInputStream = entry.openInputStream(0); + + const CHUNK_SIZE = 0x10000; // 65k + const PR_UINT32_MAX = 0xFFFFFFFF; + let storageStream = new self.StorageStream(CHUNK_SIZE, PR_UINT32_MAX, null); + let storageOutStream = storageStream.getOutputStream(0); + + let cleanup = function () { + nativeInputStream.close(); + storageStream.close(); + storageOutStream.close(); + entry.close(); + } + + gNetUtil.asyncCopy(nativeInputStream, storageOutStream, function (result) { + // cancel if parent window has already been closed + if (typeof UI == "undefined") { + cleanup(); + return; + } + + let imageData = null; + let isSuccess = Components.isSuccessCode(result); + + if (isSuccess) { + let storageInStream = storageStream.newInputStream(0); + imageData = gNetUtil.readInputStreamToString(storageInStream, + storageInStream.available()); + storageInStream.close(); + } + + cleanup(); + callback(isSuccess, imageData); + }); + } + + let onCacheEntryUnavailable = function () { + callback(false); + } + + this._openCacheEntry(url, Ci.nsICache.ACCESS_READ, + onCacheEntryAvailable, onCacheEntryUnavailable); + }, + + // ---------- // Function: saveTab // Saves the data for a single tab. saveTab: function Storage_saveTab(tab, data) { Utils.assert(tab, "tab"); + if (data != null) { + let imageData = data.imageData; + // Remove imageData from payload + delete data.imageData; + if (imageData != null) { + this.saveThumbnail(data.url, imageData, function (status) { + if (status) { + // Notify subscribers + tab._tabViewTabItem._sendToSubscribers("savedCachedImageData"); + } else { + Utils.log("Error while saving thumbnail: " + e); + } + }); + } + } + this._sessionStore.setTabValue(tab, this.TAB_DATA_IDENTIFIER, JSON.stringify(data)); - - // Notify subscribers - if (data && data.imageData && tab._tabViewTabItem) - tab._tabViewTabItem._sendToSubscribers("savedImageData"); }, // ---------- // Function: getTabData - // Returns the data object associated with a single tab. - getTabData: function Storage_getTabData(tab) { + // Load tab data from session store and return it. Asynchrously loads the tab's + // thumbnail from the cache and calls <callback>(imageData) when done. + getTabData: function Storage_getTabData(tab, callback) { Utils.assert(tab, "tab"); + Utils.assert(typeof callback == "function", "callback arg must be a function"); - var existingData = null; + let existingData = null; + try { - var tabData = this._sessionStore.getTabValue(tab, this.TAB_DATA_IDENTIFIER); + let tabData = this._sessionStore.getTabValue(tab, this.TAB_DATA_IDENTIFIER); if (tabData != "") { existingData = JSON.parse(tabData); } } catch (e) { - // getWindowValue will fail if the property doesn't exist + // getTabValue will fail if the property doesn't exist. Utils.log(e); } + if (existingData) { + this.loadThumbnail(existingData.url, function (status, imageData) { + if (status) { + callback(imageData); + + // Notify subscribers + tab._tabViewTabItem._sendToSubscribers("loadedCachedImageData"); + } else { + Utils.log("Error while loading thumbnail: " + e); + } + }); + } + return existingData; }, // ---------- // Function: saveGroupItem // Saves the data for a single groupItem, associated with a specific window. saveGroupItem: function Storage_saveGroupItem(win, data) { var id = data.id; @@ -219,8 +396,25 @@ let Storage = { existingData = JSON.parse(data); } catch (e) { Utils.log("Error in readData: "+e); } return existingData; } }; + +// ########## +// Class: CacheListener +// Generic CacheListener for feeding to asynchronous cache calls. +// Calls <callback>(entry, access, status) when the requested cache entry +// is available. +function CacheListener(callback) { + Utils.assert(typeof callback == "function", "callback arg must be a function"); + this.callback = callback; +}; + +CacheListener.prototype = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsICacheListener]), + onCacheEntryAvailable: function (entry, access, status) { + this.callback(entry, access, status); + } +};
--- a/browser/base/content/tabview/tabitems.js +++ b/browser/base/content/tabview/tabitems.js @@ -323,56 +323,65 @@ TabItem.prototype = Utils.extend(new Ite // ---------- // Function: _reconnect // Load the reciever's persistent data from storage. If there is none, // treats it as a new tab. _reconnect: function TabItem__reconnect() { Utils.assertThrow(!this._reconnected, "shouldn't already be reconnected"); Utils.assertThrow(this.tab, "should have a xul:tab"); - let tabData = Storage.getTabData(this.tab); + let tabData = null; + let self = this; + let imageDataCb = function(imageData) { + Utils.assertThrow(tabData, "tabData"); + + tabData.imageData = imageData; + + let currentUrl = self.tab.linkedBrowser.currentURI.spec; + // If we have a cached image, then show it if the loaded URL matches + // what the cache is from, OR the loaded URL is blank, which means + // that the page hasn't loaded yet. + if (tabData.imageData && + (tabData.url == currentUrl || currentUrl == 'about:blank')) { + self.showCachedData(tabData); + } + }; + // getTabData returns the sessionstore contents, but passes + // a callback to run when the thumbnail is finally loaded. + tabData = Storage.getTabData(this.tab, imageDataCb); if (tabData && TabItems.storageSanity(tabData)) { - if (this.parent) - this.parent.remove(this, {immediately: true}); + if (self.parent) + self.parent.remove(self, {immediately: true}); - this.setBounds(tabData.bounds, true); + self.setBounds(tabData.bounds, true); if (Utils.isPoint(tabData.userSize)) - this.userSize = new Point(tabData.userSize); + self.userSize = new Point(tabData.userSize); if (tabData.groupID) { var groupItem = GroupItems.groupItem(tabData.groupID); if (groupItem) { - groupItem.add(this, {immediately: true}); + groupItem.add(self, {immediately: true}); - // if it matches the selected tab or no active tab and the browser + // if it matches the selected tab or no active tab and the browser // tab is hidden, the active group item would be set. - if (this.tab == gBrowser.selectedTab || - (!GroupItems.getActiveGroupItem() && !this.tab.hidden)) - GroupItems.setActiveGroupItem(this.parent); + if (self.tab == gBrowser.selectedTab || + (!GroupItems.getActiveGroupItem() && !self.tab.hidden)) + GroupItems.setActiveGroupItem(self.parent); } } - - let currentUrl = this.tab.linkedBrowser.currentURI.spec; - - // If we have a cached image, then show it if the loaded URL matches - // what the cache is from, OR the loaded URL is blank, which means - // that the page hasn't loaded yet. - if (tabData.imageData && (tabData.url == currentUrl || - currentUrl == 'about:blank')) - this.showCachedData(tabData); } else { // create tab by double click is handled in UI_init(). if (!TabItems.creatingNewOrphanTab) - GroupItems.newTab(this, {immediately: true}); + GroupItems.newTab(self, {immediately: true}); } - this._reconnected = true; - this.save(); - this._sendToSubscribers("reconnected"); + self._reconnected = true; + self.save(); + self._sendToSubscribers("reconnected"); }, // ---------- // Function: setHidden // Hide/unhide this item setHidden: function TabItem_setHidden(val) { if (val) this.addClass("tabHidden");
--- a/browser/base/content/tabview/tabview.js +++ b/browser/base/content/tabview/tabview.js @@ -18,16 +18,22 @@ XPCOMUtils.defineLazyGetter(this, "gPref return Services.prefs.getBranch("browser.panorama."); }); XPCOMUtils.defineLazyGetter(this, "gPrivateBrowsing", function() { return Cc["@mozilla.org/privatebrowsing;1"]. getService(Ci.nsIPrivateBrowsingService); }); +XPCOMUtils.defineLazyGetter(this, "gNetUtil", function() { + var obj = {}; + Cu.import("resource://gre/modules/NetUtil.jsm", obj); + return obj.NetUtil; +}); + var gWindow = window.parent; var gBrowser = gWindow.gBrowser; var gTabView = gWindow.TabView; var gTabViewDeck = gWindow.document.getElementById("tab-view-deck"); var gTabViewFrame = gWindow.document.getElementById("tab-view"); # NB: Certain files need to evaluate before others
--- a/browser/base/content/tabview/ui.js +++ b/browser/base/content/tabview/ui.js @@ -122,16 +122,21 @@ let UI = { transitionMode: "", wasInTabView: false }, // Variable: _storageBusyCount // Used to keep track of how many calls to storageBusy vs storageReady. _storageBusyCount: 0, + // Variable: isDOMWindowClosing + // Tells wether we already received the "domwindowclosed" event and the parent + // windows is about to close. + isDOMWindowClosing: false, + // ---------- // Function: init // Must be called after the object is created. init: function UI_init() { try { let self = this; // initialize the direction of the page @@ -236,16 +241,17 @@ let UI = { iQ(window).resize(function() { self._resize(); }); // ___ setup observer to save canvas images function domWinClosedObserver(subject, topic, data) { if (topic == "domwindowclosed" && subject == gWindow) { + self.isDOMWindowClosing = true; if (self.isTabViewVisible()) GroupItems.removeHiddenGroups(); TabItems.saveAll(true); self._save(); } } Services.obs.addObserver( domWinClosedObserver, "domwindowclosed", false);
--- a/browser/base/content/test/tabview/Makefile.in +++ b/browser/base/content/test/tabview/Makefile.in @@ -73,16 +73,17 @@ include $(topsrcdir)/config/rules.mk browser_tabview_bug597399.js \ browser_tabview_bug597980.js \ browser_tabview_bug598600.js \ browser_tabview_bug599626.js \ browser_tabview_bug600645.js \ browser_tabview_bug600812.js \ browser_tabview_bug602432.js \ browser_tabview_bug604098.js \ + browser_tabview_bug604699.js \ browser_tabview_bug606657.js \ browser_tabview_bug606905.js \ browser_tabview_bug608037.js \ browser_tabview_bug608184.js \ browser_tabview_bug608158.js \ browser_tabview_bug608405.js \ browser_tabview_bug610242.js \ browser_tabview_bug612470.js \
--- a/browser/base/content/test/tabview/browser_tabview_bug597248.js +++ b/browser/base/content/test/tabview/browser_tabview_bug597248.js @@ -1,110 +1,59 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is tabview bug 597248 test. - * - * The Initial Developer of the Original Code is - * Mozilla Foundation. - * Portions created by the Initial Developer are Copyright (C) 2010 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Raymond Lee <raymond@appcoast.com> - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ -let newWin; -let restoredWin; let newTabOne; let newTabTwo; let restoredNewTabOneLoaded = false; let restoredNewTabTwoLoaded = false; let frameInitialized = false; function test() { waitForExplicitFinish(); - - // open a new window - newWin = openDialog(getBrowserURL(), "_blank", "chrome,all,dialog=no"); - newWin.addEventListener("load", function(event) { - newWin.removeEventListener("load", arguments.callee, false); - setupOne(); - }, false); + newWindowWithTabView(setupOne); } -function setupOne() { - let loadedCount = 0; - let allLoaded = function() { - if (++loadedCount == 2) { - newWin.addEventListener("tabviewshown", setupTwo, false); - newWin.TabView.toggle(); - } - } - - newTabOne = newWin.gBrowser.tabs[0]; - newTabTwo = newWin.gBrowser.addTab(); - load(newTabOne, "http://mochi.test:8888/browser/browser/base/content/test/tabview/search1.html", allLoaded); - load(newTabTwo, "http://mochi.test:8888/browser/browser/base/content/test/tabview/dummy_page.html", allLoaded); +function setupOne(win) { + win.TabView.firstUseExperienced = true; + + win.gBrowser.addTab("http://mochi.test:8888/browser/browser/base/content/test/tabview/search1.html"); + win.gBrowser.addTab("http://mochi.test:8888/browser/browser/base/content/test/tabview/dummy_page.html"); + + afterAllTabsLoaded(function () setupTwo(win), win); } -function setupTwo() { - newWin.removeEventListener("tabviewshown", setupTwo, false); - - let contentWindow = newWin.document.getElementById("tab-view").contentWindow; +function setupTwo(win) { + let contentWindow = win.TabView.getContentWindow(); let tabItems = contentWindow.TabItems.getItems(); - is(tabItems.length, 2, "There should be 2 tab items before closing"); + is(tabItems.length, 3, "There should be 3 tab items before closing"); let numTabsToSave = tabItems.length; // force all canvases to update, and hook in imageData save detection tabItems.forEach(function(tabItem) { contentWindow.TabItems._update(tabItem.tab); - tabItem.addSubscriber(tabItem, "savedImageData", function(item) { - item.removeSubscriber(item, "savedImageData"); + tabItem.addSubscriber(tabItem, "savedCachedImageData", function(item) { + item.removeSubscriber(item, "savedCachedImageData"); --numTabsToSave; }); }); // after the window is closed, restore it. let xulWindowDestory = function() { Services.obs.removeObserver( xulWindowDestory, "xul-window-destroyed", false); - newWin = null; // "xul-window-destroyed" is just fired just before a XUL window is // destroyed so restore window and test it after a delay executeSoon(function() { - restoredWin = undoCloseWindow(); - restoredWin.addEventListener("load", function(event) { - restoredWin.removeEventListener("load", arguments.callee, false); + let restoredWin = undoCloseWindow(); + restoredWin.addEventListener("load", function onLoad(event) { + restoredWin.removeEventListener("load", onLoad, false); // ensure that closed tabs have been saved is(numTabsToSave, 0, "All tabs were saved when window was closed."); // execute code when the frame is initialized. let onTabViewFrameInitialized = function() { restoredWin.removeEventListener( "tabviewframeinitialized", onTabViewFrameInitialized, false); @@ -117,17 +66,17 @@ function setupTwo() { restoredContentWindow.TabItems._pauseUpdateForTest = true; */ restoredWin.close(); finish(); } restoredWin.addEventListener( "tabviewframeinitialized", onTabViewFrameInitialized, false); - is(restoredWin.gBrowser.tabs.length, 2, "The total number of tabs is 2"); + is(restoredWin.gBrowser.tabs.length, 3, "The total number of tabs is 3"); /* // bug 615954 happens too often so we disable this until we have a fix restoredWin.addEventListener("tabviewshown", onTabViewShown, false); // setup tab variables and listen to the load progress. newTabOne = restoredWin.gBrowser.tabs[0]; newTabTwo = restoredWin.gBrowser.tabs[1]; @@ -135,20 +84,20 @@ function setupTwo() { */ }, false); }); }; Services.obs.addObserver( xulWindowDestory, "xul-window-destroyed", false); - newWin.close(); + win.close(); } -let gTabsProgressListener = { +/*let gTabsProgressListener = { onStateChange: function(browser, webProgress, request, stateFlags, status) { // ensure about:blank doesn't trigger the code if ((stateFlags & Ci.nsIWebProgressListener.STATE_STOP) && (stateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) && browser.currentURI.spec != "about:blank") { if (newTabOne.linkedBrowser == browser) restoredNewTabOneLoaded = true; else if (newTabTwo.linkedBrowser == browser) @@ -226,18 +175,9 @@ function updateAndCheck() { ok(!tabItem.isShowingCachedData(), "Tab item is not showing cached data anymore. " + tabItem.tab.linkedBrowser.currentURI.spec); }); // clean up and finish restoredWin.close(); finish(); -} - -function load(tab, url, callback) { - tab.linkedBrowser.addEventListener("load", function (event) { - tab.linkedBrowser.removeEventListener("load", arguments.callee, true); - callback(); - }, true); - tab.linkedBrowser.loadURI(url); -} - +}*/
new file mode 100644 --- /dev/null +++ b/browser/base/content/test/tabview/browser_tabview_bug604699.js @@ -0,0 +1,81 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function test() { + let url = "http://non.existant/url"; + let cw; + + let finishTest = function () { + is(1, gBrowser.tabs.length, "there is one tab, only"); + ok(!TabView.isVisible(), "tabview is not visible"); + finish(); + } + + waitForExplicitFinish(); + + let testErroneousLoading = function () { + cw.Storage.loadThumbnail(url, function (status, data) { + ok(!status, "thumbnail entry failed to load"); + is(null, data, "no thumbnail data received"); + next(); + }); + } + + let testAsynchronousSaving = function () { + let saved = false; + let data = "thumbnail-data-asynchronous"; + + cw.Storage.saveThumbnail(url, data, function (status) { + ok(status, "thumbnail entry was saved"); + ok(saved, "thumbnail was saved asynchronously"); + + cw.Storage.loadThumbnail(url, function (status, imageData) { + ok(status, "thumbnail entry was loaded"); + is(imageData, data, "valid thumbnail data received"); + next(); + }); + }); + + saved = true; + } + + let testSynchronousSaving = function () { + let saved = false; + let data = "thumbnail-data-synchronous"; + + cw.UI.isDOMWindowClosing = true; + registerCleanupFunction(function () cw.UI.isDOMWindowClosing = false); + + cw.Storage.saveThumbnail(url, data, function (status) { + ok(status, "thumbnail entry was saved"); + ok(!saved, "thumbnail was saved synchronously"); + + cw.Storage.loadThumbnail(url, function (status, imageData) { + ok(status, "thumbnail entry was loaded"); + is(imageData, data, "valid thumbnail data received"); + + cw.UI.isDOMWindowClosing = false; + next(); + }); + }); + + saved = true; + } + + let tests = [testErroneousLoading, testAsynchronousSaving, testSynchronousSaving]; + + let next = function () { + let test = tests.shift(); + if (test) + test(); + else + hideTabView(finishTest); + } + + showTabView(function () { + registerCleanupFunction(function () TabView.hide()); + cw = TabView.getContentWindow(); + + next(); + }); +}
--- a/browser/base/content/test/tabview/browser_tabview_bug624265.js +++ b/browser/base/content/test/tabview/browser_tabview_bug624265.js @@ -97,17 +97,18 @@ function test() { } // ---------- // [623792] duplicating tab via middle click on reload button let testDuplicateTab = function () { gBrowser.loadOneTab('http://mochi.test:8888/', {inBackground: true}); afterAllTabsLoaded(function () { - duplicateTabIn(gBrowser.selectedTab, 'current'); + // Valid choices for 'where' are window|tabshifted|tab + duplicateTabIn(gBrowser.selectedTab, 'tab'); afterAllTabsLoaded(function () { assertNumberOfVisibleTabs(3); assertOneSingleGroupItem(); next(); }); }); }
--- a/browser/base/content/test/tabview/browser_tabview_bug627288.js +++ b/browser/base/content/test/tabview/browser_tabview_bug627288.js @@ -14,20 +14,28 @@ function test() { gBrowser.removeTab(tab); cw.TabItems.pauseReconnecting(); tab = gBrowser.loadOneTab('http://mochi.test:8888/', {inBackground: true}); cw.Storage.saveTab(tab, data); whenTabAttrModified(tab, function () { tabItem = tab._tabViewTabItem; + + // Hook into loadedCachedImageData since loading cached thumbnails + // is asynchronous. + tabItem.addSubscriber(tabItem, "loadedCachedImageData", function(item) { + item.removeSubscriber(item, "loadedCachedImageData"); + + ok(tabItem.isShowingCachedData(), 'tabItem shows cached data'); + + testChangeUrlAfterReconnect(); + }); + cw.TabItems.resumeReconnecting(); - ok(tabItem.isShowingCachedData(), 'tabItem shows cached data'); - - testChangeUrlAfterReconnect(); }); }); } let testChangeUrlAfterReconnect = function () { tab.linkedBrowser.loadURI('http://mochi.test:8888/browser/'); whenTabAttrModified(tab, function () {