mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-15 06:15:43 +00:00
Bug 1312406 - part 1: remove tab groups migration code, r=Dolske
MozReview-Commit-ID: BDuX607J51w --HG-- extra : rebase_source : a22ba1e0c19680484abe7bd7344ea72a29d26755
This commit is contained in:
parent
824ce92dc8
commit
dc8d50ed3e
@ -1,46 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
|
||||
Cu.import("resource:///modules/TabGroupsMigrator.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
|
||||
"resource://gre/modules/PlacesUtils.jsm");
|
||||
|
||||
const SUPPORT_URL = "https://support.mozilla.org/kb/tab-groups-removal";
|
||||
|
||||
function createLink() {
|
||||
let link = document.getElementById("sumolink");
|
||||
link.href = SUPPORT_URL;
|
||||
link.target = "_blank";
|
||||
}
|
||||
|
||||
let loadPromise = new Promise(resolve => {
|
||||
let loadHandler = e => {
|
||||
window.removeEventListener("DOMContentLoaded", loadHandler);
|
||||
createLink();
|
||||
resolve();
|
||||
};
|
||||
window.addEventListener("DOMContentLoaded", loadHandler, false);
|
||||
});
|
||||
|
||||
let tabGroupsBookmarkItemId;
|
||||
// If the session wasn't restored this run/session, this might be null.
|
||||
// Then we shouldn't show the button:
|
||||
if (TabGroupsMigrator.bookmarkedGroupsPromise) {
|
||||
let bookmarkPromise = TabGroupsMigrator.bookmarkedGroupsPromise.then(bm => {
|
||||
return PlacesUtils.promiseItemId(bm.guid);
|
||||
}).then(itemId => { tabGroupsBookmarkItemId = itemId });
|
||||
|
||||
Promise.all([bookmarkPromise, loadPromise]).then(function() {
|
||||
document.getElementById("show-migrated-bookmarks-button").style.removeProperty("display");
|
||||
});
|
||||
}
|
||||
|
||||
function showMigratedGroups() {
|
||||
let browserWin = getBrowserWindow();
|
||||
browserWin.PlacesCommandHook.showPlacesOrganizer(["BookmarksMenu", tabGroupsBookmarkItemId]);
|
||||
}
|
||||
|
@ -1,82 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
<!DOCTYPE html [
|
||||
<!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
|
||||
%htmlDTD;
|
||||
<!ENTITY % netErrorDTD SYSTEM "chrome://global/locale/netError.dtd">
|
||||
%netErrorDTD;
|
||||
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
|
||||
%globalDTD;
|
||||
<!ENTITY % restorepageDTD SYSTEM "chrome://browser/locale/aboutSessionRestore.dtd">
|
||||
%restorepageDTD;
|
||||
]>
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
<head>
|
||||
<title>&tabgroupsmigration.tabtitle;</title>
|
||||
<link rel="stylesheet" href="chrome://global/skin/in-content/info-pages.css" type="text/css" media="all"/>
|
||||
<link rel="stylesheet" href="chrome://browser/skin/aboutSessionRestore.css" type="text/css" media="all"/>
|
||||
<link rel="icon" type="image/png" href="chrome://global/skin/icons/warning-16.png"/>
|
||||
|
||||
<script type="application/javascript;version=1.8" src="chrome://browser/content/aboutSessionRestore.js"/>
|
||||
<script type="application/javascript;version=1.8" src="chrome://browser/content/aboutTabGroupsMigration.js"/>
|
||||
</head>
|
||||
|
||||
<body dir="&locale.dir;">
|
||||
|
||||
<div class="container restore-chosen">
|
||||
|
||||
<div class="title">
|
||||
<h1 class="title-text">&tabgroupsmigration.pagetitle2;</h1>
|
||||
</div>
|
||||
<div class="description">
|
||||
<p id="mainDescription">&tabgroupsmigration.description2;<br /><a id="sumolink">&tabgroupsmigration.learnaboutaddons;</a></p>
|
||||
<button id="show-migrated-bookmarks-button" style="display:none" onclick="showMigratedGroups()">&tabgroupsmigration.bookmarkbutton;</button>
|
||||
|
||||
<p>&tabgroupsmigration.restoredescription;</p>
|
||||
</div>
|
||||
<div class="tree-container" available="true">
|
||||
<xul:tree id="tabList" seltype="single" hidecolumnpicker="true"
|
||||
onclick="onListClick(event);" onkeydown="onListKeyDown(event);"
|
||||
_window_label="&restorepage.windowLabel;">
|
||||
<xul:treecols>
|
||||
<xul:treecol cycler="true" id="restore" type="checkbox" label="&restorepage.restoreHeader;"/>
|
||||
<xul:splitter class="tree-splitter"/>
|
||||
<xul:treecol primary="true" id="title" label="&restorepage.listHeader;" flex="1"/>
|
||||
</xul:treecols>
|
||||
<xul:treechildren flex="1"/>
|
||||
</xul:tree>
|
||||
</div>
|
||||
<div class="button-container">
|
||||
#ifdef XP_UNIX
|
||||
<xul:button id="errorCancel"
|
||||
label="&restorepage.closeButton;"
|
||||
accesskey="&restorepage.close.access;"
|
||||
oncommand="startNewSession();"/>
|
||||
<xul:button class="primary"
|
||||
id="errorTryAgain"
|
||||
label="&restorepage.tryagainButton;"
|
||||
accesskey="&restorepage.restore.access;"
|
||||
oncommand="restoreSession();"/>
|
||||
#else
|
||||
<xul:button class="primary"
|
||||
id="errorTryAgain"
|
||||
label="&restorepage.tryagainButton;"
|
||||
accesskey="&restorepage.restore.access;"
|
||||
oncommand="restoreSession();"/>
|
||||
<xul:button id="errorCancel"
|
||||
label="&restorepage.closeButton;"
|
||||
accesskey="&restorepage.close.access;"
|
||||
oncommand="startNewSession();"/>
|
||||
#endif
|
||||
</div>
|
||||
<!-- holds the session data for when the tab is closed -->
|
||||
<input type="text" id="sessionData" style="display: none;"/>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -68,8 +68,6 @@ browser.jar:
|
||||
content/browser/aboutTabCrashed.css (content/aboutTabCrashed.css)
|
||||
content/browser/aboutTabCrashed.js (content/aboutTabCrashed.js)
|
||||
content/browser/aboutTabCrashed.xhtml (content/aboutTabCrashed.xhtml)
|
||||
* content/browser/aboutTabGroupsMigration.xhtml (content/aboutTabGroupsMigration.xhtml)
|
||||
content/browser/aboutTabGroupsMigration.js (content/aboutTabGroupsMigration.js)
|
||||
* content/browser/browser.css (content/browser.css)
|
||||
content/browser/browser.js (content/browser.js)
|
||||
* content/browser/browser.xul (content/browser.xul)
|
||||
|
@ -62,7 +62,6 @@ XPCOMUtils.defineLazyServiceGetter(this, "AlertsService", "@mozilla.org/alerts-s
|
||||
["ShellService", "resource:///modules/ShellService.jsm"],
|
||||
["SimpleServiceDiscovery", "resource://gre/modules/SimpleServiceDiscovery.jsm"],
|
||||
["TabCrashHandler", "resource:///modules/ContentCrashHandlers.jsm"],
|
||||
["TabGroupsMigrator", "resource:///modules/TabGroupsMigrator.jsm"],
|
||||
["Task", "resource://gre/modules/Task.jsm"],
|
||||
["UITour", "resource:///modules/UITour.jsm"],
|
||||
["URLBarZoom", "resource:///modules/URLBarZoom.jsm"],
|
||||
@ -655,9 +654,6 @@ BrowserGlue.prototype = {
|
||||
try {
|
||||
currentUIVersion = Services.prefs.getIntPref("browser.migration.version");
|
||||
} catch (ex) {}
|
||||
if (currentUIVersion < 35) {
|
||||
this._maybeMigrateTabGroups();
|
||||
}
|
||||
Services.ww.openWindow(null, "chrome://browser/content/safeMode.xul",
|
||||
"_blank", "chrome,centerscreen,modal,resizable=no", null);
|
||||
}
|
||||
@ -1257,14 +1253,6 @@ BrowserGlue.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
_maybeMigrateTabGroups() {
|
||||
let migrationObserver = (stateAsSupportsString, topic) => {
|
||||
Services.obs.removeObserver(migrationObserver, "sessionstore-state-read");
|
||||
TabGroupsMigrator.migrate(stateAsSupportsString);
|
||||
};
|
||||
Services.obs.addObserver(migrationObserver, "sessionstore-state-read", false);
|
||||
},
|
||||
|
||||
_onQuitRequest: function BG__onQuitRequest(aCancelQuit, aQuitType) {
|
||||
// If user has already dismissed quit request, then do nothing
|
||||
if ((aCancelQuit instanceof Ci.nsISupportsPRBool) && aCancelQuit.data)
|
||||
@ -2069,10 +2057,7 @@ BrowserGlue.prototype = {
|
||||
this._notifyNotificationsUpgrade().catch(Cu.reportError);
|
||||
}
|
||||
|
||||
// Only do this outside of safe mode, because in safe mode we do this earlier.
|
||||
if (currentUIVersion < 35 && !Services.appinfo.inSafeMode) {
|
||||
this._maybeMigrateTabGroups();
|
||||
}
|
||||
// version 35 migrated tab groups data.
|
||||
|
||||
if (currentUIVersion < 36) {
|
||||
xulStore.removeValue("chrome://passwordmgr/content/passwordManager.xul",
|
||||
|
@ -70,7 +70,7 @@ function initTreeView() {
|
||||
gTreeData = [];
|
||||
gStateObject.windows.forEach(function(aWinData, aIx) {
|
||||
var winState = {
|
||||
label: aWinData.tabGroupsMigrationTitle || winLabel.replace("%S", (aIx + 1)),
|
||||
label: winLabel.replace("%S", (aIx + 1)),
|
||||
open: true,
|
||||
checked: true,
|
||||
ix: aIx
|
||||
|
@ -49,12 +49,3 @@ their language.
|
||||
|
||||
<!ENTITY welcomeback2.link.pageInfo2 "learn more about what you can do.">
|
||||
|
||||
<!-- LOCALIZATION NOTE: The following 'tabgroupsmigration' strings are for
|
||||
the tab groups (panorama) migration page, not about:sessionrestore -->
|
||||
<!ENTITY tabgroupsmigration.tabtitle "Migrate your other Tab Groups">
|
||||
<!ENTITY tabgroupsmigration.pagetitle2 "We’ve removed Tab Groups, but saved your tabs">
|
||||
<!ENTITY tabgroupsmigration.learnaboutaddons "Learn about Tab Groups replacement add-ons.">
|
||||
<!ENTITY tabgroupsmigration.description2 "&brandShortName; has bookmarked all your groups so you haven’t lost anything.">
|
||||
<!ENTITY tabgroupsmigration.bookmarkbutton "Show Bookmarked Tab Groups">
|
||||
<!ENTITY tabgroupsmigration.restoredescription "You can also choose to restore some or all background groups into windows now:">
|
||||
|
||||
|
@ -710,11 +710,6 @@ certErrorDetailsHSTS.label = HTTP Strict Transport Security: %S
|
||||
certErrorDetailsKeyPinning.label = HTTP Public Key Pinning: %S
|
||||
certErrorDetailsCertChain.label = Certificate chain:
|
||||
|
||||
# LOCALIZATION NOTE (tabgroups.migration.anonGroup):
|
||||
# %S is the group number/ID
|
||||
tabgroups.migration.anonGroup = Group %S
|
||||
tabgroups.migration.tabGroupBookmarkFolderName = Bookmarked Tab Groups
|
||||
|
||||
# LOCALIZATION NOTE (pendingCrashReports2.label): Semi-colon list of plural forms
|
||||
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
|
||||
# #1 is the number of pending crash reports
|
||||
|
@ -1,297 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["TabGroupsMigrator"];
|
||||
|
||||
const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", "resource://gre/modules/PlacesUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown", "resource://gre/modules/AsyncShutdown.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", function() {
|
||||
return Services.strings.createBundle('chrome://browser/locale/browser.properties');
|
||||
});
|
||||
|
||||
const RECOVERY_URL = "chrome://browser/content/aboutTabGroupsMigration.xhtml";
|
||||
|
||||
|
||||
this.TabGroupsMigrator = {
|
||||
bookmarkedGroupsPromise: null,
|
||||
|
||||
/**
|
||||
* If this state contains tab groups, migrate the user's data. This means:
|
||||
* - make a backup of the user's data.
|
||||
* - create bookmarks of all the user's tab groups in a single folder
|
||||
* - append a tab to the active window that lets the user restore background
|
||||
* groups.
|
||||
* - remove all the tabs hidden through tab groups from the state data.
|
||||
*/
|
||||
migrate(stateAsSupportsString) {
|
||||
stateAsSupportsString.QueryInterface(Ci.nsISupportsString);
|
||||
let stateStr = stateAsSupportsString.data;
|
||||
// If this is the very first startup of this profile, this is going to be empty:
|
||||
if (!stateStr) {
|
||||
return;
|
||||
}
|
||||
let state;
|
||||
try {
|
||||
state = JSON.parse(stateStr);
|
||||
} catch (ex) {
|
||||
Cu.reportError("Failed to parse sessionstore state JSON to migrate tab groups: " + ex);
|
||||
return; // can't recover from invalid JSON
|
||||
}
|
||||
|
||||
let groupData = this._gatherGroupData(state);
|
||||
|
||||
// This strips out the hidden tab groups and puts them in a different object.
|
||||
// It also removes all tabview metadata.
|
||||
// We always do this, and always reassign the new state back into the
|
||||
// nsISupportsString for use by sessionstore, in order to tidy up the state
|
||||
// object.
|
||||
let hiddenTabState = this._removeHiddenTabGroupsFromState(state, groupData);
|
||||
|
||||
// However, we will only create a backup file, bookmarks and a new tab to
|
||||
// restore hidden tabs if tabs were actually removed from |state| by
|
||||
// _removeHiddenTabGroupsFromState.
|
||||
if (hiddenTabState.windows.length) {
|
||||
// We create the backup with the original string, from before all our
|
||||
// changes:
|
||||
this._createBackup(stateStr);
|
||||
|
||||
this._createBackgroundTabGroupRestorationPage(state, hiddenTabState);
|
||||
|
||||
// Bookmark creation is async. We need to return synchronously,
|
||||
// so we purposefully don't wait for this to be finished here. We do
|
||||
// store the promise it creates and use that in the session restore page
|
||||
// to be able to link to the bookmarks folder...
|
||||
let bookmarksFinishedPromise = this._bookmarkAllGroupsFromState(groupData);
|
||||
// ... and we make sure we finish before shutting down:
|
||||
AsyncShutdown.profileBeforeChange.addBlocker(
|
||||
"Tab groups migration bookmarks",
|
||||
bookmarksFinishedPromise
|
||||
);
|
||||
}
|
||||
|
||||
// We always write this back to ensure that any spurious tab groups data is
|
||||
// removed:
|
||||
stateAsSupportsString.data = JSON.stringify(state);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a Map from window state objects to per-window group data.
|
||||
* Specifically, the values in the Map are themselves Maps from group IDs to
|
||||
* JS Objects which have these properties:
|
||||
* - tabGroupsMigrationTitle: the title of the group (or empty string)
|
||||
* - tabs: an array of the tabs objects in this group.
|
||||
*/
|
||||
_gatherGroupData(state) {
|
||||
let allGroupData = new Map();
|
||||
let globalAnonGroupID = 0;
|
||||
for (let win of state.windows) {
|
||||
if (win.extData && win.extData["tabview-group"]) {
|
||||
let groupInfo = {};
|
||||
try {
|
||||
groupInfo = JSON.parse(win.extData["tabview-group"]);
|
||||
} catch (ex) {
|
||||
// This is annoying, but we'll try to deal with this.
|
||||
}
|
||||
|
||||
let windowGroupData = new Map();
|
||||
let activeGroupID = null;
|
||||
let tabsWithoutGroup = [];
|
||||
for (let tab of win.tabs) {
|
||||
let group;
|
||||
// Get a string group ID:
|
||||
try {
|
||||
let tabViewData = tab.extData && tab.extData["tabview-tab"] &&
|
||||
JSON.parse(tab.extData["tabview-tab"]);
|
||||
if (tabViewData && ("groupID" in tabViewData)) {
|
||||
group = tabViewData.groupID + "";
|
||||
}
|
||||
} catch (ex) {
|
||||
// Ignore errors reading group info, treat as active group
|
||||
}
|
||||
if (!group) {
|
||||
// We didn't find group info. If we already have an active group,
|
||||
// pretend this is part of that group:
|
||||
if (activeGroupID) {
|
||||
group = activeGroupID;
|
||||
} else {
|
||||
if (!tabsWithoutGroup) {
|
||||
Cu.reportError("ERROR: the list of tabs without groups was " +
|
||||
"nulled out, but there's no active group ID? " +
|
||||
"This should never happen!");
|
||||
tabsWithoutGroup = [];
|
||||
}
|
||||
// Otherwise, add to the list of tabs with no group and move to
|
||||
// the next tab immediately. We'll add all these tabs at the
|
||||
// beginning of the active group as soon as we find a tab in it,
|
||||
// so as to preserve their order.
|
||||
tabsWithoutGroup.push(tab);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let groupData = windowGroupData.get(group);
|
||||
if (!groupData) {
|
||||
let title = (groupInfo[group] && groupInfo[group].title) || "";
|
||||
groupData = {
|
||||
tabs: [],
|
||||
tabGroupsMigrationTitle: title,
|
||||
};
|
||||
if (!title) {
|
||||
groupData.anonGroupID = ++globalAnonGroupID;
|
||||
groupData.tabGroupsMigrationTitle =
|
||||
gBrowserBundle.formatStringFromName("tabgroups.migration.anonGroup",
|
||||
[groupData.anonGroupID], 1);
|
||||
}
|
||||
// If this is the active group, set the active group ID and add
|
||||
// all the already-known tabs (that didn't list a group ID), if any.
|
||||
if (!activeGroupID && !tab.hidden) {
|
||||
activeGroupID = group;
|
||||
groupData.tabs = tabsWithoutGroup;
|
||||
tabsWithoutGroup = null;
|
||||
}
|
||||
windowGroupData.set(group, groupData);
|
||||
}
|
||||
groupData.tabs.push(tab);
|
||||
}
|
||||
|
||||
// If we found tabs but no active group, assume there's just 1 group:
|
||||
if (tabsWithoutGroup && tabsWithoutGroup.length) {
|
||||
windowGroupData.set("active group", {
|
||||
tabs: tabsWithoutGroup,
|
||||
anonGroupID: ++globalAnonGroupID,
|
||||
});
|
||||
}
|
||||
|
||||
allGroupData.set(win, windowGroupData);
|
||||
}
|
||||
}
|
||||
return allGroupData;
|
||||
},
|
||||
|
||||
_createBackup(stateStr) {
|
||||
let dest = Services.dirsvc.get("ProfD", Ci.nsIFile);
|
||||
dest.append("tabgroups-session-backup.json");
|
||||
let promise = OS.File.writeAtomic(dest.path, stateStr, {encoding: "utf-8"});
|
||||
AsyncShutdown.webWorkersShutdown.addBlocker("TabGroupsMigrator", promise);
|
||||
return promise;
|
||||
},
|
||||
|
||||
_groupSorter(a, b) {
|
||||
if (!a.anonGroupID) {
|
||||
return -1;
|
||||
}
|
||||
if (!b.anonGroupID) {
|
||||
return 1;
|
||||
}
|
||||
return a.anonGroupID - b.anonGroupID;
|
||||
},
|
||||
|
||||
_bookmarkAllGroupsFromState: Task.async(function*(groupData) {
|
||||
// First create a folder in which to put all these bookmarks:
|
||||
this.bookmarkedGroupsPromise = PlacesUtils.bookmarks.insert({
|
||||
parentGuid: PlacesUtils.bookmarks.menuGuid,
|
||||
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
||||
index: 0,
|
||||
title: gBrowserBundle.GetStringFromName("tabgroups.migration.tabGroupBookmarkFolderName"),
|
||||
}).catch(Cu.reportError);
|
||||
let tabgroupsFolder = yield this.bookmarkedGroupsPromise;
|
||||
|
||||
for (let [, windowGroupMap] of groupData) {
|
||||
let windowGroups = [... windowGroupMap.values()].sort(this._groupSorter);
|
||||
for (let group of windowGroups) {
|
||||
let groupFolder = yield PlacesUtils.bookmarks.insert({
|
||||
parentGuid: tabgroupsFolder.guid,
|
||||
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
||||
title: group.tabGroupsMigrationTitle
|
||||
}).catch(Cu.reportError);
|
||||
|
||||
for (let tab of group.tabs) {
|
||||
let entry = tab.entries[tab.index - 1];
|
||||
yield PlacesUtils.bookmarks.insert({
|
||||
parentGuid: groupFolder.guid,
|
||||
title: tab.title || entry.title,
|
||||
url: entry.url,
|
||||
}).catch(Cu.reportError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
_removeHiddenTabGroupsFromState(state, groups) {
|
||||
let stateToReturn = {windows: []};
|
||||
for (let win of state.windows) {
|
||||
let groupInfoForWindow = groups.get(win);
|
||||
let hiddenGroupIDs = new Set();
|
||||
for (let i = win.tabs.length - 1; i >= 0; i--) {
|
||||
let tab = win.tabs[i];
|
||||
// Determine whether the tab is grouped:
|
||||
let tabGroupInfo = null;
|
||||
try {
|
||||
tabGroupInfo = tab.extData && tab.extData["tabview-tab"] &&
|
||||
JSON.parse(tab.extData["tabview-tab"]);
|
||||
} catch (ex) {}
|
||||
|
||||
// Then remove this data.
|
||||
if (tab.extData) {
|
||||
delete tab.extData["tabview-tab"];
|
||||
if (Object.keys(tab.extData).length == 0) {
|
||||
delete tab.extData;
|
||||
}
|
||||
}
|
||||
|
||||
// If the tab was grouped and hidden, remove it:
|
||||
if (tabGroupInfo && tab.hidden) {
|
||||
hiddenGroupIDs.add(tabGroupInfo.groupID);
|
||||
win.tabs.splice(i, 1);
|
||||
// Make sure we unhide it, or it won't get restored correctly
|
||||
tab.hidden = false;
|
||||
}
|
||||
}
|
||||
|
||||
// We then convert any hidden groups into windows for the state object
|
||||
// we show in about:tabgroupsdata
|
||||
if (groupInfoForWindow) {
|
||||
let windowsToReturn = [];
|
||||
for (let groupID of hiddenGroupIDs) {
|
||||
let group = groupInfoForWindow.get("" + groupID);
|
||||
if (group) {
|
||||
windowsToReturn.push(group);
|
||||
}
|
||||
}
|
||||
windowsToReturn.sort(this._groupSorter);
|
||||
stateToReturn.windows = stateToReturn.windows.concat(windowsToReturn);
|
||||
}
|
||||
|
||||
// Finally we remove tab groups data from the window:
|
||||
if (win.extData) {
|
||||
delete win.extData["tabview-group"];
|
||||
delete win.extData["tabview-groups"];
|
||||
delete win.extData["tabview-ui"];
|
||||
delete win.extData["tabview-visibility"];
|
||||
if (Object.keys(win.extData).length == 0) {
|
||||
delete win.extData;
|
||||
}
|
||||
}
|
||||
}
|
||||
return stateToReturn;
|
||||
},
|
||||
|
||||
_createBackgroundTabGroupRestorationPage(state, backgroundData) {
|
||||
let win = state.windows[(state.selectedWindow || 1) - 1];
|
||||
let formdata = {id: {sessionData: JSON.stringify(backgroundData)}, url: RECOVERY_URL};
|
||||
let newTab = { entries: [{url: RECOVERY_URL}], formdata, index: 1 };
|
||||
// Add tab and mark it as selected:
|
||||
win.selected = win.tabs.push(newTab);
|
||||
},
|
||||
};
|
||||
|
@ -44,7 +44,6 @@ EXTRA_JS_MODULES += [
|
||||
'SitePermissions.jsm',
|
||||
'Social.jsm',
|
||||
'SocialService.jsm',
|
||||
'TabGroupsMigrator.jsm',
|
||||
'TransientPrefs.jsm',
|
||||
'URLBarZoom.jsm',
|
||||
'webrtcUI.jsm',
|
||||
|
@ -1,406 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
|
||||
Components.utils.import("resource://gre/modules/osfile.jsm");
|
||||
Components.utils.import("resource:///modules/TabGroupsMigrator.jsm");
|
||||
|
||||
var gProfD = do_get_profile();
|
||||
|
||||
const TEST_STATES = {
|
||||
TWO_GROUPS: {
|
||||
selectedWindow: 1,
|
||||
windows: [
|
||||
{
|
||||
tabs: [
|
||||
{
|
||||
entries: [{
|
||||
url: "about:robots",
|
||||
title: "Robots 1",
|
||||
}],
|
||||
index: 1,
|
||||
hidden: false,
|
||||
extData: {
|
||||
"tabview-tab": "{\"groupID\":2,\"active\":true}",
|
||||
},
|
||||
},
|
||||
{
|
||||
entries: [{
|
||||
url: "about:robots",
|
||||
title: "Robots 2",
|
||||
}],
|
||||
index: 1,
|
||||
hidden: false,
|
||||
extData: {
|
||||
"tabview-tab": "{\"groupID\":2}",
|
||||
},
|
||||
},
|
||||
{
|
||||
entries: [{
|
||||
url: "about:robots",
|
||||
title: "Robots 3",
|
||||
}],
|
||||
index: 1,
|
||||
hidden: true,
|
||||
extData: {
|
||||
"tabview-tab": "{\"groupID\":13}",
|
||||
},
|
||||
}
|
||||
],
|
||||
extData: {
|
||||
"tabview-group": "{\"2\":{},\"13\":{\"title\":\"Foopy\"}}",
|
||||
"tabview-groups": "{\"nextID\":20,\"activeGroupId\":2,\"totalNumber\":2}",
|
||||
"tabview-visibility": "false"
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
NAMED_ACTIVE_GROUP: {
|
||||
selectedWindow: 1,
|
||||
windows: [
|
||||
{
|
||||
tabs: [
|
||||
{
|
||||
entries: [{
|
||||
url: "about:mozilla",
|
||||
title: "Mozilla 1",
|
||||
}],
|
||||
index: 1,
|
||||
hidden: false,
|
||||
extData: {
|
||||
"tabview-tab": "{\"groupID\":2,\"active\":true}",
|
||||
},
|
||||
},
|
||||
],
|
||||
extData: {
|
||||
"tabview-group": "{\"2\":{\"title\":\"Foopy\"}}",
|
||||
"tabview-groups": "{\"nextID\":20,\"activeGroupId\":2,\"totalNumber\":1}",
|
||||
"tabview-visibility": "false"
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
TAB_WITHOUT_GROUP: {
|
||||
selectedWindow: 1,
|
||||
windows: [
|
||||
{
|
||||
tabs: [
|
||||
{
|
||||
entries: [{
|
||||
url: "about:robots",
|
||||
title: "Robots 1",
|
||||
}],
|
||||
index: 1,
|
||||
hidden: false,
|
||||
},
|
||||
{
|
||||
entries: [{
|
||||
url: "about:robots",
|
||||
title: "Robots 2",
|
||||
}],
|
||||
index: 1,
|
||||
hidden: false,
|
||||
extData: {
|
||||
"tabview-tab": "{\"groupID\":2}",
|
||||
},
|
||||
},
|
||||
{
|
||||
entries: [{
|
||||
url: "about:robots",
|
||||
title: "Robots 3",
|
||||
}],
|
||||
index: 1,
|
||||
hidden: true,
|
||||
extData: {
|
||||
"tabview-tab": "{\"groupID\":1}",
|
||||
},
|
||||
}
|
||||
],
|
||||
extData: {
|
||||
"tabview-group": "{\"2\":{}, \"1\": {}}",
|
||||
"tabview-groups": "{\"nextID\":20,\"activeGroupId\":2,\"totalNumber\":2}",
|
||||
"tabview-visibility": "false"
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
ONLY_UNGROUPED_TABS: {
|
||||
selectedWindow: 1,
|
||||
windows: [
|
||||
{
|
||||
tabs: [
|
||||
{
|
||||
entries: [{
|
||||
url: "about:robots",
|
||||
title: "Robots 1",
|
||||
}],
|
||||
index: 1,
|
||||
hidden: false,
|
||||
},
|
||||
{
|
||||
entries: [{
|
||||
url: "about:robots",
|
||||
title: "Robots 2",
|
||||
}],
|
||||
index: 1,
|
||||
hidden: false,
|
||||
extData: {
|
||||
"tabview-tab": "{}",
|
||||
},
|
||||
},
|
||||
{
|
||||
entries: [{
|
||||
url: "about:robots",
|
||||
title: "Robots 3",
|
||||
}],
|
||||
index: 1,
|
||||
hidden: true,
|
||||
extData: {
|
||||
},
|
||||
}
|
||||
],
|
||||
extData: {
|
||||
"tabview-group": "{\"2\":{}}",
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
SORTING_NAMING_RESTORE_PAGE: {
|
||||
windows: [
|
||||
{
|
||||
tabs: [
|
||||
{
|
||||
entries: [{
|
||||
url: "about:robots",
|
||||
title: "Robots 1",
|
||||
}],
|
||||
index: 1,
|
||||
hidden: false,
|
||||
extData: {
|
||||
"tabview-tab": "{\"groupID\":2,\"active\":true}",
|
||||
},
|
||||
},
|
||||
{
|
||||
entries: [{
|
||||
url: "about:robots",
|
||||
title: "Robots 2",
|
||||
}],
|
||||
index: 1,
|
||||
hidden: false,
|
||||
extData: {
|
||||
"tabview-tab": "{\"groupID\":2}",
|
||||
},
|
||||
},
|
||||
{
|
||||
entries: [{
|
||||
url: "about:robots",
|
||||
title: "Robots 3",
|
||||
}],
|
||||
index: 1,
|
||||
hidden: true,
|
||||
extData: {
|
||||
"tabview-tab": "{\"groupID\":13}",
|
||||
},
|
||||
},
|
||||
{
|
||||
entries: [{
|
||||
url: "about:robots",
|
||||
title: "Robots 4",
|
||||
}],
|
||||
index: 1,
|
||||
hidden: true,
|
||||
extData: {
|
||||
"tabview-tab": "{\"groupID\":15}",
|
||||
},
|
||||
},
|
||||
{
|
||||
entries: [{
|
||||
url: "about:robots",
|
||||
title: "Robots 5",
|
||||
}],
|
||||
index: 1,
|
||||
hidden: true,
|
||||
extData: {
|
||||
"tabview-tab": "{\"groupID\":16}",
|
||||
},
|
||||
},
|
||||
{
|
||||
entries: [{
|
||||
url: "about:robots",
|
||||
title: "Robots 6",
|
||||
}],
|
||||
index: 1,
|
||||
hidden: true,
|
||||
extData: {
|
||||
"tabview-tab": "{\"groupID\":17}",
|
||||
},
|
||||
}
|
||||
],
|
||||
extData: {
|
||||
"tabview-group": "{\"2\":{},\"13\":{\"title\":\"Foopy\"}, \"15\":{\"title\":\"Barry\"}, \"16\":{}, \"17\":{}}",
|
||||
"tabview-groups": "{\"nextID\":20,\"activeGroupId\":2,\"totalNumber\":5}",
|
||||
"tabview-visibility": "false"
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
};
|
||||
|
||||
add_task(function* gatherGroupDataTest() {
|
||||
let groupInfo = TabGroupsMigrator._gatherGroupData(TEST_STATES.TWO_GROUPS);
|
||||
Assert.equal(groupInfo.size, 1, "Information about 1 window");
|
||||
let singleWinGroups = [... groupInfo.values()][0];
|
||||
Assert.equal(singleWinGroups.size, 2, "2 groups");
|
||||
let group2 = singleWinGroups.get("2");
|
||||
Assert.ok(!!group2, "group 2 should exist");
|
||||
Assert.equal(group2.tabs.length, 2, "2 tabs in group 2");
|
||||
// Note that this has groupID 2 in the internal representation of tab groups,
|
||||
// but because it was the first group we encountered when migrating, it was
|
||||
// labeled "group 1" for the user
|
||||
Assert.equal(group2.tabGroupsMigrationTitle, "Group 1", "We assign a numeric title to untitled groups");
|
||||
Assert.equal(group2.anonGroupID, "1", "We mark an untitled group with an anonymous id");
|
||||
let group13 = singleWinGroups.get("13");
|
||||
Assert.ok(!!group13, "group 13 should exist");
|
||||
Assert.equal(group13.tabs.length, 1, "1 tabs in group 13");
|
||||
Assert.equal(group13.tabGroupsMigrationTitle, "Foopy", "Group with title has correct title");
|
||||
Assert.ok(!("anonGroupID" in group13), "We don't mark a titled group with an anonymous id");
|
||||
});
|
||||
|
||||
add_task(function* bookmarkingTest() {
|
||||
let stateClone = JSON.parse(JSON.stringify(TEST_STATES.TWO_GROUPS));
|
||||
let groupInfo = TabGroupsMigrator._gatherGroupData(stateClone);
|
||||
let removedGroups = TabGroupsMigrator._removeHiddenTabGroupsFromState(stateClone, groupInfo);
|
||||
yield TabGroupsMigrator._bookmarkAllGroupsFromState(groupInfo);
|
||||
let bmCounter = 0;
|
||||
let bmParents = {};
|
||||
let bookmarks = [];
|
||||
let onResult = bm => {
|
||||
bmCounter++;
|
||||
bmParents[bm.parentGuid] = (bmParents[bm.parentGuid] || 0) + 1;
|
||||
Assert.ok(bm.title.startsWith("Robots "), "Bookmark title(" + bm.title + ") should start with 'Robots '");
|
||||
};
|
||||
yield PlacesUtils.bookmarks.fetch({url: "about:robots"}, onResult);
|
||||
Assert.equal(bmCounter, 3, "Should have seen 3 bookmarks");
|
||||
Assert.equal(Object.keys(bmParents).length, 2, "Should be in 2 folders");
|
||||
|
||||
let ancestorGuid;
|
||||
let parents = Object.keys(bmParents).map(guid => {
|
||||
PlacesUtils.bookmarks.fetch({guid}, bm => {
|
||||
ancestorGuid = bm.parentGuid;
|
||||
if (bmParents[bm.guid] == 1) {
|
||||
Assert.equal(bm.title, "Foopy", "Group with 1 kid has right title");
|
||||
} else {
|
||||
Assert.ok(bm.title.includes("1"), "Group with more kids should have anon ID in title (" + bm.title + ")");
|
||||
}
|
||||
});
|
||||
});
|
||||
yield Promise.all(parents);
|
||||
|
||||
yield PlacesUtils.bookmarks.fetch({guid: ancestorGuid}, bm => {
|
||||
Assert.equal(bm.title,
|
||||
gBrowserBundle.GetStringFromName("tabgroups.migration.tabGroupBookmarkFolderName"),
|
||||
"Should have the right title");
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function* bookmarkNamedActiveGroup() {
|
||||
let stateClone = JSON.parse(JSON.stringify(TEST_STATES.NAMED_ACTIVE_GROUP));
|
||||
let groupInfo = TabGroupsMigrator._gatherGroupData(stateClone);
|
||||
let removedGroups = TabGroupsMigrator._removeHiddenTabGroupsFromState(stateClone, groupInfo);
|
||||
yield TabGroupsMigrator._bookmarkAllGroupsFromState(groupInfo);
|
||||
let bmParents = {};
|
||||
let bmCounter = 0;
|
||||
let onResult = bm => {
|
||||
bmCounter++;
|
||||
bmParents[bm.parentGuid] = (bmParents[bm.parentGuid] || 0) + 1;
|
||||
Assert.ok(bm.title.startsWith("Mozilla "), "Bookmark title (" + bm.title + ") should start with 'Mozilla '");
|
||||
};
|
||||
yield PlacesUtils.bookmarks.fetch({url: "about:mozilla"}, onResult);
|
||||
Assert.equal(bmCounter, 1, "Should have seen 1 bookmarks");
|
||||
let parentPromise = PlacesUtils.bookmarks.fetch({guid: Object.keys(bmParents)[0]}, bm => {
|
||||
Assert.equal(bm.title, "Foopy", "Group with 1 kid has right title");
|
||||
});
|
||||
yield parentPromise;
|
||||
});
|
||||
|
||||
add_task(function* removingTabGroupsFromJSONTest() {
|
||||
let stateClone = JSON.parse(JSON.stringify(TEST_STATES.TWO_GROUPS));
|
||||
let groupInfo = TabGroupsMigrator._gatherGroupData(stateClone);
|
||||
let removedGroups = TabGroupsMigrator._removeHiddenTabGroupsFromState(stateClone, groupInfo);
|
||||
Assert.equal(removedGroups.windows.length, 1, "Removed 1 group which looks like a window in removed data");
|
||||
Assert.equal(removedGroups.windows[0].tabs.length, 1, "Removed group had 1 tab");
|
||||
Assert.ok(!stateClone.windows[0].extData, "extData removed from window");
|
||||
stateClone.windows[0].tabs.forEach(tab => {
|
||||
Assert.ok(!tab.extData, "extData removed from tab");
|
||||
});
|
||||
Assert.ok(stateClone.windows[0].tabs.length, 2, "Only 2 tabs remain in the window");
|
||||
});
|
||||
|
||||
add_task(function* backupTest() {
|
||||
yield TabGroupsMigrator._createBackup(JSON.stringify(TEST_STATES.TWO_GROUPS));
|
||||
let f = Services.dirsvc.get("ProfD", Components.interfaces.nsIFile);
|
||||
f.append("tabgroups-session-backup.json");
|
||||
ok(f.exists(), "Should have created the file");
|
||||
|
||||
let txt = (new TextDecoder()).decode(yield OS.File.read(f.path));
|
||||
Assert.deepEqual(JSON.parse(txt), TEST_STATES.TWO_GROUPS, "Should have written the expected state.");
|
||||
|
||||
f.remove(false);
|
||||
});
|
||||
|
||||
add_task(function* migrationPageDataTest() {
|
||||
let stateClone = JSON.parse(JSON.stringify(TEST_STATES.TWO_GROUPS));
|
||||
let groupInfo = TabGroupsMigrator._gatherGroupData(stateClone);
|
||||
let removedGroups = TabGroupsMigrator._removeHiddenTabGroupsFromState(stateClone, groupInfo);
|
||||
TabGroupsMigrator._createBackgroundTabGroupRestorationPage(stateClone, removedGroups);
|
||||
Assert.equal(stateClone.windows.length, 1, "Should still only have 1 window");
|
||||
Assert.equal(stateClone.windows[0].tabs.length, 3, "Should now have 3 tabs");
|
||||
|
||||
let url = "chrome://browser/content/aboutTabGroupsMigration.xhtml";
|
||||
let formdata = {id: {sessionData: JSON.stringify(removedGroups)}, url};
|
||||
Assert.deepEqual(stateClone.windows[0].tabs[2],
|
||||
{
|
||||
entries: [{url}],
|
||||
formdata,
|
||||
index: 1
|
||||
},
|
||||
"Should have added expected tab at the end of the tab list.");
|
||||
});
|
||||
|
||||
add_task(function* correctMissingTabGroupInfo() {
|
||||
let stateClone = JSON.parse(JSON.stringify(TEST_STATES.TAB_WITHOUT_GROUP));
|
||||
let groupInfo = TabGroupsMigrator._gatherGroupData(stateClone);
|
||||
Assert.equal(groupInfo.size, 1, "Should have 1 window");
|
||||
let windowGroups = [...groupInfo][0][1];
|
||||
Assert.equal(windowGroups.size, 2, "Window should have 2 groups");
|
||||
let group2 = windowGroups.get("2");
|
||||
Assert.ok(group2, "Group 2 should exist");
|
||||
Assert.equal(group2.tabs.length, 2, "There should be 2 tabs in group 2");
|
||||
Assert.equal(group2.tabs[0].entries[0].title, "Robots 1", "The first tab of group 2 should be the tab with no group info.");
|
||||
});
|
||||
|
||||
add_task(function* dealWithNoGroupInfo() {
|
||||
let stateClone = JSON.parse(JSON.stringify(TEST_STATES.ONLY_UNGROUPED_TABS));
|
||||
let groupInfo = TabGroupsMigrator._gatherGroupData(stateClone);
|
||||
Assert.equal(groupInfo.size, 1, "Should have 1 window");
|
||||
let windowGroups = [...groupInfo][0][1];
|
||||
Assert.equal(windowGroups.size, 1, "Window should have 1 group");
|
||||
let fallbackActiveGroup = windowGroups.get("active group");
|
||||
Assert.ok(fallbackActiveGroup, "Fallback group should exist");
|
||||
Assert.equal(fallbackActiveGroup.tabs.length, 3, "There should be 3 tabs in the fallback group");
|
||||
});
|
||||
|
||||
add_task(function* groupSortingInRemovedDataUsedForRestorePage() {
|
||||
let stateClone = JSON.parse(JSON.stringify(TEST_STATES.SORTING_NAMING_RESTORE_PAGE));
|
||||
let groupInfo = TabGroupsMigrator._gatherGroupData(stateClone);
|
||||
let removedGroups = TabGroupsMigrator._removeHiddenTabGroupsFromState(stateClone, groupInfo);
|
||||
Assert.equal(stateClone.windows.length, 1, "Should still only have 1 window");
|
||||
Assert.equal(stateClone.windows[0].tabs.length, 2, "Should now have 2 tabs");
|
||||
|
||||
let restoredWindowTitles = removedGroups.windows.map(win => win.tabGroupsMigrationTitle);
|
||||
// Note that group 1 is the active group and as such it won't appear in the list of
|
||||
// things the user can restore:
|
||||
Assert.deepEqual(restoredWindowTitles,
|
||||
["Barry", "Foopy", "Group 2", "Group 3"]);
|
||||
});
|
||||
|
@ -8,5 +8,4 @@ skip-if = toolkit == 'android' || toolkit == 'gonk'
|
||||
skip-if = os != 'win'
|
||||
[test_DirectoryLinksProvider.js]
|
||||
[test_SitePermissions.js]
|
||||
[test_TabGroupsMigrator.js]
|
||||
[test_LaterRun.js]
|
||||
|
Loading…
Reference in New Issue
Block a user