зеркало из https://github.com/mozilla/gecko-dev.git
Merge inbound to mozilla-central. a=merge
This commit is contained in:
Коммит
3c9fcd1d07
|
@ -345,7 +345,7 @@ var PlacesUIUtils = {
|
|||
while (Element.isInstance(node)) {
|
||||
if (node._placesView)
|
||||
return node._placesView;
|
||||
if (node.localName == "tree" && node.getAttribute("type") == "places")
|
||||
if (node.localName == "tree" && node.getAttribute("is") == "places-tree")
|
||||
return node;
|
||||
|
||||
node = node.parentNode;
|
||||
|
|
|
@ -38,6 +38,8 @@
|
|||
src="chrome://global/content/globalOverlay.js"/>
|
||||
<script type="application/javascript"
|
||||
src="chrome://browser/content/utilityOverlay.js"/>
|
||||
<script type="application/javascript"
|
||||
src="chrome://browser/content/places/places-tree.js"/>
|
||||
|
||||
#include editBookmarkPanel.inc.xul
|
||||
|
||||
|
|
|
@ -33,6 +33,8 @@
|
|||
src="chrome://browser/content/utilityOverlay.js"/>
|
||||
<script type="application/javascript"
|
||||
src="chrome://browser/content/contentTheme.js"/>
|
||||
<script type="application/javascript"
|
||||
src="chrome://browser/content/places/places-tree.js"/>
|
||||
|
||||
#include placesCommands.inc.xul
|
||||
#include ../../../../toolkit/content/editMenuCommands.inc.xul
|
||||
|
@ -48,7 +50,7 @@
|
|||
|
||||
<tree id="bookmarks-view"
|
||||
class="sidebar-placesTree"
|
||||
type="places"
|
||||
is="places-tree"
|
||||
flex="1"
|
||||
hidecolumnpicker="true"
|
||||
context="placesContext"
|
||||
|
|
|
@ -1078,7 +1078,31 @@ var gEditItemOverlay = {
|
|||
onItemVisited() { },
|
||||
};
|
||||
|
||||
for (let elt of ["folderMenuList", "folderTree", "namePicker",
|
||||
XPCOMUtils.defineLazyGetter(gEditItemOverlay, "_folderTree", () => {
|
||||
if (!customElements.get("places-tree")) {
|
||||
Services.scriptloader.loadSubScript("chrome://browser/content/places/places-tree.js", window);
|
||||
}
|
||||
gEditItemOverlay._element("folderTreeRow").prepend(MozXULElement.parseXULToFragment(`
|
||||
<tree id="editBMPanel_folderTree"
|
||||
flex="1"
|
||||
class="placesTree"
|
||||
is="places-tree"
|
||||
height="150"
|
||||
minheight="150"
|
||||
editable="true"
|
||||
onselect="gEditItemOverlay.onFolderTreeSelect();"
|
||||
disableUserActions="true"
|
||||
hidecolumnpicker="true">
|
||||
<treecols>
|
||||
<treecol anonid="title" flex="1" primary="true" hideheader="true"/>
|
||||
</treecols>
|
||||
<treechildren flex="1"/>
|
||||
</tree>
|
||||
`));
|
||||
return gEditItemOverlay._element("folderTree");
|
||||
});
|
||||
|
||||
for (let elt of ["folderMenuList", "namePicker",
|
||||
"locationField", "keywordField",
|
||||
"tagsField" ]) {
|
||||
let eltScoped = elt;
|
||||
|
|
|
@ -58,22 +58,7 @@
|
|||
|
||||
<vbox id="editBMPanel_folderTreeRow"
|
||||
collapsed="true">
|
||||
<tree id="editBMPanel_folderTree"
|
||||
flex="1"
|
||||
class="placesTree"
|
||||
type="places"
|
||||
height="150"
|
||||
minheight="150"
|
||||
editable="true"
|
||||
onselect="gEditItemOverlay.onFolderTreeSelect();"
|
||||
disableUserActions="true"
|
||||
hidecolumnpicker="true">
|
||||
<treecols>
|
||||
<treecol anonid="title" flex="1" primary="true" hideheader="true"/>
|
||||
</treecols>
|
||||
<treechildren flex="1"/>
|
||||
</tree>
|
||||
|
||||
<!-- editBMPanel_folderTree will go here when this is shown -->
|
||||
<hbox id="editBMPanel_newFolderBox">
|
||||
<button label="&editBookmarkOverlay.newFolderButton.label;"
|
||||
id="editBMPanel_newFolderButton"
|
||||
|
|
|
@ -33,6 +33,8 @@
|
|||
src="chrome://browser/content/utilityOverlay.js"/>
|
||||
<script type="application/javascript"
|
||||
src="chrome://browser/content/contentTheme.js"/>
|
||||
<script type="application/javascript"
|
||||
src="chrome://browser/content/places/places-tree.js"/>
|
||||
|
||||
#include ../../../../toolkit/content/editMenuCommands.inc.xul
|
||||
|
||||
|
@ -82,7 +84,7 @@
|
|||
<tree id="historyTree"
|
||||
class="sidebar-placesTree"
|
||||
flex="1"
|
||||
type="places"
|
||||
is="places-tree"
|
||||
hidecolumnpicker="true"
|
||||
context="placesContext"
|
||||
onkeypress="PlacesUIUtils.onSidebarTreeKeyPress(event);"
|
||||
|
|
|
@ -0,0 +1,764 @@
|
|||
/* 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/. */
|
||||
|
||||
/* import-globals-from controller.js */
|
||||
/* import-globals-from treeView.js */
|
||||
|
||||
// This is loaded into all XUL windows. Wrap in a block to prevent
|
||||
// leaking to window scope.
|
||||
{
|
||||
class MozPlacesTree extends customElements.get("tree") {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.addEventListener("focus", (event) => {
|
||||
this._cachedInsertionPoint = undefined;
|
||||
|
||||
// See select handler. We need the sidebar's places commandset to be
|
||||
// updated as well
|
||||
document.commandDispatcher.updateCommands("focus");
|
||||
});
|
||||
|
||||
this.addEventListener("select", (event) => {
|
||||
this._cachedInsertionPoint = undefined;
|
||||
|
||||
// This additional complexity is here for the sidebars
|
||||
var win = window;
|
||||
while (true) {
|
||||
win.document.commandDispatcher.updateCommands("focus");
|
||||
if (win == window.top)
|
||||
break;
|
||||
|
||||
win = win.parent;
|
||||
}
|
||||
});
|
||||
|
||||
this.addEventListener("dragstart", (event) => {
|
||||
if (event.target.localName != "treechildren")
|
||||
return;
|
||||
|
||||
if (this.disableUserActions) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
let nodes = this.selectedNodes;
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
let node = nodes[i];
|
||||
|
||||
// Disallow dragging the root node of a tree.
|
||||
if (!node.parent) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
// If this node is child of a readonly container or cannot be moved,
|
||||
// we must force a copy.
|
||||
if (!this.controller.canMoveNode(node)) {
|
||||
event.dataTransfer.effectAllowed = "copyLink";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this._controller.setDataTransfer(event);
|
||||
event.stopPropagation();
|
||||
});
|
||||
|
||||
this.addEventListener("dragover", (event) => {
|
||||
if (event.target.localName != "treechildren")
|
||||
return;
|
||||
|
||||
let cell = this.getCellAt(event.clientX, event.clientY);
|
||||
let node = cell.row != -1 ?
|
||||
this.view.nodeForTreeIndex(cell.row) :
|
||||
this.result.root;
|
||||
// cache the dropTarget for the view
|
||||
PlacesControllerDragHelper.currentDropTarget = node;
|
||||
|
||||
// We have to calculate the orientation since view.canDrop will use
|
||||
// it and we want to be consistent with the dropfeedback.
|
||||
let rowHeight = this.rowHeight;
|
||||
let eventY = event.clientY - this.treeBody.boxObject.y -
|
||||
rowHeight * (cell.row - this.getFirstVisibleRow());
|
||||
|
||||
let orientation = Ci.nsITreeView.DROP_BEFORE;
|
||||
|
||||
if (cell.row == -1) {
|
||||
// If the row is not valid we try to insert inside the resultNode.
|
||||
orientation = Ci.nsITreeView.DROP_ON;
|
||||
} else if (PlacesUtils.nodeIsContainer(node) &&
|
||||
eventY > rowHeight * 0.75) {
|
||||
// If we are below the 75% of a container the treeview we try
|
||||
// to drop after the node.
|
||||
orientation = Ci.nsITreeView.DROP_AFTER;
|
||||
} else if (PlacesUtils.nodeIsContainer(node) &&
|
||||
eventY > rowHeight * 0.25) {
|
||||
// If we are below the 25% of a container the treeview we try
|
||||
// to drop inside the node.
|
||||
orientation = Ci.nsITreeView.DROP_ON;
|
||||
}
|
||||
|
||||
if (!this.view.canDrop(cell.row, orientation, event.dataTransfer))
|
||||
return;
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
});
|
||||
|
||||
this.addEventListener("dragend", (event) => {
|
||||
PlacesControllerDragHelper.currentDropTarget = null;
|
||||
});
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
if (this.delayConnectedCallback()) {
|
||||
return;
|
||||
}
|
||||
super.connectedCallback();
|
||||
this._contextMenuShown = false;
|
||||
|
||||
this._active = true;
|
||||
|
||||
// Force an initial build.
|
||||
if (this.place)
|
||||
this.place = this.place;
|
||||
}
|
||||
|
||||
get controller() {
|
||||
return this._controller;
|
||||
}
|
||||
|
||||
set disableUserActions(val) {
|
||||
if (val) this.setAttribute("disableUserActions", "true");
|
||||
else this.removeAttribute("disableUserActions");
|
||||
return val;
|
||||
}
|
||||
|
||||
get disableUserActions() {
|
||||
return this.getAttribute("disableUserActions") == "true";
|
||||
}
|
||||
/**
|
||||
* overriding
|
||||
*/
|
||||
set view(val) {
|
||||
/* eslint-disable no-undef */
|
||||
return Object.getOwnPropertyDescriptor(XULTreeElement.prototype, "view").set.call(this, val);
|
||||
/* eslint-enable no-undef */
|
||||
}
|
||||
|
||||
get view() {
|
||||
try {
|
||||
/* eslint-disable no-undef */
|
||||
return Object.getOwnPropertyDescriptor(XULTreeElement.prototype, "view").get.
|
||||
call(this).wrappedJSObject || null;
|
||||
/* eslint-enable no-undef */
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
get associatedElement() {
|
||||
return this;
|
||||
}
|
||||
|
||||
set flatList(val) {
|
||||
if (this.flatList != val) {
|
||||
this.setAttribute("flatList", val);
|
||||
// reload with the last place set
|
||||
if (this.place)
|
||||
this.place = this.place;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
get flatList() {
|
||||
return this.getAttribute("flatList") == "true";
|
||||
}
|
||||
|
||||
/**
|
||||
* nsIPlacesView
|
||||
*/
|
||||
get result() {
|
||||
try {
|
||||
return this.view.QueryInterface(Ci.nsINavHistoryResultObserver).result;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* nsIPlacesView
|
||||
*/
|
||||
set place(val) {
|
||||
this.setAttribute("place", val);
|
||||
|
||||
let query = {},
|
||||
options = {};
|
||||
PlacesUtils.history.queryStringToQuery(val, query, options);
|
||||
this.load(query.value, options.value);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
get place() {
|
||||
return this.getAttribute("place");
|
||||
}
|
||||
/**
|
||||
* nsIPlacesView
|
||||
*/
|
||||
get hasSelection() {
|
||||
return this.view && this.view.selection.count >= 1;
|
||||
}
|
||||
/**
|
||||
* nsIPlacesView
|
||||
*/
|
||||
get selectedNodes() {
|
||||
let nodes = [];
|
||||
if (!this.hasSelection)
|
||||
return nodes;
|
||||
|
||||
let selection = this.view.selection;
|
||||
let rc = selection.getRangeCount();
|
||||
let resultview = this.view;
|
||||
for (let i = 0; i < rc; ++i) {
|
||||
let min = {},
|
||||
max = {};
|
||||
selection.getRangeAt(i, min, max);
|
||||
for (let j = min.value; j <= max.value; ++j) {
|
||||
nodes.push(resultview.nodeForTreeIndex(j));
|
||||
}
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
/**
|
||||
* nsIPlacesView
|
||||
*/
|
||||
get removableSelectionRanges() {
|
||||
// This property exists in addition to selectedNodes because it
|
||||
// encodes selection ranges (which only occur in list views) into
|
||||
// the return value. For each removed range, the index at which items
|
||||
// will be re-inserted upon the remove transaction being performed is
|
||||
// the first index of the range, so that the view updates correctly.
|
||||
//
|
||||
// For example, if we remove rows 2,3,4 and 7,8 from a list, when we
|
||||
// undo that operation, if we insert what was at row 3 at row 3 again,
|
||||
// it will show up _after_ the item that was at row 5. So we need to
|
||||
// insert all items at row 2, and the tree view will update correctly.
|
||||
//
|
||||
// Also, this function collapses the selection to remove redundant
|
||||
// data, e.g. when deleting this selection:
|
||||
//
|
||||
// http://www.foo.com/
|
||||
// (-) Some Folder
|
||||
// http://www.bar.com/
|
||||
//
|
||||
// ... returning http://www.bar.com/ as part of the selection is
|
||||
// redundant because it is implied by removing "Some Folder". We
|
||||
// filter out all such redundancies since some partial amount of
|
||||
// the folder's children may be selected.
|
||||
//
|
||||
let nodes = [];
|
||||
if (!this.hasSelection)
|
||||
return nodes;
|
||||
|
||||
var selection = this.view.selection;
|
||||
var rc = selection.getRangeCount();
|
||||
var resultview = this.view;
|
||||
// This list is kept independently of the range selected (i.e. OUTSIDE
|
||||
// the for loop) since the row index of a container is unique for the
|
||||
// entire view, and we could have some really wacky selection and we
|
||||
// don't want to blow up.
|
||||
var containers = {};
|
||||
for (var i = 0; i < rc; ++i) {
|
||||
var range = [];
|
||||
var min = {},
|
||||
max = {};
|
||||
selection.getRangeAt(i, min, max);
|
||||
|
||||
for (var j = min.value; j <= max.value; ++j) {
|
||||
if (this.view.isContainer(j))
|
||||
containers[j] = true;
|
||||
if (!(this.view.getParentIndex(j) in containers))
|
||||
range.push(resultview.nodeForTreeIndex(j));
|
||||
}
|
||||
nodes.push(range);
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
/**
|
||||
* nsIPlacesView
|
||||
*/
|
||||
get draggableSelection() {
|
||||
return this.selectedNodes;
|
||||
}
|
||||
/**
|
||||
* nsIPlacesView
|
||||
*/
|
||||
get selectedNode() {
|
||||
var view = this.view;
|
||||
if (!view || view.selection.count != 1)
|
||||
return null;
|
||||
|
||||
var selection = view.selection;
|
||||
var min = {},
|
||||
max = {};
|
||||
selection.getRangeAt(0, min, max);
|
||||
|
||||
return this.view.nodeForTreeIndex(min.value);
|
||||
}
|
||||
/**
|
||||
* nsIPlacesView
|
||||
*/
|
||||
get insertionPoint() {
|
||||
// invalidated on selection and focus changes
|
||||
if (this._cachedInsertionPoint !== undefined)
|
||||
return this._cachedInsertionPoint;
|
||||
|
||||
// there is no insertion point for history queries
|
||||
// so bail out now and save a lot of work when updating commands
|
||||
var resultNode = this.result.root;
|
||||
if (PlacesUtils.nodeIsQuery(resultNode) &&
|
||||
PlacesUtils.asQuery(resultNode).queryOptions.queryType ==
|
||||
Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY)
|
||||
return this._cachedInsertionPoint = null;
|
||||
|
||||
var orientation = Ci.nsITreeView.DROP_BEFORE;
|
||||
// If there is no selection, insert at the end of the container.
|
||||
if (!this.hasSelection) {
|
||||
var index = this.view.rowCount - 1;
|
||||
this._cachedInsertionPoint =
|
||||
this._getInsertionPoint(index, orientation);
|
||||
return this._cachedInsertionPoint;
|
||||
}
|
||||
|
||||
// This is a two-part process. The first part is determining the drop
|
||||
// orientation.
|
||||
// * The default orientation is to drop _before_ the selected item.
|
||||
// * If the selected item is a container, the default orientation
|
||||
// is to drop _into_ that container.
|
||||
//
|
||||
// Warning: It may be tempting to use tree indexes in this code, but
|
||||
// you must not, since the tree is nested and as your tree
|
||||
// index may change when folders before you are opened and
|
||||
// closed. You must convert your tree index to a node, and
|
||||
// then use getChildIndex to find your absolute index in
|
||||
// the parent container instead.
|
||||
//
|
||||
var resultView = this.view;
|
||||
var selection = resultView.selection;
|
||||
var rc = selection.getRangeCount();
|
||||
var min = {},
|
||||
max = {};
|
||||
selection.getRangeAt(rc - 1, min, max);
|
||||
|
||||
// If the sole selection is a container, and we are not in
|
||||
// a flatlist, insert into it.
|
||||
// Note that this only applies to _single_ selections,
|
||||
// if the last element within a multi-selection is a
|
||||
// container, insert _adjacent_ to the selection.
|
||||
//
|
||||
// If the sole selection is the bookmarks toolbar folder, we insert
|
||||
// into it even if it is not opened
|
||||
if (selection.count == 1 && resultView.isContainer(max.value) &&
|
||||
!this.flatList)
|
||||
orientation = Ci.nsITreeView.DROP_ON;
|
||||
|
||||
this._cachedInsertionPoint =
|
||||
this._getInsertionPoint(max.value, orientation);
|
||||
return this._cachedInsertionPoint;
|
||||
}
|
||||
|
||||
get ownerWindow() {
|
||||
return window;
|
||||
}
|
||||
|
||||
set active(val) {
|
||||
return this._active = val;
|
||||
}
|
||||
|
||||
get active() {
|
||||
return this._active;
|
||||
}
|
||||
|
||||
applyFilter(filterString, folderRestrict, includeHidden) {
|
||||
// preserve grouping
|
||||
var queryNode = PlacesUtils.asQuery(this.result.root);
|
||||
var options = queryNode.queryOptions.clone();
|
||||
|
||||
// Make sure we're getting uri results.
|
||||
// We do not yet support searching into grouped queries or into
|
||||
// tag containers, so we must fall to the default case.
|
||||
if (PlacesUtils.nodeIsHistoryContainer(queryNode) ||
|
||||
PlacesUtils.nodeIsTagQuery(queryNode) ||
|
||||
options.resultType == options.RESULTS_AS_TAGS_ROOT ||
|
||||
options.resultType == options.RESULTS_AS_ROOTS_QUERY)
|
||||
options.resultType = options.RESULTS_AS_URI;
|
||||
|
||||
var query = PlacesUtils.history.getNewQuery();
|
||||
query.searchTerms = filterString;
|
||||
|
||||
if (folderRestrict) {
|
||||
query.setParents(folderRestrict, folderRestrict.length);
|
||||
options.queryType = options.QUERY_TYPE_BOOKMARKS;
|
||||
}
|
||||
|
||||
options.includeHidden = !!includeHidden;
|
||||
|
||||
this.load(query, options);
|
||||
}
|
||||
|
||||
load(query, options) {
|
||||
let result = PlacesUtils.history
|
||||
.executeQuery(query, options);
|
||||
|
||||
if (!this._controller) {
|
||||
this._controller = new PlacesController(this);
|
||||
this._controller.disableUserActions = this.disableUserActions;
|
||||
this.controllers.appendController(this._controller);
|
||||
}
|
||||
|
||||
let treeView = new PlacesTreeView(this);
|
||||
|
||||
// Observer removal is done within the view itself. When the tree
|
||||
// goes away, view.setTree(null) is called, which then
|
||||
// calls removeObserver.
|
||||
result.addObserver(treeView);
|
||||
this.view = treeView;
|
||||
|
||||
if (this.getAttribute("selectfirstnode") == "true" && treeView.rowCount > 0) {
|
||||
treeView.selection.select(0);
|
||||
}
|
||||
|
||||
this._cachedInsertionPoint = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Causes a particular node represented by the specified placeURI to be
|
||||
* selected in the tree. All containers above the node in the hierarchy
|
||||
* will be opened, so that the node is visible.
|
||||
*/
|
||||
selectPlaceURI(placeURI) {
|
||||
// Do nothing if a node matching the given uri is already selected
|
||||
if (this.hasSelection && this.selectedNode.uri == placeURI)
|
||||
return;
|
||||
|
||||
function findNode(container, nodesURIChecked) {
|
||||
var containerURI = container.uri;
|
||||
if (containerURI == placeURI)
|
||||
return container;
|
||||
if (nodesURIChecked.includes(containerURI))
|
||||
return null;
|
||||
|
||||
// never check the contents of the same query
|
||||
nodesURIChecked.push(containerURI);
|
||||
|
||||
var wasOpen = container.containerOpen;
|
||||
if (!wasOpen)
|
||||
container.containerOpen = true;
|
||||
for (var i = 0; i < container.childCount; ++i) {
|
||||
var child = container.getChild(i);
|
||||
var childURI = child.uri;
|
||||
if (childURI == placeURI)
|
||||
return child;
|
||||
else if (PlacesUtils.nodeIsContainer(child)) {
|
||||
var nested = findNode(PlacesUtils.asContainer(child), nodesURIChecked);
|
||||
if (nested)
|
||||
return nested;
|
||||
}
|
||||
}
|
||||
|
||||
if (!wasOpen)
|
||||
container.containerOpen = false;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
var container = this.result.root;
|
||||
console.assert(container, "No result, cannot select place URI!");
|
||||
if (!container)
|
||||
return;
|
||||
|
||||
var child = findNode(container, []);
|
||||
if (child)
|
||||
this.selectNode(child);
|
||||
else {
|
||||
// If the specified child could not be located, clear the selection
|
||||
var selection = this.view.selection;
|
||||
selection.clearSelection();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Causes a particular node to be selected in the tree, resulting in all
|
||||
* containers above the node in the hierarchy to be opened, so that the
|
||||
* node is visible.
|
||||
*/
|
||||
selectNode(node) {
|
||||
var view = this.view;
|
||||
|
||||
var parent = node.parent;
|
||||
if (parent && !parent.containerOpen) {
|
||||
// Build a list of all of the nodes that are the parent of this one
|
||||
// in the result.
|
||||
var parents = [];
|
||||
var root = this.result.root;
|
||||
while (parent && parent != root) {
|
||||
parents.push(parent);
|
||||
parent = parent.parent;
|
||||
}
|
||||
|
||||
// Walk the list backwards (opening from the root of the hierarchy)
|
||||
// opening each folder as we go.
|
||||
for (var i = parents.length - 1; i >= 0; --i) {
|
||||
let index = view.treeIndexForNode(parents[i]);
|
||||
if (index != -1 &&
|
||||
view.isContainer(index) && !view.isContainerOpen(index))
|
||||
view.toggleOpenState(index);
|
||||
}
|
||||
// Select the specified node...
|
||||
}
|
||||
|
||||
let index = view.treeIndexForNode(node);
|
||||
if (index == -1)
|
||||
return;
|
||||
|
||||
view.selection.select(index);
|
||||
// ... and ensure it's visible, not scrolled off somewhere.
|
||||
this.ensureRowIsVisible(index);
|
||||
}
|
||||
|
||||
toggleCutNode(aNode, aValue) {
|
||||
this.view.toggleCutNode(aNode, aValue);
|
||||
}
|
||||
|
||||
_getInsertionPoint(index, orientation) {
|
||||
var result = this.result;
|
||||
var resultview = this.view;
|
||||
var container = result.root;
|
||||
var dropNearNode = null;
|
||||
console.assert(container, "null container");
|
||||
// When there's no selection, assume the container is the container
|
||||
// the view is populated from (i.e. the result's itemId).
|
||||
if (index != -1) {
|
||||
var lastSelected = resultview.nodeForTreeIndex(index);
|
||||
if (resultview.isContainer(index) && orientation == Ci.nsITreeView.DROP_ON) {
|
||||
// If the last selected item is an open container, append _into_
|
||||
// it, rather than insert adjacent to it.
|
||||
container = lastSelected;
|
||||
index = -1;
|
||||
} else if (lastSelected.containerOpen &&
|
||||
orientation == Ci.nsITreeView.DROP_AFTER &&
|
||||
lastSelected.hasChildren) {
|
||||
// If the last selected item is an open container and the user is
|
||||
// trying to drag into it as a first item, really insert into it.
|
||||
container = lastSelected;
|
||||
orientation = Ci.nsITreeView.DROP_ON;
|
||||
index = 0;
|
||||
} else {
|
||||
// Use the last-selected node's container.
|
||||
container = lastSelected.parent;
|
||||
|
||||
// See comment in the treeView.js's copy of this method
|
||||
if (!container || !container.containerOpen)
|
||||
return null;
|
||||
|
||||
// Avoid the potentially expensive call to getChildIndex
|
||||
// if we know this container doesn't allow insertion
|
||||
if (this.controller.disallowInsertion(container))
|
||||
return null;
|
||||
|
||||
var queryOptions = PlacesUtils.asQuery(result.root).queryOptions;
|
||||
if (queryOptions.sortingMode !=
|
||||
Ci.nsINavHistoryQueryOptions.SORT_BY_NONE) {
|
||||
// If we are within a sorted view, insert at the end
|
||||
index = -1;
|
||||
} else if (queryOptions.excludeItems ||
|
||||
queryOptions.excludeQueries) {
|
||||
// Some item may be invisible, insert near last selected one.
|
||||
// We don't replace index here to avoid requests to the db,
|
||||
// instead it will be calculated later by the controller.
|
||||
index = -1;
|
||||
dropNearNode = lastSelected;
|
||||
} else {
|
||||
var lsi = container.getChildIndex(lastSelected);
|
||||
index = orientation == Ci.nsITreeView.DROP_BEFORE ? lsi : lsi + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.controller.disallowInsertion(container))
|
||||
return null;
|
||||
|
||||
let tagName = PlacesUtils.nodeIsTagQuery(container) ?
|
||||
PlacesUtils.asQuery(container).query.tags[0] : null;
|
||||
|
||||
return new PlacesInsertionPoint({
|
||||
parentId: PlacesUtils.getConcreteItemId(container),
|
||||
parentGuid: PlacesUtils.getConcreteItemGuid(container),
|
||||
index,
|
||||
orientation,
|
||||
tagName,
|
||||
dropNearNode,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* nsIPlacesView
|
||||
*/
|
||||
selectAll() {
|
||||
this.view.selection.selectAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will select the first node in the tree that matches
|
||||
* each given item guid. It will open any folder nodes that it needs
|
||||
* to in order to show the selected items.
|
||||
*/
|
||||
selectItems(aGuids, aOpenContainers) {
|
||||
// Never open containers in flat lists.
|
||||
if (this.flatList)
|
||||
aOpenContainers = false;
|
||||
// By default, we do search and select within containers which were
|
||||
// closed (note that containers in which nodes were not found are
|
||||
// closed).
|
||||
if (aOpenContainers === undefined)
|
||||
aOpenContainers = true;
|
||||
|
||||
var guids = aGuids; // don't manipulate the caller's array
|
||||
|
||||
// Array of nodes found by findNodes which are to be selected
|
||||
var nodes = [];
|
||||
|
||||
// Array of nodes found by findNodes which should be opened
|
||||
var nodesToOpen = [];
|
||||
|
||||
// A set of GUIDs of container-nodes that were previously searched,
|
||||
// and thus shouldn't be searched again. This is empty at the initial
|
||||
// start of the recursion and gets filled in as the recursion
|
||||
// progresses.
|
||||
var checkedGuidsSet = new Set();
|
||||
|
||||
/**
|
||||
* Recursively search through a node's children for items
|
||||
* with the given GUIDs. When a matching item is found, remove its GUID
|
||||
* from the GUIDs array, and add the found node to the nodes dictionary.
|
||||
*
|
||||
* NOTE: This method will leave open any node that had matching items
|
||||
* in its subtree.
|
||||
*/
|
||||
function findNodes(node) {
|
||||
var foundOne = false;
|
||||
// See if node matches an ID we wanted; add to results.
|
||||
// For simple folder queries, check both itemId and the concrete
|
||||
// item id.
|
||||
var index = guids.indexOf(node.bookmarkGuid);
|
||||
if (index == -1) {
|
||||
let concreteGuid = PlacesUtils.getConcreteItemGuid(node);
|
||||
if (concreteGuid != node.bookmarkGuid) {
|
||||
index = guids.indexOf(concreteGuid);
|
||||
}
|
||||
}
|
||||
|
||||
if (index != -1) {
|
||||
nodes.push(node);
|
||||
foundOne = true;
|
||||
guids.splice(index, 1);
|
||||
}
|
||||
|
||||
var concreteGuid = PlacesUtils.getConcreteItemGuid(node);
|
||||
if (guids.length == 0 || !PlacesUtils.nodeIsContainer(node) ||
|
||||
checkedGuidsSet.has(concreteGuid))
|
||||
return foundOne;
|
||||
|
||||
// Only follow a query if it has been been explicitly opened by the
|
||||
// caller. We support the "AllBookmarks" case to allow callers to
|
||||
// specify just the top-level bookmark folders.
|
||||
let shouldOpen = aOpenContainers && (PlacesUtils.nodeIsFolder(node) ||
|
||||
(PlacesUtils.nodeIsQuery(node) && node.bookmarkGuid == PlacesUIUtils.virtualAllBookmarksGuid));
|
||||
|
||||
PlacesUtils.asContainer(node);
|
||||
if (!node.containerOpen && !shouldOpen)
|
||||
return foundOne;
|
||||
|
||||
checkedGuidsSet.add(concreteGuid);
|
||||
|
||||
// Remember the beginning state so that we can re-close
|
||||
// this node if we don't find any additional results here.
|
||||
var previousOpenness = node.containerOpen;
|
||||
node.containerOpen = true;
|
||||
for (var child = 0; child < node.childCount && guids.length > 0; child++) {
|
||||
var childNode = node.getChild(child);
|
||||
var found = findNodes(childNode);
|
||||
if (!foundOne)
|
||||
foundOne = found;
|
||||
}
|
||||
|
||||
// If we didn't find any additional matches in this node's
|
||||
// subtree, revert the node to its previous openness.
|
||||
if (foundOne)
|
||||
nodesToOpen.unshift(node);
|
||||
node.containerOpen = previousOpenness;
|
||||
return foundOne;
|
||||
}
|
||||
|
||||
// Disable notifications while looking for nodes.
|
||||
let result = this.result;
|
||||
let didSuppressNotifications = result.suppressNotifications;
|
||||
if (!didSuppressNotifications)
|
||||
result.suppressNotifications = true;
|
||||
try {
|
||||
findNodes(this.result.root);
|
||||
} finally {
|
||||
if (!didSuppressNotifications)
|
||||
result.suppressNotifications = false;
|
||||
}
|
||||
|
||||
// For all the nodes we've found, highlight the corresponding
|
||||
// index in the tree.
|
||||
var resultview = this.view;
|
||||
var selection = this.view.selection;
|
||||
selection.selectEventsSuppressed = true;
|
||||
selection.clearSelection();
|
||||
// Open nodes containing found items
|
||||
for (let i = 0; i < nodesToOpen.length; i++) {
|
||||
nodesToOpen[i].containerOpen = true;
|
||||
}
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
var index = resultview.treeIndexForNode(nodes[i]);
|
||||
if (index == -1)
|
||||
continue;
|
||||
selection.rangedSelect(index, index, true);
|
||||
}
|
||||
selection.selectEventsSuppressed = false;
|
||||
}
|
||||
|
||||
buildContextMenu(aPopup) {
|
||||
this._contextMenuShown = true;
|
||||
return this.controller.buildContextMenu(aPopup);
|
||||
}
|
||||
|
||||
destroyContextMenu(aPopup) {}
|
||||
disconnectedCallback() {
|
||||
// Unregister the controllber before unlinking the view, otherwise it
|
||||
// may still try to update commands on a view with a null result.
|
||||
if (this._controller) {
|
||||
this._controller.terminate();
|
||||
this.controllers.removeController(this._controller);
|
||||
}
|
||||
|
||||
if (this.view) {
|
||||
this.view.uninit();
|
||||
}
|
||||
// view.setTree(null) will be called upon unsetting the view, which
|
||||
// breaks the reference cycle between the PlacesTreeView and result.
|
||||
// See the "setTree" method of PlacesTreeView in treeView.js.
|
||||
this.view = null;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("places-tree", MozPlacesTree, {
|
||||
extends: "tree",
|
||||
});
|
||||
}
|
|
@ -2,11 +2,7 @@
|
|||
* 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/. */
|
||||
|
||||
tree[type="places"] {
|
||||
-moz-binding: url("chrome://browser/content/places/tree.xml#places-tree");
|
||||
}
|
||||
|
||||
tree[type="places"] > treechildren::-moz-tree-cell {
|
||||
tree[is="places-tree"] > treechildren::-moz-tree-cell {
|
||||
/* ensure we use the direction of the website title / url instead of the
|
||||
* browser locale */
|
||||
unicode-bidi: plaintext;
|
||||
|
|
|
@ -71,6 +71,8 @@
|
|||
src="chrome://browser/content/downloads/allDownloadsView.js"/>
|
||||
<script type="application/javascript"
|
||||
src="chrome://global/content/contentAreaUtils.js"/>
|
||||
<script type="application/javascript"
|
||||
src="chrome://browser/content/places/places-tree.js"/>
|
||||
|
||||
<commandset id="organizerCommandSet">
|
||||
<command id="OrganizerCommand_find:all"
|
||||
|
@ -357,7 +359,7 @@
|
|||
<hbox flex="1" id="placesView">
|
||||
<tree id="placesList"
|
||||
class="plain placesTree"
|
||||
type="places"
|
||||
is="places-tree"
|
||||
hidecolumnpicker="true" context="placesContext"
|
||||
onselect="PlacesOrganizer.onPlaceSelected(true);"
|
||||
onclick="PlacesOrganizer.onPlacesListClick(event);"
|
||||
|
@ -382,7 +384,7 @@
|
|||
context="placesContext"
|
||||
hidecolumnpicker="true"
|
||||
flex="1"
|
||||
type="places"
|
||||
is="places-tree"
|
||||
flatList="true"
|
||||
selectfirstnode="true"
|
||||
enableColumnDrag="true"
|
||||
|
|
|
@ -1,781 +0,0 @@
|
|||
<?xml version="1.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/. -->
|
||||
|
||||
<!-- import-globals-from controller.js -->
|
||||
<!-- import-globals-from treeView.js -->
|
||||
|
||||
<bindings id="placesTreeBindings"
|
||||
xmlns="http://www.mozilla.org/xbl"
|
||||
xmlns:xbl="http://www.mozilla.org/xbl"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<binding id="places-tree" extends="chrome://global/content/bindings/tree.xml#tree">
|
||||
<implementation>
|
||||
<constructor><![CDATA[
|
||||
// Force an initial build.
|
||||
if (this.place)
|
||||
this.place = this.place;
|
||||
]]></constructor>
|
||||
|
||||
<destructor><![CDATA[
|
||||
// Unregister the controllber before unlinking the view, otherwise it
|
||||
// may still try to update commands on a view with a null result.
|
||||
if (this._controller) {
|
||||
this._controller.terminate();
|
||||
this.controllers.removeController(this._controller);
|
||||
}
|
||||
|
||||
if (this.view) {
|
||||
this.view.uninit();
|
||||
}
|
||||
// view.setTree(null) will be called upon unsetting the view, which
|
||||
// breaks the reference cycle between the PlacesTreeView and result.
|
||||
// See the "setTree" method of PlacesTreeView in treeView.js.
|
||||
this.view = null;
|
||||
]]></destructor>
|
||||
|
||||
<property name="controller"
|
||||
readonly="true"
|
||||
onget="return this._controller"/>
|
||||
|
||||
<property name="disableUserActions"
|
||||
onget="return this.getAttribute('disableUserActions') == 'true';"
|
||||
onset="if (val) this.setAttribute('disableUserActions', 'true');
|
||||
else this.removeAttribute('disableUserActions'); return val;"/>
|
||||
|
||||
<!-- overriding -->
|
||||
<property name="view">
|
||||
<getter><![CDATA[
|
||||
try {
|
||||
/* eslint-disable no-undef */
|
||||
return Object.getOwnPropertyDescriptor(XULTreeElement.prototype, "view").get.
|
||||
call(this).wrappedJSObject || null;
|
||||
/* eslint-enable no-undef */
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
]]></getter>
|
||||
<setter><![CDATA[
|
||||
/* eslint-disable no-undef */
|
||||
return Object.getOwnPropertyDescriptor(XULTreeElement.prototype, "view").set.call(this, val);
|
||||
/* eslint-enable no-undef */
|
||||
]]></setter>
|
||||
</property>
|
||||
|
||||
<property name="associatedElement"
|
||||
readonly="true"
|
||||
onget="return this"/>
|
||||
|
||||
<method name="applyFilter">
|
||||
<parameter name="filterString"/>
|
||||
<parameter name="folderRestrict"/>
|
||||
<parameter name="includeHidden"/>
|
||||
<body><![CDATA[
|
||||
// preserve grouping
|
||||
var queryNode = PlacesUtils.asQuery(this.result.root);
|
||||
var options = queryNode.queryOptions.clone();
|
||||
|
||||
// Make sure we're getting uri results.
|
||||
// We do not yet support searching into grouped queries or into
|
||||
// tag containers, so we must fall to the default case.
|
||||
if (PlacesUtils.nodeIsHistoryContainer(queryNode) ||
|
||||
PlacesUtils.nodeIsTagQuery(queryNode) ||
|
||||
options.resultType == options.RESULTS_AS_TAGS_ROOT ||
|
||||
options.resultType == options.RESULTS_AS_ROOTS_QUERY)
|
||||
options.resultType = options.RESULTS_AS_URI;
|
||||
|
||||
var query = PlacesUtils.history.getNewQuery();
|
||||
query.searchTerms = filterString;
|
||||
|
||||
if (folderRestrict) {
|
||||
query.setParents(folderRestrict, folderRestrict.length);
|
||||
options.queryType = options.QUERY_TYPE_BOOKMARKS;
|
||||
}
|
||||
|
||||
options.includeHidden = !!includeHidden;
|
||||
|
||||
this.load(query, options);
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="load">
|
||||
<parameter name="query"/>
|
||||
<parameter name="options"/>
|
||||
<body><![CDATA[
|
||||
let result = PlacesUtils.history
|
||||
.executeQuery(query, options);
|
||||
if (!this._controller) {
|
||||
this._controller = new PlacesController(this);
|
||||
this._controller.disableUserActions = this.disableUserActions;
|
||||
this.controllers.appendController(this._controller);
|
||||
}
|
||||
|
||||
let treeView = new PlacesTreeView(this);
|
||||
|
||||
// Observer removal is done within the view itself. When the tree
|
||||
// goes away, view.setTree(null) is called, which then
|
||||
// calls removeObserver.
|
||||
result.addObserver(treeView);
|
||||
this.view = treeView;
|
||||
|
||||
if (this.getAttribute("selectfirstnode") == "true" && treeView.rowCount > 0) {
|
||||
treeView.selection.select(0);
|
||||
}
|
||||
|
||||
this._cachedInsertionPoint = undefined;
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<property name="flatList">
|
||||
<getter><![CDATA[
|
||||
return this.getAttribute("flatList") == "true";
|
||||
]]></getter>
|
||||
<setter><![CDATA[
|
||||
if (this.flatList != val) {
|
||||
this.setAttribute("flatList", val);
|
||||
// reload with the last place set
|
||||
if (this.place)
|
||||
this.place = this.place;
|
||||
}
|
||||
return val;
|
||||
]]></setter>
|
||||
</property>
|
||||
|
||||
<!--
|
||||
Causes a particular node represented by the specified placeURI to be
|
||||
selected in the tree. All containers above the node in the hierarchy
|
||||
will be opened, so that the node is visible.
|
||||
-->
|
||||
<method name="selectPlaceURI">
|
||||
<parameter name="placeURI"/>
|
||||
<body><![CDATA[
|
||||
// Do nothing if a node matching the given uri is already selected
|
||||
if (this.hasSelection && this.selectedNode.uri == placeURI)
|
||||
return;
|
||||
|
||||
function findNode(container, nodesURIChecked) {
|
||||
var containerURI = container.uri;
|
||||
if (containerURI == placeURI)
|
||||
return container;
|
||||
if (nodesURIChecked.includes(containerURI))
|
||||
return null;
|
||||
|
||||
// never check the contents of the same query
|
||||
nodesURIChecked.push(containerURI);
|
||||
|
||||
var wasOpen = container.containerOpen;
|
||||
if (!wasOpen)
|
||||
container.containerOpen = true;
|
||||
for (var i = 0; i < container.childCount; ++i) {
|
||||
var child = container.getChild(i);
|
||||
var childURI = child.uri;
|
||||
if (childURI == placeURI)
|
||||
return child;
|
||||
else if (PlacesUtils.nodeIsContainer(child)) {
|
||||
var nested = findNode(PlacesUtils.asContainer(child), nodesURIChecked);
|
||||
if (nested)
|
||||
return nested;
|
||||
}
|
||||
}
|
||||
|
||||
if (!wasOpen)
|
||||
container.containerOpen = false;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
var container = this.result.root;
|
||||
console.assert(container, "No result, cannot select place URI!");
|
||||
if (!container)
|
||||
return;
|
||||
|
||||
var child = findNode(container, []);
|
||||
if (child)
|
||||
this.selectNode(child);
|
||||
else {
|
||||
// If the specified child could not be located, clear the selection
|
||||
var selection = this.view.selection;
|
||||
selection.clearSelection();
|
||||
}
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
Causes a particular node to be selected in the tree, resulting in all
|
||||
containers above the node in the hierarchy to be opened, so that the
|
||||
node is visible.
|
||||
-->
|
||||
<method name="selectNode">
|
||||
<parameter name="node"/>
|
||||
<body><![CDATA[
|
||||
var view = this.view;
|
||||
|
||||
var parent = node.parent;
|
||||
if (parent && !parent.containerOpen) {
|
||||
// Build a list of all of the nodes that are the parent of this one
|
||||
// in the result.
|
||||
var parents = [];
|
||||
var root = this.result.root;
|
||||
while (parent && parent != root) {
|
||||
parents.push(parent);
|
||||
parent = parent.parent;
|
||||
}
|
||||
|
||||
// Walk the list backwards (opening from the root of the hierarchy)
|
||||
// opening each folder as we go.
|
||||
for (var i = parents.length - 1; i >= 0; --i) {
|
||||
let index = view.treeIndexForNode(parents[i]);
|
||||
if (index != -1 &&
|
||||
view.isContainer(index) && !view.isContainerOpen(index))
|
||||
view.toggleOpenState(index);
|
||||
}
|
||||
// Select the specified node...
|
||||
}
|
||||
|
||||
let index = view.treeIndexForNode(node);
|
||||
if (index == -1)
|
||||
return;
|
||||
|
||||
view.selection.select(index);
|
||||
// ... and ensure it's visible, not scrolled off somewhere.
|
||||
this.ensureRowIsVisible(index);
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<!-- nsIPlacesView -->
|
||||
<property name="result">
|
||||
<getter><![CDATA[
|
||||
try {
|
||||
return this.view.QueryInterface(Ci.nsINavHistoryResultObserver).result;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
]]></getter>
|
||||
</property>
|
||||
|
||||
<!-- nsIPlacesView -->
|
||||
<property name="place">
|
||||
<getter><![CDATA[
|
||||
return this.getAttribute("place");
|
||||
]]></getter>
|
||||
<setter><![CDATA[
|
||||
this.setAttribute("place", val);
|
||||
|
||||
let query = {}, options = {};
|
||||
PlacesUtils.history.queryStringToQuery(val, query, options);
|
||||
this.load(query.value, options.value);
|
||||
|
||||
return val;
|
||||
]]></setter>
|
||||
</property>
|
||||
|
||||
<!-- nsIPlacesView -->
|
||||
<property name="hasSelection">
|
||||
<getter><![CDATA[
|
||||
return this.view && this.view.selection.count >= 1;
|
||||
]]></getter>
|
||||
</property>
|
||||
|
||||
<!-- nsIPlacesView -->
|
||||
<property name="selectedNodes">
|
||||
<getter><![CDATA[
|
||||
let nodes = [];
|
||||
if (!this.hasSelection)
|
||||
return nodes;
|
||||
|
||||
let selection = this.view.selection;
|
||||
let rc = selection.getRangeCount();
|
||||
let resultview = this.view;
|
||||
for (let i = 0; i < rc; ++i) {
|
||||
let min = { }, max = { };
|
||||
selection.getRangeAt(i, min, max);
|
||||
for (let j = min.value; j <= max.value; ++j) {
|
||||
nodes.push(resultview.nodeForTreeIndex(j));
|
||||
}
|
||||
}
|
||||
return nodes;
|
||||
]]></getter>
|
||||
</property>
|
||||
|
||||
<method name="toggleCutNode">
|
||||
<parameter name="aNode"/>
|
||||
<parameter name="aValue"/>
|
||||
<body><![CDATA[
|
||||
this.view.toggleCutNode(aNode, aValue);
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<!-- nsIPlacesView -->
|
||||
<property name="removableSelectionRanges">
|
||||
<getter><![CDATA[
|
||||
// This property exists in addition to selectedNodes because it
|
||||
// encodes selection ranges (which only occur in list views) into
|
||||
// the return value. For each removed range, the index at which items
|
||||
// will be re-inserted upon the remove transaction being performed is
|
||||
// the first index of the range, so that the view updates correctly.
|
||||
//
|
||||
// For example, if we remove rows 2,3,4 and 7,8 from a list, when we
|
||||
// undo that operation, if we insert what was at row 3 at row 3 again,
|
||||
// it will show up _after_ the item that was at row 5. So we need to
|
||||
// insert all items at row 2, and the tree view will update correctly.
|
||||
//
|
||||
// Also, this function collapses the selection to remove redundant
|
||||
// data, e.g. when deleting this selection:
|
||||
//
|
||||
// http://www.foo.com/
|
||||
// (-) Some Folder
|
||||
// http://www.bar.com/
|
||||
//
|
||||
// ... returning http://www.bar.com/ as part of the selection is
|
||||
// redundant because it is implied by removing "Some Folder". We
|
||||
// filter out all such redundancies since some partial amount of
|
||||
// the folder's children may be selected.
|
||||
//
|
||||
let nodes = [];
|
||||
if (!this.hasSelection)
|
||||
return nodes;
|
||||
|
||||
var selection = this.view.selection;
|
||||
var rc = selection.getRangeCount();
|
||||
var resultview = this.view;
|
||||
// This list is kept independently of the range selected (i.e. OUTSIDE
|
||||
// the for loop) since the row index of a container is unique for the
|
||||
// entire view, and we could have some really wacky selection and we
|
||||
// don't want to blow up.
|
||||
var containers = { };
|
||||
for (var i = 0; i < rc; ++i) {
|
||||
var range = [];
|
||||
var min = { }, max = { };
|
||||
selection.getRangeAt(i, min, max);
|
||||
|
||||
for (var j = min.value; j <= max.value; ++j) {
|
||||
if (this.view.isContainer(j))
|
||||
containers[j] = true;
|
||||
if (!(this.view.getParentIndex(j) in containers))
|
||||
range.push(resultview.nodeForTreeIndex(j));
|
||||
}
|
||||
nodes.push(range);
|
||||
}
|
||||
return nodes;
|
||||
]]></getter>
|
||||
</property>
|
||||
|
||||
<!-- nsIPlacesView -->
|
||||
<property name="draggableSelection"
|
||||
onget="return this.selectedNodes"/>
|
||||
|
||||
<!-- nsIPlacesView -->
|
||||
<property name="selectedNode">
|
||||
<getter><![CDATA[
|
||||
var view = this.view;
|
||||
if (!view || view.selection.count != 1)
|
||||
return null;
|
||||
|
||||
var selection = view.selection;
|
||||
var min = { }, max = { };
|
||||
selection.getRangeAt(0, min, max);
|
||||
|
||||
return this.view.nodeForTreeIndex(min.value);
|
||||
]]></getter>
|
||||
</property>
|
||||
|
||||
<!-- nsIPlacesView -->
|
||||
<property name="insertionPoint">
|
||||
<getter><![CDATA[
|
||||
// invalidated on selection and focus changes
|
||||
if (this._cachedInsertionPoint !== undefined)
|
||||
return this._cachedInsertionPoint;
|
||||
|
||||
// there is no insertion point for history queries
|
||||
// so bail out now and save a lot of work when updating commands
|
||||
var resultNode = this.result.root;
|
||||
if (PlacesUtils.nodeIsQuery(resultNode) &&
|
||||
PlacesUtils.asQuery(resultNode).queryOptions.queryType ==
|
||||
Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY)
|
||||
return this._cachedInsertionPoint = null;
|
||||
|
||||
var orientation = Ci.nsITreeView.DROP_BEFORE;
|
||||
// If there is no selection, insert at the end of the container.
|
||||
if (!this.hasSelection) {
|
||||
var index = this.view.rowCount - 1;
|
||||
this._cachedInsertionPoint =
|
||||
this._getInsertionPoint(index, orientation);
|
||||
return this._cachedInsertionPoint;
|
||||
}
|
||||
|
||||
// This is a two-part process. The first part is determining the drop
|
||||
// orientation.
|
||||
// * The default orientation is to drop _before_ the selected item.
|
||||
// * If the selected item is a container, the default orientation
|
||||
// is to drop _into_ that container.
|
||||
//
|
||||
// Warning: It may be tempting to use tree indexes in this code, but
|
||||
// you must not, since the tree is nested and as your tree
|
||||
// index may change when folders before you are opened and
|
||||
// closed. You must convert your tree index to a node, and
|
||||
// then use getChildIndex to find your absolute index in
|
||||
// the parent container instead.
|
||||
//
|
||||
var resultView = this.view;
|
||||
var selection = resultView.selection;
|
||||
var rc = selection.getRangeCount();
|
||||
var min = { }, max = { };
|
||||
selection.getRangeAt(rc - 1, min, max);
|
||||
|
||||
// If the sole selection is a container, and we are not in
|
||||
// a flatlist, insert into it.
|
||||
// Note that this only applies to _single_ selections,
|
||||
// if the last element within a multi-selection is a
|
||||
// container, insert _adjacent_ to the selection.
|
||||
//
|
||||
// If the sole selection is the bookmarks toolbar folder, we insert
|
||||
// into it even if it is not opened
|
||||
if (selection.count == 1 && resultView.isContainer(max.value) &&
|
||||
!this.flatList)
|
||||
orientation = Ci.nsITreeView.DROP_ON;
|
||||
|
||||
this._cachedInsertionPoint =
|
||||
this._getInsertionPoint(max.value, orientation);
|
||||
return this._cachedInsertionPoint;
|
||||
]]></getter>
|
||||
</property>
|
||||
|
||||
<method name="_getInsertionPoint">
|
||||
<parameter name="index"/>
|
||||
<parameter name="orientation"/>
|
||||
<body><![CDATA[
|
||||
var result = this.result;
|
||||
var resultview = this.view;
|
||||
var container = result.root;
|
||||
var dropNearNode = null;
|
||||
console.assert(container, "null container");
|
||||
// When there's no selection, assume the container is the container
|
||||
// the view is populated from (i.e. the result's itemId).
|
||||
if (index != -1) {
|
||||
var lastSelected = resultview.nodeForTreeIndex(index);
|
||||
if (resultview.isContainer(index) && orientation == Ci.nsITreeView.DROP_ON) {
|
||||
// If the last selected item is an open container, append _into_
|
||||
// it, rather than insert adjacent to it.
|
||||
container = lastSelected;
|
||||
index = -1;
|
||||
} else if (lastSelected.containerOpen &&
|
||||
orientation == Ci.nsITreeView.DROP_AFTER &&
|
||||
lastSelected.hasChildren) {
|
||||
// If the last selected item is an open container and the user is
|
||||
// trying to drag into it as a first item, really insert into it.
|
||||
container = lastSelected;
|
||||
orientation = Ci.nsITreeView.DROP_ON;
|
||||
index = 0;
|
||||
} else {
|
||||
// Use the last-selected node's container.
|
||||
container = lastSelected.parent;
|
||||
|
||||
// See comment in the treeView.js's copy of this method
|
||||
if (!container || !container.containerOpen)
|
||||
return null;
|
||||
|
||||
// Avoid the potentially expensive call to getChildIndex
|
||||
// if we know this container doesn't allow insertion
|
||||
if (this.controller.disallowInsertion(container))
|
||||
return null;
|
||||
|
||||
var queryOptions = PlacesUtils.asQuery(result.root).queryOptions;
|
||||
if (queryOptions.sortingMode !=
|
||||
Ci.nsINavHistoryQueryOptions.SORT_BY_NONE) {
|
||||
// If we are within a sorted view, insert at the end
|
||||
index = -1;
|
||||
} else if (queryOptions.excludeItems ||
|
||||
queryOptions.excludeQueries) {
|
||||
// Some item may be invisible, insert near last selected one.
|
||||
// We don't replace index here to avoid requests to the db,
|
||||
// instead it will be calculated later by the controller.
|
||||
index = -1;
|
||||
dropNearNode = lastSelected;
|
||||
} else {
|
||||
var lsi = container.getChildIndex(lastSelected);
|
||||
index = orientation == Ci.nsITreeView.DROP_BEFORE ? lsi : lsi + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.controller.disallowInsertion(container))
|
||||
return null;
|
||||
|
||||
let tagName = PlacesUtils.nodeIsTagQuery(container) ?
|
||||
PlacesUtils.asQuery(container).query.tags[0] : null;
|
||||
|
||||
return new PlacesInsertionPoint({
|
||||
parentId: PlacesUtils.getConcreteItemId(container),
|
||||
parentGuid: PlacesUtils.getConcreteItemGuid(container),
|
||||
index, orientation, tagName, dropNearNode,
|
||||
});
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<!-- nsIPlacesView -->
|
||||
<method name="selectAll">
|
||||
<body><![CDATA[
|
||||
this.view.selection.selectAll();
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<!-- This method will select the first node in the tree that matches
|
||||
each given item guid. It will open any folder nodes that it needs
|
||||
to in order to show the selected items.
|
||||
-->
|
||||
<method name="selectItems">
|
||||
<parameter name="aGuids"/>
|
||||
<parameter name="aOpenContainers"/>
|
||||
<body><![CDATA[
|
||||
// Never open containers in flat lists.
|
||||
if (this.flatList)
|
||||
aOpenContainers = false;
|
||||
// By default, we do search and select within containers which were
|
||||
// closed (note that containers in which nodes were not found are
|
||||
// closed).
|
||||
if (aOpenContainers === undefined)
|
||||
aOpenContainers = true;
|
||||
|
||||
var guids = aGuids; // don't manipulate the caller's array
|
||||
|
||||
// Array of nodes found by findNodes which are to be selected
|
||||
var nodes = [];
|
||||
|
||||
// Array of nodes found by findNodes which should be opened
|
||||
var nodesToOpen = [];
|
||||
|
||||
// A set of GUIDs of container-nodes that were previously searched,
|
||||
// and thus shouldn't be searched again. This is empty at the initial
|
||||
// start of the recursion and gets filled in as the recursion
|
||||
// progresses.
|
||||
var checkedGuidsSet = new Set();
|
||||
|
||||
/**
|
||||
* Recursively search through a node's children for items
|
||||
* with the given GUIDs. When a matching item is found, remove its GUID
|
||||
* from the GUIDs array, and add the found node to the nodes dictionary.
|
||||
*
|
||||
* NOTE: This method will leave open any node that had matching items
|
||||
* in its subtree.
|
||||
*/
|
||||
function findNodes(node) {
|
||||
var foundOne = false;
|
||||
// See if node matches an ID we wanted; add to results.
|
||||
// For simple folder queries, check both itemId and the concrete
|
||||
// item id.
|
||||
var index = guids.indexOf(node.bookmarkGuid);
|
||||
if (index == -1) {
|
||||
let concreteGuid = PlacesUtils.getConcreteItemGuid(node);
|
||||
if (concreteGuid != node.bookmarkGuid) {
|
||||
index = guids.indexOf(concreteGuid);
|
||||
}
|
||||
}
|
||||
|
||||
if (index != -1) {
|
||||
nodes.push(node);
|
||||
foundOne = true;
|
||||
guids.splice(index, 1);
|
||||
}
|
||||
|
||||
var concreteGuid = PlacesUtils.getConcreteItemGuid(node);
|
||||
if (guids.length == 0 || !PlacesUtils.nodeIsContainer(node) ||
|
||||
checkedGuidsSet.has(concreteGuid))
|
||||
return foundOne;
|
||||
|
||||
// Only follow a query if it has been been explicitly opened by the
|
||||
// caller. We support the "AllBookmarks" case to allow callers to
|
||||
// specify just the top-level bookmark folders.
|
||||
let shouldOpen = aOpenContainers && (PlacesUtils.nodeIsFolder(node) ||
|
||||
(PlacesUtils.nodeIsQuery(node) && node.bookmarkGuid == PlacesUIUtils.virtualAllBookmarksGuid));
|
||||
|
||||
PlacesUtils.asContainer(node);
|
||||
if (!node.containerOpen && !shouldOpen)
|
||||
return foundOne;
|
||||
|
||||
checkedGuidsSet.add(concreteGuid);
|
||||
|
||||
// Remember the beginning state so that we can re-close
|
||||
// this node if we don't find any additional results here.
|
||||
var previousOpenness = node.containerOpen;
|
||||
node.containerOpen = true;
|
||||
for (var child = 0; child < node.childCount && guids.length > 0; child++) {
|
||||
var childNode = node.getChild(child);
|
||||
var found = findNodes(childNode);
|
||||
if (!foundOne)
|
||||
foundOne = found;
|
||||
}
|
||||
|
||||
// If we didn't find any additional matches in this node's
|
||||
// subtree, revert the node to its previous openness.
|
||||
if (foundOne)
|
||||
nodesToOpen.unshift(node);
|
||||
node.containerOpen = previousOpenness;
|
||||
return foundOne;
|
||||
}
|
||||
|
||||
// Disable notifications while looking for nodes.
|
||||
let result = this.result;
|
||||
let didSuppressNotifications = result.suppressNotifications;
|
||||
if (!didSuppressNotifications)
|
||||
result.suppressNotifications = true;
|
||||
try {
|
||||
findNodes(this.result.root);
|
||||
} finally {
|
||||
if (!didSuppressNotifications)
|
||||
result.suppressNotifications = false;
|
||||
}
|
||||
|
||||
// For all the nodes we've found, highlight the corresponding
|
||||
// index in the tree.
|
||||
var resultview = this.view;
|
||||
var selection = this.view.selection;
|
||||
selection.selectEventsSuppressed = true;
|
||||
selection.clearSelection();
|
||||
// Open nodes containing found items
|
||||
for (let i = 0; i < nodesToOpen.length; i++) {
|
||||
nodesToOpen[i].containerOpen = true;
|
||||
}
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
var index = resultview.treeIndexForNode(nodes[i]);
|
||||
if (index == -1)
|
||||
continue;
|
||||
selection.rangedSelect(index, index, true);
|
||||
}
|
||||
selection.selectEventsSuppressed = false;
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<field name="_contextMenuShown">false</field>
|
||||
|
||||
<method name="buildContextMenu">
|
||||
<parameter name="aPopup"/>
|
||||
<body><![CDATA[
|
||||
this._contextMenuShown = true;
|
||||
return this.controller.buildContextMenu(aPopup);
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="destroyContextMenu">
|
||||
<parameter name="aPopup"/>
|
||||
this._contextMenuShown = false;
|
||||
<body/>
|
||||
</method>
|
||||
|
||||
<property name="ownerWindow"
|
||||
readonly="true"
|
||||
onget="return window;"/>
|
||||
|
||||
<field name="_active">true</field>
|
||||
<property name="active"
|
||||
onget="return this._active"
|
||||
onset="return this._active = val"/>
|
||||
|
||||
</implementation>
|
||||
<handlers>
|
||||
<handler event="focus"><![CDATA[
|
||||
this._cachedInsertionPoint = undefined;
|
||||
|
||||
// See select handler. We need the sidebar's places commandset to be
|
||||
// updated as well
|
||||
document.commandDispatcher.updateCommands("focus");
|
||||
]]></handler>
|
||||
<handler event="select"><![CDATA[
|
||||
this._cachedInsertionPoint = undefined;
|
||||
|
||||
// This additional complexity is here for the sidebars
|
||||
var win = window;
|
||||
while (true) {
|
||||
win.document.commandDispatcher.updateCommands("focus");
|
||||
if (win == window.top)
|
||||
break;
|
||||
|
||||
win = win.parent;
|
||||
}
|
||||
]]></handler>
|
||||
|
||||
<handler event="dragstart"><![CDATA[
|
||||
if (event.target.localName != "treechildren")
|
||||
return;
|
||||
|
||||
if (this.disableUserActions) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
let nodes = this.selectedNodes;
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
let node = nodes[i];
|
||||
|
||||
// Disallow dragging the root node of a tree.
|
||||
if (!node.parent) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
// If this node is child of a readonly container or cannot be moved,
|
||||
// we must force a copy.
|
||||
if (!this.controller.canMoveNode(node)) {
|
||||
event.dataTransfer.effectAllowed = "copyLink";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this._controller.setDataTransfer(event);
|
||||
event.stopPropagation();
|
||||
]]></handler>
|
||||
|
||||
<handler event="dragover"><![CDATA[
|
||||
if (event.target.localName != "treechildren")
|
||||
return;
|
||||
|
||||
let cell = this.getCellAt(event.clientX, event.clientY);
|
||||
let node = cell.row != -1 ?
|
||||
this.view.nodeForTreeIndex(cell.row) :
|
||||
this.result.root;
|
||||
// cache the dropTarget for the view
|
||||
PlacesControllerDragHelper.currentDropTarget = node;
|
||||
|
||||
// We have to calculate the orientation since view.canDrop will use
|
||||
// it and we want to be consistent with the dropfeedback.
|
||||
let rowHeight = this.rowHeight;
|
||||
let eventY = event.clientY - this.treeBody.boxObject.y -
|
||||
rowHeight * (cell.row - this.getFirstVisibleRow());
|
||||
|
||||
let orientation = Ci.nsITreeView.DROP_BEFORE;
|
||||
|
||||
if (cell.row == -1) {
|
||||
// If the row is not valid we try to insert inside the resultNode.
|
||||
orientation = Ci.nsITreeView.DROP_ON;
|
||||
} else if (PlacesUtils.nodeIsContainer(node) &&
|
||||
eventY > rowHeight * 0.75) {
|
||||
// If we are below the 75% of a container the treeview we try
|
||||
// to drop after the node.
|
||||
orientation = Ci.nsITreeView.DROP_AFTER;
|
||||
} else if (PlacesUtils.nodeIsContainer(node) &&
|
||||
eventY > rowHeight * 0.25) {
|
||||
// If we are below the 25% of a container the treeview we try
|
||||
// to drop inside the node.
|
||||
orientation = Ci.nsITreeView.DROP_ON;
|
||||
}
|
||||
|
||||
if (!this.view.canDrop(cell.row, orientation, event.dataTransfer))
|
||||
return;
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
]]></handler>
|
||||
|
||||
<handler event="dragend"><![CDATA[
|
||||
PlacesControllerDragHelper.currentDropTarget = null;
|
||||
]]></handler>
|
||||
|
||||
</handlers>
|
||||
</binding>
|
||||
|
||||
</bindings>
|
|
@ -12,7 +12,7 @@ browser.jar:
|
|||
* content/browser/places/bookmarkProperties.xul (content/bookmarkProperties.xul)
|
||||
content/browser/places/bookmarkProperties.js (content/bookmarkProperties.js)
|
||||
content/browser/places/menu.xml (content/menu.xml)
|
||||
content/browser/places/tree.xml (content/tree.xml)
|
||||
content/browser/places/places-tree.js (content/places-tree.js)
|
||||
content/browser/places/controller.js (content/controller.js)
|
||||
content/browser/places/treeView.js (content/treeView.js)
|
||||
content/browser/places/browserPlacesViews.js (content/browserPlacesViews.js)
|
||||
|
|
|
@ -282,7 +282,7 @@ gTests.push({
|
|||
async run() {
|
||||
// Open folder selector.
|
||||
var foldersExpander = this.window.document.getElementById("editBMPanel_foldersExpander");
|
||||
var folderTree = this.window.document.getElementById("editBMPanel_folderTree");
|
||||
var folderTree = this.window.gEditItemOverlay._folderTree;
|
||||
var self = this;
|
||||
|
||||
let unloadPromise = new Promise(resolve => {
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
title="510634: Wrong icons on bookmarks sidebar"
|
||||
onload="runTest();">
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://browser/content/places/places-tree.js"/>
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
|
||||
<script type="application/javascript" src="head.js" />
|
||||
|
@ -22,7 +24,7 @@
|
|||
<body xmlns="http://www.w3.org/1999/xhtml" />
|
||||
|
||||
<tree id="tree"
|
||||
type="places"
|
||||
is="places-tree"
|
||||
flex="1">
|
||||
<treecols>
|
||||
<treecol label="Title" id="title" anonid="title" primary="true" ordinal="1" flex="1"/>
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
title="1163447: selectItems in Places no longer selects items within Toolbar or Sidebar folders"
|
||||
onload="runTest();">
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://browser/content/places/places-tree.js"/>
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
|
||||
<script type="application/javascript" src="head.js" />
|
||||
|
@ -23,7 +25,7 @@
|
|||
<body xmlns="http://www.w3.org/1999/xhtml" />
|
||||
|
||||
<tree id="tree"
|
||||
type="places"
|
||||
is="places-tree"
|
||||
flex="1">
|
||||
<treecols>
|
||||
<treecol label="Title" id="title" anonid="title" primary="true" ordinal="1" flex="1"/>
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
title="549192: History view not updated after deleting entry"
|
||||
onload="runTest();">
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://browser/content/places/places-tree.js"/>
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
|
||||
<script type="application/javascript" src="head.js" />
|
||||
|
@ -23,7 +25,7 @@
|
|||
<body xmlns="http://www.w3.org/1999/xhtml" />
|
||||
|
||||
<tree id="tree"
|
||||
type="places"
|
||||
is="places-tree"
|
||||
flatList="true"
|
||||
flex="1">
|
||||
<treecols>
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
title="549491: 'The root node is never visible' exception when details of the root node are modified "
|
||||
onload="runTest();">
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://browser/content/places/places-tree.js"/>
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
|
||||
<script type="application/javascript" src="head.js" />
|
||||
|
@ -23,7 +25,7 @@
|
|||
<body xmlns="http://www.w3.org/1999/xhtml" />
|
||||
|
||||
<tree id="tree"
|
||||
type="places"
|
||||
is="places-tree"
|
||||
flatList="true"
|
||||
flex="1">
|
||||
<treecols>
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
title="549192: History view not updated after deleting entry"
|
||||
onload="runTest();">
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://browser/content/places/places-tree.js"/>
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
|
||||
<script type="application/javascript" src="head.js" />
|
||||
|
@ -23,7 +25,7 @@
|
|||
<body xmlns="http://www.w3.org/1999/xhtml" />
|
||||
|
||||
<tree id="tree"
|
||||
type="places"
|
||||
is="places-tree"
|
||||
flex="1">
|
||||
<treecols>
|
||||
<treecol label="Title" id="title" anonid="title" primary="true" ordinal="1" flex="1"/>
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
title="435322: Places tree view's formatting"
|
||||
onload="runTest();">
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://browser/content/places/places-tree.js"/>
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
|
||||
<script type="application/javascript" src="head.js" />
|
||||
|
@ -22,7 +24,7 @@
|
|||
<body xmlns="http://www.w3.org/1999/xhtml" />
|
||||
|
||||
<tree id="tree"
|
||||
type="places"
|
||||
is="places-tree"
|
||||
flatList="true"
|
||||
flex="1">
|
||||
<treecols>
|
||||
|
|
|
@ -28,11 +28,14 @@
|
|||
src="chrome://global/content/globalOverlay.js"/>
|
||||
<script type="application/javascript"
|
||||
src="chrome://browser/content/utilityOverlay.js"/>
|
||||
<script type="application/javascript"
|
||||
src="chrome://browser/content/places/places-tree.js"/>
|
||||
|
||||
<description data-l10n-id="select-bookmark-desc"/>
|
||||
|
||||
<separator class="thin"/>
|
||||
|
||||
<tree id="bookmarks" flex="1" type="places"
|
||||
<tree id="bookmarks" flex="1" is="places-tree"
|
||||
style="height: 15em;"
|
||||
hidecolumnpicker="true"
|
||||
seltype="single"
|
||||
|
|
|
@ -23,6 +23,14 @@ stl_headers = [
|
|||
# throwing exceptions
|
||||
'algorithm',
|
||||
'atomic',
|
||||
'cassert',
|
||||
'climits',
|
||||
'cmath',
|
||||
'cstdarg',
|
||||
'cstdio',
|
||||
'cstdlib',
|
||||
'cstring',
|
||||
'cwchar',
|
||||
'deque',
|
||||
'functional',
|
||||
'ios',
|
||||
|
@ -35,23 +43,16 @@ stl_headers = [
|
|||
'map',
|
||||
'memory',
|
||||
'ostream',
|
||||
'regex',
|
||||
'set',
|
||||
'stack',
|
||||
'string',
|
||||
'thread',
|
||||
'tuple',
|
||||
'type_traits',
|
||||
'unordered_map',
|
||||
'unordered_set',
|
||||
'utility',
|
||||
'vector',
|
||||
'cassert',
|
||||
'climits',
|
||||
'cmath',
|
||||
'cstdarg',
|
||||
'cstdio',
|
||||
'cstdlib',
|
||||
'cstring',
|
||||
'cwchar',
|
||||
'tuple',
|
||||
'xutility',
|
||||
]
|
||||
|
|
|
@ -366,11 +366,8 @@ GLContextEGL::~GLContextEGL() {
|
|||
}
|
||||
|
||||
bool GLContextEGL::Init() {
|
||||
#if defined(ANDROID)
|
||||
// We can't use LoadApitraceLibrary here because the GLContext
|
||||
// expects its own handle to the GL library
|
||||
if (!OpenLibrary(APITRACE_LIB))
|
||||
#endif
|
||||
mLibrary = LoadApitraceLibrary();
|
||||
if (!mLibrary) {
|
||||
if (!OpenLibrary(GLES2_LIB)) {
|
||||
#if defined(XP_UNIX)
|
||||
if (!OpenLibrary(GLES2_LIB2)) {
|
||||
|
@ -379,6 +376,7 @@ bool GLContextEGL::Init() {
|
|||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
SetupLookupFunction();
|
||||
if (!InitWithPrefix("gl", true)) return false;
|
||||
|
|
|
@ -72,9 +72,18 @@ static const char* sEGLExtensionNames[] = {
|
|||
"EGL_KHR_create_context_no_error",
|
||||
"EGL_MOZ_create_context_provoking_vertex_dont_care"};
|
||||
|
||||
#if defined(ANDROID)
|
||||
PRLibrary* LoadApitraceLibrary() {
|
||||
const char* path = nullptr;
|
||||
|
||||
#ifdef ANDROID
|
||||
// We only need to explicitly dlopen egltrace
|
||||
// on android as we can use LD_PRELOAD or other tricks
|
||||
// on other platforms. We look for it in /data/local
|
||||
// as that's writeable by all users.
|
||||
path = "/data/local/tmp/egltrace.so";
|
||||
#endif
|
||||
if (!path) return nullptr;
|
||||
|
||||
static PRLibrary* LoadApitraceLibrary() {
|
||||
// Initialization of gfx prefs here is only needed during the unit tests...
|
||||
gfxPrefs::GetSingleton();
|
||||
if (!gfxPrefs::UseApitrace()) {
|
||||
|
@ -82,7 +91,6 @@ static PRLibrary* LoadApitraceLibrary() {
|
|||
}
|
||||
|
||||
static PRLibrary* sApitraceLibrary = nullptr;
|
||||
|
||||
if (sApitraceLibrary) return sApitraceLibrary;
|
||||
|
||||
nsAutoCString logFile;
|
||||
|
@ -96,20 +104,19 @@ static PRLibrary* LoadApitraceLibrary() {
|
|||
nsAutoCString logPath;
|
||||
logPath.AppendPrintf("%s/%s", getenv("GRE_HOME"), logFile.get());
|
||||
|
||||
#ifndef XP_WIN // Windows is missing setenv and forbids PR_LoadLibrary.
|
||||
// apitrace uses the TRACE_FILE environment variable to determine where
|
||||
// to log trace output to
|
||||
printf_stderr("Logging GL tracing output to %s", logPath.get());
|
||||
setenv("TRACE_FILE", logPath.get(), false);
|
||||
|
||||
printf_stderr("Attempting load of %s\n", APITRACE_LIB);
|
||||
|
||||
sApitraceLibrary = PR_LoadLibrary(APITRACE_LIB);
|
||||
printf_stderr("Attempting load of %s\n", path);
|
||||
sApitraceLibrary = PR_LoadLibrary(path);
|
||||
#endif
|
||||
|
||||
return sApitraceLibrary;
|
||||
}
|
||||
|
||||
#endif // ANDROID
|
||||
|
||||
#ifdef XP_WIN
|
||||
// see the comment in GLLibraryEGL::EnsureInitialized() for the rationale here.
|
||||
static PRLibrary* LoadLibraryForEGLOnWindows(const nsAString& filename) {
|
||||
|
|
|
@ -18,18 +18,6 @@
|
|||
#include <bitset>
|
||||
#include <vector>
|
||||
|
||||
#ifdef ANDROID
|
||||
// We only need to explicitly dlopen egltrace
|
||||
// on android as we can use LD_PRELOAD or other tricks
|
||||
// on other platforms. We look for it in /data/local
|
||||
// as that's writeable by all users
|
||||
//
|
||||
// This should really go in GLLibraryEGL.cpp but we currently reference
|
||||
// APITRACE_LIB in GLContextProviderEGL.cpp. Further refactoring
|
||||
// will come in subsequent patches on Bug 732865
|
||||
# define APITRACE_LIB "/data/local/tmp/egltrace.so"
|
||||
#endif
|
||||
|
||||
#if defined(MOZ_X11)
|
||||
# define EGL_DEFAULT_DISPLAY ((EGLNativeDisplayType)mozilla::DefaultXDisplay())
|
||||
#else
|
||||
|
@ -51,6 +39,7 @@ class DataSourceSurface;
|
|||
namespace gl {
|
||||
|
||||
class GLContext;
|
||||
PRLibrary* LoadApitraceLibrary();
|
||||
|
||||
void BeforeEGLCall(const char* funcName);
|
||||
void AfterEGLCall(const char* funcName);
|
||||
|
|
|
@ -87,8 +87,12 @@ UniquePtr<GLScreenBuffer> GLScreenBuffer::Create(GLContext* gl,
|
|||
factory = MakeUnique<SurfaceFactory_GLTexture>(mGLContext, caps, ipcChannel,
|
||||
mFlags);
|
||||
#elif defined(MOZ_WIDGET_ANDROID)
|
||||
factory =
|
||||
SurfaceFactory_SurfaceTexture::Create(gl, caps, ipcChannel, flags);
|
||||
if (XRE_IsParentProcess() && !gfxPrefs::WebGLSurfaceTextureEnabled()) {
|
||||
factory = SurfaceFactory_EGLImage::Create(gl, caps, ipcChannel, flags);
|
||||
} else {
|
||||
factory =
|
||||
SurfaceFactory_SurfaceTexture::Create(gl, caps, ipcChannel, flags);
|
||||
}
|
||||
#else
|
||||
if (gl->GetContextType() == GLContextType::EGL) {
|
||||
if (XRE_IsParentProcess()) {
|
||||
|
|
|
@ -451,9 +451,7 @@ class gfxPrefs final {
|
|||
SmoothScrollMSDPhysicsRegularSpringConstant, int32_t, 1000);
|
||||
|
||||
DECL_GFX_PREF(Once, "gfx.android.rgb16.force", AndroidRGB16Force, bool, false);
|
||||
#if defined(ANDROID)
|
||||
DECL_GFX_PREF(Once, "gfx.apitrace.enabled", UseApitrace, bool, false);
|
||||
#endif
|
||||
#if defined(RELEASE_OR_BETA)
|
||||
// "Skip" means this is locked to the default value in beta and release.
|
||||
DECL_GFX_PREF(Skip, "gfx.blocklist.all", BlocklistAll, int32_t, 0);
|
||||
|
@ -810,6 +808,7 @@ class gfxPrefs final {
|
|||
|
||||
DECL_GFX_PREF(Live, "webgl.enable-draft-extensions", WebGLDraftExtensionsEnabled, bool, false);
|
||||
DECL_GFX_PREF(Live, "webgl.enable-privileged-extensions", WebGLPrivilegedExtensionsEnabled, bool, false);
|
||||
DECL_GFX_PREF(Live, "webgl.enable-surface-texture", WebGLSurfaceTextureEnabled, bool, false);
|
||||
DECL_GFX_PREF(Live, "webgl.enable-webgl2", WebGL2Enabled, bool, true);
|
||||
DECL_GFX_PREF(Live, "webgl.force-enabled", WebGLForceEnabled, bool, false);
|
||||
DECL_GFX_PREF(Once, "webgl.force-layers-readback", WebGLForceLayersReadback, bool, false);
|
||||
|
|
|
@ -158,7 +158,9 @@ bool RenderCompositorANGLE::Initialize() {
|
|||
desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
|
||||
desc.SampleDesc.Count = 1;
|
||||
desc.SampleDesc.Quality = 0;
|
||||
desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
||||
// DXGI_USAGE_SHADER_INPUT is set for improving performanc of copying from
|
||||
// framebuffer to texture on intel gpu.
|
||||
desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT;
|
||||
// Do not use DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL, since it makes HWND
|
||||
// unreusable.
|
||||
// desc.BufferCount = 2;
|
||||
|
@ -186,7 +188,9 @@ bool RenderCompositorANGLE::Initialize() {
|
|||
swapDesc.BufferDesc.RefreshRate.Denominator = 1;
|
||||
swapDesc.SampleDesc.Count = 1;
|
||||
swapDesc.SampleDesc.Quality = 0;
|
||||
swapDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
||||
// DXGI_USAGE_SHADER_INPUT is set for improving performanc of copying from
|
||||
// framebuffer to texture on intel gpu.
|
||||
swapDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT;
|
||||
swapDesc.BufferCount = 1;
|
||||
swapDesc.OutputWindow = hwnd;
|
||||
swapDesc.Windowed = TRUE;
|
||||
|
@ -281,7 +285,9 @@ void RenderCompositorANGLE::CreateSwapChainForDCompIfPossible(
|
|||
desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
|
||||
desc.SampleDesc.Count = 1;
|
||||
desc.SampleDesc.Quality = 0;
|
||||
desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
||||
// DXGI_USAGE_SHADER_INPUT is set for improving performanc of copying from
|
||||
// framebuffer to texture on intel gpu.
|
||||
desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT;
|
||||
if (useTripleBuffering) {
|
||||
desc.BufferCount = 3;
|
||||
} else {
|
||||
|
|
|
@ -132,6 +132,14 @@ MOZ_THROW_NORETURN MOZ_THROW_EXPORT MOZ_THROW_INLINE void __throw_system_error(
|
|||
mozalloc_abort(error);
|
||||
}
|
||||
|
||||
MOZ_THROW_NORETURN MOZ_EXPORT MOZ_ALWAYS_INLINE void __throw_regex_error(
|
||||
int err) {
|
||||
char error[128];
|
||||
snprintf(error, sizeof(error) - 1, "fatal: STL threw regex_error: %s (%d)",
|
||||
strerror(err), err);
|
||||
mozalloc_abort(error);
|
||||
}
|
||||
|
||||
} // namespace std
|
||||
|
||||
#undef MOZ_THROW_NORETURN
|
||||
|
|
|
@ -25,3 +25,5 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
|
|||
]
|
||||
|
||||
NO_PGO = True
|
||||
DisableStlWrapping()
|
||||
|
||||
|
|
|
@ -28,8 +28,4 @@ XPCOM_MANIFESTS += [
|
|||
'components.conf',
|
||||
]
|
||||
|
||||
XPCOM_MANIFESTS += [
|
||||
'components.conf',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
|
|
@ -41,7 +41,7 @@ async function editUsernamePromises(site, oldUsername, newUsername) {
|
|||
let login = Services.logins.findLogins({}, site, "", "")[0];
|
||||
is(login.username, oldUsername, "Correct username saved");
|
||||
is(getUsername(0), oldUsername, "Correct username shown");
|
||||
let focusPromise = BrowserTestUtils.waitForEvent(signonsTree, "focus", true);
|
||||
let focusPromise = BrowserTestUtils.waitForEvent(signonsTree.inputField, "focus", true);
|
||||
synthesizeDblClickOnCell(signonsTree, 1, 0);
|
||||
await focusPromise;
|
||||
|
||||
|
@ -63,7 +63,7 @@ async function editPasswordPromises(site, oldPassword, newPassword) {
|
|||
is(login.password, oldPassword, "Correct password saved");
|
||||
is(getPassword(0), oldPassword, "Correct password shown");
|
||||
|
||||
let focusPromise = BrowserTestUtils.waitForEvent(signonsTree, "focus", true);
|
||||
let focusPromise = BrowserTestUtils.waitForEvent(signonsTree.inputField, "focus", true);
|
||||
synthesizeDblClickOnCell(signonsTree, 2, 0);
|
||||
await focusPromise;
|
||||
|
||||
|
|
|
@ -83,10 +83,6 @@ if CONFIG['MOZ_PLACES']:
|
|||
'components.conf',
|
||||
]
|
||||
|
||||
XPCOM_MANIFESTS += [
|
||||
'components.conf',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
||||
include('/ipc/chromium/chromium-config.mozbuild')
|
||||
|
|
|
@ -40,10 +40,6 @@ XPCOM_MANIFESTS += [
|
|||
'components.conf',
|
||||
]
|
||||
|
||||
XPCOM_MANIFESTS += [
|
||||
'components.conf',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
||||
JAR_MANIFESTS += ['jar.mn']
|
||||
|
|
|
@ -83,7 +83,6 @@ toolkit.jar:
|
|||
content/global/bindings/timekeeper.js (widgets/timekeeper.js)
|
||||
content/global/bindings/timepicker.js (widgets/timepicker.js)
|
||||
content/global/bindings/toolbarbutton.xml (widgets/toolbarbutton.xml)
|
||||
content/global/bindings/tree.xml (widgets/tree.xml)
|
||||
* content/global/bindings/wizard.xml (widgets/wizard.xml)
|
||||
content/global/elements/autocomplete-richlistitem.js (widgets/autocomplete-richlistitem.js)
|
||||
content/global/elements/browser-custom-element.js (widgets/browser-custom-element.js)
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
/* 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/. */
|
||||
* 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";
|
||||
/* globals XULTreeElement */
|
||||
|
||||
// This is loaded into all XUL windows. Wrap in a block to prevent
|
||||
// leaking to window scope.
|
||||
{
|
||||
"use strict";
|
||||
|
||||
// This is loaded into all XUL windows. Wrap in a block to prevent
|
||||
// leaking to window scope.
|
||||
{
|
||||
class MozTreeChildren extends MozElements.BaseControl {
|
||||
constructor() {
|
||||
super();
|
||||
|
@ -153,6 +155,8 @@
|
|||
return;
|
||||
}
|
||||
|
||||
this.setAttribute("slot", "treechildren");
|
||||
|
||||
this._lastSelectedRow = -1;
|
||||
|
||||
if ("_ensureColumnOrder" in this.parentNode)
|
||||
|
@ -477,6 +481,8 @@
|
|||
return;
|
||||
}
|
||||
|
||||
this.setAttribute("slot", "treecols");
|
||||
|
||||
if (!this.querySelector("treecolpicker")) {
|
||||
this.appendChild(MozXULElement.parseXULToFragment(`
|
||||
<treecolpicker class="treecol-image" fixed="true"></treecolpicker>
|
||||
|
@ -494,4 +500,898 @@
|
|||
}
|
||||
|
||||
customElements.define("treecols", MozTreecols);
|
||||
|
||||
class MozTree extends BaseControlMixin(MozElementMixin(XULTreeElement)) {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.addEventListener("underflow", (event) => {
|
||||
// Scrollport event orientation
|
||||
// 0: vertical
|
||||
// 1: horizontal
|
||||
// 2: both (not used)
|
||||
if (event.target.tagName != "treechildren")
|
||||
return;
|
||||
if (event.detail == 1)
|
||||
this.setAttribute("hidehscroll", "true");
|
||||
else if (event.detail == 0)
|
||||
this.setAttribute("hidevscroll", "true");
|
||||
event.stopPropagation();
|
||||
});
|
||||
|
||||
this.addEventListener("overflow", (event) => {
|
||||
if (event.target.tagName != "treechildren")
|
||||
return;
|
||||
if (event.detail == 1)
|
||||
this.removeAttribute("hidehscroll");
|
||||
else if (event.detail == 0)
|
||||
this.removeAttribute("hidevscroll");
|
||||
event.stopPropagation();
|
||||
});
|
||||
|
||||
this.addEventListener("touchstart", (event) => {
|
||||
function isScrollbarElement(target) {
|
||||
return (target.localName == "thumb" || target.localName == "slider") &&
|
||||
target.namespaceURI == "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
}
|
||||
if (event.touches.length > 1 || isScrollbarElement(event.touches[0].target)) {
|
||||
// Multiple touch points detected, abort. In particular this aborts
|
||||
// the panning gesture when the user puts a second finger down after
|
||||
// already panning with one finger. Aborting at this point prevents
|
||||
// the pan gesture from being resumed until all fingers are lifted
|
||||
// (as opposed to when the user is back down to one finger).
|
||||
// Additionally, if the user lands on the scrollbar don't use this
|
||||
// code for scrolling, instead allow gecko to handle scrollbar
|
||||
// interaction normally.
|
||||
this._touchY = -1;
|
||||
} else {
|
||||
this._touchY = event.touches[0].screenY;
|
||||
}
|
||||
});
|
||||
|
||||
this.addEventListener("touchmove", (event) => {
|
||||
if (event.touches.length == 1 &&
|
||||
this._touchY >= 0) {
|
||||
var deltaY = this._touchY - event.touches[0].screenY;
|
||||
var lines = Math.trunc(deltaY / this.rowHeight);
|
||||
if (Math.abs(lines) > 0) {
|
||||
this.scrollByLines(lines);
|
||||
deltaY -= lines * this.rowHeight;
|
||||
this._touchY = event.touches[0].screenY + deltaY;
|
||||
}
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
this.addEventListener("touchend", (event) => {
|
||||
this._touchY = -1;
|
||||
});
|
||||
|
||||
this.addEventListener("MozMousePixelScroll", (event) => {
|
||||
if (!(this.getAttribute("allowunderflowscroll") == "true" &&
|
||||
this.getAttribute("hidevscroll") == "true"))
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
this.addEventListener("DOMMouseScroll", (event) => {
|
||||
if (!(this.getAttribute("allowunderflowscroll") == "true" &&
|
||||
this.getAttribute("hidevscroll") == "true"))
|
||||
event.preventDefault();
|
||||
|
||||
if (this._editingColumn)
|
||||
return;
|
||||
if (event.axis == event.HORIZONTAL_AXIS)
|
||||
return;
|
||||
|
||||
var rows = event.detail;
|
||||
if (rows == UIEvent.SCROLL_PAGE_UP)
|
||||
this.scrollByPages(-1);
|
||||
else if (rows == UIEvent.SCROLL_PAGE_DOWN)
|
||||
this.scrollByPages(1);
|
||||
else
|
||||
this.scrollByLines(rows);
|
||||
});
|
||||
|
||||
this.addEventListener("MozSwipeGesture", (event) => {
|
||||
// Figure out which row to show
|
||||
let targetRow = 0;
|
||||
|
||||
// Only handle swipe gestures up and down
|
||||
switch (event.direction) {
|
||||
case event.DIRECTION_DOWN:
|
||||
targetRow = this.view.rowCount - 1;
|
||||
// Fall through for actual action
|
||||
case event.DIRECTION_UP:
|
||||
this.ensureRowIsVisible(targetRow);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
this.addEventListener("select", (event) => { if (event.originalTarget == this) this.stopEditing(true); });
|
||||
|
||||
this.addEventListener("focus", (event) => {
|
||||
this.focused = true;
|
||||
if (this.currentIndex == -1 && this.view.rowCount > 0) {
|
||||
this.currentIndex = this.getFirstVisibleRow();
|
||||
}
|
||||
});
|
||||
|
||||
this.addEventListener("blur", (event) => { this.focused = false; });
|
||||
|
||||
this.addEventListener("blur", (event) => { if (event.originalTarget == this.inputField.inputField) this.stopEditing(true); }, true);
|
||||
|
||||
this.addEventListener("keydown", (event) => {
|
||||
if (event.keyCode != KeyEvent.DOM_VK_RETURN) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._handleEnter(event)) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
this.addEventListener("keydown", (event) => {
|
||||
if (event.keyCode != KeyEvent.DOM_VK_ESCAPE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._editingColumn) {
|
||||
this.stopEditing(false);
|
||||
this.focus();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
this.addEventListener("keydown", (event) => {
|
||||
if (event.keyCode != KeyEvent.DOM_VK_LEFT) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._editingColumn)
|
||||
return;
|
||||
|
||||
var row = this.currentIndex;
|
||||
if (row < 0)
|
||||
return;
|
||||
|
||||
if (this.changeOpenState(this.currentIndex, false)) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
var parentIndex = this.view.getParentIndex(this.currentIndex);
|
||||
if (parentIndex >= 0) {
|
||||
this.view.selection.select(parentIndex);
|
||||
this.ensureRowIsVisible(parentIndex);
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
this.addEventListener("keydown", (event) => {
|
||||
if (event.keyCode != KeyEvent.DOM_VK_RIGHT) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._editingColumn)
|
||||
return;
|
||||
|
||||
var row = this.currentIndex;
|
||||
if (row < 0)
|
||||
return;
|
||||
|
||||
if (this.changeOpenState(row, true)) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
var c = row + 1;
|
||||
var view = this.view;
|
||||
if (c < view.rowCount &&
|
||||
view.getParentIndex(c) == row) {
|
||||
// If already opened, select the first child.
|
||||
// The getParentIndex test above ensures that the children
|
||||
// are already populated and ready.
|
||||
this.view.selection.timedSelect(c, this._selectDelay);
|
||||
this.ensureRowIsVisible(c);
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
this.addEventListener("keydown", (event) => {
|
||||
if (event.keyCode != KeyEvent.DOM_VK_UP) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._editingColumn)
|
||||
return;
|
||||
|
||||
if (event.getModifierState("Shift")) {
|
||||
this._moveByOffsetShift(-1, 0, event);
|
||||
} else {
|
||||
this._moveByOffset(-1, 0, event);
|
||||
}
|
||||
});
|
||||
|
||||
this.addEventListener("keydown", (event) => {
|
||||
if (event.keyCode != KeyEvent.DOM_VK_DOWN) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._editingColumn)
|
||||
return;
|
||||
if (event.getModifierState("Shift")) {
|
||||
this._moveByOffsetShift(1, this.view.rowCount - 1, event);
|
||||
} else {
|
||||
this._moveByOffset(1, this.view.rowCount - 1, event);
|
||||
}
|
||||
});
|
||||
|
||||
this.addEventListener("keydown", (event) => {
|
||||
if (event.keyCode != KeyEvent.DOM_VK_PAGE_UP) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._editingColumn)
|
||||
return;
|
||||
|
||||
if (event.getModifierState("Shift")) {
|
||||
this._moveByPageShift(-1, 0, event);
|
||||
} else {
|
||||
this._moveByPage(-1, 0, event);
|
||||
}
|
||||
});
|
||||
|
||||
this.addEventListener("keydown", (event) => {
|
||||
if (event.keyCode != KeyEvent.DOM_VK_PAGE_DOWN) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._editingColumn)
|
||||
return;
|
||||
|
||||
if (event.getModifierState("Shift")) {
|
||||
this._moveByPageShift(1, this.view.rowCount - 1, event);
|
||||
} else {
|
||||
this._moveByPage(1, this.view.rowCount - 1, event);
|
||||
}
|
||||
});
|
||||
|
||||
this.addEventListener("keydown", (event) => {
|
||||
if (event.keyCode != KeyEvent.DOM_VK_HOME) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._editingColumn)
|
||||
return;
|
||||
|
||||
if (event.getModifierState("Shift")) {
|
||||
this._moveToEdgeShift(0, event);
|
||||
} else {
|
||||
this._moveToEdge(0, event);
|
||||
}
|
||||
});
|
||||
|
||||
this.addEventListener("keydown", (event) => {
|
||||
if (event.keyCode != KeyEvent.DOM_VK_END) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._editingColumn)
|
||||
return;
|
||||
|
||||
if (event.getModifierState("Shift")) {
|
||||
this._moveToEdgeShift(this.view.rowCount - 1, event);
|
||||
} else {
|
||||
this._moveToEdge(this.view.rowCount - 1, event);
|
||||
}
|
||||
});
|
||||
|
||||
this.addEventListener("keypress", (event) => {
|
||||
if (this._editingColumn)
|
||||
return;
|
||||
|
||||
if (event.charCode == " ".charCodeAt(0)) {
|
||||
var c = this.currentIndex;
|
||||
if (!this.view.selection.isSelected(c) ||
|
||||
(!this.view.selection.single && event.getModifierState("Accel"))) {
|
||||
this.view.selection.toggleSelect(c);
|
||||
event.preventDefault();
|
||||
}
|
||||
} else if (!this.disableKeyNavigation && event.charCode > 0 &&
|
||||
!event.altKey && !event.getModifierState("Accel") &&
|
||||
!event.metaKey && !event.ctrlKey) {
|
||||
var l = this._keyNavigate(event);
|
||||
if (l >= 0) {
|
||||
this.view.selection.timedSelect(l, this._selectDelay);
|
||||
this.ensureRowIsVisible(l);
|
||||
}
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
this.attachShadow({ mode: "open" });
|
||||
this.shadowRoot.appendChild(MozXULElement.parseXULToFragment(`
|
||||
<html:link rel="stylesheet" href="chrome://global/skin/tree.css" />
|
||||
<html:slot name="treecols"></html:slot>
|
||||
<stack class="tree-stack" flex="1">
|
||||
<hbox class="tree-rows" flex="1">
|
||||
<hbox flex="1" class="tree-bodybox">
|
||||
<html:slot name="treechildren"></html:slot>
|
||||
</hbox>
|
||||
<scrollbar height="0" minwidth="0" minheight="0" orient="vertical" inherits="collapsed=hidevscroll" style="position:relative; z-index:2147483647;" oncontextmenu="event.stopPropagation(); event.preventDefault();" onclick="event.stopPropagation(); event.preventDefault();" ondblclick="event.stopPropagation();" oncommand="event.stopPropagation();"></scrollbar>
|
||||
</hbox>
|
||||
<textbox class="tree-input" left="0" top="0" hidden="true"></textbox>
|
||||
</stack>
|
||||
<hbox inherits="collapsed=hidehscroll">
|
||||
<scrollbar orient="horizontal" flex="1" increment="16" style="position:relative; z-index:2147483647;" oncontextmenu="event.stopPropagation(); event.preventDefault();" onclick="event.stopPropagation(); event.preventDefault();" ondblclick="event.stopPropagation();" oncommand="event.stopPropagation();"></scrollbar>
|
||||
<scrollcorner inherits="collapsed=hidevscroll" oncontextmenu="event.stopPropagation(); event.preventDefault();" onclick="event.stopPropagation(); event.preventDefault();" ondblclick="event.stopPropagation();" oncommand="event.stopPropagation();"></scrollcorner>
|
||||
</hbox>
|
||||
`));
|
||||
}
|
||||
|
||||
static get observedAttributes() {
|
||||
return [
|
||||
"hidehscroll",
|
||||
"hidevscroll",
|
||||
];
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, oldValue, newValue) {
|
||||
if (this.isConnectedAndReady && oldValue != newValue) {
|
||||
this._updateAttributes();
|
||||
}
|
||||
}
|
||||
|
||||
_updateAttributes() {
|
||||
for (let [ el, attrs ] of this._inheritedAttributeMap.entries()) {
|
||||
for (let attr of attrs) {
|
||||
this.inheritAttribute(el, attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get _inheritedAttributeMap() {
|
||||
if (!this.__inheritedAttributeMap) {
|
||||
this.__inheritedAttributeMap = new Map();
|
||||
for (let el of this.shadowRoot.querySelectorAll("[inherits]")) {
|
||||
this.__inheritedAttributeMap.set(el, el.getAttribute("inherits").split(","));
|
||||
}
|
||||
}
|
||||
return this.__inheritedAttributeMap;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
if (this.delayConnectedCallback()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setAttribute("hidevscroll", "true");
|
||||
this.setAttribute("hidehscroll", "true");
|
||||
this.setAttribute("clickthrough", "never");
|
||||
|
||||
this._updateAttributes();
|
||||
|
||||
this.pageUpOrDownMovesSelection = !/Mac/.test(navigator.platform);
|
||||
|
||||
this._inputField = null;
|
||||
|
||||
this._editingRow = -1;
|
||||
|
||||
this._editingColumn = null;
|
||||
|
||||
this._columnsDirty = true;
|
||||
|
||||
this._lastKeyTime = 0;
|
||||
|
||||
this._incrementalString = "";
|
||||
|
||||
this._touchY = -1;
|
||||
}
|
||||
|
||||
get body() {
|
||||
return this.treeBody;
|
||||
}
|
||||
|
||||
set editable(val) {
|
||||
if (val) this.setAttribute("editable", "true");
|
||||
else this.removeAttribute("editable");
|
||||
return val;
|
||||
}
|
||||
|
||||
get editable() {
|
||||
return this.getAttribute("editable") == "true";
|
||||
}
|
||||
/**
|
||||
* ///////////////// nsIDOMXULSelectControlElement ///////////////// ///////////////// nsIDOMXULMultiSelectControlElement /////////////////
|
||||
*/
|
||||
set selType(val) {
|
||||
this.setAttribute("seltype", val);
|
||||
return val;
|
||||
}
|
||||
|
||||
get selType() {
|
||||
return this.getAttribute("seltype");
|
||||
}
|
||||
|
||||
set currentIndex(val) {
|
||||
if (this.view) return this.view.selection.currentIndex = val;
|
||||
return val;
|
||||
}
|
||||
|
||||
get currentIndex() {
|
||||
return this.view ? this.view.selection.currentIndex : -1;
|
||||
}
|
||||
|
||||
set keepCurrentInView(val) {
|
||||
if (val) this.setAttribute("keepcurrentinview", "true");
|
||||
else this.removeAttribute("keepcurrentinview");
|
||||
return val;
|
||||
}
|
||||
|
||||
get keepCurrentInView() {
|
||||
return (this.getAttribute("keepcurrentinview") == "true");
|
||||
}
|
||||
|
||||
set enableColumnDrag(val) {
|
||||
if (val) this.setAttribute("enableColumnDrag", "true");
|
||||
else this.removeAttribute("enableColumnDrag");
|
||||
return val;
|
||||
}
|
||||
|
||||
get enableColumnDrag() {
|
||||
return this.hasAttribute("enableColumnDrag");
|
||||
}
|
||||
|
||||
get inputField() {
|
||||
if (!this._inputField) {
|
||||
this._inputField = this.shadowRoot.querySelector(".tree-input");
|
||||
this._inputField.addEventListener("blur", () => this.stopEditing(true), true);
|
||||
}
|
||||
return this._inputField;
|
||||
}
|
||||
|
||||
set disableKeyNavigation(val) {
|
||||
if (val) this.setAttribute("disableKeyNavigation", "true");
|
||||
else this.removeAttribute("disableKeyNavigation");
|
||||
return val;
|
||||
}
|
||||
|
||||
get disableKeyNavigation() {
|
||||
return this.hasAttribute("disableKeyNavigation");
|
||||
}
|
||||
|
||||
get editingRow() {
|
||||
return this._editingRow;
|
||||
}
|
||||
|
||||
get editingColumn() {
|
||||
return this._editingColumn;
|
||||
}
|
||||
|
||||
set _selectDelay(val) {
|
||||
this.setAttribute("_selectDelay", val);
|
||||
}
|
||||
|
||||
get _selectDelay() {
|
||||
return this.getAttribute("_selectDelay") || 50;
|
||||
}
|
||||
|
||||
_ensureColumnOrder() {
|
||||
if (!this._columnsDirty)
|
||||
return;
|
||||
|
||||
if (this.columns) {
|
||||
// update the ordinal position of each column to assure that it is
|
||||
// an odd number and 2 positions above its next sibling
|
||||
var cols = [];
|
||||
var i;
|
||||
for (var col = this.columns.getFirstColumn(); col; col = col.getNext())
|
||||
cols.push(col.element);
|
||||
for (i = 0; i < cols.length; ++i)
|
||||
cols[i].setAttribute("ordinal", (i * 2) + 1);
|
||||
|
||||
// update the ordinal positions of splitters to even numbers, so that
|
||||
// they are in between columns
|
||||
var splitters = this.getElementsByTagName("splitter");
|
||||
for (i = 0; i < splitters.length; ++i)
|
||||
splitters[i].setAttribute("ordinal", (i + 1) * 2);
|
||||
}
|
||||
this._columnsDirty = false;
|
||||
}
|
||||
|
||||
_reorderColumn(aColMove, aColBefore, aBefore) {
|
||||
this._ensureColumnOrder();
|
||||
|
||||
var i;
|
||||
var cols = [];
|
||||
var col = this.columns.getColumnFor(aColBefore);
|
||||
if (parseInt(aColBefore.ordinal) < parseInt(aColMove.ordinal)) {
|
||||
if (aBefore)
|
||||
cols.push(aColBefore);
|
||||
for (col = col.getNext(); col.element != aColMove; col = col.getNext())
|
||||
cols.push(col.element);
|
||||
|
||||
aColMove.ordinal = cols[0].ordinal;
|
||||
for (i = 0; i < cols.length; ++i)
|
||||
cols[i].ordinal = parseInt(cols[i].ordinal) + 2;
|
||||
} else if (aColBefore.ordinal != aColMove.ordinal) {
|
||||
if (!aBefore)
|
||||
cols.push(aColBefore);
|
||||
for (col = col.getPrevious(); col.element != aColMove; col = col.getPrevious())
|
||||
cols.push(col.element);
|
||||
|
||||
aColMove.ordinal = cols[0].ordinal;
|
||||
for (i = 0; i < cols.length; ++i)
|
||||
cols[i].ordinal = parseInt(cols[i].ordinal) - 2;
|
||||
}
|
||||
}
|
||||
|
||||
_getColumnAtX(aX, aThresh, aPos) {
|
||||
var isRTL = document.defaultView.getComputedStyle(this)
|
||||
.direction == "rtl";
|
||||
|
||||
if (aPos)
|
||||
aPos.value = isRTL ? "after" : "before";
|
||||
|
||||
var columns = [];
|
||||
var col = this.columns.getFirstColumn();
|
||||
while (col) {
|
||||
columns.push(col);
|
||||
col = col.getNext();
|
||||
}
|
||||
if (isRTL)
|
||||
columns.reverse();
|
||||
var currentX = this.boxObject.x;
|
||||
var adjustedX = aX + this.horizontalPosition;
|
||||
for (var i = 0; i < columns.length; ++i) {
|
||||
col = columns[i];
|
||||
var cw = col.element.boxObject.width;
|
||||
if (cw > 0) {
|
||||
currentX += cw;
|
||||
if (currentX - (cw * aThresh) > adjustedX)
|
||||
return col.element;
|
||||
}
|
||||
}
|
||||
|
||||
if (aPos)
|
||||
aPos.value = isRTL ? "before" : "after";
|
||||
return columns.pop().element;
|
||||
}
|
||||
|
||||
changeOpenState(row, openState) {
|
||||
if (row < 0 || !this.view.isContainer(row)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.view.isContainerOpen(row) != openState) {
|
||||
this.view.toggleOpenState(row);
|
||||
if (row == this.currentIndex) {
|
||||
// Only fire event when current row is expanded or collapsed
|
||||
// because that's all the assistive technology really cares about.
|
||||
var event = document.createEvent("Events");
|
||||
event.initEvent("OpenStateChange", true, true);
|
||||
this.dispatchEvent(event);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
_keyNavigate(event) {
|
||||
var key = String.fromCharCode(event.charCode).toLowerCase();
|
||||
if (event.timeStamp - this._lastKeyTime > 1000)
|
||||
this._incrementalString = key;
|
||||
else
|
||||
this._incrementalString += key;
|
||||
this._lastKeyTime = event.timeStamp;
|
||||
|
||||
var length = this._incrementalString.length;
|
||||
var incrementalString = this._incrementalString;
|
||||
var charIndex = 1;
|
||||
while (charIndex < length && incrementalString[charIndex] == incrementalString[charIndex - 1])
|
||||
charIndex++;
|
||||
// If all letters in incremental string are same, just try to match the first one
|
||||
if (charIndex == length) {
|
||||
length = 1;
|
||||
incrementalString = incrementalString.substring(0, length);
|
||||
}
|
||||
|
||||
var keyCol = this.columns.getKeyColumn();
|
||||
var rowCount = this.view.rowCount;
|
||||
var start = 1;
|
||||
|
||||
var c = this.currentIndex;
|
||||
if (length > 1) {
|
||||
start = 0;
|
||||
if (c < 0)
|
||||
c = 0;
|
||||
}
|
||||
|
||||
for (var i = 0; i < rowCount; i++) {
|
||||
var l = (i + start + c) % rowCount;
|
||||
var cellText = this.view.getCellText(l, keyCol);
|
||||
cellText = cellText.substring(0, length).toLowerCase();
|
||||
if (cellText == incrementalString)
|
||||
return l;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
startEditing(row, column) {
|
||||
if (!this.editable)
|
||||
return false;
|
||||
if (row < 0 || row >= this.view.rowCount || !column)
|
||||
return false;
|
||||
if (column.type != window.TreeColumn.TYPE_TEXT &&
|
||||
column.type != window.TreeColumn.TYPE_PASSWORD)
|
||||
return false;
|
||||
if (column.cycler || !this.view.isEditable(row, column))
|
||||
return false;
|
||||
|
||||
// Beyond this point, we are going to edit the cell.
|
||||
if (this._editingColumn)
|
||||
this.stopEditing();
|
||||
|
||||
var input = this.inputField;
|
||||
|
||||
this.ensureCellIsVisible(row, column);
|
||||
|
||||
// Get the coordinates of the text inside the cell.
|
||||
var textRect = this.getCoordsForCellItem(row, column, "text");
|
||||
|
||||
// Get the coordinates of the cell itself.
|
||||
var cellRect = this.getCoordsForCellItem(row, column, "cell");
|
||||
|
||||
// Calculate the top offset of the textbox.
|
||||
var style = window.getComputedStyle(input);
|
||||
var topadj = parseInt(style.borderTopWidth) + parseInt(style.paddingTop);
|
||||
input.top = textRect.y - topadj;
|
||||
|
||||
// The leftside of the textbox is aligned to the left side of the text
|
||||
// in LTR mode, and left side of the cell in RTL mode.
|
||||
var left, widthdiff;
|
||||
if (style.direction == "rtl") {
|
||||
left = cellRect.x;
|
||||
widthdiff = cellRect.x - textRect.x;
|
||||
} else {
|
||||
left = textRect.x;
|
||||
widthdiff = textRect.x - cellRect.x;
|
||||
}
|
||||
|
||||
input.left = left;
|
||||
input.height = textRect.height + topadj +
|
||||
parseInt(style.borderBottomWidth) +
|
||||
parseInt(style.paddingBottom);
|
||||
input.width = cellRect.width - widthdiff;
|
||||
input.hidden = false;
|
||||
|
||||
input.value = this.view.getCellText(row, column);
|
||||
|
||||
input.select();
|
||||
input.inputField.focus();
|
||||
|
||||
this._editingRow = row;
|
||||
this._editingColumn = column;
|
||||
this.setAttribute("editing", "true");
|
||||
|
||||
this.invalidateCell(row, column);
|
||||
return true;
|
||||
}
|
||||
|
||||
stopEditing(accept) {
|
||||
if (!this._editingColumn)
|
||||
return;
|
||||
|
||||
var input = this.inputField;
|
||||
var editingRow = this._editingRow;
|
||||
var editingColumn = this._editingColumn;
|
||||
this._editingRow = -1;
|
||||
this._editingColumn = null;
|
||||
|
||||
if (accept) {
|
||||
var value = input.value;
|
||||
this.view.setCellText(editingRow, editingColumn, value);
|
||||
}
|
||||
input.hidden = true;
|
||||
input.value = "";
|
||||
this.removeAttribute("editing");
|
||||
}
|
||||
|
||||
_moveByOffset(offset, edge, event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (this.view.rowCount == 0)
|
||||
return;
|
||||
|
||||
if (event.getModifierState("Accel") && this.view.selection.single) {
|
||||
this.scrollByLines(offset);
|
||||
return;
|
||||
}
|
||||
|
||||
var c = this.currentIndex + offset;
|
||||
if (offset > 0 ? c > edge : c < edge) {
|
||||
if (this.view.selection.isSelected(edge) && this.view.selection.count <= 1)
|
||||
return;
|
||||
c = edge;
|
||||
}
|
||||
|
||||
if (!event.getModifierState("Accel"))
|
||||
this.view.selection.timedSelect(c, this._selectDelay);
|
||||
else // Ctrl+Up/Down moves the anchor without selecting
|
||||
this.currentIndex = c;
|
||||
this.ensureRowIsVisible(c);
|
||||
}
|
||||
|
||||
_moveByOffsetShift(offset, edge, event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (this.view.rowCount == 0)
|
||||
return;
|
||||
|
||||
if (this.view.selection.single) {
|
||||
this.scrollByLines(offset);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.view.rowCount == 1 && !this.view.selection.isSelected(0)) {
|
||||
this.view.selection.timedSelect(0, this._selectDelay);
|
||||
return;
|
||||
}
|
||||
|
||||
var c = this.currentIndex;
|
||||
if (c == -1)
|
||||
c = 0;
|
||||
|
||||
if (c == edge) {
|
||||
if (this.view.selection.isSelected(c))
|
||||
return;
|
||||
}
|
||||
|
||||
// Extend the selection from the existing pivot, if any
|
||||
this.view.selection.rangedSelect(-1, c + offset,
|
||||
event.getModifierState("Accel"));
|
||||
this.ensureRowIsVisible(c + offset);
|
||||
}
|
||||
|
||||
_moveByPage(offset, edge, event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (this.view.rowCount == 0)
|
||||
return;
|
||||
|
||||
if (this.pageUpOrDownMovesSelection == event.getModifierState("Accel")) {
|
||||
this.scrollByPages(offset);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.view.rowCount == 1 && !this.view.selection.isSelected(0)) {
|
||||
this.view.selection.timedSelect(0, this._selectDelay);
|
||||
return;
|
||||
}
|
||||
|
||||
var c = this.currentIndex;
|
||||
if (c == -1)
|
||||
return;
|
||||
|
||||
if (c == edge && this.view.selection.isSelected(c)) {
|
||||
this.ensureRowIsVisible(c);
|
||||
return;
|
||||
}
|
||||
var i = this.getFirstVisibleRow();
|
||||
var p = this.getPageLength();
|
||||
|
||||
if (offset > 0) {
|
||||
i += p - 1;
|
||||
if (c >= i) {
|
||||
i = c + p;
|
||||
this.ensureRowIsVisible(i > edge ? edge : i);
|
||||
}
|
||||
i = i > edge ? edge : i;
|
||||
} else if (c <= i) {
|
||||
i = c <= p ? 0 : c - p;
|
||||
this.ensureRowIsVisible(i);
|
||||
}
|
||||
this.view.selection.timedSelect(i, this._selectDelay);
|
||||
}
|
||||
|
||||
_moveByPageShift(offset, edge, event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (this.view.rowCount == 0)
|
||||
return;
|
||||
|
||||
if (this.view.rowCount == 1 && !this.view.selection.isSelected(0) &&
|
||||
!(this.pageUpOrDownMovesSelection == event.getModifierState("Accel"))) {
|
||||
this.view.selection.timedSelect(0, this._selectDelay);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.view.selection.single)
|
||||
return;
|
||||
|
||||
var c = this.currentIndex;
|
||||
if (c == -1)
|
||||
return;
|
||||
if (c == edge && this.view.selection.isSelected(c)) {
|
||||
this.ensureRowIsVisible(edge);
|
||||
return;
|
||||
}
|
||||
var i = this.getFirstVisibleRow();
|
||||
var p = this.getPageLength();
|
||||
|
||||
if (offset > 0) {
|
||||
i += p - 1;
|
||||
if (c >= i) {
|
||||
i = c + p;
|
||||
this.ensureRowIsVisible(i > edge ? edge : i);
|
||||
}
|
||||
// Extend the selection from the existing pivot, if any
|
||||
this.view.selection.rangedSelect(-1, i > edge ? edge : i, event.getModifierState("Accel"));
|
||||
} else {
|
||||
if (c <= i) {
|
||||
i = c <= p ? 0 : c - p;
|
||||
this.ensureRowIsVisible(i);
|
||||
}
|
||||
// Extend the selection from the existing pivot, if any
|
||||
this.view.selection.rangedSelect(-1, i, event.getModifierState("Accel"));
|
||||
}
|
||||
}
|
||||
|
||||
_moveToEdge(edge, event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (this.view.rowCount == 0)
|
||||
return;
|
||||
|
||||
if (this.view.selection.isSelected(edge) && this.view.selection.count == 1) {
|
||||
this.currentIndex = edge;
|
||||
return;
|
||||
}
|
||||
|
||||
// Normal behaviour is to select the first/last row
|
||||
if (!event.getModifierState("Accel"))
|
||||
this.view.selection.timedSelect(edge, this._selectDelay);
|
||||
|
||||
// In a multiselect tree Ctrl+Home/End moves the anchor
|
||||
else if (!this.view.selection.single)
|
||||
this.currentIndex = edge;
|
||||
|
||||
this.ensureRowIsVisible(edge);
|
||||
}
|
||||
|
||||
_moveToEdgeShift(edge, event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (this.view.rowCount == 0)
|
||||
return;
|
||||
|
||||
if (this.view.rowCount == 1 && !this.view.selection.isSelected(0)) {
|
||||
this.view.selection.timedSelect(0, this._selectDelay);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.view.selection.single ||
|
||||
(this.view.selection.isSelected(edge)) && this.view.selection.isSelected(this.currentIndex))
|
||||
return;
|
||||
|
||||
// Extend the selection from the existing pivot, if any.
|
||||
// -1 doesn't work here, so using currentIndex instead
|
||||
this.view.selection.rangedSelect(this.currentIndex, edge, event.getModifierState("Accel"));
|
||||
|
||||
this.ensureRowIsVisible(edge);
|
||||
}
|
||||
|
||||
_handleEnter(event) {
|
||||
if (this._editingColumn) {
|
||||
this.stopEditing(true);
|
||||
this.focus();
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.changeOpenState(this.currentIndex);
|
||||
}
|
||||
}
|
||||
|
||||
MozXULElement.implementCustomInterface(MozTree, [Ci.nsIDOMXULMultiSelectControlElement]);
|
||||
customElements.define("tree", MozTree);
|
||||
}
|
||||
|
|
|
@ -1,905 +0,0 @@
|
|||
<?xml version="1.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/. -->
|
||||
|
||||
|
||||
<!DOCTYPE bindings [
|
||||
<!ENTITY % treeDTD SYSTEM "chrome://global/locale/tree.dtd">
|
||||
%treeDTD;
|
||||
]>
|
||||
|
||||
<bindings id="treeBindings"
|
||||
xmlns="http://www.mozilla.org/xbl"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:xbl="http://www.mozilla.org/xbl">
|
||||
|
||||
<binding id="tree" extends="chrome://global/content/bindings/general.xml#basecontrol">
|
||||
<content hidevscroll="true" hidehscroll="true" clickthrough="never">
|
||||
<children includes="treecols"/>
|
||||
<xul:stack class="tree-stack" flex="1">
|
||||
<xul:hbox class="tree-rows" flex="1">
|
||||
<xul:hbox flex="1" class="tree-bodybox">
|
||||
<children/>
|
||||
</xul:hbox>
|
||||
<xul:scrollbar height="0" minwidth="0" minheight="0" orient="vertical" xbl:inherits="collapsed=hidevscroll" style="position:relative; z-index:2147483647;"
|
||||
oncontextmenu="event.stopPropagation(); event.preventDefault();"
|
||||
onclick="event.stopPropagation(); event.preventDefault();"
|
||||
ondblclick="event.stopPropagation();"
|
||||
oncommand="event.stopPropagation();"/>
|
||||
</xul:hbox>
|
||||
<xul:textbox anonid="input" class="tree-input" left="0" top="0" hidden="true"/>
|
||||
</xul:stack>
|
||||
<xul:hbox xbl:inherits="collapsed=hidehscroll">
|
||||
<xul:scrollbar orient="horizontal" flex="1" increment="16" style="position:relative; z-index:2147483647;"
|
||||
oncontextmenu="event.stopPropagation(); event.preventDefault();"
|
||||
onclick="event.stopPropagation(); event.preventDefault();"
|
||||
ondblclick="event.stopPropagation();"
|
||||
oncommand="event.stopPropagation();"/>
|
||||
<xul:scrollcorner xbl:inherits="collapsed=hidevscroll"
|
||||
oncontextmenu="event.stopPropagation(); event.preventDefault();"
|
||||
onclick="event.stopPropagation(); event.preventDefault();"
|
||||
ondblclick="event.stopPropagation();"
|
||||
oncommand="event.stopPropagation();"/>
|
||||
</xul:hbox>
|
||||
</content>
|
||||
|
||||
<implementation implements="nsIDOMXULMultiSelectControlElement">
|
||||
<property name="body"
|
||||
onget="return this.treeBody;"/>
|
||||
|
||||
<property name="editable"
|
||||
onget="return this.getAttribute('editable') == 'true';"
|
||||
onset="if (val) this.setAttribute('editable', 'true');
|
||||
else this.removeAttribute('editable'); return val;"/>
|
||||
|
||||
<!-- ///////////////// nsIDOMXULSelectControlElement ///////////////// -->
|
||||
|
||||
<!-- ///////////////// nsIDOMXULMultiSelectControlElement ///////////////// -->
|
||||
|
||||
<property name="selType"
|
||||
onget="return this.getAttribute('seltype')"
|
||||
onset="this.setAttribute('seltype', val); return val;"/>
|
||||
|
||||
<property name="currentIndex"
|
||||
onget="return this.view ? this.view.selection.currentIndex: - 1;"
|
||||
onset="if (this.view) return this.view.selection.currentIndex = val; return val;"/>
|
||||
|
||||
<field name="pageUpOrDownMovesSelection">
|
||||
!/Mac/.test(navigator.platform)
|
||||
</field>
|
||||
<property name="keepCurrentInView"
|
||||
onget="return (this.getAttribute('keepcurrentinview') == 'true');"
|
||||
onset="if (val) this.setAttribute('keepcurrentinview', 'true');
|
||||
else this.removeAttribute('keepcurrentinview'); return val;"/>
|
||||
|
||||
<property name="enableColumnDrag"
|
||||
onget="return this.hasAttribute('enableColumnDrag');"
|
||||
onset="if (val) this.setAttribute('enableColumnDrag', 'true');
|
||||
else this.removeAttribute('enableColumnDrag'); return val;"/>
|
||||
|
||||
<field name="_inputField">null</field>
|
||||
|
||||
<property name="inputField" readonly="true">
|
||||
<getter><![CDATA[
|
||||
if (!this._inputField)
|
||||
this._inputField = document.getAnonymousElementByAttribute(this, "anonid", "input");
|
||||
return this._inputField;
|
||||
]]></getter>
|
||||
</property>
|
||||
|
||||
<property name="disableKeyNavigation"
|
||||
onget="return this.hasAttribute('disableKeyNavigation');"
|
||||
onset="if (val) this.setAttribute('disableKeyNavigation', 'true');
|
||||
else this.removeAttribute('disableKeyNavigation'); return val;"/>
|
||||
|
||||
<field name="_editingRow">-1</field>
|
||||
<field name="_editingColumn">null</field>
|
||||
|
||||
<property name="editingRow" readonly="true"
|
||||
onget="return this._editingRow;"/>
|
||||
<property name="editingColumn" readonly="true"
|
||||
onget="return this._editingColumn;"/>
|
||||
|
||||
<property name="_selectDelay"
|
||||
onset="this.setAttribute('_selectDelay', val);"
|
||||
onget="return this.getAttribute('_selectDelay') || 50;"/>
|
||||
<field name="_columnsDirty">true</field>
|
||||
<field name="_lastKeyTime">0</field>
|
||||
<field name="_incrementalString">""</field>
|
||||
|
||||
<field name="_touchY">-1</field>
|
||||
|
||||
<method name="_ensureColumnOrder">
|
||||
<body><![CDATA[
|
||||
if (!this._columnsDirty)
|
||||
return;
|
||||
|
||||
if (this.columns) {
|
||||
// update the ordinal position of each column to assure that it is
|
||||
// an odd number and 2 positions above its next sibling
|
||||
var cols = [];
|
||||
var i;
|
||||
for (var col = this.columns.getFirstColumn(); col; col = col.getNext())
|
||||
cols.push(col.element);
|
||||
for (i = 0; i < cols.length; ++i)
|
||||
cols[i].setAttribute("ordinal", (i * 2) + 1);
|
||||
|
||||
// update the ordinal positions of splitters to even numbers, so that
|
||||
// they are in between columns
|
||||
var splitters = this.getElementsByTagName("splitter");
|
||||
for (i = 0; i < splitters.length; ++i)
|
||||
splitters[i].setAttribute("ordinal", (i + 1) * 2);
|
||||
}
|
||||
this._columnsDirty = false;
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="_reorderColumn">
|
||||
<parameter name="aColMove"/>
|
||||
<parameter name="aColBefore"/>
|
||||
<parameter name="aBefore"/>
|
||||
<body><![CDATA[
|
||||
this._ensureColumnOrder();
|
||||
|
||||
var i;
|
||||
var cols = [];
|
||||
var col = this.columns.getColumnFor(aColBefore);
|
||||
if (parseInt(aColBefore.ordinal) < parseInt(aColMove.ordinal)) {
|
||||
if (aBefore)
|
||||
cols.push(aColBefore);
|
||||
for (col = col.getNext(); col.element != aColMove;
|
||||
col = col.getNext())
|
||||
cols.push(col.element);
|
||||
|
||||
aColMove.ordinal = cols[0].ordinal;
|
||||
for (i = 0; i < cols.length; ++i)
|
||||
cols[i].ordinal = parseInt(cols[i].ordinal) + 2;
|
||||
} else if (aColBefore.ordinal != aColMove.ordinal) {
|
||||
if (!aBefore)
|
||||
cols.push(aColBefore);
|
||||
for (col = col.getPrevious(); col.element != aColMove;
|
||||
col = col.getPrevious())
|
||||
cols.push(col.element);
|
||||
|
||||
aColMove.ordinal = cols[0].ordinal;
|
||||
for (i = 0; i < cols.length; ++i)
|
||||
cols[i].ordinal = parseInt(cols[i].ordinal) - 2;
|
||||
}
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="_getColumnAtX">
|
||||
<parameter name="aX"/>
|
||||
<parameter name="aThresh"/>
|
||||
<parameter name="aPos"/>
|
||||
<body><![CDATA[
|
||||
var isRTL = document.defaultView.getComputedStyle(this)
|
||||
.direction == "rtl";
|
||||
|
||||
if (aPos)
|
||||
aPos.value = isRTL ? "after" : "before";
|
||||
|
||||
var columns = [];
|
||||
var col = this.columns.getFirstColumn();
|
||||
while (col) {
|
||||
columns.push(col);
|
||||
col = col.getNext();
|
||||
}
|
||||
if (isRTL)
|
||||
columns.reverse();
|
||||
var currentX = this.boxObject.x;
|
||||
var adjustedX = aX + this.horizontalPosition;
|
||||
for (var i = 0; i < columns.length; ++i) {
|
||||
col = columns[i];
|
||||
var cw = col.element.boxObject.width;
|
||||
if (cw > 0) {
|
||||
currentX += cw;
|
||||
if (currentX - (cw * aThresh) > adjustedX)
|
||||
return col.element;
|
||||
}
|
||||
}
|
||||
|
||||
if (aPos)
|
||||
aPos.value = isRTL ? "before" : "after";
|
||||
return columns.pop().element;
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="changeOpenState">
|
||||
<parameter name="row"/>
|
||||
<!-- Optional parameter openState == true or false to set.
|
||||
No openState param == toggle -->
|
||||
<parameter name="openState"/>
|
||||
<body><![CDATA[
|
||||
if (row < 0 || !this.view.isContainer(row)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.view.isContainerOpen(row) != openState) {
|
||||
this.view.toggleOpenState(row);
|
||||
if (row == this.currentIndex) {
|
||||
// Only fire event when current row is expanded or collapsed
|
||||
// because that's all the assistive technology really cares about.
|
||||
var event = document.createEvent("Events");
|
||||
event.initEvent("OpenStateChange", true, true);
|
||||
this.dispatchEvent(event);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="_keyNavigate">
|
||||
<parameter name="event"/>
|
||||
<body><![CDATA[
|
||||
var key = String.fromCharCode(event.charCode).toLowerCase();
|
||||
if (event.timeStamp - this._lastKeyTime > 1000)
|
||||
this._incrementalString = key;
|
||||
else
|
||||
this._incrementalString += key;
|
||||
this._lastKeyTime = event.timeStamp;
|
||||
|
||||
var length = this._incrementalString.length;
|
||||
var incrementalString = this._incrementalString;
|
||||
var charIndex = 1;
|
||||
while (charIndex < length && incrementalString[charIndex] == incrementalString[charIndex - 1])
|
||||
charIndex++;
|
||||
// If all letters in incremental string are same, just try to match the first one
|
||||
if (charIndex == length) {
|
||||
length = 1;
|
||||
incrementalString = incrementalString.substring(0, length);
|
||||
}
|
||||
|
||||
var keyCol = this.columns.getKeyColumn();
|
||||
var rowCount = this.view.rowCount;
|
||||
var start = 1;
|
||||
|
||||
var c = this.currentIndex;
|
||||
if (length > 1) {
|
||||
start = 0;
|
||||
if (c < 0)
|
||||
c = 0;
|
||||
}
|
||||
|
||||
for (var i = 0; i < rowCount; i++) {
|
||||
var l = (i + start + c) % rowCount;
|
||||
var cellText = this.view.getCellText(l, keyCol);
|
||||
cellText = cellText.substring(0, length).toLowerCase();
|
||||
if (cellText == incrementalString)
|
||||
return l;
|
||||
}
|
||||
return -1;
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="startEditing">
|
||||
<parameter name="row"/>
|
||||
<parameter name="column"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (!this.editable)
|
||||
return false;
|
||||
if (row < 0 || row >= this.view.rowCount || !column)
|
||||
return false;
|
||||
if (column.type != window.TreeColumn.TYPE_TEXT &&
|
||||
column.type != window.TreeColumn.TYPE_PASSWORD)
|
||||
return false;
|
||||
if (column.cycler || !this.view.isEditable(row, column))
|
||||
return false;
|
||||
|
||||
// Beyond this point, we are going to edit the cell.
|
||||
if (this._editingColumn)
|
||||
this.stopEditing();
|
||||
|
||||
var input = this.inputField;
|
||||
|
||||
this.ensureCellIsVisible(row, column);
|
||||
|
||||
// Get the coordinates of the text inside the cell.
|
||||
var textRect = this.getCoordsForCellItem(row, column, "text");
|
||||
|
||||
// Get the coordinates of the cell itself.
|
||||
var cellRect = this.getCoordsForCellItem(row, column, "cell");
|
||||
|
||||
// Calculate the top offset of the textbox.
|
||||
var style = window.getComputedStyle(input);
|
||||
var topadj = parseInt(style.borderTopWidth) + parseInt(style.paddingTop);
|
||||
input.top = textRect.y - topadj;
|
||||
|
||||
// The leftside of the textbox is aligned to the left side of the text
|
||||
// in LTR mode, and left side of the cell in RTL mode.
|
||||
var left, widthdiff;
|
||||
if (style.direction == "rtl") {
|
||||
left = cellRect.x;
|
||||
widthdiff = cellRect.x - textRect.x;
|
||||
} else {
|
||||
left = textRect.x;
|
||||
widthdiff = textRect.x - cellRect.x;
|
||||
}
|
||||
|
||||
input.left = left;
|
||||
input.height = textRect.height + topadj +
|
||||
parseInt(style.borderBottomWidth) +
|
||||
parseInt(style.paddingBottom);
|
||||
input.width = cellRect.width - widthdiff;
|
||||
input.hidden = false;
|
||||
|
||||
input.value = this.view.getCellText(row, column);
|
||||
|
||||
input.select();
|
||||
input.inputField.focus();
|
||||
|
||||
this._editingRow = row;
|
||||
this._editingColumn = column;
|
||||
this.setAttribute("editing", "true");
|
||||
|
||||
this.invalidateCell(row, column);
|
||||
return true;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="stopEditing">
|
||||
<parameter name="accept"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (!this._editingColumn)
|
||||
return;
|
||||
|
||||
var input = this.inputField;
|
||||
var editingRow = this._editingRow;
|
||||
var editingColumn = this._editingColumn;
|
||||
this._editingRow = -1;
|
||||
this._editingColumn = null;
|
||||
|
||||
if (accept) {
|
||||
var value = input.value;
|
||||
this.view.setCellText(editingRow, editingColumn, value);
|
||||
}
|
||||
input.hidden = true;
|
||||
input.value = "";
|
||||
this.removeAttribute("editing");
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="_moveByOffset">
|
||||
<parameter name="offset"/>
|
||||
<parameter name="edge"/>
|
||||
<parameter name="event"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
event.preventDefault();
|
||||
|
||||
if (this.view.rowCount == 0)
|
||||
return;
|
||||
|
||||
if (event.getModifierState("Accel") && this.view.selection.single) {
|
||||
this.scrollByLines(offset);
|
||||
return;
|
||||
}
|
||||
|
||||
var c = this.currentIndex + offset;
|
||||
if (offset > 0 ? c > edge : c < edge) {
|
||||
if (this.view.selection.isSelected(edge) && this.view.selection.count <= 1)
|
||||
return;
|
||||
c = edge;
|
||||
}
|
||||
|
||||
if (!event.getModifierState("Accel"))
|
||||
this.view.selection.timedSelect(c, this._selectDelay);
|
||||
else // Ctrl+Up/Down moves the anchor without selecting
|
||||
this.currentIndex = c;
|
||||
this.ensureRowIsVisible(c);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="_moveByOffsetShift">
|
||||
<parameter name="offset"/>
|
||||
<parameter name="edge"/>
|
||||
<parameter name="event"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
event.preventDefault();
|
||||
|
||||
if (this.view.rowCount == 0)
|
||||
return;
|
||||
|
||||
if (this.view.selection.single) {
|
||||
this.scrollByLines(offset);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.view.rowCount == 1 && !this.view.selection.isSelected(0)) {
|
||||
this.view.selection.timedSelect(0, this._selectDelay);
|
||||
return;
|
||||
}
|
||||
|
||||
var c = this.currentIndex;
|
||||
if (c == -1)
|
||||
c = 0;
|
||||
|
||||
if (c == edge) {
|
||||
if (this.view.selection.isSelected(c))
|
||||
return;
|
||||
}
|
||||
|
||||
// Extend the selection from the existing pivot, if any
|
||||
this.view.selection.rangedSelect(-1, c + offset,
|
||||
event.getModifierState("Accel"));
|
||||
this.ensureRowIsVisible(c + offset);
|
||||
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="_moveByPage">
|
||||
<parameter name="offset"/>
|
||||
<parameter name="edge"/>
|
||||
<parameter name="event"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
event.preventDefault();
|
||||
|
||||
if (this.view.rowCount == 0)
|
||||
return;
|
||||
|
||||
if (this.pageUpOrDownMovesSelection == event.getModifierState("Accel")) {
|
||||
this.scrollByPages(offset);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.view.rowCount == 1 && !this.view.selection.isSelected(0)) {
|
||||
this.view.selection.timedSelect(0, this._selectDelay);
|
||||
return;
|
||||
}
|
||||
|
||||
var c = this.currentIndex;
|
||||
if (c == -1)
|
||||
return;
|
||||
|
||||
if (c == edge && this.view.selection.isSelected(c)) {
|
||||
this.ensureRowIsVisible(c);
|
||||
return;
|
||||
}
|
||||
var i = this.getFirstVisibleRow();
|
||||
var p = this.getPageLength();
|
||||
|
||||
if (offset > 0) {
|
||||
i += p - 1;
|
||||
if (c >= i) {
|
||||
i = c + p;
|
||||
this.ensureRowIsVisible(i > edge ? edge : i);
|
||||
}
|
||||
i = i > edge ? edge : i;
|
||||
} else if (c <= i) {
|
||||
i = c <= p ? 0 : c - p;
|
||||
this.ensureRowIsVisible(i);
|
||||
}
|
||||
this.view.selection.timedSelect(i, this._selectDelay);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="_moveByPageShift">
|
||||
<parameter name="offset"/>
|
||||
<parameter name="edge"/>
|
||||
<parameter name="event"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
event.preventDefault();
|
||||
|
||||
if (this.view.rowCount == 0)
|
||||
return;
|
||||
|
||||
if (this.view.rowCount == 1 && !this.view.selection.isSelected(0) &&
|
||||
!(this.pageUpOrDownMovesSelection == event.getModifierState("Accel"))) {
|
||||
this.view.selection.timedSelect(0, this._selectDelay);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.view.selection.single)
|
||||
return;
|
||||
|
||||
var c = this.currentIndex;
|
||||
if (c == -1)
|
||||
return;
|
||||
if (c == edge && this.view.selection.isSelected(c)) {
|
||||
this.ensureRowIsVisible(edge);
|
||||
return;
|
||||
}
|
||||
var i = this.getFirstVisibleRow();
|
||||
var p = this.getPageLength();
|
||||
|
||||
if (offset > 0) {
|
||||
i += p - 1;
|
||||
if (c >= i) {
|
||||
i = c + p;
|
||||
this.ensureRowIsVisible(i > edge ? edge : i);
|
||||
}
|
||||
// Extend the selection from the existing pivot, if any
|
||||
this.view.selection.rangedSelect(-1, i > edge ? edge : i, event.getModifierState("Accel"));
|
||||
} else {
|
||||
if (c <= i) {
|
||||
i = c <= p ? 0 : c - p;
|
||||
this.ensureRowIsVisible(i);
|
||||
}
|
||||
// Extend the selection from the existing pivot, if any
|
||||
this.view.selection.rangedSelect(-1, i, event.getModifierState("Accel"));
|
||||
}
|
||||
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="_moveToEdge">
|
||||
<parameter name="edge"/>
|
||||
<parameter name="event"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
event.preventDefault();
|
||||
|
||||
if (this.view.rowCount == 0)
|
||||
return;
|
||||
|
||||
if (this.view.selection.isSelected(edge) && this.view.selection.count == 1) {
|
||||
this.currentIndex = edge;
|
||||
return;
|
||||
}
|
||||
|
||||
// Normal behaviour is to select the first/last row
|
||||
if (!event.getModifierState("Accel"))
|
||||
this.view.selection.timedSelect(edge, this._selectDelay);
|
||||
|
||||
// In a multiselect tree Ctrl+Home/End moves the anchor
|
||||
else if (!this.view.selection.single)
|
||||
this.currentIndex = edge;
|
||||
|
||||
this.ensureRowIsVisible(edge);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="_moveToEdgeShift">
|
||||
<parameter name="edge"/>
|
||||
<parameter name="event"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
event.preventDefault();
|
||||
|
||||
if (this.view.rowCount == 0)
|
||||
return;
|
||||
|
||||
if (this.view.rowCount == 1 && !this.view.selection.isSelected(0)) {
|
||||
this.view.selection.timedSelect(0, this._selectDelay);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.view.selection.single ||
|
||||
(this.view.selection.isSelected(edge)) && this.view.selection.isSelected(this.currentIndex))
|
||||
return;
|
||||
|
||||
// Extend the selection from the existing pivot, if any.
|
||||
// -1 doesn't work here, so using currentIndex instead
|
||||
this.view.selection.rangedSelect(this.currentIndex, edge, event.getModifierState("Accel"));
|
||||
|
||||
this.ensureRowIsVisible(edge);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
<method name="_handleEnter">
|
||||
<parameter name="event"/>
|
||||
<body><![CDATA[
|
||||
if (this._editingColumn) {
|
||||
this.stopEditing(true);
|
||||
this.focus();
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.changeOpenState(this.currentIndex);
|
||||
]]></body>
|
||||
</method>
|
||||
</implementation>
|
||||
|
||||
<handlers>
|
||||
<handler event="underflow">
|
||||
<![CDATA[
|
||||
// Scrollport event orientation
|
||||
// 0: vertical
|
||||
// 1: horizontal
|
||||
// 2: both (not used)
|
||||
if (event.target.tagName != "treechildren")
|
||||
return;
|
||||
if (event.detail == 1)
|
||||
this.setAttribute("hidehscroll", "true");
|
||||
else if (event.detail == 0)
|
||||
this.setAttribute("hidevscroll", "true");
|
||||
event.stopPropagation();
|
||||
]]>
|
||||
</handler>
|
||||
<handler event="overflow">
|
||||
<![CDATA[
|
||||
if (event.target.tagName != "treechildren")
|
||||
return;
|
||||
if (event.detail == 1)
|
||||
this.removeAttribute("hidehscroll");
|
||||
else if (event.detail == 0)
|
||||
this.removeAttribute("hidevscroll");
|
||||
event.stopPropagation();
|
||||
]]>
|
||||
</handler>
|
||||
<handler event="touchstart">
|
||||
<![CDATA[
|
||||
function isScrollbarElement(target) {
|
||||
return (target.localName == "thumb" || target.localName == "slider")
|
||||
&& target.namespaceURI == "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
}
|
||||
if (event.touches.length > 1 || isScrollbarElement(event.touches[0].target)) {
|
||||
// Multiple touch points detected, abort. In particular this aborts
|
||||
// the panning gesture when the user puts a second finger down after
|
||||
// already panning with one finger. Aborting at this point prevents
|
||||
// the pan gesture from being resumed until all fingers are lifted
|
||||
// (as opposed to when the user is back down to one finger).
|
||||
// Additionally, if the user lands on the scrollbar don't use this
|
||||
// code for scrolling, instead allow gecko to handle scrollbar
|
||||
// interaction normally.
|
||||
this._touchY = -1;
|
||||
} else {
|
||||
this._touchY = event.touches[0].screenY;
|
||||
}
|
||||
]]>
|
||||
</handler>
|
||||
<handler event="touchmove">
|
||||
<![CDATA[
|
||||
if (event.touches.length == 1 &&
|
||||
this._touchY >= 0) {
|
||||
var deltaY = this._touchY - event.touches[0].screenY;
|
||||
var lines = Math.trunc(deltaY / this.rowHeight);
|
||||
if (Math.abs(lines) > 0) {
|
||||
this.scrollByLines(lines);
|
||||
deltaY -= lines * this.rowHeight;
|
||||
this._touchY = event.touches[0].screenY + deltaY;
|
||||
}
|
||||
event.preventDefault();
|
||||
}
|
||||
]]>
|
||||
</handler>
|
||||
<handler event="touchend">
|
||||
<![CDATA[
|
||||
this._touchY = -1;
|
||||
]]>
|
||||
</handler>
|
||||
<handler event="MozMousePixelScroll">
|
||||
<![CDATA[
|
||||
if (!(this.getAttribute("allowunderflowscroll") == "true" &&
|
||||
this.getAttribute("hidevscroll") == "true"))
|
||||
event.preventDefault();
|
||||
]]>
|
||||
</handler>
|
||||
<handler event="DOMMouseScroll">
|
||||
<![CDATA[
|
||||
if (!(this.getAttribute("allowunderflowscroll") == "true" &&
|
||||
this.getAttribute("hidevscroll") == "true"))
|
||||
event.preventDefault();
|
||||
|
||||
if (this._editingColumn)
|
||||
return;
|
||||
if (event.axis == event.HORIZONTAL_AXIS)
|
||||
return;
|
||||
|
||||
var rows = event.detail;
|
||||
if (rows == UIEvent.SCROLL_PAGE_UP)
|
||||
this.scrollByPages(-1);
|
||||
else if (rows == UIEvent.SCROLL_PAGE_DOWN)
|
||||
this.scrollByPages(1);
|
||||
else
|
||||
this.scrollByLines(rows);
|
||||
]]>
|
||||
</handler>
|
||||
<handler event="MozSwipeGesture" preventdefault="true">
|
||||
<![CDATA[
|
||||
// Figure out which row to show
|
||||
let targetRow = 0;
|
||||
|
||||
// Only handle swipe gestures up and down
|
||||
switch (event.direction) {
|
||||
case event.DIRECTION_DOWN:
|
||||
targetRow = this.view.rowCount - 1;
|
||||
// Fall through for actual action
|
||||
case event.DIRECTION_UP:
|
||||
this.ensureRowIsVisible(targetRow);
|
||||
break;
|
||||
}
|
||||
]]>
|
||||
</handler>
|
||||
<handler event="select" phase="target"
|
||||
action="if (event.originalTarget == this) this.stopEditing(true);"/>
|
||||
<handler event="focus">
|
||||
<![CDATA[
|
||||
this.focused = true;
|
||||
if (this.currentIndex == -1 && this.view.rowCount > 0) {
|
||||
this.currentIndex = this.getFirstVisibleRow();
|
||||
}
|
||||
]]>
|
||||
</handler>
|
||||
<handler event="blur" action="this.focused = false;"/>
|
||||
<handler event="blur" phase="capturing"
|
||||
action="if (event.originalTarget == this.inputField.inputField) this.stopEditing(true);"/>
|
||||
<handler event="keydown" keycode="VK_RETURN">
|
||||
if (this._handleEnter(event)) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
</handler>
|
||||
<handler event="keydown" keycode="VK_ESCAPE">
|
||||
<![CDATA[
|
||||
if (this._editingColumn) {
|
||||
this.stopEditing(false);
|
||||
this.focus();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
]]>
|
||||
</handler>
|
||||
<handler event="keydown" keycode="VK_LEFT">
|
||||
<![CDATA[
|
||||
if (this._editingColumn)
|
||||
return;
|
||||
|
||||
var row = this.currentIndex;
|
||||
if (row < 0)
|
||||
return;
|
||||
|
||||
if (this.changeOpenState(this.currentIndex, false)) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
var parentIndex = this.view.getParentIndex(this.currentIndex);
|
||||
if (parentIndex >= 0) {
|
||||
this.view.selection.select(parentIndex);
|
||||
this.ensureRowIsVisible(parentIndex);
|
||||
event.preventDefault();
|
||||
}
|
||||
]]>
|
||||
</handler>
|
||||
<handler event="keydown" keycode="VK_RIGHT">
|
||||
<![CDATA[
|
||||
if (this._editingColumn)
|
||||
return;
|
||||
|
||||
var row = this.currentIndex;
|
||||
if (row < 0)
|
||||
return;
|
||||
|
||||
if (this.changeOpenState(row, true)) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
var c = row + 1;
|
||||
var view = this.view;
|
||||
if (c < view.rowCount &&
|
||||
view.getParentIndex(c) == row) {
|
||||
// If already opened, select the first child.
|
||||
// The getParentIndex test above ensures that the children
|
||||
// are already populated and ready.
|
||||
this.view.selection.timedSelect(c, this._selectDelay);
|
||||
this.ensureRowIsVisible(c);
|
||||
event.preventDefault();
|
||||
}
|
||||
]]>
|
||||
</handler>
|
||||
<handler event="keydown" keycode="VK_UP" modifiers="accel any">
|
||||
<![CDATA[
|
||||
if (this._editingColumn)
|
||||
return;
|
||||
this._moveByOffset(-1, 0, event);
|
||||
]]>
|
||||
</handler>
|
||||
<handler event="keydown" keycode="VK_DOWN" modifiers="accel any">
|
||||
<![CDATA[
|
||||
if (this._editingColumn)
|
||||
return;
|
||||
this._moveByOffset(1, this.view.rowCount - 1, event);
|
||||
]]>
|
||||
</handler>
|
||||
<handler event="keydown" keycode="VK_UP" modifiers="accel any, shift">
|
||||
<![CDATA[
|
||||
if (this._editingColumn)
|
||||
return;
|
||||
this._moveByOffsetShift(-1, 0, event);
|
||||
]]>
|
||||
</handler>
|
||||
<handler event="keydown" keycode="VK_DOWN" modifiers="accel any, shift">
|
||||
<![CDATA[
|
||||
if (this._editingColumn)
|
||||
return;
|
||||
this._moveByOffsetShift(1, this.view.rowCount - 1, event);
|
||||
]]>
|
||||
</handler>
|
||||
<handler event="keydown" keycode="VK_PAGE_UP" modifiers="accel any">
|
||||
<![CDATA[
|
||||
if (this._editingColumn)
|
||||
return;
|
||||
this._moveByPage(-1, 0, event);
|
||||
]]>
|
||||
</handler>
|
||||
<handler event="keydown" keycode="VK_PAGE_DOWN" modifiers="accel any">
|
||||
<![CDATA[
|
||||
if (this._editingColumn)
|
||||
return;
|
||||
this._moveByPage(1, this.view.rowCount - 1, event);
|
||||
]]>
|
||||
</handler>
|
||||
<handler event="keydown" keycode="VK_PAGE_UP" modifiers="accel any, shift">
|
||||
<![CDATA[
|
||||
if (this._editingColumn)
|
||||
return;
|
||||
this._moveByPageShift(-1, 0, event);
|
||||
]]>
|
||||
</handler>
|
||||
<handler event="keydown" keycode="VK_PAGE_DOWN" modifiers="accel any, shift">
|
||||
<![CDATA[
|
||||
if (this._editingColumn)
|
||||
return;
|
||||
this._moveByPageShift(1, this.view.rowCount - 1, event);
|
||||
]]>
|
||||
</handler>
|
||||
<handler event="keydown" keycode="VK_HOME" modifiers="accel any">
|
||||
<![CDATA[
|
||||
if (this._editingColumn)
|
||||
return;
|
||||
this._moveToEdge(0, event);
|
||||
]]>
|
||||
</handler>
|
||||
<handler event="keydown" keycode="VK_END" modifiers="accel any">
|
||||
<![CDATA[
|
||||
if (this._editingColumn)
|
||||
return;
|
||||
this._moveToEdge(this.view.rowCount - 1, event);
|
||||
]]>
|
||||
</handler>
|
||||
<handler event="keydown" keycode="VK_HOME" modifiers="accel any, shift">
|
||||
<![CDATA[
|
||||
if (this._editingColumn)
|
||||
return;
|
||||
this._moveToEdgeShift(0, event);
|
||||
]]>
|
||||
</handler>
|
||||
<handler event="keydown" keycode="VK_END" modifiers="accel any, shift">
|
||||
<![CDATA[
|
||||
if (this._editingColumn)
|
||||
return;
|
||||
this._moveToEdgeShift(this.view.rowCount - 1, event);
|
||||
]]>
|
||||
</handler>
|
||||
<handler event="keypress">
|
||||
<![CDATA[
|
||||
if (this._editingColumn)
|
||||
return;
|
||||
|
||||
if (event.charCode == " ".charCodeAt(0)) {
|
||||
var c = this.currentIndex;
|
||||
if (!this.view.selection.isSelected(c) ||
|
||||
(!this.view.selection.single && event.getModifierState("Accel"))) {
|
||||
this.view.selection.toggleSelect(c);
|
||||
event.preventDefault();
|
||||
}
|
||||
} else if (!this.disableKeyNavigation && event.charCode > 0 &&
|
||||
!event.altKey && !event.getModifierState("Accel") &&
|
||||
!event.metaKey && !event.ctrlKey) {
|
||||
var l = this._keyNavigate(event);
|
||||
if (l >= 0) {
|
||||
this.view.selection.timedSelect(l, this._selectDelay);
|
||||
this.ensureRowIsVisible(l);
|
||||
}
|
||||
event.preventDefault();
|
||||
}
|
||||
]]>
|
||||
</handler>
|
||||
</handlers>
|
||||
</binding>
|
||||
|
||||
</bindings>
|
|
@ -61,7 +61,7 @@ broadcasterset, broadcaster, observes,
|
|||
keyset, key, toolbarpalette, toolbarset,
|
||||
template, rule, conditions, action,
|
||||
bindings, binding, content, member, triple,
|
||||
treechildren, treeitem, treeseparator, treerow, treecell {
|
||||
treeitem, treeseparator, treerow, treecell {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
@ -426,27 +426,24 @@ column {
|
|||
|
||||
/******** tree ******/
|
||||
|
||||
tree {
|
||||
-moz-binding: url("chrome://global/content/bindings/tree.xml#tree");
|
||||
}
|
||||
|
||||
treecolpicker {
|
||||
-moz-box-ordinal-group: 2147483646;
|
||||
}
|
||||
|
||||
tree > treechildren {
|
||||
treechildren {
|
||||
display: -moz-box;
|
||||
-moz-user-select: none;
|
||||
-moz-box-flex: 1;
|
||||
}
|
||||
|
||||
tree {
|
||||
-moz-binding: none;
|
||||
-moz-box-orient: vertical;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
tree[hidecolumnpicker="true"] > treecols > treecolpicker {
|
||||
tree[hidecolumnpicker="true"] treecolpicker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
@ -460,7 +457,7 @@ treecol[hidden="true"] {
|
|||
}
|
||||
|
||||
/* ::::: lines connecting cells ::::: */
|
||||
tree:not([treelines="true"]) > treechildren::-moz-tree-line {
|
||||
tree:not([treelines="true"]) treechildren::-moz-tree-line {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
|
|
|
@ -385,14 +385,6 @@ xul|textbox {
|
|||
padding-left: 8px;
|
||||
}
|
||||
|
||||
/* Create a separate rule to unset these styles on .tree-input instead of
|
||||
using :not(.tree-input) so the selector specifity doesn't change. */
|
||||
xul|textbox.tree-input {
|
||||
min-height: unset;
|
||||
padding-right: unset;
|
||||
padding-left: unset;
|
||||
}
|
||||
|
||||
html|input[type="email"],
|
||||
html|input[type="tel"],
|
||||
html|input[type="text"],
|
||||
|
|
|
@ -48,6 +48,7 @@ const MAPPINGS = {
|
|||
"panelUI.js": "browser/components/customizableui/content/panelUI.js",
|
||||
"viewSourceUtils.js":
|
||||
"toolkit/components/viewsource/content/viewSourceUtils.js",
|
||||
"places-tree.js": "browser/components/places/content/places-tree.js",
|
||||
};
|
||||
|
||||
const globalScriptsRegExp =
|
||||
|
|
|
@ -1332,6 +1332,15 @@ const nsTArray<GfxDriverInfo>& GfxInfo::GetGfxDriverInfo() {
|
|||
DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions,
|
||||
"FEATURE_FAILURE_BUG_1207665_2");
|
||||
|
||||
APPEND_TO_DRIVER_BLOCKLIST2(
|
||||
OperatingSystem::Windows10,
|
||||
(nsAString&)GfxDriverInfo::GetDeviceVendor(VendorQualcomm),
|
||||
GfxDriverInfo::allDevices,
|
||||
nsIGfxInfo::FEATURE_DIRECT2D,
|
||||
nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
|
||||
DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions,
|
||||
"FEATURE_FAILURE_QUALCOMM");
|
||||
|
||||
/* Disable D2D on AMD Catalyst 14.4 until 14.6
|
||||
* See bug 984488
|
||||
*/
|
||||
|
|
Загрузка…
Ссылка в новой задаче