Bug 812291 - Hook up hide/restore of Top Sites

This commit is contained in:
Sam Foster 2013-04-03 22:16:14 +01:00
Родитель 199cf96754
Коммит ddeeac52cf
5 изменённых файлов: 178 добавлений и 52 удалений

Просмотреть файл

@ -37,7 +37,9 @@ Site.prototype = {
this.pinned ? 'unpin' : 'pin'
];
},
blocked: false,
get blocked() {
return NewTabUtils.blockedLinks.isBlocked(this);
},
get attributeValues() {
return {
value: this.url,

Просмотреть файл

@ -98,13 +98,29 @@ let TopSites = {
if (!(aSite && aSite.url)) {
throw Cr.NS_ERROR_INVALID_ARG
}
// FIXME: implementation needed, covered by bug 812291
aSite._restorePinIndex = NewTabUtils.pinnedLinks._indexOfLink(aSite);
// blocked state is a pref, using Storage apis therefore sync
NewTabUtils.blockedLinks.block(aSite);
// clear out the cache, we'll fetch and re-render
this._sites = null;
this._sitesDirty.clear();
this.update();
},
restoreSite: function(aSite) {
if (!(aSite && aSite.url)) {
throw Cr.NS_ERROR_INVALID_ARG
}
// FIXME: implementation needed, covered by bug 812291
NewTabUtils.blockedLinks.unblock(aSite);
let pinIndex = aSite._restorePinIndex;
if (!isNaN(pinIndex) && pinIndex > -1) {
NewTabUtils.pinnedLinks.pin(aSite, pinIndex);
}
// clear out the cache, we'll fetch and re-render
this._sites = null;
this._sitesDirty.clear();
this.update();
},
_linkFromNode: function _linkFromNode(aNode) {
return {
@ -124,6 +140,9 @@ function TopSitesView(aGrid, aMaxSites, aUseThumbnails) {
// handle selectionchange DOM events from the grid/tile group
this._set.addEventListener("context-action", this, false);
// clean up state when the appbar closes
window.addEventListener('MozAppbarDismissing', this, false);
let history = Cc["@mozilla.org/browser/nav-history-service;1"].
getService(Ci.nsINavHistoryService);
history.addObserver(this, false);
@ -141,6 +160,8 @@ function TopSitesView(aGrid, aMaxSites, aUseThumbnails) {
TopSitesView.prototype = {
_set:null,
_topSitesMax: null,
// _lastSelectedSites used to temporarily store blocked/removed sites for undo/restore-ing
_lastSelectedSites: null,
// isUpdating used only for testing currently
isUpdating: false,
@ -149,52 +170,77 @@ TopSitesView.prototype = {
BrowserUI.goToURI(url);
},
doActionOnSelectedTiles: function(aActionName) {
doActionOnSelectedTiles: function(aActionName, aEvent) {
let tileGroup = this._set;
let selectedTiles = tileGroup.selectedItems;
let nextContextActions = new Set();
switch (aActionName){
case "delete":
Array.forEach(selectedTiles, function(aNode) {
let site = TopSites._linkFromNode(aNode);
// add some class to transition element before deletion?
aNode.contextActions.delete('delete');
// we need new context buttons to show (the tile node will go away though)
nextContextActions.add('restore');
TopSites.hideSite(site);
if (aNode.contextActions){
aNode.contextActions.delete('delete');
aNode.contextActions.add('restore');
if (!this._lastSelectedSites) {
this._lastSelectedSites = [];
}
});
this._lastSelectedSites.push(site);
}, this);
break;
case "restore":
// usually restore is an undo action, so there's no tiles/selection to act on
if (this._lastSelectedSites) {
for (let site of this._lastSelectedSites) {
TopSites.restoreSite(site);
}
}
break;
case "pin":
Array.forEach(selectedTiles, function(aNode) {
let site = TopSites._linkFromNode(aNode);
let index = Array.indexOf(aNode.control.children, aNode);
aNode.contextActions.delete('pin');
aNode.contextActions.add('unpin');
TopSites.pinSite(site, index);
if (aNode.contextActions) {
aNode.contextActions.delete('pin');
aNode.contextActions.add('unpin');
}
});
break;
case "unpin":
Array.forEach(selectedTiles, function(aNode) {
let site = TopSites._linkFromNode(aNode);
aNode.contextActions.delete('unpin');
aNode.contextActions.add('pin');
TopSites.unpinSite(site);
if (aNode.contextActions) {
aNode.contextActions.delete('unpin');
aNode.contextActions.add('pin');
}
});
break;
// default: no action
}
if (nextContextActions.size) {
// stop the appbar from dismissing
aEvent.preventDefault();
// at next tick, re-populate the context appbar
setTimeout(function(){
// fire a MozContextActionsChange event to update the context appbar
let event = document.createEvent("Events");
event.actions = [...nextContextActions];
event.initEvent("MozContextActionsChange", true, false);
tileGroup.dispatchEvent(event);
},0);
}
},
handleEvent: function(aEvent) {
switch (aEvent.type){
case "context-action":
this.doActionOnSelectedTiles(aEvent.action);
this.doActionOnSelectedTiles(aEvent.action, aEvent);
aEvent.stopPropagation(); // event is handled, no need to let it bubble further
break;
case "MozAppbarDismissing":
// clean up when the context appbar is dismissed - we don't remember selections
this._lastSelectedSites = null;
this._set.clearSelection();
}
},
@ -297,6 +343,7 @@ TopSitesView.prototype = {
Services.obs.removeObserver(this, "Metro:RefreshTopsiteThumbnail");
PageThumbs.removeExpirationFilter(this);
}
window.removeEventListener('MozAppbarDismissing', this, false);
},
// nsIObservers

Просмотреть файл

@ -15,6 +15,7 @@ var Appbar = {
window.addEventListener('MozContextUIShow', this, false);
window.addEventListener('MozPrecisePointer', this, false);
window.addEventListener('MozImprecisePointer', this, false);
window.addEventListener('MozContextActionsChange', this, false);
this._updateDebugButtons();
this._updateZoomButtons();
@ -33,6 +34,11 @@ var Appbar = {
case 'MozImprecisePointer':
this._updateZoomButtons();
break;
case 'MozContextActionsChange':
let actions = aEvent.actions;
// could transition in old, new buttons?
this.showContextualActions(actions);
break;
case "selectionchange":
let nodeName = aEvent.target.nodeName;
if ('richgrid' === nodeName) {
@ -139,13 +145,13 @@ var Appbar = {
// but we keep coupling loose so grid doesn't need to know about appbar
let event = document.createEvent("Events");
event.action = aActionName;
event.initEvent("context-action", true, false);
event.initEvent("context-action", true, true); // is cancelable
activeTileset.dispatchEvent(event);
// done with this selection, explicitly clear it
activeTileset.clearSelection();
if (!event.defaultPrevented) {
activeTileset.clearSelection();
this.appbar.dismiss();
}
}
this.appbar.dismiss();
},
showContextualActions: function(aVerbs){
@ -198,11 +204,14 @@ var Appbar = {
let contextActions = activeTileset.contextActions;
let verbs = [v for (v of contextActions)];
// could transition in old, new buttons?
this.showContextualActions(verbs);
// fire event with these verbs as payload
let event = document.createEvent("Events");
event.actions = verbs;
event.initEvent("MozContextActionsChange", true, false);
this.appbar.dispatchEvent(event);
if (verbs.length) {
this.appbar.show();
this.appbar.show(); // should be no-op if we're already showing
} else {
this.appbar.dismiss();
}

Просмотреть файл

@ -39,8 +39,6 @@
for (let childItem of this.selectedItems) {
childItem.removeAttribute("selected");
}
// reset context actions
this._contextActions = null;
]]>
</body>
</method>
@ -107,16 +105,10 @@
]]>
</body>
</method>
<field name="_contextActions">null</field>
<property name="contextActions">
<getter>
<![CDATA[
// return the subset of verbs that apply to all selected tiles
// use cached list, it'll get cleared out when selection changes
if (this._contextActions) {
return this._contextActions;
}
let tileNodes = this.selectedItems;
if (!tileNodes.length) {
return new Set();
@ -145,10 +137,8 @@
}
}
}
this._contextActions = verbSet;
// returns Set
return this._contextActions;
return verbSet;
]]>
</getter>
</property>
@ -215,7 +205,6 @@
this.removeChild(this.firstChild);
}
this._grid.style.width = "0px";
this._contextActions = null;
]]>
</body>
</method>
@ -476,8 +465,6 @@
<body>
<![CDATA[
// flush out selection-related cached properties so they get recalc'd next time
this._contextActions = null;
// fire an event?
if (this.suppressOnSelect || this._suppressOnSelect)
return;
@ -542,8 +529,8 @@
// selected getter just looks directly at attribute
// pinned getter just looks directly at attribute
// value getter just looks directly at attribute
this.refreshBackgroundImage();
this._contextActions = null;
this.refreshBackgroundImage();
]]>
</body>
</method>
@ -612,18 +599,6 @@
return this._contextActions;
]]>
</getter>
<setter>
<![CDATA[
let actionSet = this._contextActions = new Set();
let actions = val;
if (actions) {
actions.split(/[,\s]+/).forEach(function(verb){
actionSet.add(verb);
});
}
return this._contextActions;
]]>
</setter>
</property>
</implementation>

Просмотреть файл

@ -331,3 +331,96 @@ gTests.push({
ok( !NewTabUtils.pinnedLinks.isPinned(site), "2nd item should no longer be pinned" );
}
});
gTests.push({
desc: "block/unblock sites",
setUp: function() {
// setup - set topsites to known state
yield setLinks(
"brian,dougal,dylan,ermintrude,florence,moose,sgtsam,train,zebedee,zeebad",
",dougal"
);
yield updatePagesAndWait();
// pause until the update has fired and the view is finished updating
yield waitForCondition(function(){
let grid = document.getElementById("start-topsites-grid");
return !grid.controller.isUpdating;
});
},
run: function() {
try {
// block a site
// test that sites are removed from the grid as expected
let grid = document.getElementById("start-topsites-grid"),
items = grid.children;
is(items.length, 8, this.desc + ": should be 8 topsites");
let brianSite = {
url: items[0].getAttribute("value"),
title: items[0].getAttribute("label")
};
let dougalSite = {
url: items[1].getAttribute("value"),
title: items[1].getAttribute("label")
};
// we'll block brian (he's not pinned)
TopSites.hideSite(brianSite);
// pause until the update has fired and the view is finished updating
yield waitForCondition(function(){
return !grid.controller.isUpdating;
});
// verify brian is blocked and removed from the grid
ok( (new Site(brianSite)).blocked, "Site has truthy blocked property" );
ok( NewTabUtils.blockedLinks.isBlocked(brianSite), "Site was blocked" );
is( grid.querySelectorAll("[value='"+brianSite.url+"']").length, 0, "Blocked site was removed from grid");
// make sure the empty slot was backfilled
is(items.length, 8, this.desc + ": should be 8 topsites");
// block dougal, who is currently pinned at index 1
TopSites.hideSite(dougalSite);
// pause until the update has fired and the view is finished updating
yield waitForCondition(function(){
return !grid.controller.isUpdating;
});
// verify dougal is blocked and removed from the grid
ok( (new Site(dougalSite)).blocked, "Site has truthy blocked property" );
ok( NewTabUtils.blockedLinks.isBlocked(dougalSite), "Site was blocked" );
ok( !NewTabUtils.pinnedLinks.isPinned(dougalSite), "Blocked Site is no longer pinned" );
is( grid.querySelectorAll("[value='"+dougalSite.url+"']").length, 0, "Blocked site was removed from grid");
// make sure the empty slot was backfilled
is(items.length, 8, this.desc + ": should be 8 topsites");
TopSites.restoreSite(brianSite);
TopSites.restoreSite(dougalSite);
yield waitForCondition(function(){
return !grid.controller.isUpdating;
});
// verify brian and dougal are unblocked and back in the grid
ok( !NewTabUtils.blockedLinks.isBlocked(brianSite), "site was unblocked" );
is( grid.querySelectorAll("[value='"+brianSite.url+"']").length, 1, "Unblocked site is back in the grid");
ok( !NewTabUtils.blockedLinks.isBlocked(dougalSite), "site was unblocked" );
is( grid.querySelectorAll("[value='"+dougalSite.url+"']").length, 1, "Unblocked site is back in the grid");
// ..and that a previously pinned site is re-pinned after being blocked, then restored
ok( NewTabUtils.pinnedLinks.isPinned(dougalSite), "Restoring previously pinned site makes it pinned again" );
is( grid.children[1].getAttribute("value"), dougalSite.url, "Blocked Site restored to pinned index" );
} catch(ex) {
ok(false, this.desc+": Caught exception in test: " + ex);
info("trace: " + ex.stack);
}
}
});