Bug 940013 - fix Australis' CustomizableUI to have better interaction between registerToolbarNode and registerArea, r=Unfocused

--HG--
extra : rebase_source : 17670d2d198701a114b0fc7452f3ca72c1b835b2
This commit is contained in:
Gijs Kruitbosch 2013-11-29 00:20:34 +01:00
parent 1be00c4aed
commit 1b225d6cbe
5 changed files with 153 additions and 16 deletions

View File

@ -97,6 +97,13 @@ let gSeenWidgets = new Set();
*/
let gDirtyAreaCache = new Set();
/**
* gPendingBuildAreas is a map from area IDs to map from build nodes to their
* existing children at the time of node registration, that are waiting
* for the area to be registered
*/
let gPendingBuildAreas = new Map();
let gSavedState = null;
let gRestoring = false;
let gDirty = false;
@ -263,11 +270,9 @@ let CustomizableUIInternal = {
if (typeof aName != "string" || !/^[a-z0-9-_]{1,}$/i.test(aName)) {
throw new Error("Invalid area name");
}
if (gAreas.has(aName)) {
throw new Error("Area already registered");
}
let props = new Map();
let areaIsKnown = gAreas.has(aName);
let props = areaIsKnown ? gAreas.get(aName) : new Map();
for (let key in aProperties) {
//XXXgijs for special items, we need to make sure they have an appropriate ID
// so we aren't perpetually in a non-default state:
@ -277,14 +282,44 @@ let CustomizableUIInternal = {
props.set(key, aProperties[key]);
}
}
gAreas.set(aName, props);
// Default to a toolbar:
if (!props.has("type")) {
props.set("type", CustomizableUI.TYPE_TOOLBAR);
}
// Sanity check type:
let allTypes = [CustomizableUI.TYPE_TOOLBAR, CustomizableUI.TYPE_MENU_PANEL];
if (allTypes.indexOf(props.get("type")) == -1) {
throw new Error("Invalid area type " + props.get("type"));
}
if (props.get("legacy")) {
// Guarantee this area exists in gFuturePlacements, to avoid checking it in
// various places elsewhere.
gFuturePlacements.set(aName, new Set());
} else {
this.restoreStateForArea(aName);
// And to no placements:
if (!props.has("defaultPlacements")) {
props.set("defaultPlacements", []);
}
// Sanity check default placements array:
if (!Array.isArray(props.get("defaultPlacements"))) {
throw new Error("Should provide an array of default placements");
}
if (!areaIsKnown) {
gAreas.set(aName, props);
if (props.get("legacy")) {
// Guarantee this area exists in gFuturePlacements, to avoid checking it in
// various places elsewhere.
gFuturePlacements.set(aName, new Set());
} else {
this.restoreStateForArea(aName);
}
// If we have pending build area nodes, register all of them
if (gPendingBuildAreas.has(aName)) {
let pendingNodes = gPendingBuildAreas.get(aName);
for (let [pendingNode, existingChildren] of pendingNodes) {
this.registerToolbarNode(pendingNode, existingChildren);
}
gPendingBuildAreas.delete(aName);
}
}
},
@ -331,8 +366,23 @@ let CustomizableUIInternal = {
let document = aToolbar.ownerDocument;
let areaProperties = gAreas.get(area);
// If this area is not registered, try to do it automatically:
if (!areaProperties) {
throw new Error("Unknown customization area: " + area);
// If there's no default set attribute at all, we assume that we should
// wait for registerArea to be called:
if (!aToolbar.hasAttribute("defaultset")) {
if (!gPendingBuildAreas.has(area)) {
gPendingBuildAreas.set(area, new Map());
}
let pendingNodes = gPendingBuildAreas.get(area);
pendingNodes.set(aToolbar, aExistingChildren);
return;
}
let props = {type: CustomizableUI.TYPE_TOOLBAR, legacy: true};
let defaultsetAttribute = aToolbar.getAttribute("defaultset");
props.defaultPlacements = defaultsetAttribute.split(',').filter(s => s);
this.registerArea(area, props);
areaProperties = gAreas.get(area);
}
this.beginBatchUpdate();
@ -730,6 +780,18 @@ let CustomizableUIInternal = {
widget.instances.delete(document);
this.notifyListeners("onWidgetInstanceRemoved", widget.id, document);
}
for (let [area, areaMap] of gPendingBuildAreas) {
let toDelete = [];
for (let [areaNode, ] of areaMap) {
if (areaNode.ownerDocument == document) {
toDelete.push(areaNode);
}
}
for (let areaNode of toDelete) {
areaMap.delete(toDelete);
}
}
},
setLocationAttributes: function(aNode, aArea) {

View File

@ -38,6 +38,7 @@ skip-if = os == "mac"
[browser_938980_navbar_collapsed.js]
[browser_938995_indefaultstate_nonremovable.js]
[browser_940013_registerToolbarNode_calls_registerArea.js]
[browser_940946_removable_from_navbar_customizemode.js]
[browser_941083_invalidate_wrapper_cache_createWidget.js]
[browser_942581_unregisterArea_keeps_placements.js]

View File

@ -12,9 +12,6 @@ let gTests = [
SimpleTest.doesThrow(function() CustomizableUI.registerArea([]),
"Registering areas with an invalid ID should throw.");
SimpleTest.doesThrow(function() CustomizableUI.registerArea(CustomizableUI.AREA_NAVBAR),
"Registering an area with an ID that's already registered should throw.");
SimpleTest.doesThrow(function() CustomizableUI.unregisterArea("@foo"),
"Unregistering areas with an invalid ID should throw.");

View File

@ -7,7 +7,7 @@ let gTests = [
{
desc: "Attempting to drag an item to an empty container should work.",
setup: function() {
createToolbarWithPlacements(kTestToolbarId, "");
createToolbarWithPlacements(kTestToolbarId, []);
},
run: function() {
yield startCustomizing();

View File

@ -0,0 +1,77 @@
/* 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/. */
const kToolbarId = "test-registerToolbarNode-toolbar";
const kButtonId = "test-registerToolbarNode-button";
let gTests = [
{
desc: "Registering a toolbar with defaultset attribute should work",
run: function() {
let btn = createDummyXULButton(kButtonId);
let toolbar = document.createElement("toolbar");
toolbar.id = kToolbarId;
toolbar.setAttribute("customizable", true);
toolbar.setAttribute("defaultset", kButtonId);
gNavToolbox.appendChild(toolbar);
ok(CustomizableUI.areas.indexOf(kToolbarId) != -1,
"Toolbar should have been registered automatically.");
is(CustomizableUI.getAreaType(kToolbarId), CustomizableUI.TYPE_TOOLBAR,
"Area should be registered as toolbar");
assertAreaPlacements(kToolbarId, [kButtonId]);
ok(CustomizableUI.inDefaultState, "Everything should be in its default state.");
CustomizableUI.unregisterArea(kToolbarId, true);
toolbar.remove();
ok(CustomizableUI.inDefaultState, "Everything should be in its default state.");
btn.remove();
}
},
{
desc: "Registering a toolbar without a defaultset attribute should " +
"wait for the registerArea call",
run: function() {
let btn = createDummyXULButton(kButtonId);
let toolbar = document.createElement("toolbar");
toolbar.id = kToolbarId;
toolbar.setAttribute("customizable", true);
gNavToolbox.appendChild(toolbar);
ok(CustomizableUI.areas.indexOf(kToolbarId) == -1,
"Toolbar should not yet have been registered automatically.");
CustomizableUI.registerArea(kToolbarId, {defaultPlacements: [kButtonId]});
ok(CustomizableUI.areas.indexOf(kToolbarId) != -1,
"Toolbar should have been registered now.");
is(CustomizableUI.getAreaType(kToolbarId), CustomizableUI.TYPE_TOOLBAR,
"Area should be registered as toolbar");
assertAreaPlacements(kToolbarId, [kButtonId]);
ok(CustomizableUI.inDefaultState, "Everything should be in its default state.");
CustomizableUI.unregisterArea(kToolbarId, true);
toolbar.remove();
ok(CustomizableUI.inDefaultState, "Everything should be in its default state.");
btn.remove();
}
}
];
function asyncCleanup() {
yield resetCustomization();
}
function cleanup() {
let toolbar = document.getElementById(kToolbarId);
if (toolbar) {
toolbar.remove();
}
let btn = document.getElementById(kButtonId) ||
gNavToolbox.querySelector("#" + kButtonId);
if (btn) {
btn.remove();
}
}
function test() {
waitForExplicitFinish();
registerCleanupFunction(cleanup);
runTests(gTests, asyncCleanup);
}