mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 925425 - richgrid slots implementation, revised tests. r=mbrubeck
This commit is contained in:
parent
de521cfd9b
commit
74f0f4cf41
@ -9,7 +9,7 @@
|
||||
* link parameter/model object expected to have a .url property, and optionally .title
|
||||
*/
|
||||
function Site(aLink) {
|
||||
if(!aLink.url) {
|
||||
if (!aLink.url) {
|
||||
throw Cr.NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
this._link = aLink;
|
||||
@ -64,7 +64,7 @@ Site.prototype = {
|
||||
}
|
||||
}
|
||||
// is binding already applied?
|
||||
if (aNode.refresh) {
|
||||
if ('refresh' in aNode) {
|
||||
// just update it
|
||||
aNode.refresh();
|
||||
} else {
|
||||
|
@ -27,7 +27,7 @@
|
||||
<field name="controller">null</field>
|
||||
|
||||
<!-- collection of child items excluding empty tiles -->
|
||||
<property name="items" readonly="true" onget="return this.querySelectorAll('richgriditem');"/>
|
||||
<property name="items" readonly="true" onget="return this.querySelectorAll('richgriditem[value]');"/>
|
||||
<property name="itemCount" readonly="true" onget="return this.items.length;"/>
|
||||
|
||||
<!-- nsIDOMXULMultiSelectControlElement (not fully implemented) -->
|
||||
@ -96,7 +96,7 @@
|
||||
<parameter name="aEvent"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if(!this.isBound)
|
||||
if (!this.isBound)
|
||||
return;
|
||||
|
||||
if ("single" == this.getAttribute("seltype")) {
|
||||
@ -116,7 +116,7 @@
|
||||
<parameter name="aEvent"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if(!this.isBound || this.suppressOnSelect)
|
||||
if (!this.isBound || this.suppressOnSelect)
|
||||
return;
|
||||
// we'll republish this as a selectionchange event on the grid
|
||||
aEvent.stopPropagation();
|
||||
@ -175,7 +175,7 @@
|
||||
<property name="selectedItems">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
return this.querySelectorAll("richgriditem[selected]");
|
||||
return this.querySelectorAll("richgriditem[value][selected]");
|
||||
]]>
|
||||
</getter>
|
||||
</property>
|
||||
@ -204,28 +204,105 @@
|
||||
<parameter name="aSkipArrange"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
let addition = this._createItemElement(aLabel, aValue);
|
||||
this.appendChild(addition);
|
||||
let item = this.nextSlot();
|
||||
item.setAttribute("value", aValue);
|
||||
item.setAttribute("label", aLabel);
|
||||
|
||||
if (!aSkipArrange)
|
||||
this.arrangeItems();
|
||||
return addition;
|
||||
return item;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="_slotValues">
|
||||
<body><![CDATA[
|
||||
return Array.map(this.children, (cnode) => cnode.getAttribute("value"));
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<property name="minSlots" readonly="true"
|
||||
onget="return this.getAttribute('minSlots') || 3;"/>
|
||||
|
||||
<method name="clearAll">
|
||||
<parameter name="aSkipArrange"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
while (this.firstChild) {
|
||||
this.removeChild(this.firstChild);
|
||||
const ELEMENT_NODE_TYPE = Components.interfaces.nsIDOMNode.ELEMENT_NODE;
|
||||
let slotCount = this.minSlots;
|
||||
let childIndex = 0;
|
||||
let child = this.firstChild;
|
||||
while (child) {
|
||||
// remove excess elements and non-element nodes
|
||||
if (child.nodeType !== ELEMENT_NODE_TYPE || childIndex+1 > slotCount) {
|
||||
let orphanNode = child;
|
||||
child = orphanNode.nextSibling;
|
||||
this.removeChild(orphanNode);
|
||||
continue;
|
||||
}
|
||||
if (child.hasAttribute("value")) {
|
||||
this._releaseSlot(child);
|
||||
}
|
||||
child = child.nextSibling;
|
||||
childIndex++;
|
||||
}
|
||||
// create our quota of item slots
|
||||
for (let count = this.childElementCount; count < slotCount; count++) {
|
||||
this.appendChild( this._createItemElement() );
|
||||
}
|
||||
|
||||
if (!aSkipArrange)
|
||||
this.arrangeItems();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="_slotAt">
|
||||
<parameter name="anIndex"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
// backfill with new slots as necessary
|
||||
let count = Math.max(1+anIndex, this.minSlots) - this.childElementCount;
|
||||
for (; count > 0; count--) {
|
||||
this.appendChild( this._createItemElement() );
|
||||
}
|
||||
return this.children[anIndex];
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="nextSlot">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (!this.itemCount) {
|
||||
return this._slotAt(0);
|
||||
}
|
||||
let lastItem = this.items[this.itemCount-1];
|
||||
let nextIndex = 1 + Array.indexOf(this.children, lastItem);
|
||||
return this._slotAt(nextIndex);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="_releaseSlot">
|
||||
<parameter name="anItem"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
// Flush out data and state attributes so we can recycle this slot/element
|
||||
let exclude = { value: 1, tiletype: 1 };
|
||||
let attrNames = [attr.name for (attr of anItem.attributes)];
|
||||
for (let attrName of attrNames) {
|
||||
if (!(attrName in exclude))
|
||||
anItem.removeAttribute(attrName);
|
||||
}
|
||||
// clear out inline styles
|
||||
anItem.removeAttribute("style");
|
||||
// finally clear the value, which should apply the richgrid-empty-item binding
|
||||
anItem.removeAttribute("value");
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="insertItemAt">
|
||||
<parameter name="anIndex"/>
|
||||
<parameter name="aLabel"/>
|
||||
@ -233,19 +310,30 @@
|
||||
<parameter name="aSkipArrange"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
anIndex = Math.min(this.itemCount, anIndex);
|
||||
let insertedItem;
|
||||
let existing = this.getItemAtIndex(anIndex);
|
||||
let addition = this._createItemElement(aLabel, aValue);
|
||||
if (existing) {
|
||||
this.insertBefore(addition, existing);
|
||||
} else {
|
||||
this.appendChild(addition);
|
||||
// use an empty slot if we have one, otherwise insert it
|
||||
let childIndex = Array.indexOf(this.children, existing);
|
||||
if (childIndex > 0 && !this.children[childIndex-1].hasAttribute("value")) {
|
||||
insertedItem = this.children[childIndex-1];
|
||||
} else {
|
||||
insertedItem = this.insertBefore(this._createItemElement(),existing);
|
||||
}
|
||||
}
|
||||
if (!insertedItem) {
|
||||
insertedItem = this._slotAt(anIndex);
|
||||
}
|
||||
insertedItem.setAttribute("value", aValue);
|
||||
insertedItem.setAttribute("label", aLabel);
|
||||
if (!aSkipArrange)
|
||||
this.arrangeItems();
|
||||
return addition;
|
||||
return insertedItem;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="removeItemAt">
|
||||
<parameter name="anIndex"/>
|
||||
<parameter name="aSkipArrange"/>
|
||||
@ -266,7 +354,13 @@
|
||||
<![CDATA[
|
||||
if (!aItem || Array.indexOf(this.items, aItem) < 0)
|
||||
return null;
|
||||
|
||||
let removal = this.removeChild(aItem);
|
||||
// replace the slot if necessary
|
||||
if (this.childElementCount < this.minSlots) {
|
||||
this.nextSlot();
|
||||
}
|
||||
|
||||
if (removal && !aSkipArrange)
|
||||
this.arrangeItems();
|
||||
|
||||
@ -422,6 +516,7 @@
|
||||
<field name="_scheduledArrangeItemsTimerId">null</field>
|
||||
<field name="_scheduledArrangeItemsTries">0</field>
|
||||
<field name="_maxArrangeItemsRetries">5</field>
|
||||
|
||||
<method name="_scheduleArrangeItems">
|
||||
<parameter name="aTime"/>
|
||||
<body>
|
||||
@ -453,6 +548,7 @@
|
||||
|
||||
let itemDims = this._itemSize;
|
||||
let containerDims = this._containerSize;
|
||||
let slotsCount = this.childElementCount;
|
||||
|
||||
// reset the flags
|
||||
if (this._scheduledArrangeItemsTimerId) {
|
||||
@ -468,25 +564,25 @@
|
||||
|
||||
if (this.hasAttribute("vertical")) {
|
||||
this._columnCount = Math.floor(containerDims.width / itemDims.width) || 1;
|
||||
this._rowCount = Math.floor(this.itemCount / this._columnCount);
|
||||
this._rowCount = Math.floor(slotsCount / this._columnCount);
|
||||
} else {
|
||||
// We favor overflowing horizontally, not vertically (rows then colums)
|
||||
// rows attribute = max rows
|
||||
let maxRowCount = Math.min(this.getAttribute("rows") || Infinity, Math.floor(containerDims.height / itemDims.height));
|
||||
this._rowCount = Math.min(this.itemCount, maxRowCount);
|
||||
// rows attribute is fixed number of rows
|
||||
let maxRows = Math.floor(containerDims.height / itemDims.height);
|
||||
this._rowCount = this.getAttribute("rows") ?
|
||||
// fit indicated rows when possible
|
||||
Math.min(maxRows, this.getAttribute("rows")) :
|
||||
// at least 1 row
|
||||
Math.min(maxRows, slotsCount) || 1;
|
||||
|
||||
// columns attribute = min cols
|
||||
this._columnCount = this.itemCount ?
|
||||
Math.max(
|
||||
// at least 1 column when there are items
|
||||
this.getAttribute("columns") || 1,
|
||||
Math.ceil(this.itemCount / this._rowCount)
|
||||
) : this.getAttribute("columns") || 0;
|
||||
// columns attribute is min number of cols
|
||||
this._columnCount = Math.ceil(slotsCount / this._rowCount) || 1;
|
||||
if (this.getAttribute("columns")) {
|
||||
this._columnCount = Math.max(this._columnCount, this.getAttribute("columns"));
|
||||
}
|
||||
}
|
||||
|
||||
// width is typically auto, cap max columns by truncating items collection
|
||||
// or, setting max-width style property with overflow hidden
|
||||
// '0' is an invalid value, just leave the property unset when 0 columns
|
||||
if (this._columnCount) {
|
||||
gridStyle.MozColumnCount = this._columnCount;
|
||||
}
|
||||
@ -521,6 +617,12 @@
|
||||
<field name="_xslideHandler"/>
|
||||
<constructor>
|
||||
<![CDATA[
|
||||
// create our quota of item slots
|
||||
for (let count = this.childElementCount, slotCount = this.minSlots;
|
||||
count < slotCount; count++) {
|
||||
this.appendChild( this._createItemElement() );
|
||||
}
|
||||
|
||||
if (this.controller && this.controller.gridBoundCallback != undefined)
|
||||
this.controller.gridBoundCallback();
|
||||
|
||||
@ -605,6 +707,7 @@
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="_isIndexInBounds">
|
||||
<parameter name="anIndex"/>
|
||||
<body>
|
||||
@ -626,7 +729,7 @@
|
||||
if (aLabel) {
|
||||
item.setAttribute("label", aLabel);
|
||||
}
|
||||
if(this.hasAttribute("tiletype")) {
|
||||
if (this.hasAttribute("tiletype")) {
|
||||
item.setAttribute("tiletype", this.getAttribute("tiletype"));
|
||||
}
|
||||
return item;
|
||||
@ -704,6 +807,7 @@
|
||||
aItem.setAttribute("bending", true);
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="unbendItem">
|
||||
<parameter name="aItem"/>
|
||||
<body><![CDATA[
|
||||
@ -749,7 +853,7 @@
|
||||
let state = event.crossSlidingState;
|
||||
let thresholds = this._xslideHandler.thresholds;
|
||||
let transformValue;
|
||||
switch(state) {
|
||||
switch (state) {
|
||||
case "cancelled":
|
||||
this.unbendItem(event.target);
|
||||
event.target.removeAttribute('crosssliding');
|
||||
@ -844,7 +948,7 @@
|
||||
<body>
|
||||
<![CDATA[
|
||||
// Prevent an exception in case binding is not done yet.
|
||||
if(!this.isBound)
|
||||
if (!this.isBound)
|
||||
return;
|
||||
|
||||
// Seed the binding properties from bound-node attribute values
|
||||
@ -914,7 +1018,7 @@
|
||||
|
||||
<method name="refreshBackgroundImage">
|
||||
<body><![CDATA[
|
||||
if(!this.isBound)
|
||||
if (!this.isBound)
|
||||
return;
|
||||
if (this.backgroundImage) {
|
||||
this._top.style.removeProperty("background-image");
|
||||
@ -927,7 +1031,7 @@
|
||||
<property name="contextActions">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
if(!this._contextActions) {
|
||||
if (!this._contextActions) {
|
||||
this._contextActions = new Set();
|
||||
let actionSet = this._contextActions;
|
||||
let actions = this.getAttribute("data-contextactions");
|
||||
@ -959,7 +1063,7 @@
|
||||
// fires for right-click, long-click and (keyboard) contextmenu input
|
||||
// toggle the selected state of tiles in a grid
|
||||
let gridParent = this.control;
|
||||
if(!this.isBound || !gridParent)
|
||||
if (!this.isBound || !gridParent)
|
||||
return;
|
||||
gridParent.handleItemContextMenu(this, event);
|
||||
]]>
|
||||
@ -967,4 +1071,10 @@
|
||||
</handlers>
|
||||
</binding>
|
||||
|
||||
<binding id="richgrid-empty-item">
|
||||
<content>
|
||||
<html:div anonid="anon-tile" class="tile-content"></html:div>
|
||||
</content>
|
||||
</binding>
|
||||
|
||||
</bindings>
|
||||
|
@ -119,6 +119,9 @@ richgrid {
|
||||
}
|
||||
|
||||
richgriditem {
|
||||
-moz-binding: url("chrome://browser/content/bindings/grid.xml#richgrid-empty-item");
|
||||
}
|
||||
richgriditem[value] {
|
||||
-moz-binding: url("chrome://browser/content/bindings/grid.xml#richgrid-item");
|
||||
}
|
||||
|
||||
|
@ -73,11 +73,11 @@ BookmarksView.prototype = Util.extend(Object.create(View.prototype), {
|
||||
},
|
||||
|
||||
_getItemForBookmarkId: function bv__getItemForBookmark(aBookmarkId) {
|
||||
return this._set.querySelector("richgriditem[bookmarkId='" + aBookmarkId + "']");
|
||||
return this._set.querySelector("richgriditem[anonid='" + aBookmarkId + "']");
|
||||
},
|
||||
|
||||
_getBookmarkIdForItem: function bv__getBookmarkForItem(aItem) {
|
||||
return +aItem.getAttribute("bookmarkId");
|
||||
return +aItem.getAttribute("anonid");
|
||||
},
|
||||
|
||||
_updateItemWithAttrs: function dv__updateItemWithAttrs(anItem, aAttrs) {
|
||||
@ -142,6 +142,7 @@ BookmarksView.prototype = Util.extend(Object.create(View.prototype), {
|
||||
this._set.removeItemAt(this._set.itemCount - 1, true);
|
||||
}
|
||||
this._set.arrangeItems();
|
||||
this._set.removeAttribute("fade");
|
||||
this._inBatch = false;
|
||||
rootNode.containerOpen = false;
|
||||
},
|
||||
@ -154,7 +155,8 @@ BookmarksView.prototype = Util.extend(Object.create(View.prototype), {
|
||||
},
|
||||
|
||||
clearBookmarks: function bv_clearBookmarks() {
|
||||
this._set.clearAll();
|
||||
if ('clearAll' in this._set)
|
||||
this._set.clearAll();
|
||||
},
|
||||
|
||||
addBookmark: function bv_addBookmark(aBookmarkId, aPos) {
|
||||
@ -162,7 +164,7 @@ BookmarksView.prototype = Util.extend(Object.create(View.prototype), {
|
||||
let uri = this._bookmarkService.getBookmarkURI(aBookmarkId);
|
||||
let title = this._bookmarkService.getItemTitle(aBookmarkId) || uri.spec;
|
||||
let item = this._set.insertItemAt(aPos || index, title, uri.spec, this._inBatch);
|
||||
item.setAttribute("bookmarkId", aBookmarkId);
|
||||
item.setAttribute("anonid", aBookmarkId);
|
||||
this._setContextActions(item);
|
||||
this._updateFavicon(item, uri);
|
||||
},
|
||||
@ -198,6 +200,7 @@ BookmarksView.prototype = Util.extend(Object.create(View.prototype), {
|
||||
let uri = this._bookmarkService.getBookmarkURI(aBookmarkId);
|
||||
let title = this._bookmarkService.getItemTitle(aBookmarkId) || uri.spec;
|
||||
|
||||
item.setAttribute("anonid", aBookmarkId);
|
||||
item.setAttribute("value", uri.spec);
|
||||
item.setAttribute("label", title);
|
||||
|
||||
|
@ -95,6 +95,7 @@ HistoryView.prototype = Util.extend(Object.create(View.prototype), {
|
||||
|
||||
rootNode.containerOpen = false;
|
||||
this._set.arrangeItems();
|
||||
this._set.removeAttribute("fade");
|
||||
if (this._inBatch > 0)
|
||||
this._inBatch--;
|
||||
},
|
||||
@ -130,6 +131,9 @@ HistoryView.prototype = Util.extend(Object.create(View.prototype), {
|
||||
let tileGroup = this._set;
|
||||
let selectedTiles = tileGroup.selectedItems;
|
||||
|
||||
// just arrange the grid once at the end of any action handling
|
||||
this._inBatch = true;
|
||||
|
||||
switch (aActionName){
|
||||
case "delete":
|
||||
Array.forEach(selectedTiles, function(aNode) {
|
||||
@ -182,9 +186,11 @@ HistoryView.prototype = Util.extend(Object.create(View.prototype), {
|
||||
break;
|
||||
|
||||
default:
|
||||
this._inBatch = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this._inBatch = false;
|
||||
// Send refresh event so all view are in sync.
|
||||
this._sendNeedsRefresh();
|
||||
},
|
||||
@ -254,7 +260,8 @@ HistoryView.prototype = Util.extend(Object.create(View.prototype), {
|
||||
},
|
||||
|
||||
onClearHistory: function() {
|
||||
this._set.clearAll();
|
||||
if ('clearAll' in this._set)
|
||||
this._set.clearAll();
|
||||
},
|
||||
|
||||
onPageChanged: function(aURI, aWhat, aValue) {
|
||||
@ -264,7 +271,7 @@ HistoryView.prototype = Util.extend(Object.create(View.prototype), {
|
||||
let currIcon = item.getAttribute("iconURI");
|
||||
if (currIcon != aValue) {
|
||||
item.setAttribute("iconURI", aValue);
|
||||
if("refresh" in item)
|
||||
if ("refresh" in item)
|
||||
item.refresh();
|
||||
}
|
||||
}
|
||||
|
@ -93,6 +93,7 @@ RemoteTabsView.prototype = Util.extend(Object.create(View.prototype), {
|
||||
}
|
||||
this.setUIAccessVisible(show);
|
||||
this._set.arrangeItems();
|
||||
this._set.removeAttribute("fade");
|
||||
},
|
||||
|
||||
destruct: function destruct() {
|
||||
|
@ -48,7 +48,17 @@
|
||||
<html:div class="meta-section-title narrow-title" onclick="StartUI.onNarrowTitleClick('start-topsites')">
|
||||
&narrowTopSitesHeader.label;
|
||||
</html:div>
|
||||
<richgrid id="start-topsites-grid" observes="bcast_windowState" set-name="topSites" rows="3" columns="3" tiletype="thumbnail" seltype="multiple" flex="1"/>
|
||||
<richgrid id="start-topsites-grid" observes="bcast_windowState" set-name="topSites" rows="3" columns="3" tiletype="thumbnail" seltype="multiple" minSlots="8" fade="true" flex="1">
|
||||
<richgriditem/>
|
||||
<richgriditem/>
|
||||
<richgriditem/>
|
||||
<richgriditem/>
|
||||
<richgriditem/>
|
||||
<richgriditem/>
|
||||
<richgriditem/>
|
||||
<richgriditem/>
|
||||
<richgriditem/>
|
||||
</richgrid>
|
||||
</vbox>
|
||||
|
||||
<vbox id="start-bookmarks" class="meta-section">
|
||||
@ -56,7 +66,10 @@
|
||||
<html:div class="meta-section-title narrow-title" onclick="StartUI.onNarrowTitleClick('start-bookmarks')">
|
||||
&narrowBookmarksHeader.label;
|
||||
</html:div>
|
||||
<richgrid id="start-bookmarks-grid" observes="bcast_windowState" set-name="bookmarks" seltype="multiple" flex="1"/>
|
||||
<richgrid id="start-bookmarks-grid" observes="bcast_windowState" set-name="bookmarks" seltype="multiple" fade="true" flex="1" minSlots="2">
|
||||
<richgriditem/>
|
||||
<richgriditem/>
|
||||
</richgrid>
|
||||
</vbox>
|
||||
|
||||
<vbox id="start-history" class="meta-section">
|
||||
@ -64,7 +77,11 @@
|
||||
<html:div class="meta-section-title narrow-title" onclick="StartUI.onNarrowTitleClick('start-history')">
|
||||
&narrowRecentHistoryHeader.label;
|
||||
</html:div>
|
||||
<richgrid id="start-history-grid" observes="bcast_windowState" set-name="recentHistory" seltype="multiple" flex="1"/>
|
||||
<richgrid id="start-history-grid" observes="bcast_windowState" set-name="recentHistory" seltype="multiple" fade="true" flex="1">
|
||||
<richgriditem/>
|
||||
<richgriditem/>
|
||||
<richgriditem/>
|
||||
</richgrid>
|
||||
</vbox>
|
||||
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
@ -73,7 +90,12 @@
|
||||
<html:div id="snappedRemoteTabsLabel" class="meta-section-title narrow-title" onclick="StartUI.onNarrowTitleClick('start-remotetabs')">
|
||||
&narrowRemoteTabsHeader.label;
|
||||
</html:div>
|
||||
<richgrid id="start-remotetabs-grid" observes="bcast_windowState" set-name="remoteTabs" seltype="multiple" flex="1"/>
|
||||
<richgrid id="start-remotetabs-grid" observes="bcast_windowState" set-name="remoteTabs" seltype="multiple" fade="true" flex="1">
|
||||
<richgriditem/>
|
||||
<richgriditem/>
|
||||
<richgriditem/>
|
||||
</richgrid>
|
||||
|
||||
</vbox>
|
||||
#endif
|
||||
</hbox>
|
||||
|
@ -158,6 +158,9 @@ TopSitesView.prototype = Util.extend(Object.create(View.prototype), {
|
||||
},
|
||||
|
||||
updateTile: function(aTileNode, aSite, aArrangeGrid) {
|
||||
if (!(aSite && aSite.url)) {
|
||||
throw new Error("Invalid Site object passed to TopSitesView updateTile");
|
||||
}
|
||||
this._updateFavicon(aTileNode, Util.makeURI(aSite.url));
|
||||
|
||||
Task.spawn(function() {
|
||||
@ -192,14 +195,11 @@ TopSitesView.prototype = Util.extend(Object.create(View.prototype), {
|
||||
tileset.clearAll(true);
|
||||
|
||||
for (let site of sites) {
|
||||
// call to private _createItemElement is a temp measure
|
||||
// we'll eventually just request the next slot
|
||||
let item = tileset._createItemElement(site.title, site.url);
|
||||
|
||||
this.updateTile(item, site);
|
||||
tileset.appendChild(item);
|
||||
let slot = tileset.nextSlot();
|
||||
this.updateTile(slot, site);
|
||||
}
|
||||
tileset.arrangeItems();
|
||||
tileset.removeAttribute("fade");
|
||||
this.isUpdating = false;
|
||||
},
|
||||
|
||||
@ -244,7 +244,7 @@ TopSitesView.prototype = Util.extend(Object.create(View.prototype), {
|
||||
|
||||
// nsIObservers
|
||||
observe: function (aSubject, aTopic, aState) {
|
||||
switch(aTopic) {
|
||||
switch (aTopic) {
|
||||
case "Metro:RefreshTopsiteThumbnail":
|
||||
this.forceReloadOfThumbnail(aState);
|
||||
break;
|
||||
@ -269,7 +269,8 @@ TopSitesView.prototype = Util.extend(Object.create(View.prototype), {
|
||||
},
|
||||
|
||||
onClearHistory: function() {
|
||||
this._set.clearAll();
|
||||
if ('clearAll' in this._set)
|
||||
this._set.clearAll();
|
||||
},
|
||||
|
||||
onPageChanged: function(aURI, aWhat, aValue) {
|
||||
|
@ -67,7 +67,7 @@ gTests.push({
|
||||
|
||||
ok(!item, "Item not in grid");
|
||||
ok(!gStartView._pinHelper.isPinned(uriFromIndex(2)), "Item unpinned");
|
||||
ok(gStartView._set.itemCount === gStartView._limit, "Grid repopulated");
|
||||
is(gStartView._set.itemCount, gStartView._limit, "Grid repopulated");
|
||||
|
||||
// --------- unpin multiple items
|
||||
|
||||
@ -124,7 +124,7 @@ gTests.push({
|
||||
item = gStartView._set.getItemsByUrl(uriFromIndex(2))[0];
|
||||
|
||||
ok(!item, "Item not in grid");
|
||||
ok(HistoryTestHelper._nodes[uriFromIndex(2)], "Item not deleted yet");
|
||||
ok(HistoryTestHelper._nodes[uriFromIndex(2)], "Item not actually deleted yet");
|
||||
ok(!restoreButton.hidden, "Restore button is visible.");
|
||||
ok(gStartView._set.itemCount === gStartView._limit, "Grid repopulated");
|
||||
|
||||
@ -150,9 +150,13 @@ gTests.push({
|
||||
ok(!deleteButton.hidden, "Delete button is visible.");
|
||||
|
||||
let promise = waitForCondition(() => !restoreButton.hidden);
|
||||
let populateGridSpy = spyOnMethod(gStartView, "populateGrid");
|
||||
|
||||
EventUtils.synthesizeMouse(deleteButton, 10, 10, {}, window);
|
||||
yield promise;
|
||||
|
||||
is(populateGridSpy.callCount, 1, "populateGrid was called in response to the deleting a tile");
|
||||
|
||||
item = gStartView._set.getItemsByUrl(uriFromIndex(2))[0];
|
||||
|
||||
ok(!item, "Item not in grid");
|
||||
@ -163,11 +167,14 @@ gTests.push({
|
||||
Elements.contextappbar.dismiss();
|
||||
yield promise;
|
||||
|
||||
is(populateGridSpy.callCount, 1, "populateGrid not called when a removed item is actually deleted");
|
||||
populateGridSpy.restore();
|
||||
|
||||
item = gStartView._set.getItemsByUrl(uriFromIndex(2))[0];
|
||||
|
||||
ok(!item, "Item not in grid");
|
||||
ok(!HistoryTestHelper._nodes[uriFromIndex(2)], "Item RIP");
|
||||
ok(gStartView._set.itemCount === gStartView._limit, "Grid repopulated");
|
||||
is(gStartView._set.itemCount, gStartView._limit, "Grid repopulated");
|
||||
|
||||
// --------- delete multiple items and restore
|
||||
|
||||
|
@ -17,6 +17,9 @@
|
||||
<richgrid id="grid_layout" seltype="single" flex="1">
|
||||
</richgrid>
|
||||
</vbox>
|
||||
<vbox>
|
||||
<richgrid id="slots_grid" seltype="single" minSlots="6" flex="1"/>
|
||||
</vbox>
|
||||
<vbox style="height:600px">
|
||||
<hbox>
|
||||
<richgrid id="clearGrid" seltype="single" flex="1" rows="2">
|
||||
@ -26,7 +29,7 @@
|
||||
</richgrid>
|
||||
</hbox>
|
||||
<hbox>
|
||||
<richgrid id="emptyGrid" seltype="single" flex="1" rows="2">
|
||||
<richgrid id="emptyGrid" seltype="single" flex="1" rows="2" minSlots="6">
|
||||
</richgrid>
|
||||
</hbox>
|
||||
<hbox>
|
||||
|
@ -9,6 +9,14 @@ function test() {
|
||||
}).then(runTests);
|
||||
}
|
||||
|
||||
function _checkIfBoundByRichGrid_Item(expected, node, idx) {
|
||||
let binding = node.ownerDocument.defaultView.getComputedStyle(node).MozBinding;
|
||||
let result = ('url("chrome://browser/content/bindings/grid.xml#richgrid-item")' == binding);
|
||||
return (result == expected);
|
||||
}
|
||||
let isBoundByRichGrid_Item = _checkIfBoundByRichGrid_Item.bind(this, true);
|
||||
let isNotBoundByRichGrid_Item = _checkIfBoundByRichGrid_Item.bind(this, false);
|
||||
|
||||
gTests.push({
|
||||
desc: "richgrid binding is applied",
|
||||
run: function() {
|
||||
@ -17,9 +25,9 @@ gTests.push({
|
||||
let grid = doc.querySelector("#grid1");
|
||||
ok(grid, "#grid1 is found");
|
||||
is(typeof grid.clearSelection, "function", "#grid1 has the binding applied");
|
||||
|
||||
is(grid.items.length, 2, "#grid1 has a 2 items");
|
||||
is(grid.items[0].control, grid, "#grid1 item's control points back at #grid1'");
|
||||
ok(Array.every(grid.items, isBoundByRichGrid_Item), "All items are bound by richgrid-item");
|
||||
}
|
||||
});
|
||||
|
||||
@ -125,19 +133,28 @@ gTests.push({
|
||||
gTests.push({
|
||||
desc: "empty grid",
|
||||
run: function() {
|
||||
// XXX grids have minSlots and may not be ever truly empty
|
||||
|
||||
let grid = doc.getElementById("emptyGrid");
|
||||
grid.arrangeItems();
|
||||
yield waitForCondition(() => !grid.isArranging);
|
||||
|
||||
// grid has rows=2 but 0 items
|
||||
// grid has 2 rows, 6 slots, 0 items
|
||||
ok(grid.isBound, "binding was applied");
|
||||
is(grid.itemCount, 0, "empty grid has 0 items");
|
||||
is(grid.rowCount, 0, "empty grid has 0 rows");
|
||||
is(grid.columnCount, 0, "empty grid has 0 cols");
|
||||
// minSlots attr. creates unpopulated slots
|
||||
is(grid.rowCount, grid.getAttribute("rows"), "empty grid with rows-attribute has that number of rows");
|
||||
is(grid.columnCount, 3, "empty grid has expected number of columns");
|
||||
|
||||
let columnsNode = grid._grid;
|
||||
let cStyle = doc.defaultView.getComputedStyle(columnsNode);
|
||||
is(cStyle.getPropertyValue("-moz-column-count"), "auto", "empty grid has -moz-column-count: auto");
|
||||
// remove rows attribute and allow space for the grid to find its own height
|
||||
// for its number of slots
|
||||
grid.removeAttribute("rows");
|
||||
grid.parentNode.style.height = 20+(grid.tileHeight*grid.minSlots)+"px";
|
||||
|
||||
grid.arrangeItems();
|
||||
yield waitForCondition(() => !grid.isArranging);
|
||||
is(grid.rowCount, grid.minSlots, "empty grid has this.minSlots rows");
|
||||
is(grid.columnCount, 1, "empty grid has 1 column");
|
||||
}
|
||||
});
|
||||
|
||||
@ -211,16 +228,25 @@ gTests.push({
|
||||
is(typeof grid.insertItemAt, "function", "insertItemAt is a function on the grid");
|
||||
|
||||
let arrangeStub = stubMethod(grid, "arrangeItems");
|
||||
let insertedItem = grid.insertItemAt(1, "inserted item", "http://example.com/inserted");
|
||||
let insertedAt0 = grid.insertItemAt(0, "inserted item 0", "http://example.com/inserted0");
|
||||
let insertedAt00 = grid.insertItemAt(0, "inserted item 00", "http://example.com/inserted00");
|
||||
|
||||
ok(insertedItem, "insertItemAt gives back an item");
|
||||
is(grid.items[1], insertedItem, "item is inserted at the correct index");
|
||||
is(insertedItem.getAttribute("label"), "inserted item", "insertItemAt creates item with the correct label");
|
||||
is(insertedItem.getAttribute("value"), "http://example.com/inserted", "insertItemAt creates item with the correct url value");
|
||||
is(grid.items[2].getAttribute("id"), "grid3_item2", "following item ends up at the correct index");
|
||||
is(grid.itemCount, 3, "itemCount is incremented when we insertItemAt");
|
||||
ok(insertedAt0 && insertedAt00, "insertItemAt gives back an item");
|
||||
|
||||
is(arrangeStub.callCount, 1, "arrangeItems is called when we insertItemAt");
|
||||
is(insertedAt0.getAttribute("label"), "inserted item 0", "insertItemAt creates item with the correct label");
|
||||
is(insertedAt0.getAttribute("value"), "http://example.com/inserted0", "insertItemAt creates item with the correct url value");
|
||||
|
||||
is(grid.items[0], insertedAt00, "item is inserted at the correct index");
|
||||
is(grid.children[0], insertedAt00, "first item occupies the first slot");
|
||||
is(grid.items[1], insertedAt0, "item is inserted at the correct index");
|
||||
is(grid.children[1], insertedAt0, "next item occupies the next slot");
|
||||
|
||||
is(grid.items[2].getAttribute("label"), "First item", "Old first item is now at index 2");
|
||||
is(grid.items[3].getAttribute("label"), "2nd item", "Old 2nd item is now at index 3");
|
||||
|
||||
is(grid.itemCount, 4, "itemCount is incremented when we insertItemAt");
|
||||
|
||||
is(arrangeStub.callCount, 2, "arrangeItems is called when we insertItemAt");
|
||||
arrangeStub.restore();
|
||||
}
|
||||
});
|
||||
@ -417,3 +443,172 @@ gTests.push({
|
||||
doc.defaultView.removeEventListener("selectionchange", handler, false);
|
||||
}
|
||||
});
|
||||
|
||||
function gridSlotsSetup() {
|
||||
let grid = this.grid = doc.createElement("richgrid");
|
||||
grid.setAttribute("minSlots", 6);
|
||||
doc.documentElement.appendChild(grid);
|
||||
is(grid.ownerDocument, doc, "created grid in the expected document");
|
||||
}
|
||||
function gridSlotsTearDown() {
|
||||
this.grid && this.grid.parentNode.removeChild(this.grid);
|
||||
}
|
||||
|
||||
gTests.push({
|
||||
desc: "richgrid slots init",
|
||||
setUp: gridSlotsSetup,
|
||||
run: function() {
|
||||
let grid = this.grid;
|
||||
// grid is initially populated with empty slots matching the minSlots attribute
|
||||
is(grid.children.length, 6, "minSlots slots are created");
|
||||
is(grid.itemCount, 0, "slots do not count towards itemCount");
|
||||
ok(Array.every(grid.children, (node) => node.nodeName == 'richgriditem'), "slots have nodeName richgriditem");
|
||||
ok(Array.every(grid.children, isNotBoundByRichGrid_Item), "slots aren't bound by the richgrid-item binding");
|
||||
},
|
||||
tearDown: gridSlotsTearDown
|
||||
});
|
||||
|
||||
gTests.push({
|
||||
desc: "richgrid using slots for items",
|
||||
setUp: gridSlotsSetup, // creates grid with minSlots = num. slots = 6
|
||||
run: function() {
|
||||
let grid = this.grid;
|
||||
let numSlots = grid.getAttribute("minSlots");
|
||||
is(grid.children.length, numSlots);
|
||||
// adding items occupies those slots
|
||||
for (let idx of [0,1,2,3,4,5,6]) {
|
||||
let slot = grid.children[idx];
|
||||
let item = grid.appendItem("item "+idx, "about:mozilla");
|
||||
if (idx < numSlots) {
|
||||
is(grid.children.length, numSlots);
|
||||
is(slot, item, "The same node is reused when an item is assigned to a slot");
|
||||
} else {
|
||||
is(typeof slot, 'undefined');
|
||||
ok(item);
|
||||
is(grid.children.length, grid.itemCount);
|
||||
}
|
||||
}
|
||||
},
|
||||
tearDown: gridSlotsTearDown
|
||||
});
|
||||
|
||||
gTests.push({
|
||||
desc: "richgrid assign and release slots",
|
||||
setUp: function(){
|
||||
info("assign and release slots setUp");
|
||||
this.grid = doc.getElementById("slots_grid");
|
||||
this.grid.scrollIntoView();
|
||||
let rect = this.grid.getBoundingClientRect();
|
||||
info("slots grid at top: " + rect.top + ", window.pageYOffset: " + doc.defaultView.pageYOffset);
|
||||
},
|
||||
run: function() {
|
||||
let grid = this.grid;
|
||||
// start with 5 of 6 slots occupied
|
||||
for (let idx of [0,1,2,3,4]) {
|
||||
let item = grid.appendItem("item "+idx, "about:mozilla");
|
||||
item.setAttribute("id", "test_item_"+idx);
|
||||
}
|
||||
is(grid.itemCount, 5);
|
||||
is(grid.children.length, 6); // see setup, where we init with 6 slots
|
||||
let firstItem = grid.items[0];
|
||||
|
||||
ok(firstItem.ownerDocument, "item has ownerDocument");
|
||||
is(doc, firstItem.ownerDocument, "item's ownerDocument is the document we expect");
|
||||
|
||||
is(firstItem, grid.children[0], "Item and assigned slot are one and the same");
|
||||
is(firstItem.control, grid, "Item is bound and its .control points back at the grid");
|
||||
|
||||
// before releasing, the grid should be nofified of clicks on that slot
|
||||
let testWindow = grid.ownerDocument.defaultView;
|
||||
|
||||
let rect = firstItem.getBoundingClientRect();
|
||||
{
|
||||
let handleStub = stubMethod(grid, 'handleItemClick');
|
||||
// send click to item and wait for next tick;
|
||||
sendElementTap(testWindow, firstItem);
|
||||
yield waitForMs(0);
|
||||
|
||||
is(handleStub.callCount, 1, "handleItemClick was called when we clicked an item");
|
||||
handleStub.restore();
|
||||
}
|
||||
// _releaseSlot is semi-private, we don't expect consumers of the binding to call it
|
||||
// but want to be sure it does what we expect
|
||||
grid._releaseSlot(firstItem);
|
||||
|
||||
is(grid.itemCount, 4, "Releasing a slot gives us one less item");
|
||||
is(firstItem, grid.children[0],"Released slot is still the same node we started with");
|
||||
|
||||
// after releasing, the grid should NOT be nofified of clicks
|
||||
{
|
||||
let handleStub = stubMethod(grid, 'handleItemClick');
|
||||
// send click to item and wait for next tick;
|
||||
sendElementTap(testWindow, firstItem);
|
||||
yield waitForMs(0);
|
||||
|
||||
is(handleStub.callCount, 0, "handleItemClick was NOT called when we clicked a released slot");
|
||||
handleStub.restore();
|
||||
}
|
||||
|
||||
ok(!firstItem.mozMatchesSelector("richgriditem[value]"), "Released slot doesn't match binding selector");
|
||||
ok(isNotBoundByRichGrid_Item(firstItem), "Released slot is no longer bound");
|
||||
|
||||
waitForCondition(() => isNotBoundByRichGrid_Item(firstItem));
|
||||
ok(true, "Slot eventually gets unbound");
|
||||
is(firstItem, grid.children[0], "Released slot is still at expected index in children collection");
|
||||
|
||||
let firstSlot = grid.children[0];
|
||||
firstItem = grid.insertItemAt(0, "New item 0", "about:blank");
|
||||
ok(firstItem == grid.items[0], "insertItemAt 0 creates item at expected index");
|
||||
ok(firstItem == firstSlot, "insertItemAt occupies the released slot with the new item");
|
||||
is(grid.itemCount, 5);
|
||||
is(grid.children.length, 6);
|
||||
is(firstItem.control, grid,"Item is bound and its .control points back at the grid");
|
||||
|
||||
let nextSlotIndex = grid.itemCount;
|
||||
let lastItem = grid.insertItemAt(9, "New item 9", "about:blank");
|
||||
// Check we don't create sparse collection of items
|
||||
is(lastItem, grid.children[nextSlotIndex], "Item is appended at the next index when an out of bounds index is provided");
|
||||
is(grid.children.length, 6);
|
||||
is(grid.itemCount, 6);
|
||||
|
||||
grid.appendItem("one more", "about:blank");
|
||||
is(grid.children.length, 7);
|
||||
is(grid.itemCount, 7);
|
||||
|
||||
// clearAll results in slots being emptied
|
||||
grid.clearAll();
|
||||
is(grid.children.length, 6, "Extra slots are trimmed when we clearAll");
|
||||
ok(!Array.some(grid.children, (node) => node.hasAttribute("value")), "All slots have no value attribute after clearAll")
|
||||
},
|
||||
tearDown: gridSlotsTearDown
|
||||
});
|
||||
|
||||
gTests.push({
|
||||
desc: "richgrid slot management",
|
||||
setUp: gridSlotsSetup,
|
||||
run: function() {
|
||||
let grid = this.grid;
|
||||
// populate grid with some items
|
||||
let numSlots = grid.getAttribute("minSlots");
|
||||
for (let idx of [0,1,2,3,4,5]) {
|
||||
let item = grid.appendItem("item "+idx, "about:mozilla");
|
||||
}
|
||||
|
||||
is(grid.itemCount, 6, "Grid setup with 6 items");
|
||||
is(grid.children.length, 6, "Full grid has the expected number of slots");
|
||||
|
||||
// removing an item creates a replacement slot *on the end of the stack*
|
||||
let item = grid.removeItemAt(0);
|
||||
is(item.getAttribute("label"), "item 0", "removeItemAt gives back the populated node");
|
||||
is(grid.children.length, 6);
|
||||
is(grid.itemCount, 5);
|
||||
is(grid.items[0].getAttribute("label"), "item 1", "removeItemAt removes the node so the nextSibling takes its place");
|
||||
ok(grid.children[5] && !grid.children[5].hasAttribute("value"), "empty slot is added at the end of the existing children");
|
||||
|
||||
let item1 = grid.removeItem(grid.items[0]);
|
||||
is(grid.children.length, 6);
|
||||
is(grid.itemCount, 4);
|
||||
is(grid.items[0].getAttribute("label"), "item 2", "removeItem removes the node so the nextSibling takes its place");
|
||||
},
|
||||
tearDown: gridSlotsTearDown
|
||||
});
|
||||
|
@ -275,6 +275,26 @@ richgriditem[bending] > .tile-content {
|
||||
transform-origin: center center;
|
||||
}
|
||||
|
||||
/* Empty/unused tiles */
|
||||
richgriditem:not([value]) {
|
||||
visibility: hidden;
|
||||
}
|
||||
richgriditem[tiletype="thumbnail"]:not([value]) {
|
||||
visibility: visible;
|
||||
}
|
||||
richgriditem:not([value]) > .tile-content {
|
||||
padding: 10px 14px;
|
||||
}
|
||||
richgriditem[tiletype="thumbnail"]:not([value]) > .tile-content {
|
||||
box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.05);
|
||||
background-image: url("chrome://browser/skin/images/firefox-watermark.png");
|
||||
background-origin: content-box;
|
||||
background-repeat: no-repeat;
|
||||
background-color: rgba(255,255,255, 0.2);
|
||||
background-position: center center;
|
||||
background-size: @grid_row_height@;
|
||||
}
|
||||
|
||||
/* Snapped-view variation
|
||||
We use the compact, single-column grid treatment for <=320px */
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user