зеркало из https://github.com/mozilla/gecko-dev.git
Bug 812291 - Hook up hide/restore of Top Sites
This commit is contained in:
Родитель
199cf96754
Коммит
ddeeac52cf
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче