зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-c to inbound. a=merge
This commit is contained in:
Коммит
dec7bdc30e
|
@ -211,19 +211,12 @@ var StarUI = {
|
|||
|
||||
this._isNewBookmark = aIsNewBookmark;
|
||||
this._uriForRemoval = "";
|
||||
// TODO: Deprecate this once async transactions are enabled and the legacy
|
||||
// transactions code is gone (bug 1131491) - we don't want addons to to use
|
||||
// the completeNodeLikeObjectForItemId, so it's better if they keep passing
|
||||
// the item-id for now).
|
||||
// TODO (bug 1131491): Deprecate this once async transactions are enabled
|
||||
// and the legacy transactions code is gone.
|
||||
if (typeof(aNode) == "number") {
|
||||
let itemId = aNode;
|
||||
if (PlacesUIUtils.useAsyncTransactions) {
|
||||
let guid = await PlacesUtils.promiseItemGuid(itemId);
|
||||
aNode = await PlacesUIUtils.promiseNodeLike(guid);
|
||||
} else {
|
||||
aNode = { itemId };
|
||||
await PlacesUIUtils.completeNodeLikeObjectForItemId(aNode);
|
||||
}
|
||||
let guid = await PlacesUtils.promiseItemGuid(itemId);
|
||||
aNode = await PlacesUIUtils.fetchNodeLike(guid);
|
||||
}
|
||||
|
||||
// Performance: load the overlay the first time the panel is opened
|
||||
|
@ -425,7 +418,7 @@ var PlacesCommandHook = {
|
|||
charset = aBrowser.characterSet;
|
||||
} catch (e) { }
|
||||
|
||||
if (aShowEditUI && isNewBookmark) {
|
||||
if (aShowEditUI) {
|
||||
// If we bookmark the page here but open right into a cancelable
|
||||
// state (i.e. new bookmark in Library), start batching here so
|
||||
// all of the actions can be undone in a single undo step.
|
||||
|
@ -457,17 +450,17 @@ var PlacesCommandHook = {
|
|||
// 2. the identity icon
|
||||
// 3. the content area
|
||||
if (BookmarkingUI.anchor && isVisible(BookmarkingUI.anchor)) {
|
||||
StarUI.showEditBookmarkPopup(itemId, BookmarkingUI.anchor,
|
||||
"bottomcenter topright", isNewBookmark);
|
||||
await StarUI.showEditBookmarkPopup(itemId, BookmarkingUI.anchor,
|
||||
"bottomcenter topright", isNewBookmark);
|
||||
return;
|
||||
}
|
||||
|
||||
let identityIcon = document.getElementById("identity-icon");
|
||||
if (isVisible(identityIcon)) {
|
||||
StarUI.showEditBookmarkPopup(itemId, identityIcon,
|
||||
"bottomcenter topright", isNewBookmark);
|
||||
await StarUI.showEditBookmarkPopup(itemId, identityIcon,
|
||||
"bottomcenter topright", isNewBookmark);
|
||||
} else {
|
||||
StarUI.showEditBookmarkPopup(itemId, aBrowser, "overlap", isNewBookmark);
|
||||
await StarUI.showEditBookmarkPopup(itemId, aBrowser, "overlap", isNewBookmark);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -489,9 +482,14 @@ var PlacesCommandHook = {
|
|||
let docInfo = await this._getPageDetails(aBrowser);
|
||||
|
||||
try {
|
||||
info.title = docInfo.isErrorPage ?
|
||||
(await PlacesUtils.history.fetch(aBrowser.currentURI)).title :
|
||||
aBrowser.contentTitle;
|
||||
if (docInfo.isErrorPage) {
|
||||
let entry = await PlacesUtils.history.fetch(aBrowser.currentURI);
|
||||
if (entry) {
|
||||
info.title = entry.title;
|
||||
}
|
||||
} else {
|
||||
info.title = aBrowser.contentTitle;
|
||||
}
|
||||
info.title = info.title || url.href;
|
||||
description = docInfo.description;
|
||||
charset = aBrowser.characterSet;
|
||||
|
@ -532,17 +530,17 @@ var PlacesCommandHook = {
|
|||
// 2. the identity icon
|
||||
// 3. the content area
|
||||
if (BookmarkingUI.anchor && isVisible(BookmarkingUI.anchor)) {
|
||||
StarUI.showEditBookmarkPopup(node, BookmarkingUI.anchor,
|
||||
await StarUI.showEditBookmarkPopup(node, BookmarkingUI.anchor,
|
||||
"bottomcenter topright", isNewBookmark);
|
||||
return;
|
||||
}
|
||||
|
||||
let identityIcon = document.getElementById("identity-icon");
|
||||
if (isVisible(identityIcon)) {
|
||||
StarUI.showEditBookmarkPopup(node, identityIcon,
|
||||
await StarUI.showEditBookmarkPopup(node, identityIcon,
|
||||
"bottomcenter topright", isNewBookmark);
|
||||
} else {
|
||||
StarUI.showEditBookmarkPopup(node, aBrowser, "overlap", isNewBookmark);
|
||||
await StarUI.showEditBookmarkPopup(node, aBrowser, "overlap", isNewBookmark);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -562,7 +560,8 @@ var PlacesCommandHook = {
|
|||
* Adds a bookmark to the page loaded in the current tab.
|
||||
*/
|
||||
bookmarkCurrentPage: function PCH_bookmarkCurrentPage(aShowEditUI, aParent) {
|
||||
this.bookmarkPage(gBrowser.selectedBrowser, aParent, aShowEditUI);
|
||||
this.bookmarkPage(gBrowser.selectedBrowser, aParent, aShowEditUI)
|
||||
.catch(Components.utils.reportError);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -1808,7 +1808,9 @@ nsContextMenu.prototype = {
|
|||
},
|
||||
|
||||
bookmarkThisPage: function CM_bookmarkThisPage() {
|
||||
window.top.PlacesCommandHook.bookmarkPage(this.browser, PlacesUtils.bookmarksMenuFolderId, true);
|
||||
window.top.PlacesCommandHook
|
||||
.bookmarkPage(this.browser, PlacesUtils.bookmarksMenuFolderId, true)
|
||||
.catch(Components.utils.reportError);
|
||||
},
|
||||
|
||||
bookmarkLink: function CM_bookmarkLink() {
|
||||
|
|
|
@ -5,83 +5,88 @@
|
|||
// This file is tests for the default titles that new bookmarks get.
|
||||
|
||||
var tests = [
|
||||
// Common page.
|
||||
["http://example.com/browser/browser/base/content/test/general/dummy_page.html",
|
||||
"Dummy test page"],
|
||||
// Data URI.
|
||||
["data:text/html;charset=utf-8,<title>test%20data:%20url</title>",
|
||||
"test data: url"],
|
||||
// about:neterror
|
||||
["data:application/vnd.mozilla.xul+xml,",
|
||||
"data:application/vnd.mozilla.xul+xml,"],
|
||||
// about:certerror
|
||||
["https://untrusted.example.com/somepage.html",
|
||||
"https://untrusted.example.com/somepage.html"]
|
||||
// Common page.
|
||||
["http://example.com/browser/browser/base/content/test/general/dummy_page.html",
|
||||
"Dummy test page"],
|
||||
// Data URI.
|
||||
["data:text/html;charset=utf-8,<title>test%20data:%20url</title>",
|
||||
"test data: url"],
|
||||
// about:neterror
|
||||
["data:application/vnd.mozilla.xul+xml,",
|
||||
"data:application/vnd.mozilla.xul+xml,"],
|
||||
// about:certerror
|
||||
["https://untrusted.example.com/somepage.html",
|
||||
"https://untrusted.example.com/somepage.html"]
|
||||
];
|
||||
|
||||
add_task(async function() {
|
||||
gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
browser.stop(); // stop the about:blank load.
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
|
||||
let browser = tab.linkedBrowser;
|
||||
|
||||
// Test that a bookmark of each URI gets the corresponding default title.
|
||||
for (let i = 0; i < tests.length; ++i) {
|
||||
let [uri, title] = tests[i];
|
||||
|
||||
let promiseLoaded = promisePageLoaded(browser);
|
||||
BrowserTestUtils.loadURI(browser, uri);
|
||||
await promiseLoaded;
|
||||
await checkBookmark(uri, title);
|
||||
}
|
||||
|
||||
// Network failure test: now that dummy_page.html is in history, bookmarking
|
||||
// it should give the last known page title as the default bookmark title.
|
||||
|
||||
// Simulate a network outage with offline mode. (Localhost is still
|
||||
// accessible in offline mode, so disable the test proxy as well.)
|
||||
BrowserOffline.toggleOfflineStatus();
|
||||
let proxy = Services.prefs.getIntPref("network.proxy.type");
|
||||
Services.prefs.setIntPref("network.proxy.type", 0);
|
||||
registerCleanupFunction(function() {
|
||||
BrowserOffline.toggleOfflineStatus();
|
||||
Services.prefs.setIntPref("network.proxy.type", proxy);
|
||||
});
|
||||
|
||||
// LOAD_FLAGS_BYPASS_CACHE isn't good enough. So clear the cache.
|
||||
Services.cache2.clear();
|
||||
|
||||
let [uri, title] = tests[0];
|
||||
// Test that a bookmark of each URI gets the corresponding default title.
|
||||
for (let i = 0; i < tests.length; ++i) {
|
||||
let [url, title] = tests[i];
|
||||
|
||||
// We use promisePageLoaded rather than BrowserTestUtils.browserLoaded - see
|
||||
// note on function definition below.
|
||||
let promiseLoaded = promisePageLoaded(browser);
|
||||
BrowserTestUtils.loadURI(browser, uri);
|
||||
BrowserTestUtils.loadURI(browser, url);
|
||||
await promiseLoaded;
|
||||
|
||||
// The offline mode test is only good if the page failed to load.
|
||||
await ContentTask.spawn(browser, null, function() {
|
||||
is(content.document.documentURI.substring(0, 14), "about:neterror",
|
||||
"Offline mode successfully simulated network outage.");
|
||||
});
|
||||
await checkBookmark(uri, title);
|
||||
await checkBookmark(url, title);
|
||||
}
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
// Network failure test: now that dummy_page.html is in history, bookmarking
|
||||
// it should give the last known page title as the default bookmark title.
|
||||
|
||||
// Simulate a network outage with offline mode. (Localhost is still
|
||||
// accessible in offline mode, so disable the test proxy as well.)
|
||||
BrowserOffline.toggleOfflineStatus();
|
||||
let proxy = Services.prefs.getIntPref("network.proxy.type");
|
||||
Services.prefs.setIntPref("network.proxy.type", 0);
|
||||
registerCleanupFunction(function() {
|
||||
BrowserOffline.toggleOfflineStatus();
|
||||
Services.prefs.setIntPref("network.proxy.type", proxy);
|
||||
});
|
||||
|
||||
// LOAD_FLAGS_BYPASS_CACHE isn't good enough. So clear the cache.
|
||||
Services.cache2.clear();
|
||||
|
||||
let [url, title] = tests[0];
|
||||
|
||||
// We use promisePageLoaded rather than BrowserTestUtils.browserLoaded - see
|
||||
// note on function definition below.
|
||||
let promiseLoaded = promisePageLoaded(browser);
|
||||
BrowserTestUtils.loadURI(browser, url);
|
||||
await promiseLoaded;
|
||||
|
||||
// The offline mode test is only good if the page failed to load.
|
||||
await ContentTask.spawn(browser, null, function() {
|
||||
Assert.equal(content.document.documentURI.substring(0, 14), "about:neterror",
|
||||
"Offline mode successfully simulated network outage.");
|
||||
});
|
||||
await checkBookmark(url, title);
|
||||
|
||||
await BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
||||
// Bookmark the current page and confirm that the new bookmark has the expected
|
||||
// title. (Then delete the bookmark.)
|
||||
async function checkBookmark(uri, expected_title) {
|
||||
is(gBrowser.selectedBrowser.currentURI.spec, uri,
|
||||
"Trying to bookmark the expected uri");
|
||||
async function checkBookmark(url, expected_title) {
|
||||
Assert.equal(gBrowser.selectedBrowser.currentURI.spec, url,
|
||||
"Trying to bookmark the expected uri");
|
||||
|
||||
let promiseBookmark = promiseOnBookmarkItemAdded(gBrowser.selectedBrowser.currentURI);
|
||||
PlacesCommandHook.bookmarkCurrentPage(false);
|
||||
await promiseBookmark;
|
||||
let promiseBookmark = PlacesTestUtils.waitForNotification("onItemAdded",
|
||||
(id, parentId, index, type, itemUrl) => itemUrl.equals(gBrowser.selectedBrowser.currentURI));
|
||||
PlacesCommandHook.bookmarkCurrentPage(false);
|
||||
await promiseBookmark;
|
||||
|
||||
let id = PlacesUtils.getMostRecentBookmarkForURI(PlacesUtils._uri(uri));
|
||||
ok(id > 0, "Found the expected bookmark");
|
||||
let title = PlacesUtils.bookmarks.getItemTitle(id);
|
||||
is(title, expected_title, "Bookmark got a good default title.");
|
||||
let bookmark = await PlacesUtils.bookmarks.fetch({url});
|
||||
|
||||
PlacesUtils.bookmarks.removeItem(id);
|
||||
Assert.ok(bookmark, "Found the expected bookmark");
|
||||
Assert.equal(bookmark.title, expected_title, "Bookmark got a good default title.");
|
||||
|
||||
await PlacesUtils.bookmarks.remove(bookmark);
|
||||
}
|
||||
|
||||
// BrowserTestUtils.browserLoaded doesn't work for the about pages, so use a
|
||||
|
@ -89,9 +94,9 @@ async function checkBookmark(uri, expected_title) {
|
|||
function promisePageLoaded(browser) {
|
||||
return ContentTask.spawn(browser, null, async function() {
|
||||
await ContentTaskUtils.waitForEvent(this, "DOMContentLoaded", true,
|
||||
(event) => {
|
||||
return event.originalTarget === content.document &&
|
||||
event.target.location.href !== "about:blank"
|
||||
});
|
||||
(event) => {
|
||||
return event.originalTarget === content.document &&
|
||||
event.target.location.href !== "about:blank"
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -24,11 +24,10 @@ add_task(async function test_star_redirect() {
|
|||
|
||||
await promiseStarState(BookmarkingUI.STATUS_UNSTARRED);
|
||||
|
||||
let promiseBookmark = promiseOnBookmarkItemAdded(gBrowser.currentURI);
|
||||
let bookmarkPanel = document.getElementById("editBookmarkPanel");
|
||||
let shownPromise = promisePopupShown(bookmarkPanel);
|
||||
BookmarkingUI.star.click();
|
||||
// This resolves on the next tick, so the star should have already been
|
||||
// updated at that point.
|
||||
await promiseBookmark;
|
||||
await shownPromise;
|
||||
|
||||
is(BookmarkingUI.status, BookmarkingUI.STATUS_STARRED, "The star is starred");
|
||||
});
|
||||
|
|
|
@ -1432,67 +1432,6 @@ this.PlacesUIUtils = {
|
|||
optionsParam.value.maxResults == 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* WARNING TO ADDON AUTHORS: DO NOT USE THIS METHOD. IT"S LIKELY TO BE REMOVED IN A
|
||||
* FUTURE RELEASE.
|
||||
*
|
||||
* Helpers for consumers of editBookmarkOverlay which don't have a node as their input.
|
||||
* Given a partial node-like object, having at least the itemId property set, this
|
||||
* method completes the rest of the properties necessary for initialising the edit
|
||||
* overlay with it.
|
||||
*
|
||||
* @param aNodeLike
|
||||
* an object having at least the itemId nsINavHistoryResultNode property set,
|
||||
* along with any other properties available.
|
||||
*/
|
||||
completeNodeLikeObjectForItemId(aNodeLike) {
|
||||
if (this.useAsyncTransactions) {
|
||||
// When async-transactions are enabled, node-likes must have
|
||||
// bookmarkGuid set, and we cannot set it synchronously.
|
||||
throw new Error("completeNodeLikeObjectForItemId cannot be used when " +
|
||||
"async transactions are enabled");
|
||||
}
|
||||
if (!("itemId" in aNodeLike))
|
||||
throw new Error("itemId missing in aNodeLike");
|
||||
|
||||
let itemId = aNodeLike.itemId;
|
||||
let defGetter = XPCOMUtils.defineLazyGetter.bind(XPCOMUtils, aNodeLike);
|
||||
|
||||
if (!("title" in aNodeLike))
|
||||
defGetter("title", () => PlacesUtils.bookmarks.getItemTitle(itemId));
|
||||
|
||||
if (!("uri" in aNodeLike)) {
|
||||
defGetter("uri", () => {
|
||||
let uri = null;
|
||||
try {
|
||||
uri = PlacesUtils.bookmarks.getBookmarkURI(itemId);
|
||||
} catch (ex) { }
|
||||
return uri ? uri.spec : "";
|
||||
});
|
||||
}
|
||||
|
||||
if (!("type" in aNodeLike)) {
|
||||
defGetter("type", () => {
|
||||
if (aNodeLike.uri.length > 0) {
|
||||
if (/^place:/.test(aNodeLike.uri)) {
|
||||
if (this.isFolderShortcutQueryString(aNodeLike.uri))
|
||||
return Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT;
|
||||
|
||||
return Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY;
|
||||
}
|
||||
|
||||
return Ci.nsINavHistoryResultNode.RESULT_TYPE_URI;
|
||||
}
|
||||
|
||||
let itemType = PlacesUtils.bookmarks.getItemType(itemId);
|
||||
if (itemType == PlacesUtils.bookmarks.TYPE_FOLDER)
|
||||
return Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER;
|
||||
|
||||
throw new Error("Unexpected item type");
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Helpers for consumers of editBookmarkOverlay which don't have a node as their input.
|
||||
*
|
||||
|
@ -1509,6 +1448,12 @@ this.PlacesUIUtils = {
|
|||
if (aFetchInfo.itemType == PlacesUtils.bookmarks.TYPE_SEPARATOR)
|
||||
throw new Error("promiseNodeLike doesn't support separators");
|
||||
|
||||
let parent = {
|
||||
itemId: await PlacesUtils.promiseItemId(aFetchInfo.parentGuid),
|
||||
bookmarkGuid: aFetchInfo.parentGuid,
|
||||
type: Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER
|
||||
};
|
||||
|
||||
return Object.freeze({
|
||||
itemId: await PlacesUtils.promiseItemId(aFetchInfo.guid),
|
||||
bookmarkGuid: aFetchInfo.guid,
|
||||
|
@ -1530,6 +1475,10 @@ this.PlacesUIUtils = {
|
|||
}
|
||||
|
||||
return Ci.nsINavHistoryResultNode.RESULT_TYPE_URI;
|
||||
},
|
||||
|
||||
get parent() {
|
||||
return parent;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
|
|
@ -618,7 +618,12 @@ var BookmarkPropertiesPanel = {
|
|||
uri: this._uri ? this._uri.spec : "",
|
||||
type: this._itemType == BOOKMARK_ITEM ?
|
||||
Ci.nsINavHistoryResultNode.RESULT_TYPE_URI :
|
||||
Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER
|
||||
Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER,
|
||||
parent: {
|
||||
itemId: container,
|
||||
bookmarkGuid: await PlacesUtils.promiseItemGuid(container),
|
||||
type: Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -677,7 +682,12 @@ var BookmarkPropertiesPanel = {
|
|||
uri: this._uri ? this._uri.spec : "",
|
||||
type: this._itemType == BOOKMARK_ITEM ?
|
||||
Ci.nsINavHistoryResultNode.RESULT_TYPE_URI :
|
||||
Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER
|
||||
Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER,
|
||||
parent: {
|
||||
itemId: containerId,
|
||||
bookmarkGuid: parentGuid,
|
||||
type: Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -21,17 +21,18 @@ var gEditItemOverlay = {
|
|||
if (!("uris" in aInitInfo) && !("node" in aInitInfo))
|
||||
throw new Error("Neither node nor uris set for pane info");
|
||||
|
||||
// Once we stop supporting legacy add-ons the code should throw if a node is
|
||||
// not passed.
|
||||
let node = "node" in aInitInfo ? aInitInfo.node : null;
|
||||
|
||||
// Since there's no true UI for folder shortcuts (they show up just as their target
|
||||
// folders), when the pane shows for them it's opened in read-only mode, showing the
|
||||
// properties of the target folder.
|
||||
let itemId = node ? node.itemId : -1;
|
||||
let itemGuid = PlacesUIUtils.useAsyncTransactions && node ?
|
||||
PlacesUtils.getConcreteItemGuid(node) : null;
|
||||
let itemGuid = node ? PlacesUtils.getConcreteItemGuid(node) : null;
|
||||
let isItem = itemId != -1;
|
||||
let isFolderShortcut = isItem &&
|
||||
node.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT;
|
||||
node.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT;
|
||||
let isTag = node && PlacesUtils.nodeIsTagQuery(node);
|
||||
if (isTag) {
|
||||
itemId = PlacesUtils.getConcreteItemId(node);
|
||||
|
@ -47,17 +48,24 @@ var gEditItemOverlay = {
|
|||
let visibleRows = new Set();
|
||||
let isParentReadOnly = false;
|
||||
let postData = aInitInfo.postData;
|
||||
if (node && "parent" in node) {
|
||||
let parent = node.parent;
|
||||
if (parent) {
|
||||
isParentReadOnly = !PlacesUtils.nodeIsFolder(parent) ||
|
||||
PlacesUIUtils.isContentsReadOnly(parent);
|
||||
let parentId = -1;
|
||||
let parentGuid = null;
|
||||
|
||||
if (node && isItem) {
|
||||
if (!node.parent || (node.parent.itemId > 0 && !node.parent.bookmarkGuid)) {
|
||||
throw new Error("Cannot use an incomplete node to initialize the edit bookmark panel");
|
||||
}
|
||||
let parent = node.parent;
|
||||
isParentReadOnly = !PlacesUtils.nodeIsFolder(parent) ||
|
||||
PlacesUIUtils.isContentsReadOnly(parent);
|
||||
parentId = parent.itemId;
|
||||
parentGuid = parent.bookmarkGuid;
|
||||
}
|
||||
|
||||
let focusedElement = aInitInfo.focusedElement;
|
||||
let onPanelReady = aInitInfo.onPanelReady;
|
||||
|
||||
return this._paneInfo = { itemId, itemGuid, isItem,
|
||||
return this._paneInfo = { itemId, itemGuid, parentId, parentGuid, isItem,
|
||||
isURI, uri, title,
|
||||
isBookmark, isFolderShortcut, isParentReadOnly,
|
||||
bulkTagging, uris,
|
||||
|
@ -212,7 +220,7 @@ var gEditItemOverlay = {
|
|||
if (this.initialized)
|
||||
this.uninitPanel(false);
|
||||
|
||||
let { itemId, isItem, isURI,
|
||||
let { parentId, isItem, isURI,
|
||||
isBookmark, bulkTagging, uris,
|
||||
visibleRows, focusedElement,
|
||||
onPanelReady } = this._setPaneInfo(aInfo);
|
||||
|
@ -268,8 +276,7 @@ var gEditItemOverlay = {
|
|||
// not cheap (we don't always have the parent), and there's no use case for
|
||||
// this (it's only the Star UI that shows the folderPicker)
|
||||
if (showOrCollapse("folderRow", isItem, "folderPicker")) {
|
||||
let containerId = PlacesUtils.bookmarks.getFolderIdForItem(itemId);
|
||||
this._initFolderMenuList(containerId);
|
||||
this._initFolderMenuList(parentId).catch(Components.utils.reportError);
|
||||
}
|
||||
|
||||
// Selection count.
|
||||
|
@ -374,14 +381,16 @@ var gEditItemOverlay = {
|
|||
* The popup to which the menu-item should be added.
|
||||
* @param aFolderId
|
||||
* The identifier of the bookmarks folder.
|
||||
* @param aTitle
|
||||
* The title to use as a label.
|
||||
* @return the new menu item.
|
||||
*/
|
||||
_appendFolderItemToMenupopup(aMenupopup, aFolderId) {
|
||||
_appendFolderItemToMenupopup(aMenupopup, aFolderId, aTitle) {
|
||||
// First make sure the folders-separator is visible
|
||||
this._element("foldersSeparator").hidden = false;
|
||||
|
||||
var folderMenuItem = document.createElement("menuitem");
|
||||
var folderTitle = PlacesUtils.bookmarks.getItemTitle(aFolderId)
|
||||
var folderTitle = aTitle;
|
||||
folderMenuItem.folderId = aFolderId;
|
||||
folderMenuItem.setAttribute("label", folderTitle);
|
||||
folderMenuItem.className = "menuitem-iconic folder-icon";
|
||||
|
@ -389,31 +398,29 @@ var gEditItemOverlay = {
|
|||
return folderMenuItem;
|
||||
},
|
||||
|
||||
_initFolderMenuList: function EIO__initFolderMenuList(aSelectedFolder) {
|
||||
async _initFolderMenuList(aSelectedFolder) {
|
||||
// clean up first
|
||||
var menupopup = this._folderMenuList.menupopup;
|
||||
while (menupopup.childNodes.length > 6)
|
||||
menupopup.removeChild(menupopup.lastChild);
|
||||
|
||||
const bms = PlacesUtils.bookmarks;
|
||||
const annos = PlacesUtils.annotations;
|
||||
|
||||
// Build the static list
|
||||
var unfiledItem = this._element("unfiledRootItem");
|
||||
if (!this._staticFoldersListBuilt) {
|
||||
unfiledItem.label = bms.getItemTitle(PlacesUtils.unfiledBookmarksFolderId);
|
||||
let unfiledItem = this._element("unfiledRootItem");
|
||||
unfiledItem.label = PlacesUtils.getString("OtherBookmarksFolderTitle");
|
||||
unfiledItem.folderId = PlacesUtils.unfiledBookmarksFolderId;
|
||||
var bmMenuItem = this._element("bmRootItem");
|
||||
bmMenuItem.label = bms.getItemTitle(PlacesUtils.bookmarksMenuFolderId);
|
||||
let bmMenuItem = this._element("bmRootItem");
|
||||
bmMenuItem.label = PlacesUtils.getString("BookmarksMenuFolderTitle");
|
||||
bmMenuItem.folderId = PlacesUtils.bookmarksMenuFolderId;
|
||||
var toolbarItem = this._element("toolbarFolderItem");
|
||||
toolbarItem.label = bms.getItemTitle(PlacesUtils.toolbarFolderId);
|
||||
let toolbarItem = this._element("toolbarFolderItem");
|
||||
toolbarItem.label = PlacesUtils.getString("BookmarksToolbarFolderTitle");
|
||||
toolbarItem.folderId = PlacesUtils.toolbarFolderId;
|
||||
this._staticFoldersListBuilt = true;
|
||||
}
|
||||
|
||||
// List of recently used folders:
|
||||
var folderIds = annos.getItemsWithAnnotation(LAST_USED_ANNO);
|
||||
var folderIds =
|
||||
PlacesUtils.annotations.getItemsWithAnnotation(LAST_USED_ANNO);
|
||||
|
||||
/**
|
||||
* The value of the LAST_USED_ANNO annotation is the time (in the form of
|
||||
|
@ -424,9 +431,12 @@ var gEditItemOverlay = {
|
|||
* set. Then we sort it descendingly based on the time field.
|
||||
*/
|
||||
this._recentFolders = [];
|
||||
for (let i = 0; i < folderIds.length; i++) {
|
||||
var lastUsed = annos.getItemAnnotation(folderIds[i], LAST_USED_ANNO);
|
||||
this._recentFolders.push({ folderId: folderIds[i], lastUsed });
|
||||
for (let folderId of folderIds) {
|
||||
var lastUsed =
|
||||
PlacesUtils.annotations.getItemAnnotation(folderId, LAST_USED_ANNO);
|
||||
let guid = await PlacesUtils.promiseItemGuid(folderId);
|
||||
let title = (await PlacesUtils.bookmarks.fetch(guid)).title;
|
||||
this._recentFolders.push({ folderId, guid, title, lastUsed });
|
||||
}
|
||||
this._recentFolders.sort(function(a, b) {
|
||||
if (b.lastUsed < a.lastUsed)
|
||||
|
@ -439,11 +449,14 @@ var gEditItemOverlay = {
|
|||
var numberOfItems = Math.min(MAX_FOLDER_ITEM_IN_MENU_LIST,
|
||||
this._recentFolders.length);
|
||||
for (let i = 0; i < numberOfItems; i++) {
|
||||
this._appendFolderItemToMenupopup(menupopup,
|
||||
this._recentFolders[i].folderId);
|
||||
await this._appendFolderItemToMenupopup(menupopup,
|
||||
this._recentFolders[i].folderId,
|
||||
this._recentFolders[i].title);
|
||||
}
|
||||
|
||||
var defaultItem = this._getFolderMenuItem(aSelectedFolder);
|
||||
let selectedFolderGuid = await PlacesUtils.promiseItemGuid(aSelectedFolder);
|
||||
let title = (await PlacesUtils.bookmarks.fetch(selectedFolderGuid)).title;
|
||||
var defaultItem = this._getFolderMenuItem(aSelectedFolder, title);
|
||||
this._folderMenuList.selectedItem = defaultItem;
|
||||
|
||||
// Set a selectedIndex attribute to show special icons
|
||||
|
@ -607,14 +620,13 @@ var gEditItemOverlay = {
|
|||
prefs.setCharPref("browser.bookmarks.editDialog.firstEditField", aNewField);
|
||||
},
|
||||
|
||||
onNamePickerChange() {
|
||||
async onNamePickerChange() {
|
||||
if (this.readOnly || !(this._paneInfo.isItem || this._paneInfo.isTag))
|
||||
return;
|
||||
|
||||
// Here we update either the item title or its cached static title
|
||||
let newTitle = this._namePicker.value;
|
||||
if (!newTitle &&
|
||||
PlacesUtils.bookmarks.getFolderIdForItem(this._paneInfo.itemId) == PlacesUtils.tagsFolderId) {
|
||||
if (!newTitle && this._paneInfo.parentGuid == PlacesUtils.bookmarks.tagsGuid) {
|
||||
// We don't allow setting an empty title for a tag, restore the old one.
|
||||
this._initNamePicker();
|
||||
} else {
|
||||
|
@ -625,13 +637,11 @@ var gEditItemOverlay = {
|
|||
PlacesUtils.transactionManager.doTransaction(txn);
|
||||
return;
|
||||
}
|
||||
(async () => {
|
||||
let guid = this._paneInfo.isTag
|
||||
? (await PlacesUtils.promiseItemGuid(this._paneInfo.itemId))
|
||||
: this._paneInfo.itemGuid;
|
||||
PlacesTransactions.EditTitle({ guid, title: newTitle })
|
||||
.transact().catch(Components.utils.reportError);
|
||||
})().catch(Components.utils.reportError);
|
||||
|
||||
let guid = this._paneInfo.isTag
|
||||
? (await PlacesUtils.promiseItemGuid(this._paneInfo.itemId))
|
||||
: this._paneInfo.itemGuid;
|
||||
await PlacesTransactions.EditTitle({ guid, title: newTitle }).transact();
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -748,19 +758,11 @@ var gEditItemOverlay = {
|
|||
|
||||
this._element("chooseFolderSeparator").hidden =
|
||||
this._element("chooseFolderMenuItem").hidden = true;
|
||||
var currentFolder = this._getFolderIdFromMenuList();
|
||||
this._folderTree.selectItems([currentFolder]);
|
||||
this._folderTree.selectItems([this._paneInfo.parentId]);
|
||||
this._folderTree.focus();
|
||||
}
|
||||
},
|
||||
|
||||
_getFolderIdFromMenuList() {
|
||||
var selectedItem = this._folderMenuList.selectedItem;
|
||||
NS_ASSERT("folderId" in selectedItem,
|
||||
"Invalid menuitem in the folders-menulist");
|
||||
return selectedItem.folderId;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the corresponding menu-item in the folder-menu-list for a bookmarks
|
||||
* folder if such an item exists. Otherwise, this creates a menu-item for the
|
||||
|
@ -768,8 +770,11 @@ var gEditItemOverlay = {
|
|||
* the new item replaces the last menu-item.
|
||||
* @param aFolderId
|
||||
* The identifier of the bookmarks folder.
|
||||
* @param aTitle
|
||||
* The title to use in case of menuitem creation.
|
||||
* @return handle to the menuitem.
|
||||
*/
|
||||
_getFolderMenuItem(aFolderId) {
|
||||
_getFolderMenuItem(aFolderId, aTitle) {
|
||||
let menupopup = this._folderMenuList.menupopup;
|
||||
let menuItem = Array.prototype.find.call(
|
||||
menupopup.childNodes, item => item.folderId === aFolderId);
|
||||
|
@ -780,10 +785,10 @@ var gEditItemOverlay = {
|
|||
if (menupopup.childNodes.length == 4 + MAX_FOLDER_ITEM_IN_MENU_LIST)
|
||||
menupopup.removeChild(menupopup.lastChild);
|
||||
|
||||
return this._appendFolderItemToMenupopup(menupopup, aFolderId);
|
||||
return this._appendFolderItemToMenupopup(menupopup, aFolderId, aTitle);
|
||||
},
|
||||
|
||||
onFolderMenuListCommand(aEvent) {
|
||||
async onFolderMenuListCommand(aEvent) {
|
||||
// Check for _paneInfo existing as the dialog may be closing but receiving
|
||||
// async updates from unresolved promises.
|
||||
if (!this._paneInfo) {
|
||||
|
@ -796,8 +801,8 @@ var gEditItemOverlay = {
|
|||
if (aEvent.target.id == "editBMPanel_chooseFolderMenuItem") {
|
||||
// reset the selection back to where it was and expand the tree
|
||||
// (this menu-item is hidden when the tree is already visible
|
||||
let containerId = PlacesUtils.bookmarks.getFolderIdForItem(this._paneInfo.itemId);
|
||||
let item = this._getFolderMenuItem(containerId);
|
||||
let item = this._getFolderMenuItem(this._paneInfo.parentId,
|
||||
this._paneInfo.title);
|
||||
this._folderMenuList.selectedItem = item;
|
||||
// XXXmano HACK: setTimeout 100, otherwise focus goes back to the
|
||||
// menulist right away
|
||||
|
@ -806,15 +811,13 @@ var gEditItemOverlay = {
|
|||
}
|
||||
|
||||
// Move the item
|
||||
let containerId = this._getFolderIdFromMenuList();
|
||||
if (PlacesUtils.bookmarks.getFolderIdForItem(this._paneInfo.itemId) != containerId &&
|
||||
let containerId = this._folderMenuList.selectedItem.folderId;
|
||||
if (this._paneInfo.parentId != containerId &&
|
||||
this._paneInfo.itemId != containerId) {
|
||||
if (PlacesUIUtils.useAsyncTransactions) {
|
||||
(async () => {
|
||||
let newParentGuid = await PlacesUtils.promiseItemGuid(containerId);
|
||||
let guid = this._paneInfo.itemGuid;
|
||||
await PlacesTransactions.Move({ guid, newParentGuid }).transact();
|
||||
})();
|
||||
let newParentGuid = await PlacesUtils.promiseItemGuid(containerId);
|
||||
let guid = this._paneInfo.itemGuid;
|
||||
await PlacesTransactions.Move({ guid, newParentGuid }).transact();
|
||||
} else {
|
||||
let txn = new PlacesMoveItemTransaction(this._paneInfo.itemId,
|
||||
containerId,
|
||||
|
@ -858,10 +861,10 @@ var gEditItemOverlay = {
|
|||
return;
|
||||
|
||||
var folderId = PlacesUtils.getConcreteItemId(selectedNode);
|
||||
if (this._getFolderIdFromMenuList() == folderId)
|
||||
if (this._folderMenuList.selectedItem.folderId == folderId)
|
||||
return;
|
||||
|
||||
var folderItem = this._getFolderMenuItem(folderId);
|
||||
var folderItem = this._getFolderMenuItem(folderId, selectedNode.title);
|
||||
this._folderMenuList.selectedItem = folderItem;
|
||||
folderItem.doCommand();
|
||||
},
|
||||
|
@ -1069,18 +1072,24 @@ var gEditItemOverlay = {
|
|||
this._initTextField(this._tagsField, tags.join(", "));
|
||||
},
|
||||
|
||||
_onTagsChange(aItemId) {
|
||||
async _onTagsChange(guid, changedURI = null) {
|
||||
let paneInfo = this._paneInfo;
|
||||
let updateTagsField = false;
|
||||
if (paneInfo.isURI) {
|
||||
if (paneInfo.isBookmark && aItemId == paneInfo.itemId) {
|
||||
if (paneInfo.isBookmark && guid == paneInfo.itemGuid) {
|
||||
updateTagsField = true;
|
||||
} else if (!paneInfo.isBookmark) {
|
||||
let changedURI = PlacesUtils.bookmarks.getBookmarkURI(aItemId);
|
||||
if (!changedURI) {
|
||||
let href = (await PlacesUtils.bookmarks.fetch(guid)).url.href;
|
||||
changedURI = Services.io.newURI(href);
|
||||
}
|
||||
updateTagsField = changedURI.equals(paneInfo.uri);
|
||||
}
|
||||
} else if (paneInfo.bulkTagging) {
|
||||
let changedURI = PlacesUtils.bookmarks.getBookmarkURI(aItemId);
|
||||
if (!changedURI) {
|
||||
let href = (await PlacesUtils.bookmarks.fetch(guid)).url.href;
|
||||
changedURI = Services.io.newURI(href);
|
||||
}
|
||||
if (paneInfo.uris.some(uri => uri.equals(changedURI))) {
|
||||
updateTagsField = true;
|
||||
delete this._paneInfo._cachedCommonTags;
|
||||
|
@ -1089,12 +1098,13 @@ var gEditItemOverlay = {
|
|||
throw new Error("_onTagsChange called unexpectedly");
|
||||
}
|
||||
|
||||
if (updateTagsField)
|
||||
this._initTagsField().catch(Components.utils.reportError);
|
||||
|
||||
// Any tags change should be reflected in the tags selector.
|
||||
if (this._element("tagsSelector"))
|
||||
this._rebuildTagsSelectorList().catch(Components.utils.reportError);
|
||||
if (updateTagsField) {
|
||||
await this._initTagsField();
|
||||
// Any tags change should be reflected in the tags selector.
|
||||
if (this._element("tagsSelector")) {
|
||||
await this._rebuildTagsSelectorList();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_onItemTitleChange(aItemId, aNewTitle) {
|
||||
|
@ -1115,17 +1125,29 @@ var gEditItemOverlay = {
|
|||
}
|
||||
}
|
||||
}
|
||||
// We need to also update title of recent folders.
|
||||
for (let folder of this._recentFolders) {
|
||||
if (folder.folderId == aItemId) {
|
||||
folder.title = aNewTitle;
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// nsINavBookmarkObserver
|
||||
onItemChanged(aItemId, aProperty, aIsAnnotationProperty, aValue,
|
||||
aLastModified, aItemType) {
|
||||
aLastModified, aItemType, aParentId, aGuid) {
|
||||
if (aProperty == "tags" && this._paneInfo.visibleRows.has("tagsRow")) {
|
||||
this._onTagsChange(aItemId);
|
||||
} else if (aProperty == "title" && this._paneInfo.isItem) {
|
||||
this._onTagsChange(aGuid).catch(Components.utils.reportError);
|
||||
return;
|
||||
}
|
||||
if (aProperty == "title" && this._paneInfo.isItem) {
|
||||
// This also updates titles of folders in the folder menu list.
|
||||
this._onItemTitleChange(aItemId, aValue);
|
||||
} else if (!this._paneInfo.isItem || this._paneInfo.itemId != aItemId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._paneInfo.isItem || this._paneInfo.itemId != aItemId) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1139,7 +1161,7 @@ var gEditItemOverlay = {
|
|||
|
||||
if (this._paneInfo.visibleRows.has("tagsRow")) {
|
||||
delete this._paneInfo._cachedCommonTags;
|
||||
this._onTagsChange(aItemId);
|
||||
this._onTagsChange(aGuid, newURI).catch(Components.utils.reportError);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -1158,18 +1180,26 @@ var gEditItemOverlay = {
|
|||
}
|
||||
},
|
||||
|
||||
onItemMoved(aItemId, aOldParent, aOldIndex,
|
||||
aNewParent, aNewIndex, aItemType) {
|
||||
if (!this._paneInfo.isItem ||
|
||||
!this._paneInfo.visibleRows.has("folderRow") ||
|
||||
this._paneInfo.itemId != aItemId ||
|
||||
aNewParent == this._getFolderIdFromMenuList()) {
|
||||
onItemMoved(id, oldParentId, oldIndex, newParentId, newIndex, type, guid,
|
||||
oldParentGuid, newParentGuid) {
|
||||
if (!this._paneInfo.isItem || this._paneInfo.itemId != id) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._paneInfo.parentId = newParentId;
|
||||
this._paneInfo.parentGuid = newParentGuid;
|
||||
|
||||
if (!this._paneInfo.visibleRows.has("folderRow") ||
|
||||
newParentId == this._folderMenuList.selectedItem.folderId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Just setting selectItem _does not_ trigger oncommand, so we don't
|
||||
// recurse.
|
||||
this._folderMenuList.selectedItem = this._getFolderMenuItem(aNewParent);
|
||||
PlacesUtils.bookmarks.fetch(newParentGuid).then(bm => {
|
||||
this._folderMenuList.selectedItem = this._getFolderMenuItem(newParentId,
|
||||
bm.title);
|
||||
});
|
||||
},
|
||||
|
||||
onItemAdded(aItemId, aParentId, aIndex, aItemType, aURI) {
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
accesskey="&editBookmarkOverlay.name.accesskey;"
|
||||
control="editBMPanel_namePicker"/>
|
||||
<textbox id="editBMPanel_namePicker"
|
||||
onchange="gEditItemOverlay.onNamePickerChange();"/>
|
||||
onchange="gEditItemOverlay.onNamePickerChange().catch(Components.utils.reportError);"/>
|
||||
</row>
|
||||
|
||||
<row id="editBMPanel_locationRow"
|
||||
|
@ -57,7 +57,7 @@
|
|||
<menulist id="editBMPanel_folderMenuList"
|
||||
class="folder-icon"
|
||||
flex="1"
|
||||
oncommand="gEditItemOverlay.onFolderMenuListCommand(event);">
|
||||
oncommand="gEditItemOverlay.onFolderMenuListCommand(event).catch(Components.utils.reportError);">
|
||||
<menupopup>
|
||||
<!-- Static item for special folders -->
|
||||
<menuitem id="editBMPanel_toolbarFolderItem"
|
||||
|
|
|
@ -31,7 +31,7 @@ async function withAddBookmarkForFrame(taskFn) {
|
|||
|
||||
add_task(async function test_open_add_bookmark_for_frame() {
|
||||
info("Test basic opening of the add bookmark for frame dialog.");
|
||||
await withAddBookmarkForFrame(function test(dialogWin) {
|
||||
await withAddBookmarkForFrame(async dialogWin => {
|
||||
let namepicker = dialogWin.document.getElementById("editBMPanel_namePicker");
|
||||
Assert.ok(!namepicker.readOnly, "Name field is writable");
|
||||
Assert.equal(namepicker.value, "Left frame", "Name field is correct.");
|
||||
|
@ -40,9 +40,9 @@ add_task(async function test_open_add_bookmark_for_frame() {
|
|||
PlacesUtils.getString("BookmarksMenuFolderTitle");
|
||||
|
||||
let folderPicker = dialogWin.document.getElementById("editBMPanel_folderMenuList");
|
||||
|
||||
Assert.equal(folderPicker.selectedItem.label,
|
||||
expectedFolderName, "The folder is the expected one.");
|
||||
await BrowserTestUtils.waitForCondition(
|
||||
() => folderPicker.selectedItem.label == expectedFolderName,
|
||||
"The folder is the expected one.");
|
||||
|
||||
let tagsField = dialogWin.document.getElementById("editBMPanel_tagsField");
|
||||
Assert.equal(tagsField.value, "", "The tags field should be empty");
|
||||
|
@ -51,7 +51,7 @@ add_task(async function test_open_add_bookmark_for_frame() {
|
|||
|
||||
add_task(async function test_move_bookmark_whilst_add_bookmark_open() {
|
||||
info("Test moving a bookmark whilst the add bookmark for frame dialog is open.");
|
||||
await withAddBookmarkForFrame(async function test(dialogWin) {
|
||||
await withAddBookmarkForFrame(async dialogWin => {
|
||||
let bookmarksMenuFolderName = PlacesUtils.getString("BookmarksMenuFolderTitle");
|
||||
let toolbarFolderName = PlacesUtils.getString("BookmarksToolbarFolderTitle");
|
||||
|
||||
|
@ -59,8 +59,9 @@ add_task(async function test_move_bookmark_whilst_add_bookmark_open() {
|
|||
let folderPicker = dialogWin.document.getElementById("editBMPanel_folderMenuList");
|
||||
|
||||
// Check the initial state of the folder picker.
|
||||
Assert.equal(folderPicker.selectedItem.label,
|
||||
bookmarksMenuFolderName, "The folder is the expected one.");
|
||||
await BrowserTestUtils.waitForCondition(
|
||||
() => folderPicker.selectedItem.label == bookmarksMenuFolderName,
|
||||
"The folder is the expected one.");
|
||||
|
||||
// Check the bookmark has been created as expected.
|
||||
let bookmark = await PlacesUtils.bookmarks.fetch({url});
|
||||
|
@ -75,7 +76,8 @@ add_task(async function test_move_bookmark_whilst_add_bookmark_open() {
|
|||
|
||||
await PlacesUtils.bookmarks.update(bookmark);
|
||||
|
||||
Assert.equal(folderPicker.selectedItem.label,
|
||||
toolbarFolderName, "The folder picker has changed to the new folder");
|
||||
await BrowserTestUtils.waitForCondition(
|
||||
() => folderPicker.selectedItem.label == toolbarFolderName,
|
||||
"The folder picker has changed to the new folder");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -317,14 +317,6 @@ gTests.push({
|
|||
let bookmark = await PlacesUtils.bookmarks.fetch({url: TEST_URL});
|
||||
self._bookmarkGuid = bookmark.guid;
|
||||
|
||||
// TODO: Bug 1378711. We shouldn't need to wait for onItemChanged here,
|
||||
// however we are working around an async bug in the PlacesTransactions
|
||||
// manager and ensuring that async functions have completed before moving
|
||||
// on.
|
||||
let promiseItemChanged = PlacesTestUtils.waitForNotification("onItemChanged",
|
||||
(itemId, property, isAnnotationProperty, newValue, lastModified, itemType) =>
|
||||
itemType === PlacesUtils.bookmarks.TYPE_FOLDER && isAnnotationProperty);
|
||||
|
||||
// Create a new folder.
|
||||
var newFolderButton = self.window.document.getElementById("editBMPanel_newFolderButton");
|
||||
newFolderButton.doCommand();
|
||||
|
@ -337,7 +329,7 @@ gTests.push({
|
|||
EventUtils.synthesizeKey("VK_ESCAPE", {}, self.window);
|
||||
Assert.ok(!folderTree.hasAttribute("editing"),
|
||||
"We have finished editing folder name in folder tree");
|
||||
await promiseItemChanged;
|
||||
|
||||
self._cleanShutdown = true;
|
||||
self._removeObserver = PlacesTestUtils.waitForNotification("onItemRemoved",
|
||||
(itemId, parentId, index, type, uri, guid) => guid == self._bookmarkGuid);
|
||||
|
|
|
@ -7,137 +7,111 @@
|
|||
* additionalInfoFields in infoBox section of library
|
||||
*/
|
||||
|
||||
const TEST_URI = "http://www.mozilla.org/";
|
||||
|
||||
var gTests = [];
|
||||
var gLibrary;
|
||||
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
gTests.push({
|
||||
desc: "Bug 430148 - Remove or hide the more/less button in details pane...",
|
||||
run() {
|
||||
var PO = gLibrary.PlacesOrganizer;
|
||||
let ContentTree = gLibrary.ContentTree;
|
||||
var infoBoxExpanderWrapper = getAndCheckElmtById("infoBoxExpanderWrapper");
|
||||
|
||||
function addVisitsCallback() {
|
||||
// open all bookmarks node
|
||||
PO.selectLeftPaneQuery("AllBookmarks");
|
||||
isnot(PO._places.selectedNode, null,
|
||||
"Correctly selected all bookmarks node.");
|
||||
checkInfoBoxSelected(PO);
|
||||
ok(infoBoxExpanderWrapper.hidden,
|
||||
"Expander button is hidden for all bookmarks node.");
|
||||
checkAddInfoFieldsCollapsed(PO);
|
||||
|
||||
// open history node
|
||||
PO.selectLeftPaneQuery("History");
|
||||
isnot(PO._places.selectedNode, null, "Correctly selected history node.");
|
||||
checkInfoBoxSelected(PO);
|
||||
ok(infoBoxExpanderWrapper.hidden,
|
||||
"Expander button is hidden for history node.");
|
||||
checkAddInfoFieldsCollapsed(PO);
|
||||
|
||||
// open history child node
|
||||
var historyNode = PO._places.selectedNode.
|
||||
QueryInterface(Ci.nsINavHistoryContainerResultNode);
|
||||
historyNode.containerOpen = true;
|
||||
var childNode = historyNode.getChild(0);
|
||||
isnot(childNode, null, "History node first child is not null.");
|
||||
PO._places.selectNode(childNode);
|
||||
checkInfoBoxSelected(PO);
|
||||
ok(infoBoxExpanderWrapper.hidden,
|
||||
"Expander button is hidden for history child node.");
|
||||
checkAddInfoFieldsCollapsed(PO);
|
||||
|
||||
// open history item
|
||||
var view = ContentTree.view.view;
|
||||
ok(view.rowCount > 0, "History item exists.");
|
||||
view.selection.select(0);
|
||||
ok(infoBoxExpanderWrapper.hidden,
|
||||
"Expander button is hidden for history item.");
|
||||
checkAddInfoFieldsCollapsed(PO);
|
||||
|
||||
historyNode.containerOpen = false;
|
||||
|
||||
// open bookmarks menu node
|
||||
PO.selectLeftPaneQuery("BookmarksMenu");
|
||||
isnot(PO._places.selectedNode, null,
|
||||
"Correctly selected bookmarks menu node.");
|
||||
checkInfoBoxSelected(PO);
|
||||
ok(infoBoxExpanderWrapper.hidden,
|
||||
"Expander button is hidden for bookmarks menu node.");
|
||||
checkAddInfoFieldsCollapsed(PO);
|
||||
|
||||
// open recently bookmarked node
|
||||
PlacesUtils.bookmarks.insertBookmark(PlacesUtils.bookmarksMenuFolderId,
|
||||
NetUtil.newURI("place:folder=BOOKMARKS_MENU" +
|
||||
"&folder=UNFILED_BOOKMARKS" +
|
||||
"&folder=TOOLBAR" +
|
||||
"&queryType=" + Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS +
|
||||
"&sort=" + Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_DESCENDING +
|
||||
"&maxResults=10" +
|
||||
"&excludeQueries=1"),
|
||||
0, "Recent Bookmarks");
|
||||
PlacesUtils.bookmarks.insertBookmark(PlacesUtils.bookmarksMenuFolderId,
|
||||
NetUtil.newURI("http://mozilla.org/"),
|
||||
1, "Mozilla");
|
||||
var menuNode = PO._places.selectedNode.
|
||||
QueryInterface(Ci.nsINavHistoryContainerResultNode);
|
||||
menuNode.containerOpen = true;
|
||||
childNode = menuNode.getChild(0);
|
||||
isnot(childNode, null, "Bookmarks menu child node exists.");
|
||||
is(childNode.title, "Recent Bookmarks",
|
||||
"Correctly selected recently bookmarked node.");
|
||||
PO._places.selectNode(childNode);
|
||||
checkInfoBoxSelected(PO);
|
||||
ok(!infoBoxExpanderWrapper.hidden,
|
||||
"Expander button is not hidden for recently bookmarked node.");
|
||||
checkAddInfoFieldsNotCollapsed(PO);
|
||||
|
||||
// open first bookmark
|
||||
view = ContentTree.view.view;
|
||||
ok(view.rowCount > 0, "Bookmark item exists.");
|
||||
view.selection.select(0);
|
||||
checkInfoBoxSelected(PO);
|
||||
ok(!infoBoxExpanderWrapper.hidden,
|
||||
"Expander button is not hidden for bookmark item.");
|
||||
checkAddInfoFieldsNotCollapsed(PO);
|
||||
checkAddInfoFields(PO, "bookmark item");
|
||||
|
||||
menuNode.containerOpen = false;
|
||||
|
||||
PlacesTestUtils.clearHistory().then(nextTest);
|
||||
}
|
||||
// add a visit to browser history
|
||||
PlacesTestUtils.addVisits(
|
||||
{ uri: PlacesUtils._uri(TEST_URI), visitDate: Date.now() * 1000,
|
||||
transition: PlacesUtils.history.TRANSITION_TYPED }
|
||||
).then(addVisitsCallback);
|
||||
}
|
||||
});
|
||||
|
||||
function checkInfoBoxSelected(PO) {
|
||||
is(getAndCheckElmtById("detailsDeck").selectedIndex, 1,
|
||||
"Selected element in detailsDeck is infoBox.");
|
||||
}
|
||||
|
||||
function checkAddInfoFieldsCollapsed(PO) {
|
||||
PO._additionalInfoFields.forEach(function(id) {
|
||||
ok(getAndCheckElmtById(id).collapsed,
|
||||
"Additional info field correctly collapsed: #" + id);
|
||||
let gLibrary;
|
||||
add_task(async function() {
|
||||
// Open Library.
|
||||
gLibrary = await promiseLibrary();
|
||||
registerCleanupFunction(async () => {
|
||||
gLibrary.close();
|
||||
await PlacesTestUtils.clearHistory();
|
||||
});
|
||||
}
|
||||
gLibrary.PlacesOrganizer._places.focus();
|
||||
|
||||
function checkAddInfoFieldsNotCollapsed(PO) {
|
||||
ok(PO._additionalInfoFields.some(function(id) {
|
||||
return !getAndCheckElmtById(id).collapsed;
|
||||
}), "Some additional info field correctly not collapsed");
|
||||
}
|
||||
info("Bug 430148 - Remove or hide the more/less button in details pane...");
|
||||
let PO = gLibrary.PlacesOrganizer;
|
||||
let ContentTree = gLibrary.ContentTree;
|
||||
let infoBoxExpanderWrapper = getAndCheckElmtById("infoBoxExpanderWrapper");
|
||||
|
||||
function checkAddInfoFields(PO, nodeName) {
|
||||
ok(true, "Checking additional info fields visibiity for node: " + nodeName);
|
||||
await PlacesTestUtils.addVisits("http://www.mozilla.org/");
|
||||
|
||||
// open all bookmarks node
|
||||
PO.selectLeftPaneQuery("AllBookmarks");
|
||||
isnot(PO._places.selectedNode, null,
|
||||
"Correctly selected all bookmarks node.");
|
||||
checkInfoBoxSelected();
|
||||
ok(infoBoxExpanderWrapper.hidden,
|
||||
"Expander button is hidden for all bookmarks node.");
|
||||
checkAddInfoFieldsCollapsed(PO);
|
||||
|
||||
// open history node
|
||||
PO.selectLeftPaneQuery("History");
|
||||
isnot(PO._places.selectedNode, null, "Correctly selected history node.");
|
||||
checkInfoBoxSelected();
|
||||
ok(infoBoxExpanderWrapper.hidden,
|
||||
"Expander button is hidden for history node.");
|
||||
checkAddInfoFieldsCollapsed(PO);
|
||||
|
||||
// open history child node
|
||||
var historyNode = PO._places.selectedNode.
|
||||
QueryInterface(Ci.nsINavHistoryContainerResultNode);
|
||||
historyNode.containerOpen = true;
|
||||
var childNode = historyNode.getChild(0);
|
||||
isnot(childNode, null, "History node first child is not null.");
|
||||
PO._places.selectNode(childNode);
|
||||
checkInfoBoxSelected();
|
||||
ok(infoBoxExpanderWrapper.hidden,
|
||||
"Expander button is hidden for history child node.");
|
||||
checkAddInfoFieldsCollapsed(PO);
|
||||
|
||||
// open history item
|
||||
var view = ContentTree.view.view;
|
||||
ok(view.rowCount > 0, "History item exists.");
|
||||
view.selection.select(0);
|
||||
ok(infoBoxExpanderWrapper.hidden,
|
||||
"Expander button is hidden for history item.");
|
||||
checkAddInfoFieldsCollapsed(PO);
|
||||
|
||||
historyNode.containerOpen = false;
|
||||
|
||||
// open bookmarks menu node
|
||||
PO.selectLeftPaneQuery("BookmarksMenu");
|
||||
isnot(PO._places.selectedNode, null,
|
||||
"Correctly selected bookmarks menu node.");
|
||||
checkInfoBoxSelected();
|
||||
ok(infoBoxExpanderWrapper.hidden,
|
||||
"Expander button is hidden for bookmarks menu node.");
|
||||
checkAddInfoFieldsCollapsed(PO);
|
||||
|
||||
// open recently bookmarked node
|
||||
await PlacesUtils.bookmarks.insert({
|
||||
parentGuid: PlacesUtils.bookmarks.menuGuid,
|
||||
url: "place:folder=BOOKMARKS_MENU&folder=UNFILED_BOOKMARKS&folder=TOOLBAR" +
|
||||
"&queryType=" + Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS +
|
||||
"&sort=" + Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_DESCENDING +
|
||||
"&maxResults=10" +
|
||||
"&excludeQueries=1",
|
||||
title: "Recent Bookmarks",
|
||||
index: 0
|
||||
});
|
||||
await PlacesUtils.bookmarks.insert({
|
||||
parentGuid: PlacesUtils.bookmarks.menuGuid,
|
||||
url: "http://mozilla.org/",
|
||||
title: "Mozilla",
|
||||
index: 1
|
||||
});
|
||||
var menuNode = PO._places.selectedNode.
|
||||
QueryInterface(Ci.nsINavHistoryContainerResultNode);
|
||||
menuNode.containerOpen = true;
|
||||
childNode = menuNode.getChild(0);
|
||||
isnot(childNode, null, "Bookmarks menu child node exists.");
|
||||
is(childNode.title, "Recent Bookmarks",
|
||||
"Correctly selected recently bookmarked node.");
|
||||
PO._places.selectNode(childNode);
|
||||
checkInfoBoxSelected();
|
||||
ok(!infoBoxExpanderWrapper.hidden,
|
||||
"Expander button is not hidden for recently bookmarked node.");
|
||||
checkAddInfoFieldsNotCollapsed(PO);
|
||||
|
||||
// open first bookmark
|
||||
view = ContentTree.view.view;
|
||||
ok(view.rowCount > 0, "Bookmark item exists.");
|
||||
view.selection.select(0);
|
||||
checkInfoBoxSelected();
|
||||
ok(!infoBoxExpanderWrapper.hidden,
|
||||
"Expander button is not hidden for bookmark item.");
|
||||
checkAddInfoFieldsNotCollapsed(PO);
|
||||
|
||||
ok(true, "Checking additional info fields visibiity for bookmark item");
|
||||
var expanderButton = getAndCheckElmtById("infoBoxExpander");
|
||||
|
||||
// make sure additional fields are hidden by default
|
||||
|
@ -157,6 +131,25 @@ function checkAddInfoFields(PO, nodeName) {
|
|||
ok(getAndCheckElmtById(id).hidden,
|
||||
"Additional info field correctly hidden after toggle: #" + id);
|
||||
});
|
||||
|
||||
menuNode.containerOpen = false;
|
||||
});
|
||||
|
||||
function checkInfoBoxSelected() {
|
||||
is(getAndCheckElmtById("detailsDeck").selectedIndex, 1,
|
||||
"Selected element in detailsDeck is infoBox.");
|
||||
}
|
||||
|
||||
function checkAddInfoFieldsCollapsed(PO) {
|
||||
PO._additionalInfoFields.forEach(id => {
|
||||
ok(getAndCheckElmtById(id).collapsed,
|
||||
`Additional info field should be collapsed: #${id}`);
|
||||
});
|
||||
}
|
||||
|
||||
function checkAddInfoFieldsNotCollapsed(PO) {
|
||||
ok(PO._additionalInfoFields.some(id => !getAndCheckElmtById(id).collapsed),
|
||||
`Some additional info field should not be collapsed.`);
|
||||
}
|
||||
|
||||
function getAndCheckElmtById(id) {
|
||||
|
@ -165,32 +158,3 @@ function getAndCheckElmtById(id) {
|
|||
return elmt;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
function nextTest() {
|
||||
if (gTests.length) {
|
||||
var testCase = gTests.shift();
|
||||
ok(true, "TEST: " + testCase.desc);
|
||||
dump("TEST: " + testCase.desc + "\n");
|
||||
testCase.run();
|
||||
} else {
|
||||
// Close Library window.
|
||||
gLibrary.close();
|
||||
// No need to cleanup anything, we have a correct left pane now.
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
// Sanity checks.
|
||||
ok(PlacesUtils, "PlacesUtils is running in chrome context");
|
||||
ok(PlacesUIUtils, "PlacesUIUtils is running in chrome context");
|
||||
|
||||
// Open Library.
|
||||
openLibrary(function(library) {
|
||||
gLibrary = library;
|
||||
gLibrary.PlacesOrganizer._places.focus();
|
||||
nextTest(gLibrary);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -5,3 +5,5 @@ Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
|||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
|
||||
"resource://testing-common/PlacesTestUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "BrowserTestUtils",
|
||||
"resource://testing-common/BrowserTestUtils.jsm");
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
|
||||
<script type="application/javascript"
|
||||
src="chrome://browser/content/places/editBookmarkOverlay.js"/>
|
||||
<script type="application/javascript" src="head.js" />
|
||||
|
||||
<body xmlns="http://www.w3.org/1999/xhtml" />
|
||||
|
||||
|
@ -83,16 +84,18 @@
|
|||
|
||||
is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], TEST_TAG,
|
||||
"Correctly added tag to a single bookmark");
|
||||
is(document.getElementById("editBMPanel_tagsField").value, TEST_TAG,
|
||||
"Editing a single bookmark shows the added tag");
|
||||
await BrowserTestUtils.waitForCondition(
|
||||
() => document.getElementById("editBMPanel_tagsField").value == TEST_TAG,
|
||||
"Editing a single bookmark shows the added tag.");
|
||||
checkTagsSelector([TEST_TAG], [TEST_TAG]);
|
||||
|
||||
// Remove tag.
|
||||
PlacesUtils.tagging.untagURI(TEST_URI, [TEST_TAG]);
|
||||
is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], undefined,
|
||||
"The tag has been removed");
|
||||
is(document.getElementById("editBMPanel_tagsField").value, "",
|
||||
"Editing a single bookmark should not show any tag");
|
||||
await BrowserTestUtils.waitForCondition(
|
||||
() => document.getElementById("editBMPanel_tagsField").value == "",
|
||||
"Editing a single bookmark should not show any tag");
|
||||
checkTagsSelector([], []);
|
||||
|
||||
// Add a second bookmark.
|
||||
|
@ -111,32 +114,36 @@
|
|||
PlacesUtils.tagging.tagURI(TEST_URI, [TEST_TAG]);
|
||||
is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], TEST_TAG,
|
||||
"Correctly added a tag to the first bookmark.");
|
||||
is(document.getElementById("editBMPanel_tagsField").value, "",
|
||||
"Editing multiple bookmarks without matching tags should not show any tag.");
|
||||
await BrowserTestUtils.waitForCondition(
|
||||
() => document.getElementById("editBMPanel_tagsField").value == "",
|
||||
"Editing multiple bookmarks without matching tags should not show any tag.");
|
||||
checkTagsSelector([TEST_TAG], []);
|
||||
|
||||
// Add a tag to the second uri.
|
||||
PlacesUtils.tagging.tagURI(TEST_URI2, [TEST_TAG]);
|
||||
is(PlacesUtils.tagging.getTagsForURI(TEST_URI2)[0], TEST_TAG,
|
||||
"Correctly added a tag to the second bookmark.");
|
||||
is(document.getElementById("editBMPanel_tagsField").value, TEST_TAG,
|
||||
"Editing multiple bookmarks should show matching tags.");
|
||||
await BrowserTestUtils.waitForCondition(
|
||||
() => document.getElementById("editBMPanel_tagsField").value == TEST_TAG,
|
||||
"Editing multiple bookmarks should show matching tags.");
|
||||
checkTagsSelector([TEST_TAG], [TEST_TAG]);
|
||||
|
||||
// Remove tag from the first bookmark.
|
||||
PlacesUtils.tagging.untagURI(TEST_URI, [TEST_TAG]);
|
||||
is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], undefined,
|
||||
"Correctly removed tag from the first bookmark.");
|
||||
is(document.getElementById("editBMPanel_tagsField").value, "",
|
||||
"Editing multiple bookmarks without matching tags should not show any tag.");
|
||||
await BrowserTestUtils.waitForCondition(
|
||||
() => document.getElementById("editBMPanel_tagsField").value == "",
|
||||
"Editing multiple bookmarks without matching tags should not show any tag.");
|
||||
checkTagsSelector([TEST_TAG], []);
|
||||
|
||||
// Remove tag from the second bookmark.
|
||||
PlacesUtils.tagging.untagURI(TEST_URI2, [TEST_TAG]);
|
||||
is(PlacesUtils.tagging.getTagsForURI(TEST_URI2)[0], undefined,
|
||||
"Correctly removed tag from the second bookmark.");
|
||||
is(document.getElementById("editBMPanel_tagsField").value, "",
|
||||
"Editing multiple bookmarks without matching tags should not show any tag.");
|
||||
await BrowserTestUtils.waitForCondition(
|
||||
() => document.getElementById("editBMPanel_tagsField").value == "",
|
||||
"Editing multiple bookmarks without matching tags should not show any tag.");
|
||||
checkTagsSelector([], []);
|
||||
|
||||
// Init panel with a nsIURI entry.
|
||||
|
@ -146,16 +153,18 @@
|
|||
PlacesUtils.tagging.tagURI(TEST_URI, [TEST_TAG]);
|
||||
is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], TEST_TAG,
|
||||
"Correctly added tag to the first entry.");
|
||||
is(document.getElementById("editBMPanel_tagsField").value, TEST_TAG,
|
||||
"Editing a single nsIURI entry shows the added tag");
|
||||
await BrowserTestUtils.waitForCondition(
|
||||
() => document.getElementById("editBMPanel_tagsField").value == TEST_TAG,
|
||||
"Editing a single nsIURI entry shows the added tag.");
|
||||
checkTagsSelector([TEST_TAG], [TEST_TAG]);
|
||||
|
||||
// Remove tag.
|
||||
PlacesUtils.tagging.untagURI(TEST_URI, [TEST_TAG]);
|
||||
is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], undefined,
|
||||
"Correctly removed tag from the nsIURI entry.");
|
||||
is(document.getElementById("editBMPanel_tagsField").value, "",
|
||||
"Editing a single nsIURI entry should not show any tag");
|
||||
await BrowserTestUtils.waitForCondition(
|
||||
() => document.getElementById("editBMPanel_tagsField").value == "",
|
||||
"Editing a single nsIURI entry should not show any tag.");
|
||||
checkTagsSelector([], []);
|
||||
|
||||
// Init panel with multiple nsIURI entries.
|
||||
|
@ -165,32 +174,36 @@
|
|||
PlacesUtils.tagging.tagURI(TEST_URI, [TEST_TAG]);
|
||||
is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], TEST_TAG,
|
||||
"Tag correctly added.");
|
||||
is(document.getElementById("editBMPanel_tagsField").value, "",
|
||||
"Editing multiple nsIURIs without matching tags should not show any tag.");
|
||||
await BrowserTestUtils.waitForCondition(
|
||||
() => document.getElementById("editBMPanel_tagsField").value == "",
|
||||
"Editing multiple nsIURIs without matching tags should not show any tag.");
|
||||
checkTagsSelector([TEST_TAG], []);
|
||||
|
||||
// Add a tag to the second entry.
|
||||
PlacesUtils.tagging.tagURI(TEST_URI2, [TEST_TAG]);
|
||||
is(PlacesUtils.tagging.getTagsForURI(TEST_URI2)[0], TEST_TAG,
|
||||
"Tag correctly added.");
|
||||
is(document.getElementById("editBMPanel_tagsField").value, TEST_TAG,
|
||||
"Editing multiple nsIURIs should show matching tags");
|
||||
await BrowserTestUtils.waitForCondition(
|
||||
() => document.getElementById("editBMPanel_tagsField").value == TEST_TAG,
|
||||
"Editing multiple nsIURIs should show matching tags.");
|
||||
checkTagsSelector([TEST_TAG], [TEST_TAG]);
|
||||
|
||||
// Remove tag from the first entry.
|
||||
PlacesUtils.tagging.untagURI(TEST_URI, [TEST_TAG]);
|
||||
is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], undefined,
|
||||
"Correctly removed tag from the first entry.");
|
||||
is(document.getElementById("editBMPanel_tagsField").value, "",
|
||||
"Editing multiple nsIURIs without matching tags should not show any tag.");
|
||||
await BrowserTestUtils.waitForCondition(
|
||||
() => document.getElementById("editBMPanel_tagsField").value == "",
|
||||
"Editing multiple nsIURIs without matching tags should not show any tag.");
|
||||
checkTagsSelector([TEST_TAG], []);
|
||||
|
||||
// Remove tag from the second entry.
|
||||
PlacesUtils.tagging.untagURI(TEST_URI2, [TEST_TAG]);
|
||||
is(PlacesUtils.tagging.getTagsForURI(TEST_URI2)[0], undefined,
|
||||
"Correctly removed tag from the second entry.");
|
||||
is(document.getElementById("editBMPanel_tagsField").value, "",
|
||||
"Editing multiple nsIURIs without matching tags should not show any tag.");
|
||||
await BrowserTestUtils.waitForCondition(
|
||||
() => document.getElementById("editBMPanel_tagsField").value == "",
|
||||
"Editing multiple nsIURIs without matching tags should not show any tag.");
|
||||
checkTagsSelector([], []);
|
||||
|
||||
// Cleanup.
|
||||
|
|
|
@ -35,14 +35,6 @@
|
|||
|
||||
<richlistbox id="containersView" orient="vertical" persist="lastSelectedType"
|
||||
flex="1">
|
||||
<listheader equalsize="always">
|
||||
<treecol id="typeColumn" value="type"
|
||||
persist="sortDirection"
|
||||
flex="1" sortDirection="ascending"/>
|
||||
<treecol id="actionColumn" value="action"
|
||||
persist="sortDirection"
|
||||
flex="1"/>
|
||||
</listheader>
|
||||
</richlistbox>
|
||||
</vbox>
|
||||
<vbox>
|
||||
|
|
|
@ -19,7 +19,7 @@ add_task(async function getFeedItem() {
|
|||
container = win.document.getElementById("handlersView");
|
||||
feedItem = container.querySelector("richlistitem[type='application/vnd.mozilla.maybe.feed']");
|
||||
Assert.ok(feedItem, "feedItem is present in handlersView.");
|
||||
})
|
||||
});
|
||||
|
||||
add_task(async function selectInternalOptionForFeed() {
|
||||
// Select the item.
|
||||
|
@ -77,3 +77,85 @@ add_task(async function reselectInternalOptionForFeed() {
|
|||
Ci.nsIHandlerInfo.handleInternally,
|
||||
"Selected item should still be the same as the previously selected item.");
|
||||
});
|
||||
|
||||
add_task(async function sortingCheck() {
|
||||
win = gBrowser.selectedBrowser.contentWindow;
|
||||
|
||||
const handlerView = win.document.getElementById("handlersView");
|
||||
const typeColumn = win.document.getElementById("typeColumn");
|
||||
Assert.ok(typeColumn, "typeColumn is present in handlersView.");
|
||||
|
||||
// Test default sorting
|
||||
assertSortByType("ascending");
|
||||
|
||||
const oldDir = typeColumn.getAttribute("sortDirection");
|
||||
|
||||
|
||||
// Test sorting on the type column
|
||||
typeColumn.click();
|
||||
assertSortByType("descending");
|
||||
Assert.notEqual(oldDir,
|
||||
typeColumn.getAttribute("sortDirection"),
|
||||
"Sort direction should change");
|
||||
|
||||
typeColumn.click();
|
||||
assertSortByType("ascending");
|
||||
|
||||
const actionColumn = win.document.getElementById("actionColumn");
|
||||
Assert.ok(actionColumn, "actionColumn is present in handlersView.");
|
||||
|
||||
// Test sorting on the action column
|
||||
const oldActionDir = actionColumn.getAttribute("sortDirection");
|
||||
actionColumn.click();
|
||||
assertSortByAction("ascending");
|
||||
Assert.notEqual(oldActionDir,
|
||||
actionColumn.getAttribute("sortDirection"),
|
||||
"Sort direction should change");
|
||||
|
||||
actionColumn.click();
|
||||
assertSortByAction("descending");
|
||||
|
||||
function assertSortByAction(order) {
|
||||
Assert.equal(actionColumn.getAttribute("sortDirection"),
|
||||
order,
|
||||
`Sort direction should be ${order}`);
|
||||
let siteItems = handlerView.getElementsByTagName("richlistitem");
|
||||
for (let i = 0; i < siteItems.length - 1; ++i) {
|
||||
let aType = siteItems[i].getAttribute("actionDescription").toLowerCase();
|
||||
let bType = siteItems[i + 1].getAttribute("actionDescription").toLowerCase();
|
||||
let result = 0;
|
||||
if (aType > bType) {
|
||||
result = 1;
|
||||
} else if (bType > aType) {
|
||||
result = -1;
|
||||
}
|
||||
if (order == "ascending") {
|
||||
Assert.lessOrEqual(result, 0, "Should sort applications in the ascending order by action");
|
||||
} else {
|
||||
Assert.greaterOrEqual(result, 0, "Should sort applications in the descending order by action");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function assertSortByType(order) {
|
||||
Assert.equal(typeColumn.getAttribute("sortDirection"),
|
||||
order,
|
||||
`Sort direction should be ${order}`);
|
||||
let siteItems = handlerView.getElementsByTagName("richlistitem");
|
||||
for (let i = 0; i < siteItems.length - 1; ++i) {
|
||||
let aType = siteItems[i].getAttribute("typeDescription").toLowerCase();
|
||||
let bType = siteItems[i + 1].getAttribute("typeDescription").toLowerCase();
|
||||
let result = 0;
|
||||
if (aType > bType) {
|
||||
result = 1;
|
||||
} else if (bType > aType) {
|
||||
result = -1;
|
||||
}
|
||||
if (order == "ascending") {
|
||||
Assert.lessOrEqual(result, 0, "Should sort applications in the ascending order by type");
|
||||
} else {
|
||||
Assert.greaterOrEqual(result, 0, "Should sort applications in the descending order by type");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -36,14 +36,6 @@
|
|||
|
||||
<richlistbox id="containersView" orient="vertical" persist="lastSelectedType"
|
||||
flex="1">
|
||||
<listheader equalsize="always">
|
||||
<treecol id="typeColumn" value="type"
|
||||
persist="sortDirection"
|
||||
flex="1" sortDirection="ascending"/>
|
||||
<treecol id="actionColumn" value="action"
|
||||
persist="sortDirection"
|
||||
flex="1"/>
|
||||
</listheader>
|
||||
</richlistbox>
|
||||
</vbox>
|
||||
<vbox>
|
||||
|
|
|
@ -77,3 +77,85 @@ add_task(async function reselectInternalOptionForFeed() {
|
|||
Ci.nsIHandlerInfo.handleInternally,
|
||||
"Selected item should still be the same as the previously selected item.");
|
||||
});
|
||||
|
||||
add_task(async function sortingCheck() {
|
||||
win = gBrowser.selectedBrowser.contentWindow;
|
||||
|
||||
const handlerView = win.document.getElementById("handlersView");
|
||||
const typeColumn = win.document.getElementById("typeColumn");
|
||||
Assert.ok(typeColumn, "typeColumn is present in handlersView.");
|
||||
|
||||
// Test default sorting
|
||||
assertSortByType("ascending");
|
||||
|
||||
const oldDir = typeColumn.getAttribute("sortDirection");
|
||||
|
||||
|
||||
// Test sorting on the type column
|
||||
typeColumn.click();
|
||||
assertSortByType("descending");
|
||||
Assert.notEqual(oldDir,
|
||||
typeColumn.getAttribute("sortDirection"),
|
||||
"Sort direction should change");
|
||||
|
||||
typeColumn.click();
|
||||
assertSortByType("ascending");
|
||||
|
||||
const actionColumn = win.document.getElementById("actionColumn");
|
||||
Assert.ok(actionColumn, "actionColumn is present in handlersView.");
|
||||
|
||||
// Test sorting on the action column
|
||||
const oldActionDir = actionColumn.getAttribute("sortDirection");
|
||||
actionColumn.click();
|
||||
assertSortByAction("ascending");
|
||||
Assert.notEqual(oldActionDir,
|
||||
actionColumn.getAttribute("sortDirection"),
|
||||
"Sort direction should change");
|
||||
|
||||
actionColumn.click();
|
||||
assertSortByAction("descending");
|
||||
|
||||
function assertSortByAction(order) {
|
||||
Assert.equal(actionColumn.getAttribute("sortDirection"),
|
||||
order,
|
||||
`Sort direction should be ${order}`);
|
||||
let siteItems = handlerView.getElementsByTagName("richlistitem");
|
||||
for (let i = 0; i < siteItems.length - 1; ++i) {
|
||||
let aType = siteItems[i].getAttribute("actionDescription").toLowerCase();
|
||||
let bType = siteItems[i + 1].getAttribute("actionDescription").toLowerCase();
|
||||
let result = 0;
|
||||
if (aType > bType) {
|
||||
result = 1;
|
||||
} else if (bType > aType) {
|
||||
result = -1;
|
||||
}
|
||||
if (order == "ascending") {
|
||||
Assert.lessOrEqual(result, 0, "Should sort applications in the ascending order by action");
|
||||
} else {
|
||||
Assert.greaterOrEqual(result, 0, "Should sort applications in the descending order by action");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function assertSortByType(order) {
|
||||
Assert.equal(typeColumn.getAttribute("sortDirection"),
|
||||
order,
|
||||
`Sort direction should be ${order}`);
|
||||
let siteItems = handlerView.getElementsByTagName("richlistitem");
|
||||
for (let i = 0; i < siteItems.length - 1; ++i) {
|
||||
let aType = siteItems[i].getAttribute("typeDescription").toLowerCase();
|
||||
let bType = siteItems[i + 1].getAttribute("typeDescription").toLowerCase();
|
||||
let result = 0;
|
||||
if (aType > bType) {
|
||||
result = 1;
|
||||
} else if (bType > aType) {
|
||||
result = -1;
|
||||
}
|
||||
if (order == "ascending") {
|
||||
Assert.lessOrEqual(result, 0, "Should sort applications in the ascending order by type");
|
||||
} else {
|
||||
Assert.greaterOrEqual(result, 0, "Should sort applications in the descending order by type");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -214,13 +214,17 @@ var FormAutofillNameUtils = {
|
|||
},
|
||||
|
||||
splitName(name) {
|
||||
let nameTokens = name.trim().split(/[ ,\u3000\u30FB\u00B7]+/);
|
||||
let nameParts = {
|
||||
given: "",
|
||||
middle: "",
|
||||
family: "",
|
||||
};
|
||||
|
||||
if (!name) {
|
||||
return nameParts;
|
||||
}
|
||||
|
||||
let nameTokens = name.trim().split(/[ ,\u3000\u30FB\u00B7]+/);
|
||||
nameTokens = this._stripPrefixes(nameTokens);
|
||||
|
||||
if (this._isCJKName(name)) {
|
||||
|
|
|
@ -112,7 +112,7 @@ const STORAGE_SCHEMA_VERSION = 1;
|
|||
const ADDRESS_SCHEMA_VERSION = 1;
|
||||
const CREDIT_CARD_SCHEMA_VERSION = 1;
|
||||
|
||||
const VALID_PROFILE_FIELDS = [
|
||||
const VALID_ADDRESS_FIELDS = [
|
||||
"given-name",
|
||||
"additional-name",
|
||||
"family-name",
|
||||
|
@ -126,6 +126,17 @@ const VALID_PROFILE_FIELDS = [
|
|||
"email",
|
||||
];
|
||||
|
||||
const STREET_ADDRESS_COMPONENTS = [
|
||||
"address-line1",
|
||||
"address-line2",
|
||||
"address-line3",
|
||||
];
|
||||
|
||||
const VALID_ADDRESS_COMPUTED_FIELDS = [
|
||||
"name",
|
||||
"country-name",
|
||||
].concat(STREET_ADDRESS_COMPONENTS);
|
||||
|
||||
const VALID_CREDIT_CARD_FIELDS = [
|
||||
"cc-name",
|
||||
"cc-number-encrypted",
|
||||
|
@ -134,6 +145,12 @@ const VALID_CREDIT_CARD_FIELDS = [
|
|||
"cc-exp-year",
|
||||
];
|
||||
|
||||
const VALID_CREDIT_CARD_COMPUTED_FIELDS = [
|
||||
"cc-given-name",
|
||||
"cc-additional-name",
|
||||
"cc-family-name",
|
||||
];
|
||||
|
||||
const INTERNAL_FIELDS = [
|
||||
"guid",
|
||||
"version",
|
||||
|
@ -160,17 +177,25 @@ class AutofillRecords {
|
|||
* A key of "store.data".
|
||||
* @param {Array.<string>} validFields
|
||||
* A list containing non-metadata field names.
|
||||
* @param {Array.<string>} validComputedFields
|
||||
* A list containing computed field names.
|
||||
* @param {number} schemaVersion
|
||||
* The schema version for the new record.
|
||||
*/
|
||||
constructor(store, collectionName, validFields, schemaVersion) {
|
||||
constructor(store, collectionName, validFields, validComputedFields, schemaVersion) {
|
||||
FormAutofillUtils.defineLazyLogGetter(this, "AutofillRecords:" + collectionName);
|
||||
|
||||
this.VALID_FIELDS = validFields;
|
||||
this.VALID_COMPUTED_FIELDS = validComputedFields;
|
||||
|
||||
this._store = store;
|
||||
this._collectionName = collectionName;
|
||||
this._schemaVersion = schemaVersion;
|
||||
|
||||
let hasChanges = (result, record) => this._migrateRecord(record) || result;
|
||||
if (this._store.data[this._collectionName].reduce(hasChanges, false)) {
|
||||
this._store.saveSoon();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -226,6 +251,8 @@ class AutofillRecords {
|
|||
recordToSave.timesUsed = 0;
|
||||
}
|
||||
|
||||
this._computeFields(recordToSave);
|
||||
|
||||
this._store.data[this._collectionName].push(recordToSave);
|
||||
this._store.saveSoon();
|
||||
|
||||
|
@ -261,8 +288,10 @@ class AutofillRecords {
|
|||
|
||||
recordFound.timeLastModified = Date.now();
|
||||
|
||||
this._store.saveSoon();
|
||||
this._stripComputedFields(recordFound);
|
||||
this._computeFields(recordFound);
|
||||
|
||||
this._store.saveSoon();
|
||||
Services.obs.notifyObservers(null, "formautofill-storage-changed", "update");
|
||||
}
|
||||
|
||||
|
@ -318,11 +347,13 @@ class AutofillRecords {
|
|||
*
|
||||
* @param {string} guid
|
||||
* Indicates which record to retrieve.
|
||||
* @param {boolean} [options.rawData = false]
|
||||
* Returns a raw record without modifications and the computed fields.
|
||||
* @returns {Object}
|
||||
* A clone of the record.
|
||||
*/
|
||||
get(guid) {
|
||||
this.log.debug("get:", guid);
|
||||
get(guid, {rawData = false} = {}) {
|
||||
this.log.debug("get:", guid, rawData);
|
||||
|
||||
let recordFound = this._findByGUID(guid);
|
||||
if (!recordFound) {
|
||||
|
@ -331,27 +362,37 @@ class AutofillRecords {
|
|||
|
||||
// The record is cloned to avoid accidental modifications from outside.
|
||||
let clonedRecord = this._clone(recordFound);
|
||||
this._recordReadProcessor(clonedRecord);
|
||||
if (rawData) {
|
||||
this._stripComputedFields(clonedRecord);
|
||||
} else {
|
||||
this._recordReadProcessor(clonedRecord);
|
||||
}
|
||||
return clonedRecord;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all records.
|
||||
*
|
||||
* @param {boolean} [options.noComputedFields = false]
|
||||
* Returns raw record without those computed fields.
|
||||
* @param {boolean} [options.rawData = false]
|
||||
* Returns raw records without modifications and the computed fields.
|
||||
* @param {boolean} [options.includeDeleted = false]
|
||||
* Also return any tombstone records.
|
||||
* @returns {Array.<Object>}
|
||||
* An array containing clones of all records.
|
||||
*/
|
||||
getAll({noComputedFields = false, includeDeleted = false} = {}) {
|
||||
this.log.debug("getAll", noComputedFields, includeDeleted);
|
||||
getAll({rawData = false, includeDeleted = false} = {}) {
|
||||
this.log.debug("getAll", rawData, includeDeleted);
|
||||
|
||||
let records = this._store.data[this._collectionName].filter(r => !r.deleted || includeDeleted);
|
||||
// Records are cloned to avoid accidental modifications from outside.
|
||||
let clonedRecords = records.map(this._clone);
|
||||
clonedRecords.forEach(record => this._recordReadProcessor(record, {noComputedFields}));
|
||||
clonedRecords.forEach(record => {
|
||||
if (rawData) {
|
||||
this._stripComputedFields(record);
|
||||
} else {
|
||||
this._recordReadProcessor(record);
|
||||
}
|
||||
});
|
||||
return clonedRecords;
|
||||
}
|
||||
|
||||
|
@ -397,8 +438,30 @@ class AutofillRecords {
|
|||
});
|
||||
}
|
||||
|
||||
_migrateRecord(record) {
|
||||
let hasChanges = false;
|
||||
|
||||
if (!record.version || isNaN(record.version) || record.version < 1) {
|
||||
this.log.warn("Invalid record version:", record.version);
|
||||
|
||||
// Force to run the migration.
|
||||
record.version = 0;
|
||||
}
|
||||
|
||||
if (record.version < this.version) {
|
||||
hasChanges = true;
|
||||
record.version = this.version;
|
||||
|
||||
// Force to recompute fields if we upgrade the schema.
|
||||
this._stripComputedFields(record);
|
||||
}
|
||||
|
||||
hasChanges |= this._computeFields(record);
|
||||
return hasChanges;
|
||||
}
|
||||
|
||||
_normalizeRecord(record) {
|
||||
this._recordWriteProcessor(record);
|
||||
this._normalizeFields(record);
|
||||
|
||||
for (let key in record) {
|
||||
if (!this.VALID_FIELDS.includes(key)) {
|
||||
|
@ -411,11 +474,18 @@ class AutofillRecords {
|
|||
}
|
||||
}
|
||||
|
||||
// An interface to be inherited.
|
||||
_recordReadProcessor(record, {noComputedFields = false} = {}) {}
|
||||
_stripComputedFields(record) {
|
||||
this.VALID_COMPUTED_FIELDS.forEach(field => delete record[field]);
|
||||
}
|
||||
|
||||
// An interface to be inherited.
|
||||
_recordWriteProcessor(record) {}
|
||||
_recordReadProcessor(record) {}
|
||||
|
||||
// An interface to be inherited.
|
||||
_computeFields(record) {}
|
||||
|
||||
// An interface to be inherited.
|
||||
_normalizeFields(record) {}
|
||||
|
||||
// An interface to be inherited.
|
||||
mergeIfPossible(guid, record) {}
|
||||
|
@ -426,112 +496,118 @@ class AutofillRecords {
|
|||
|
||||
class Addresses extends AutofillRecords {
|
||||
constructor(store) {
|
||||
super(store, "addresses", VALID_PROFILE_FIELDS, ADDRESS_SCHEMA_VERSION);
|
||||
super(store, "addresses", VALID_ADDRESS_FIELDS, VALID_ADDRESS_COMPUTED_FIELDS, ADDRESS_SCHEMA_VERSION);
|
||||
}
|
||||
|
||||
_recordReadProcessor(profile, {noComputedFields} = {}) {
|
||||
if (noComputedFields) {
|
||||
return;
|
||||
_recordReadProcessor(address) {
|
||||
// TODO: We only support US in MVP so hide the field if it's not. We
|
||||
// are going to support more countries in bug 1370193.
|
||||
if (address.country && address.country != "US") {
|
||||
address["country-name"] = "";
|
||||
delete address.country;
|
||||
}
|
||||
}
|
||||
|
||||
_computeFields(address) {
|
||||
// NOTE: Remember to bump the schema version number if any of the existing
|
||||
// computing algorithm changes. (No need to bump when just adding new
|
||||
// computed fields)
|
||||
|
||||
let hasNewComputedFields = false;
|
||||
|
||||
// Compute name
|
||||
let name = FormAutofillNameUtils.joinNameParts({
|
||||
given: profile["given-name"],
|
||||
middle: profile["additional-name"],
|
||||
family: profile["family-name"],
|
||||
});
|
||||
if (name) {
|
||||
profile.name = name;
|
||||
if (!("name" in address)) {
|
||||
let name = FormAutofillNameUtils.joinNameParts({
|
||||
given: address["given-name"],
|
||||
middle: address["additional-name"],
|
||||
family: address["family-name"],
|
||||
});
|
||||
address.name = name;
|
||||
hasNewComputedFields = true;
|
||||
}
|
||||
|
||||
// Compute address
|
||||
if (profile["street-address"]) {
|
||||
let streetAddress = profile["street-address"].split("\n").map(s => s.trim());
|
||||
for (let i = 0; i < 2; i++) {
|
||||
if (streetAddress[i]) {
|
||||
profile["address-line" + (i + 1)] = streetAddress[i];
|
||||
}
|
||||
// Compute address lines
|
||||
if (!("address-line1" in address)) {
|
||||
let streetAddress = [];
|
||||
if (address["street-address"]) {
|
||||
streetAddress = address["street-address"].split("\n").map(s => s.trim());
|
||||
}
|
||||
if (streetAddress.length > 2) {
|
||||
profile["address-line3"] = FormAutofillUtils.toOneLineAddress(
|
||||
for (let i = 0; i < 3; i++) {
|
||||
address["address-line" + (i + 1)] = streetAddress[i] || "";
|
||||
}
|
||||
if (streetAddress.length > 3) {
|
||||
address["address-line3"] = FormAutofillUtils.toOneLineAddress(
|
||||
streetAddress.splice(2)
|
||||
);
|
||||
}
|
||||
hasNewComputedFields = true;
|
||||
}
|
||||
|
||||
// Compute country name
|
||||
if (profile.country) {
|
||||
if (profile.country == "US") {
|
||||
let countryName = REGION_NAMES[profile.country];
|
||||
if (countryName) {
|
||||
profile["country-name"] = countryName;
|
||||
}
|
||||
if (!("country-name" in address)) {
|
||||
if (address.country && REGION_NAMES[address.country]) {
|
||||
address["country-name"] = REGION_NAMES[address.country];
|
||||
} else {
|
||||
// TODO: We only support US in MVP so hide the field if it's not. We
|
||||
// are going to support more countries in bug 1370193.
|
||||
delete profile.country;
|
||||
address["country-name"] = "";
|
||||
}
|
||||
hasNewComputedFields = true;
|
||||
}
|
||||
|
||||
return hasNewComputedFields;
|
||||
}
|
||||
|
||||
_recordWriteProcessor(profile) {
|
||||
_normalizeFields(address) {
|
||||
// Normalize name
|
||||
if (profile.name) {
|
||||
let nameParts = FormAutofillNameUtils.splitName(profile.name);
|
||||
if (!profile["given-name"] && nameParts.given) {
|
||||
profile["given-name"] = nameParts.given;
|
||||
if (address.name) {
|
||||
let nameParts = FormAutofillNameUtils.splitName(address.name);
|
||||
if (!address["given-name"] && nameParts.given) {
|
||||
address["given-name"] = nameParts.given;
|
||||
}
|
||||
if (!profile["additional-name"] && nameParts.middle) {
|
||||
profile["additional-name"] = nameParts.middle;
|
||||
if (!address["additional-name"] && nameParts.middle) {
|
||||
address["additional-name"] = nameParts.middle;
|
||||
}
|
||||
if (!profile["family-name"] && nameParts.family) {
|
||||
profile["family-name"] = nameParts.family;
|
||||
if (!address["family-name"] && nameParts.family) {
|
||||
address["family-name"] = nameParts.family;
|
||||
}
|
||||
delete profile.name;
|
||||
delete address.name;
|
||||
}
|
||||
|
||||
// Normalize address
|
||||
if (profile["address-line1"] || profile["address-line2"] ||
|
||||
profile["address-line3"]) {
|
||||
// Normalize address lines
|
||||
if (STREET_ADDRESS_COMPONENTS.some(c => address[c])) {
|
||||
// Treat "street-address" as "address-line1" if it contains only one line
|
||||
// and "address-line1" is omitted.
|
||||
if (!profile["address-line1"] && profile["street-address"] &&
|
||||
!profile["street-address"].includes("\n")) {
|
||||
profile["address-line1"] = profile["street-address"];
|
||||
delete profile["street-address"];
|
||||
if (!address["address-line1"] && address["street-address"] &&
|
||||
!address["street-address"].includes("\n")) {
|
||||
address["address-line1"] = address["street-address"];
|
||||
delete address["street-address"];
|
||||
}
|
||||
|
||||
// Remove "address-line*" but keep the values.
|
||||
let addressLines = [1, 2, 3].map(i => {
|
||||
let value = profile["address-line" + i];
|
||||
delete profile["address-line" + i];
|
||||
return value;
|
||||
});
|
||||
|
||||
// Concatenate "address-line*" if "street-address" is omitted.
|
||||
if (!profile["street-address"]) {
|
||||
profile["street-address"] = addressLines.join("\n");
|
||||
if (!address["street-address"]) {
|
||||
address["street-address"] = STREET_ADDRESS_COMPONENTS.map(c => address[c]).join("\n");
|
||||
}
|
||||
|
||||
STREET_ADDRESS_COMPONENTS.forEach(c => delete address[c]);
|
||||
}
|
||||
|
||||
// Normalize country
|
||||
if (profile.country) {
|
||||
let country = profile.country.toUpperCase();
|
||||
if (address.country) {
|
||||
let country = address.country.toUpperCase();
|
||||
// Only values included in the region list will be saved.
|
||||
if (REGION_NAMES[country]) {
|
||||
profile.country = country;
|
||||
address.country = country;
|
||||
} else {
|
||||
delete profile.country;
|
||||
delete address.country;
|
||||
}
|
||||
} else if (profile["country-name"]) {
|
||||
} else if (address["country-name"]) {
|
||||
for (let region in REGION_NAMES) {
|
||||
if (REGION_NAMES[region].toLowerCase() == profile["country-name"].toLowerCase()) {
|
||||
profile.country = region;
|
||||
if (REGION_NAMES[region].toLowerCase() == address["country-name"].toLowerCase()) {
|
||||
address.country = region;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
delete profile["country-name"];
|
||||
delete address["country-name"];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -587,6 +663,10 @@ class Addresses extends AutofillRecords {
|
|||
}
|
||||
|
||||
addressFound.timeLastModified = Date.now();
|
||||
|
||||
this._stripComputedFields(addressFound);
|
||||
this._computeFields(addressFound);
|
||||
|
||||
this._store.saveSoon();
|
||||
let str = Cc["@mozilla.org/supports-string;1"]
|
||||
.createInstance(Ci.nsISupportsString);
|
||||
|
@ -616,30 +696,29 @@ class Addresses extends AutofillRecords {
|
|||
|
||||
class CreditCards extends AutofillRecords {
|
||||
constructor(store) {
|
||||
super(store, "creditCards", VALID_CREDIT_CARD_FIELDS, CREDIT_CARD_SCHEMA_VERSION);
|
||||
super(store, "creditCards", VALID_CREDIT_CARD_FIELDS, VALID_CREDIT_CARD_COMPUTED_FIELDS, CREDIT_CARD_SCHEMA_VERSION);
|
||||
}
|
||||
|
||||
_recordReadProcessor(creditCard, {noComputedFields} = {}) {
|
||||
if (noComputedFields) {
|
||||
return;
|
||||
}
|
||||
_computeFields(creditCard) {
|
||||
// NOTE: Remember to bump the schema version number if any of the existing
|
||||
// computing algorithm changes. (No need to bump when just adding new
|
||||
// computed fields)
|
||||
|
||||
let hasNewComputedFields = false;
|
||||
|
||||
// Compute split names
|
||||
if (creditCard["cc-name"]) {
|
||||
if (!("cc-given-name" in creditCard)) {
|
||||
let nameParts = FormAutofillNameUtils.splitName(creditCard["cc-name"]);
|
||||
if (nameParts.given) {
|
||||
creditCard["cc-given-name"] = nameParts.given;
|
||||
}
|
||||
if (nameParts.middle) {
|
||||
creditCard["cc-additional-name"] = nameParts.middle;
|
||||
}
|
||||
if (nameParts.family) {
|
||||
creditCard["cc-family-name"] = nameParts.family;
|
||||
}
|
||||
creditCard["cc-given-name"] = nameParts.given;
|
||||
creditCard["cc-additional-name"] = nameParts.middle;
|
||||
creditCard["cc-family-name"] = nameParts.family;
|
||||
hasNewComputedFields = true;
|
||||
}
|
||||
|
||||
return hasNewComputedFields;
|
||||
}
|
||||
|
||||
_recordWriteProcessor(creditCard) {
|
||||
_normalizeFields(creditCard) {
|
||||
// Fields that should not be set by content.
|
||||
delete creditCard["cc-number-encrypted"];
|
||||
delete creditCard["cc-number-masked"];
|
||||
|
|
|
@ -141,8 +141,8 @@ add_task(async function test_getAll() {
|
|||
do_check_eq(addresses[0]["address-line1"], "32 Vassar Street");
|
||||
do_check_eq(addresses[0]["address-line2"], "MIT Room 32-G524");
|
||||
|
||||
// Test with noComputedFields set.
|
||||
addresses = profileStorage.addresses.getAll({noComputedFields: true});
|
||||
// Test with rawData set.
|
||||
addresses = profileStorage.addresses.getAll({rawData: true});
|
||||
do_check_eq(addresses[0].name, undefined);
|
||||
do_check_eq(addresses[0]["address-line1"], undefined);
|
||||
do_check_eq(addresses[0]["address-line2"], undefined);
|
||||
|
@ -162,6 +162,12 @@ add_task(async function test_get() {
|
|||
let address = profileStorage.addresses.get(guid);
|
||||
do_check_record_matches(address, TEST_ADDRESS_1);
|
||||
|
||||
// Test with rawData set.
|
||||
address = profileStorage.addresses.get(guid, {rawData: true});
|
||||
do_check_eq(address.name, undefined);
|
||||
do_check_eq(address["address-line1"], undefined);
|
||||
do_check_eq(address["address-line2"], undefined);
|
||||
|
||||
// Modifying output shouldn't affect the storage.
|
||||
address.organization = "test";
|
||||
do_check_record_matches(profileStorage.addresses.get(guid), TEST_ADDRESS_1);
|
||||
|
|
|
@ -123,8 +123,8 @@ add_task(async function test_getAll() {
|
|||
do_check_eq(creditCards[0]["cc-given-name"], "John");
|
||||
do_check_eq(creditCards[0]["cc-family-name"], "Doe");
|
||||
|
||||
// Test with noComputedFields set.
|
||||
creditCards = profileStorage.creditCards.getAll({noComputedFields: true});
|
||||
// Test with rawData set.
|
||||
creditCards = profileStorage.creditCards.getAll({rawData: true});
|
||||
do_check_eq(creditCards[0]["cc-given-name"], undefined);
|
||||
do_check_eq(creditCards[0]["cc-family-name"], undefined);
|
||||
|
||||
|
|
|
@ -0,0 +1,229 @@
|
|||
/**
|
||||
* Tests the migration algorithm in profileStorage.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const {ProfileStorage} = Cu.import("resource://formautofill/ProfileStorage.jsm", {});
|
||||
|
||||
const TEST_STORE_FILE_NAME = "test-profile.json";
|
||||
|
||||
const ADDRESS_SCHEMA_VERSION = 1;
|
||||
const CREDIT_CARD_SCHEMA_VERSION = 1;
|
||||
|
||||
const ADDRESS_TESTCASES = [
|
||||
{
|
||||
description: "The record version is equal to the current version. The migration shouldn't be invoked.",
|
||||
record: {
|
||||
guid: "test-guid",
|
||||
version: ADDRESS_SCHEMA_VERSION,
|
||||
"given-name": "Timothy",
|
||||
name: "John", // The cached name field doesn't align "given-name" but it
|
||||
// won't be recomputed because the migration isn't invoked.
|
||||
},
|
||||
expectedResult: {
|
||||
guid: "test-guid",
|
||||
version: ADDRESS_SCHEMA_VERSION,
|
||||
"given-name": "Timothy",
|
||||
name: "John",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "The record version is greater than the current version. The migration shouldn't be invoked.",
|
||||
record: {
|
||||
guid: "test-guid",
|
||||
version: 99,
|
||||
"given-name": "Timothy",
|
||||
name: "John",
|
||||
},
|
||||
expectedResult: {
|
||||
guid: "test-guid",
|
||||
version: 99,
|
||||
"given-name": "Timothy",
|
||||
name: "John",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "The record version is less than the current version. The migration should be invoked.",
|
||||
record: {
|
||||
guid: "test-guid",
|
||||
version: 0,
|
||||
"given-name": "Timothy",
|
||||
name: "John",
|
||||
},
|
||||
expectedResult: {
|
||||
guid: "test-guid",
|
||||
version: ADDRESS_SCHEMA_VERSION,
|
||||
"given-name": "Timothy",
|
||||
name: "Timothy",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "The record version is omitted. The migration should be invoked.",
|
||||
record: {
|
||||
guid: "test-guid",
|
||||
"given-name": "Timothy",
|
||||
name: "John",
|
||||
},
|
||||
expectedResult: {
|
||||
guid: "test-guid",
|
||||
version: ADDRESS_SCHEMA_VERSION,
|
||||
"given-name": "Timothy",
|
||||
name: "Timothy",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "The record version is an invalid value. The migration should be invoked.",
|
||||
record: {
|
||||
guid: "test-guid",
|
||||
version: "ABCDE",
|
||||
"given-name": "Timothy",
|
||||
name: "John",
|
||||
},
|
||||
expectedResult: {
|
||||
guid: "test-guid",
|
||||
version: ADDRESS_SCHEMA_VERSION,
|
||||
"given-name": "Timothy",
|
||||
name: "Timothy",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "The omitted computed fields should be always recomputed even the record version is up-to-date.",
|
||||
record: {
|
||||
guid: "test-guid",
|
||||
version: ADDRESS_SCHEMA_VERSION,
|
||||
"given-name": "Timothy",
|
||||
},
|
||||
expectedResult: {
|
||||
guid: "test-guid",
|
||||
version: ADDRESS_SCHEMA_VERSION,
|
||||
"given-name": "Timothy",
|
||||
name: "Timothy",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const CREDIT_CARD_TESTCASES = [
|
||||
{
|
||||
description: "The record version is equal to the current version. The migration shouldn't be invoked.",
|
||||
record: {
|
||||
guid: "test-guid",
|
||||
version: CREDIT_CARD_SCHEMA_VERSION,
|
||||
"cc-name": "Timothy",
|
||||
"cc-given-name": "John", // The cached "cc-given-name" field doesn't align
|
||||
// "cc-name" but it won't be recomputed because
|
||||
// the migration isn't invoked.
|
||||
},
|
||||
expectedResult: {
|
||||
guid: "test-guid",
|
||||
version: CREDIT_CARD_SCHEMA_VERSION,
|
||||
"cc-name": "Timothy",
|
||||
"cc-given-name": "John",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "The record version is greater than the current version. The migration shouldn't be invoked.",
|
||||
record: {
|
||||
guid: "test-guid",
|
||||
version: 99,
|
||||
"cc-name": "Timothy",
|
||||
"cc-given-name": "John",
|
||||
},
|
||||
expectedResult: {
|
||||
guid: "test-guid",
|
||||
version: 99,
|
||||
"cc-name": "Timothy",
|
||||
"cc-given-name": "John",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "The record version is less than the current version. The migration should be invoked.",
|
||||
record: {
|
||||
guid: "test-guid",
|
||||
version: 0,
|
||||
"cc-name": "Timothy",
|
||||
"cc-given-name": "John",
|
||||
},
|
||||
expectedResult: {
|
||||
guid: "test-guid",
|
||||
version: CREDIT_CARD_SCHEMA_VERSION,
|
||||
"cc-name": "Timothy",
|
||||
"cc-given-name": "Timothy",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "The record version is omitted. The migration should be invoked.",
|
||||
record: {
|
||||
guid: "test-guid",
|
||||
"cc-name": "Timothy",
|
||||
"cc-given-name": "John",
|
||||
},
|
||||
expectedResult: {
|
||||
guid: "test-guid",
|
||||
version: CREDIT_CARD_SCHEMA_VERSION,
|
||||
"cc-name": "Timothy",
|
||||
"cc-given-name": "Timothy",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "The record version is an invalid value. The migration should be invoked.",
|
||||
record: {
|
||||
guid: "test-guid",
|
||||
version: "ABCDE",
|
||||
"cc-name": "Timothy",
|
||||
"cc-given-name": "John",
|
||||
},
|
||||
expectedResult: {
|
||||
guid: "test-guid",
|
||||
version: CREDIT_CARD_SCHEMA_VERSION,
|
||||
"cc-name": "Timothy",
|
||||
"cc-given-name": "Timothy",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "The omitted computed fields should be always recomputed even the record version is up-to-date.",
|
||||
record: {
|
||||
guid: "test-guid",
|
||||
version: CREDIT_CARD_SCHEMA_VERSION,
|
||||
"cc-name": "Timothy",
|
||||
},
|
||||
expectedResult: {
|
||||
guid: "test-guid",
|
||||
version: CREDIT_CARD_SCHEMA_VERSION,
|
||||
"cc-name": "Timothy",
|
||||
"cc-given-name": "Timothy",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
let do_check_record_matches = (expectedRecord, record) => {
|
||||
for (let key in expectedRecord) {
|
||||
do_check_eq(expectedRecord[key], record[key]);
|
||||
}
|
||||
};
|
||||
|
||||
add_task(async function test_migrateAddressRecords() {
|
||||
let path = getTempFile(TEST_STORE_FILE_NAME).path;
|
||||
|
||||
let profileStorage = new ProfileStorage(path);
|
||||
await profileStorage.initialize();
|
||||
|
||||
ADDRESS_TESTCASES.forEach(testcase => {
|
||||
do_print(testcase.description);
|
||||
profileStorage.addresses._migrateRecord(testcase.record);
|
||||
do_check_record_matches(testcase.expectedResult, testcase.record);
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_migrateCreditCardRecords() {
|
||||
let path = getTempFile(TEST_STORE_FILE_NAME).path;
|
||||
|
||||
let profileStorage = new ProfileStorage(path);
|
||||
await profileStorage.initialize();
|
||||
|
||||
CREDIT_CARD_TESTCASES.forEach(testcase => {
|
||||
do_print(testcase.description);
|
||||
profileStorage.creditCards._migrateRecord(testcase.record);
|
||||
do_check_record_matches(testcase.expectedResult, testcase.record);
|
||||
});
|
||||
});
|
|
@ -79,7 +79,7 @@ add_storage_task(async function test_add_tombstone(storage, record) {
|
|||
Assert.equal(storage.getAll().length, 0);
|
||||
|
||||
// but getAll allows us to access deleted items.
|
||||
let all = storage.getAll({includeDeleted: true});
|
||||
let all = storage.getAll({rawData: true, includeDeleted: true});
|
||||
Assert.equal(all.length, 1);
|
||||
|
||||
do_check_tombstone_record(all[0]);
|
||||
|
@ -112,7 +112,7 @@ add_storage_task(async function test_remove_existing_tombstone(storage, record)
|
|||
let guid = storage.add({guid: "test-guid-1", deleted: true, timeLastModified: 1234});
|
||||
|
||||
storage.remove(guid);
|
||||
let all = storage.getAll({includeDeleted: true});
|
||||
let all = storage.getAll({rawData: true, includeDeleted: true});
|
||||
Assert.equal(all.length, 1);
|
||||
|
||||
do_check_tombstone_record(all[0]);
|
||||
|
|
|
@ -65,7 +65,7 @@ const ADDRESS_COMPUTE_TESTCASES = [
|
|||
expectedResult: {
|
||||
"street-address": "line1\n\nline3",
|
||||
"address-line1": "line1",
|
||||
"address-line2": undefined,
|
||||
"address-line2": "",
|
||||
"address-line3": "line3",
|
||||
},
|
||||
},
|
||||
|
@ -89,7 +89,7 @@ const ADDRESS_COMPUTE_TESTCASES = [
|
|||
expectedResult: {
|
||||
"street-address": "line1\n \nline3\n \nline5",
|
||||
"address-line1": "line1",
|
||||
"address-line2": null,
|
||||
"address-line2": "",
|
||||
"address-line3": "line3 line5",
|
||||
},
|
||||
},
|
||||
|
@ -229,7 +229,7 @@ const ADDRESS_NORMALIZE_TESTCASES = [
|
|||
},
|
||||
expectedResult: {
|
||||
"country": "US",
|
||||
"country-name": undefined,
|
||||
"country-name": "United States",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -239,7 +239,7 @@ const ADDRESS_NORMALIZE_TESTCASES = [
|
|||
},
|
||||
expectedResult: {
|
||||
"country": undefined,
|
||||
"country-name": undefined,
|
||||
"country-name": "",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -250,7 +250,7 @@ const ADDRESS_NORMALIZE_TESTCASES = [
|
|||
},
|
||||
expectedResult: {
|
||||
"country": "US",
|
||||
"country-name": undefined,
|
||||
"country-name": "United States",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -261,7 +261,17 @@ const ADDRESS_NORMALIZE_TESTCASES = [
|
|||
},
|
||||
expectedResult: {
|
||||
"country": undefined,
|
||||
"country-name": undefined,
|
||||
"country-name": "",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Has unsupported \"country\"",
|
||||
address: {
|
||||
"country": "CA",
|
||||
},
|
||||
expectedResult: {
|
||||
"country": undefined,
|
||||
"country-name": "",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
@ -30,6 +30,7 @@ support-files =
|
|||
[test_isCJKName.js]
|
||||
[test_isFieldEligibleForAutofill.js]
|
||||
[test_markAsAutofillField.js]
|
||||
[test_migrateRecords.js]
|
||||
[test_nameUtils.js]
|
||||
[test_onFormSubmitted.js]
|
||||
[test_profileAutocompleteResult.js]
|
||||
|
|
|
@ -475,6 +475,10 @@ toolbarpaletteitem[place=toolbar] > toolbarspring {
|
|||
padding-inline-start: 0;
|
||||
}
|
||||
|
||||
.customization-uidensity-menu-button {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.customization-lwtheme-menu-theme[defaulttheme] {
|
||||
list-style-image: url(chrome://browser/content/default-theme-icon.svg);
|
||||
}
|
||||
|
|
|
@ -88,6 +88,9 @@ this.TestRunner = {
|
|||
Services.prefs.setCharPref("extensions.ui.lastCategory", "addons://list/extension");
|
||||
// Don't let the caret blink since it causes false positives for image diffs
|
||||
Services.prefs.setIntPref("ui.caretBlinkTime", -1);
|
||||
// Disable some animations that can cause false positives, such as the
|
||||
// reload/stop button spinning animation.
|
||||
Services.prefs.setBoolPref("toolkit.cosmeticAnimations.enabled", false);
|
||||
|
||||
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
|
||||
|
@ -151,6 +154,7 @@ this.TestRunner = {
|
|||
gBrowser.unpinTab(gBrowser.selectedTab);
|
||||
gBrowser.selectedBrowser.loadURI("data:text/html;charset=utf-8,<h1>Done!");
|
||||
browserWindow.restore();
|
||||
Services.prefs.clearUserPref("toolkit.cosmeticAnimations.enabled");
|
||||
},
|
||||
|
||||
// helpers
|
||||
|
|
21
build.gradle
21
build.gradle
|
@ -1,11 +1,32 @@
|
|||
import java.util.regex.Pattern
|
||||
|
||||
def tryInt = { string ->
|
||||
if (string == null) {
|
||||
return string
|
||||
}
|
||||
if (string.isInteger()) {
|
||||
return string as Integer
|
||||
}
|
||||
return string
|
||||
}
|
||||
|
||||
allprojects {
|
||||
// Expose the per-object-directory configuration to all projects.
|
||||
ext {
|
||||
mozconfig = gradle.mozconfig
|
||||
topsrcdir = gradle.mozconfig.topsrcdir
|
||||
topobjdir = gradle.mozconfig.topobjdir
|
||||
|
||||
compileSdkVersion = tryInt(mozconfig.substs.ANDROID_COMPILE_SDK_VERSION)
|
||||
buildToolsVersion = tryInt(mozconfig.substs.ANDROID_BUILD_TOOLS_VERSION)
|
||||
targetSdkVersion = tryInt(mozconfig.substs.ANDROID_TARGET_SDK)
|
||||
minSdkVersion = tryInt(mozconfig.substs.MOZ_ANDROID_MIN_SDK_VERSION)
|
||||
manifestPlaceholders = [
|
||||
ANDROID_PACKAGE_NAME: mozconfig.substs.ANDROID_PACKAGE_NAME,
|
||||
ANDROID_TARGET_SDK: mozconfig.substs.ANDROID_TARGET_SDK,
|
||||
MOZ_ANDROID_MIN_SDK_VERSION: mozconfig.substs.MOZ_ANDROID_MIN_SDK_VERSION,
|
||||
MOZ_ANDROID_SHARED_ID: "${mozconfig.substs.ANDROID_PACKAGE_NAME}.sharedID",
|
||||
]
|
||||
}
|
||||
|
||||
repositories {
|
||||
|
|
|
@ -230,9 +230,10 @@ fi
|
|||
])
|
||||
|
||||
dnl Configure an Android SDK.
|
||||
dnl Arg 1: target SDK version, like 23.
|
||||
dnl Arg 2: list of build-tools versions, like "23.0.3 23.0.1".
|
||||
dnl Arg 3: target lint version, like "25.3.1" (note: we fall back to
|
||||
dnl Arg 1: compile SDK version, like 23.
|
||||
dnl Arg 2: target SDK version, like 23.
|
||||
dnl Arg 3: list of build-tools versions, like "23.0.3 23.0.1".
|
||||
dnl Arg 4: target lint version, like "25.3.1" (note: we fall back to
|
||||
dnl unversioned lint if this version is not found).
|
||||
AC_DEFUN([MOZ_ANDROID_SDK],
|
||||
[
|
||||
|
@ -257,7 +258,7 @@ case "$target" in
|
|||
AC_MSG_ERROR([Including platforms/android-* in --with-android-sdk arguments is deprecated. Use --with-android-sdk=$android_sdk_root.])
|
||||
fi
|
||||
|
||||
android_target_sdk=$1
|
||||
android_target_sdk=$2
|
||||
AC_MSG_CHECKING([for Android SDK platform version $android_target_sdk])
|
||||
android_sdk=$android_sdk_root/platforms/android-$android_target_sdk
|
||||
if ! test -e "$android_sdk/source.properties" ; then
|
||||
|
@ -268,7 +269,7 @@ case "$target" in
|
|||
AC_MSG_CHECKING([for Android build-tools])
|
||||
android_build_tools_base="$android_sdk_root"/build-tools
|
||||
android_build_tools_version=""
|
||||
for version in $2; do
|
||||
for version in $3; do
|
||||
android_build_tools="$android_build_tools_base"/$version
|
||||
if test -d "$android_build_tools" -a -f "$android_build_tools/aapt"; then
|
||||
android_build_tools_version=$version
|
||||
|
@ -277,7 +278,7 @@ case "$target" in
|
|||
fi
|
||||
done
|
||||
if test "$android_build_tools_version" = ""; then
|
||||
version=$(echo $2 | cut -d" " -f1)
|
||||
version=$(echo $3 | cut -d" " -f1)
|
||||
AC_MSG_ERROR([You must install the Android build-tools version $version. Try |mach bootstrap|. (Looked for "$android_build_tools_base"/$version)])
|
||||
fi
|
||||
|
||||
|
@ -324,12 +325,15 @@ case "$target" in
|
|||
AC_MSG_ERROR([The program emulator was not found. Try |mach bootstrap|.])
|
||||
fi
|
||||
|
||||
# `compileSdkVersion ANDROID_COMPILE_SDK_VERSION` is Gradle-only,
|
||||
# so there's no associated configure check.
|
||||
ANDROID_COMPILE_SDK_VERSION=$1
|
||||
ANDROID_TARGET_SDK="${android_target_sdk}"
|
||||
ANDROID_SDK="${android_sdk}"
|
||||
ANDROID_SDK_ROOT="${android_sdk_root}"
|
||||
ANDROID_TOOLS="${android_tools}"
|
||||
ANDROID_BUILD_TOOLS_VERSION="$android_build_tools_version"
|
||||
AC_DEFINE_UNQUOTED(ANDROID_TARGET_SDK,$ANDROID_TARGET_SDK)
|
||||
AC_SUBST(ANDROID_COMPILE_SDK_VERSION)
|
||||
AC_SUBST(ANDROID_TARGET_SDK)
|
||||
AC_SUBST(ANDROID_SDK_ROOT)
|
||||
AC_SUBST(ANDROID_SDK)
|
||||
|
@ -358,7 +362,7 @@ case "$target" in
|
|||
;;
|
||||
esac
|
||||
|
||||
android_lint_target=$3
|
||||
android_lint_target=$4
|
||||
ANDROID_LINT_CLASSPATH=""
|
||||
android_lint_versioned_jar="$ANDROID_SDK_ROOT/tools/lib/lint-$android_lint_target.jar"
|
||||
android_lint_unversioned_jar="$ANDROID_SDK_ROOT/tools/lib/lint.jar"
|
||||
|
@ -397,13 +401,9 @@ if test -n "$MOZ_ANDROID_MIN_SDK_VERSION"; then
|
|||
AC_MSG_ERROR([--with-android-min-sdk is expected to be less than $ANDROID_TARGET_SDK])
|
||||
fi
|
||||
|
||||
AC_DEFINE_UNQUOTED(MOZ_ANDROID_MIN_SDK_VERSION, $MOZ_ANDROID_MIN_SDK_VERSION)
|
||||
AC_SUBST(MOZ_ANDROID_MIN_SDK_VERSION)
|
||||
fi
|
||||
|
||||
if test -n "$MOZ_ANDROID_MAX_SDK_VERSION"; then
|
||||
AC_DEFINE_UNQUOTED(MOZ_ANDROID_MAX_SDK_VERSION, $MOZ_ANDROID_MAX_SDK_VERSION)
|
||||
AC_SUBST(MOZ_ANDROID_MAX_SDK_VERSION)
|
||||
fi
|
||||
AC_SUBST(MOZ_ANDROID_MAX_SDK_VERSION)
|
||||
|
||||
])
|
||||
|
|
|
@ -12,6 +12,10 @@ endif
|
|||
preflight_all:
|
||||
# Terminate any sccache server that might still be around
|
||||
-$(TOPSRCDIR)/sccache2/sccache --stop-server > /dev/null 2>&1
|
||||
# Start a new server, ensuring it gets the jobserver file descriptors
|
||||
# from make (but don't use the + prefix when make -n is used, so that
|
||||
# the command doesn't run in that case)
|
||||
$(if $(findstring n,$(filter-out --%, $(MAKEFLAGS))),,+)$(TOPSRCDIR)/sccache2/sccache --start-server
|
||||
|
||||
postflight_all:
|
||||
# Terminate sccache server. This prints sccache stats.
|
||||
|
|
|
@ -913,8 +913,11 @@ else
|
|||
rust_debug_info=2
|
||||
endif
|
||||
|
||||
# We use the + prefix to pass down the jobserver fds to cargo, but we
|
||||
# don't use the prefix when make -n is used, so that cargo doesn't run
|
||||
# in that case)
|
||||
define RUN_CARGO
|
||||
env $(environment_cleaner) $(rust_unlock_unstable) $(rustflags_override) $(sccache_wrap) \
|
||||
$(if $(findstring n,$(filter-out --%, $(MAKEFLAGS))),,+)env $(environment_cleaner) $(rust_unlock_unstable) $(rustflags_override) $(sccache_wrap) \
|
||||
CARGO_TARGET_DIR=$(CARGO_TARGET_DIR) \
|
||||
RUSTC=$(RUSTC) \
|
||||
RUSTFLAGS='-C debuginfo=$(rust_debug_info)' \
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
|
||||
const { createClass, DOM: dom, PropTypes } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
const { debugAddon, uninstallAddon, isTemporaryID } = require("../../modules/addon");
|
||||
const { debugAddon, isTemporaryID, parseFileUri, uninstallAddon } =
|
||||
require("../../modules/addon");
|
||||
const Services = require("Services");
|
||||
|
||||
loader.lazyImporter(this, "BrowserToolboxProcess",
|
||||
|
@ -28,7 +29,7 @@ function filePathForTarget(target) {
|
|||
if (!target.temporarilyInstalled || !target.url || !target.url.startsWith("file://")) {
|
||||
return [];
|
||||
}
|
||||
let path = target.url.slice("file://".length);
|
||||
let path = parseFileUri(target.url);
|
||||
return [
|
||||
dom.dt(
|
||||
{ className: "addon-target-info-label" },
|
||||
|
|
|
@ -32,3 +32,14 @@ exports.uninstallAddon = async function (addonID) {
|
|||
exports.isTemporaryID = function (addonID) {
|
||||
return AddonManagerPrivate.isTemporaryInstallID(addonID);
|
||||
};
|
||||
|
||||
exports.parseFileUri = function (url) {
|
||||
// Strip a leading slash from Windows drive letter URIs.
|
||||
// file:///home/foo ~> /home/foo
|
||||
// file:///C:/foo ~> C:/foo
|
||||
const windowsRegex = /^file:\/\/\/([a-zA-Z]:\/.*)/;
|
||||
if (windowsRegex.test(url)) {
|
||||
return windowsRegex.exec(url)[1];
|
||||
}
|
||||
return url.slice("file://".length);
|
||||
};
|
||||
|
|
|
@ -12,6 +12,7 @@ DIRS += [
|
|||
BROWSER_CHROME_MANIFESTS += [
|
||||
'test/browser.ini'
|
||||
]
|
||||
XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell.ini']
|
||||
|
||||
with Files('**'):
|
||||
BUG_COMPONENT = ('Firefox', 'Developer Tools: about:debugging')
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
/* global equal */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { parseFileUri } =
|
||||
require("devtools/client/aboutdebugging/modules/addon");
|
||||
|
||||
add_task(function* testParseFileUri() {
|
||||
equal(
|
||||
parseFileUri("file:///home/me/my-extension/"),
|
||||
"/home/me/my-extension/",
|
||||
"UNIX paths are supported");
|
||||
|
||||
equal(
|
||||
parseFileUri("file:///C:/Documents/my-extension/"),
|
||||
"C:/Documents/my-extension/",
|
||||
"Windows paths are supported");
|
||||
|
||||
equal(
|
||||
parseFileUri("file://home/Documents/my-extension/"),
|
||||
"home/Documents/my-extension/",
|
||||
"Windows network paths are supported");
|
||||
});
|
|
@ -0,0 +1,9 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/* eslint no-unused-vars: [2, {"vars": "local"}] */
|
||||
|
||||
const { utils: Cu } = Components;
|
||||
const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
|
|
@ -0,0 +1,7 @@
|
|||
[DEFAULT]
|
||||
tags = devtools
|
||||
head = xpcshell-head.js
|
||||
firefox-appdir = browser
|
||||
skip-if = toolkit == 'android'
|
||||
|
||||
[test_addon_path.js]
|
|
@ -7162,9 +7162,8 @@ nsContentUtils::IsPatternMatching(nsAString& aValue, nsAString& aPattern,
|
|||
{
|
||||
NS_ASSERTION(aDocument, "aDocument should be a valid pointer (not null)");
|
||||
|
||||
AutoJSAPI jsapi;
|
||||
jsapi.Init();
|
||||
JSContext* cx = jsapi.cx();
|
||||
AutoJSContext cx;
|
||||
AutoDisableJSInterruptCallback disabler(cx);
|
||||
|
||||
// We can use the junk scope here, because we're just using it for
|
||||
// regexp evaluation, not actual script execution.
|
||||
|
|
|
@ -2645,8 +2645,6 @@ GK_ATOM(refx, "refx")
|
|||
// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
|
||||
GK_ATOM(refy, "refy")
|
||||
// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
|
||||
GK_ATOM(isindex, "isindex")
|
||||
// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
|
||||
GK_ATOM(fefunca, "fefunca")
|
||||
// ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN):
|
||||
GK_ATOM(fefuncb, "fefuncb")
|
||||
|
|
|
@ -12057,15 +12057,14 @@ nsGlobalWindow::ShowSlowScriptDialog()
|
|||
if (showDebugButton)
|
||||
buttonFlags += nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_2;
|
||||
|
||||
// Null out the operation callback while we're re-entering JS here.
|
||||
bool old = JS_DisableInterruptCallback(cx);
|
||||
|
||||
// Open the dialog.
|
||||
rv = prompt->ConfirmEx(title, msg, buttonFlags, waitButton, stopButton,
|
||||
debugButton, neverShowDlg, &neverShowDlgChk,
|
||||
&buttonPressed);
|
||||
|
||||
JS_ResetInterruptCallback(cx, old);
|
||||
{
|
||||
// Null out the operation callback while we're re-entering JS here.
|
||||
AutoDisableJSInterruptCallback disabler(cx);
|
||||
// Open the dialog.
|
||||
rv = prompt->ConfirmEx(title, msg, buttonFlags, waitButton, stopButton,
|
||||
debugButton, neverShowDlg, &neverShowDlgChk,
|
||||
&buttonPressed);
|
||||
}
|
||||
|
||||
if (NS_SUCCEEDED(rv) && (buttonPressed == 0)) {
|
||||
return neverShowDlgChk ? AlwaysContinueSlowScript : ContinueSlowScript;
|
||||
|
|
|
@ -70,7 +70,7 @@ bool nsNameSpaceManager::Init()
|
|||
|
||||
// Need to be ordered according to ID.
|
||||
MOZ_ASSERT(mURIArray.IsEmpty());
|
||||
REGISTER_NAMESPACE(nsGkAtoms::empty, kNameSpaceID_None);
|
||||
REGISTER_NAMESPACE(nsGkAtoms::_empty, kNameSpaceID_None);
|
||||
REGISTER_NAMESPACE(nsGkAtoms::nsuri_xmlns, kNameSpaceID_XMLNS);
|
||||
REGISTER_NAMESPACE(nsGkAtoms::nsuri_xml, kNameSpaceID_XML);
|
||||
REGISTER_NAMESPACE(nsGkAtoms::nsuri_xhtml, kNameSpaceID_XHTML);
|
||||
|
|
|
@ -4962,7 +4962,6 @@ skip-if = (os == 'android' || os == 'linux')
|
|||
[generated/test_2_conformance2__rendering__framebuffer-completeness-unaffected.html]
|
||||
skip-if = (os == 'android' || os == 'linux')
|
||||
[generated/test_2_conformance2__rendering__framebuffer-unsupported.html]
|
||||
fail-if = (os == 'mac')
|
||||
skip-if = (os == 'android' || os == 'linux')
|
||||
[generated/test_2_conformance2__rendering__fs-color-type-mismatch-color-buffer-type.html]
|
||||
fail-if = (os == 'mac') || (os == 'win')
|
||||
|
|
|
@ -685,8 +685,6 @@ fail-if = (os == 'mac')
|
|||
fail-if = (os == 'mac')
|
||||
[generated/test_2_conformance2__rendering__blitframebuffer-filter-srgb.html]
|
||||
fail-if = (os == 'mac')
|
||||
[generated/test_2_conformance2__rendering__framebuffer-unsupported.html]
|
||||
fail-if = (os == 'mac')
|
||||
[generated/test_2_conformance2__textures__canvas_sub_rectangle__tex-3d-r11f_g11f_b10f-rgb-float.html]
|
||||
fail-if = (os == 'mac')
|
||||
[generated/test_2_conformance2__textures__canvas_sub_rectangle__tex-3d-r11f_g11f_b10f-rgb-half_float.html]
|
||||
|
|
|
@ -6,6 +6,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1013412
|
|||
<head>
|
||||
<title>Test for Bug 1013412</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
<style>
|
||||
|
@ -81,10 +82,10 @@ function runTest() {
|
|||
var content = document.getElementById('content');
|
||||
if (iteration < 300) { // enough iterations that we would scroll to the bottom of 'content'
|
||||
iteration++;
|
||||
synthesizeWheel(content, 100, 10,
|
||||
{ deltaMode: WheelEvent.DOM_DELTA_LINE,
|
||||
deltaY: 1.0, lineOrPageDeltaY: 1 });
|
||||
setTimeout(runTest, 0);
|
||||
sendWheelAndPaint(content, 100, 10,
|
||||
{ deltaMode: WheelEvent.DOM_DELTA_LINE,
|
||||
deltaY: 1.0, lineOrPageDeltaY: 1 },
|
||||
runTest);
|
||||
return;
|
||||
}
|
||||
var scrollbox = document.getElementById('scrollbox');
|
||||
|
|
|
@ -116,13 +116,6 @@ public:
|
|||
virtual nsresult
|
||||
GetEncodedSubmission(nsIURI* aURI, nsIInputStream** aPostDataStream) override;
|
||||
|
||||
virtual bool SupportsIsindexSubmission() override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual nsresult AddIsindex(const nsAString& aValue) override;
|
||||
|
||||
protected:
|
||||
|
||||
/**
|
||||
|
@ -178,25 +171,6 @@ FSURLEncoded::AddNameValuePair(const nsAString& aName,
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
FSURLEncoded::AddIsindex(const nsAString& aValue)
|
||||
{
|
||||
// Encode value
|
||||
nsCString convValue;
|
||||
nsresult rv = URLEncode(aValue, convValue);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Append data to string
|
||||
if (mQueryString.IsEmpty()) {
|
||||
Telemetry::Accumulate(Telemetry::FORM_ISINDEX_USED, true);
|
||||
mQueryString.Assign(convValue);
|
||||
} else {
|
||||
mQueryString += NS_LITERAL_CSTRING("&isindex=") + convValue;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
FSURLEncoded::AddNameBlobOrNullPair(const nsAString& aName,
|
||||
Blob* aBlob)
|
||||
|
|
|
@ -78,27 +78,6 @@ public:
|
|||
virtual nsresult AddNameDirectoryPair(const nsAString& aName,
|
||||
Directory* aDirectory) = 0;
|
||||
|
||||
/**
|
||||
* Reports whether the instance supports AddIsindex().
|
||||
*
|
||||
* @return true if supported.
|
||||
*/
|
||||
virtual bool SupportsIsindexSubmission()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an isindex value to the submission.
|
||||
*
|
||||
* @param aValue the field value
|
||||
*/
|
||||
virtual nsresult AddIsindex(const nsAString& aValue)
|
||||
{
|
||||
NS_NOTREACHED("AddIsindex called when not supported");
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a URI and the current submission, create the final URI and data
|
||||
* stream that will be submitted. Subclasses *must* implement this.
|
||||
|
|
|
@ -6389,11 +6389,6 @@ HTMLInputElement::SubmitNamesValues(HTMLFormSubmission* aFormSubmission)
|
|||
value = defaultValue;
|
||||
}
|
||||
|
||||
if (IsSingleLineTextControl(true) &&
|
||||
name.EqualsLiteral("isindex") &&
|
||||
aFormSubmission->SupportsIsindexSubmission()) {
|
||||
return aFormSubmission->AddIsindex(value);
|
||||
}
|
||||
return aFormSubmission->AddNameValuePair(name, value);
|
||||
}
|
||||
|
||||
|
|
|
@ -8,11 +8,6 @@ Browse=Browse…
|
|||
FileUpload=File Upload
|
||||
DirectoryUpload=Select Folder to Upload
|
||||
DirectoryPickerOkButtonLabel=Upload
|
||||
# LOCALIZATION NOTE (IsIndexPromptWithSpace): The last character of the string
|
||||
# should be a space (U+0020) in most locales. The prompt is followed by an
|
||||
# input field. The space needs be escaped in the property file to avoid
|
||||
# trimming.
|
||||
IsIndexPromptWithSpace=This is a searchable index. Enter search keywords:\u0020
|
||||
ForgotPostWarning=Form contains enctype=%S, but does not contain method=post. Submitting normally with method=GET and no enctype instead.
|
||||
ForgotFileEnctypeWarning=Form contains a file input, but is missing method=POST and enctype=multipart/form-data on the form. The file will not be sent.
|
||||
# LOCALIZATION NOTE (DefaultFormSubject): %S will be replaced with brandShortName
|
||||
|
@ -34,8 +29,8 @@ NoDirSelected=No directory selected.
|
|||
# %S will be a number greater or equal to 2.
|
||||
XFilesSelected=%S files selected.
|
||||
ColorPicker=Choose a color
|
||||
# LOCALIZATION NOTE (AndNMoreFiles): Semi-colon list of plural forms.
|
||||
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
|
||||
# LOCALIZATION NOTE (AndNMoreFiles): Semi-colon list of plural forms.
|
||||
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
|
||||
# This string is shown at the end of the tooltip text for <input type='file'
|
||||
# multiple> when there are more than 21 files selected (when we will only list
|
||||
# the first 20, plus an "and X more" line). #1 represents the number of files
|
||||
|
|
|
@ -16,7 +16,7 @@ EncBomlessUtf16=Detected UTF-16-encoded Basic Latin-only text without a byte ord
|
|||
EncMetaUtf16=A meta tag was used to declare the character encoding as UTF-16. This was interpreted as an UTF-8 declaration instead.
|
||||
EncMetaUserDefined=A meta tag was used to declare the character encoding as x-user-defined. This was interpreted as a windows-1252 declaration instead for compatibility with intentionally mis-encoded legacy fonts. This site should migrate to Unicode.
|
||||
|
||||
# The bulk of the messages below are derived from
|
||||
# The bulk of the messages below are derived from
|
||||
# https://hg.mozilla.org/projects/htmlparser/file/1f633cef7de7/src/nu/validator/htmlparser/impl/ErrorReportingTokenizer.java
|
||||
# which is available under the MIT license.
|
||||
|
||||
|
@ -104,7 +104,6 @@ errStartSelectWhereEndSelectExpected=“select” start tag where end tag expect
|
|||
errStartTagWithSelectOpen=“%1$S” start tag with “select” open.
|
||||
errBadStartTagInHead2=Bad start tag “%1$S” in “head”.
|
||||
errImage=Saw a start tag “image”.
|
||||
errIsindex=“isindex” seen.
|
||||
errFooSeenWhenFooOpen=An “%1$S” start tag seen but an element of the same type was already open.
|
||||
errHeadingWhenHeadingOpen=Heading cannot be a child of another heading.
|
||||
errFramesetStart=“frameset” start tag seen.
|
||||
|
@ -129,4 +128,4 @@ errSelfClosing=Self-closing syntax (“/>”) used on a non-void HTML element. I
|
|||
errNoCheckUnclosedElementsOnStack=Unclosed elements on stack.
|
||||
errEndTagDidNotMatchCurrentOpenElement=End tag “%1$S” did not match the name of the current open element (“%2$S”).
|
||||
errEndTagViolatesNestingRules=End tag “%1$S” violates nesting rules.
|
||||
errEndWithUnclosedElements=End tag for “%1$S” seen, but there were unclosed elements.
|
||||
errEndWithUnclosedElements=End tag for “%1$S” seen, but there were unclosed elements.
|
||||
|
|
|
@ -26,6 +26,7 @@ MediaDecoderStateMachine*
|
|||
ADTSDecoder::CreateStateMachine()
|
||||
{
|
||||
MediaDecoderReaderInit init(this);
|
||||
init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
|
||||
mReader = new MediaFormatReader(init, new ADTSDemuxer(mResource));
|
||||
return new MediaDecoderStateMachine(this, mReader);
|
||||
}
|
||||
|
|
|
@ -46,16 +46,6 @@ public:
|
|||
// Can be called on any thread.
|
||||
virtual void NotifyDecodedFrames(const FrameStatisticsData& aStats) = 0;
|
||||
|
||||
// Returns an event that will be notified when the owning document changes state
|
||||
// and we might have a new compositor. If this new compositor requires us to
|
||||
// recreate our decoders, then we expect the existing decoderis to return an
|
||||
// error independently of this.
|
||||
virtual MediaEventSource<RefPtr<layers::KnowsCompositor>>*
|
||||
CompositorUpdatedEvent()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Notify the media decoder that a decryption key is required before emitting
|
||||
// further output. This only needs to be overridden for decoders that expect
|
||||
// encryption, such as the MediaSource decoder.
|
||||
|
@ -82,8 +72,6 @@ public:
|
|||
// Set by Reader if the current audio track can be offloaded
|
||||
virtual void SetPlatformCanOffloadAudio(bool aCanOffloadAudio) { }
|
||||
|
||||
virtual already_AddRefed<GMPCrashHelper> GetCrashHelper() { return nullptr; }
|
||||
|
||||
// Stack based class to assist in notifying the frame statistics of
|
||||
// parsed and decoded frames. Use inside video demux & decode functions
|
||||
// to ensure all parsed and decoded frames are reported on all return paths.
|
||||
|
|
|
@ -339,7 +339,7 @@ DecoderTraits::CreateDecoder(MediaDecoderInit& aInit,
|
|||
/* static */
|
||||
MediaDecoderReader*
|
||||
DecoderTraits::CreateReader(const MediaContainerType& aType,
|
||||
const MediaDecoderReaderInit& aInit)
|
||||
MediaDecoderReaderInit& aInit)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MediaDecoderReader* decoderReader = nullptr;
|
||||
|
|
|
@ -52,7 +52,7 @@ public:
|
|||
// Create a reader for thew given MIME type aType. Returns null
|
||||
// if we were unable to create the reader.
|
||||
static MediaDecoderReader* CreateReader(const MediaContainerType& aType,
|
||||
const MediaDecoderReaderInit& aInit);
|
||||
MediaDecoderReaderInit& aInit);
|
||||
|
||||
// Returns true if MIME type aType is supported in video documents,
|
||||
// or false otherwise. Not all platforms support all MIME types, and
|
||||
|
|
|
@ -657,7 +657,7 @@ AudioCallbackDriver::Init()
|
|||
if (latencyPref) {
|
||||
latency_frames = latencyPref.value();
|
||||
} else {
|
||||
if (cubeb_get_min_latency(cubebContext, output, &latency_frames) != CUBEB_OK) {
|
||||
if (cubeb_get_min_latency(cubebContext, &output, &latency_frames) != CUBEB_OK) {
|
||||
NS_WARNING("Could not get minimal latency from cubeb.");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -321,6 +321,7 @@ MediaDecoder::Shutdown()
|
|||
mOnPlaybackErrorEvent.Disconnect();
|
||||
mOnDecoderDoctorEvent.Disconnect();
|
||||
mOnMediaNotSeekable.Disconnect();
|
||||
mOnEncrypted.Disconnect();
|
||||
|
||||
mDecoderStateMachine->BeginShutdown()
|
||||
->Then(mAbstractMainThread, __func__, this,
|
||||
|
@ -484,6 +485,9 @@ MediaDecoder::SetStateMachineParameters()
|
|||
mAbstractMainThread, this, &MediaDecoder::OnDecoderDoctorEvent);
|
||||
mOnMediaNotSeekable = mDecoderStateMachine->OnMediaNotSeekable().Connect(
|
||||
mAbstractMainThread, this, &MediaDecoder::OnMediaNotSeekable);
|
||||
|
||||
mOnEncrypted = mReader->OnEncrypted().Connect(
|
||||
mAbstractMainThread, GetOwner(), &MediaDecoderOwner::DispatchEncrypted);
|
||||
}
|
||||
|
||||
nsresult
|
||||
|
@ -726,13 +730,6 @@ MediaDecoder::OwnerHasError() const
|
|||
return GetOwner()->HasError();
|
||||
}
|
||||
|
||||
already_AddRefed<GMPCrashHelper>
|
||||
MediaDecoder::GetCrashHelper()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
return GetOwner()->CreateGMPCrashHelper();
|
||||
}
|
||||
|
||||
bool
|
||||
MediaDecoder::IsEnded() const
|
||||
{
|
||||
|
@ -1058,20 +1055,31 @@ MediaDecoder::DurationChanged()
|
|||
}
|
||||
}
|
||||
|
||||
already_AddRefed<KnowsCompositor>
|
||||
MediaDecoder::GetCompositor()
|
||||
{
|
||||
MediaDecoderOwner* owner = GetOwner();
|
||||
nsIDocument* ownerDoc = owner ? owner->GetDocument() : nullptr;
|
||||
RefPtr<LayerManager> layerManager =
|
||||
ownerDoc ? nsContentUtils::LayerManagerForDocument(ownerDoc) : nullptr;
|
||||
RefPtr<KnowsCompositor> knows =
|
||||
layerManager ? layerManager->AsShadowForwarder() : nullptr;
|
||||
return knows.forget();
|
||||
}
|
||||
|
||||
void
|
||||
MediaDecoder::NotifyCompositor()
|
||||
{
|
||||
MediaDecoderOwner* owner = GetOwner();
|
||||
NS_ENSURE_TRUE_VOID(owner);
|
||||
|
||||
nsIDocument* ownerDoc = owner->GetDocument();
|
||||
NS_ENSURE_TRUE_VOID(ownerDoc);
|
||||
|
||||
RefPtr<LayerManager> layerManager =
|
||||
nsContentUtils::LayerManagerForDocument(ownerDoc);
|
||||
if (layerManager) {
|
||||
RefPtr<KnowsCompositor> knowsCompositor = layerManager->AsShadowForwarder();
|
||||
mCompositorUpdatedEvent.Notify(knowsCompositor);
|
||||
RefPtr<KnowsCompositor> knowsCompositor = GetCompositor();
|
||||
if (knowsCompositor) {
|
||||
nsCOMPtr<nsIRunnable> r =
|
||||
NewRunnableMethod<already_AddRefed<KnowsCompositor>&&>(
|
||||
"MediaDecoderReader::UpdateCompositor",
|
||||
mReader,
|
||||
&MediaDecoderReader::UpdateCompositor,
|
||||
knowsCompositor.forget());
|
||||
mReader->OwnerThread()->Dispatch(r.forget(),
|
||||
AbstractThread::DontAssertDispatchSuccess);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -215,8 +215,6 @@ public:
|
|||
// Must be called before Shutdown().
|
||||
bool OwnerHasError() const;
|
||||
|
||||
already_AddRefed<GMPCrashHelper> GetCrashHelper() override;
|
||||
|
||||
public:
|
||||
// Returns true if this media supports random seeking. False for example with
|
||||
// chained ogg files.
|
||||
|
@ -512,6 +510,8 @@ protected:
|
|||
return mCurrentPosition.Ref();
|
||||
}
|
||||
|
||||
already_AddRefed<layers::KnowsCompositor> GetCompositor();
|
||||
|
||||
// Official duration of the media resource as observed by script.
|
||||
double mDuration;
|
||||
|
||||
|
@ -542,9 +542,6 @@ private:
|
|||
// Called when the owner's activity changed.
|
||||
void NotifyCompositor();
|
||||
|
||||
MediaEventSource<RefPtr<layers::KnowsCompositor>>*
|
||||
CompositorUpdatedEvent() override { return &mCompositorUpdatedEvent; }
|
||||
|
||||
void OnPlaybackEvent(MediaEventType aEvent);
|
||||
void OnPlaybackErrorEvent(const MediaResult& aError);
|
||||
|
||||
|
@ -560,8 +557,6 @@ private:
|
|||
void ConnectMirrors(MediaDecoderStateMachine* aObject);
|
||||
void DisconnectMirrors();
|
||||
|
||||
MediaEventProducer<RefPtr<layers::KnowsCompositor>> mCompositorUpdatedEvent;
|
||||
|
||||
// The state machine object for handling the decoding. It is safe to
|
||||
// call methods of this object from other threads. Its internal data
|
||||
// is synchronised on a monitor. The lifetime of this object is
|
||||
|
@ -708,6 +703,7 @@ protected:
|
|||
MediaEventListener mOnPlaybackErrorEvent;
|
||||
MediaEventListener mOnDecoderDoctorEvent;
|
||||
MediaEventListener mOnMediaNotSeekable;
|
||||
MediaEventListener mOnEncrypted;
|
||||
|
||||
protected:
|
||||
// PlaybackRate and pitch preservation status we should start at.
|
||||
|
|
|
@ -68,7 +68,7 @@ public:
|
|||
size_t mSize;
|
||||
};
|
||||
|
||||
MediaDecoderReader::MediaDecoderReader(const MediaDecoderReaderInit& aInit)
|
||||
MediaDecoderReader::MediaDecoderReader(MediaDecoderReaderInit& aInit)
|
||||
: mAudioCompactor(mAudioQueue)
|
||||
, mDecoder(aInit.mDecoder)
|
||||
, mTaskQueue(new TaskQueue(
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
namespace mozilla {
|
||||
|
||||
class CDMProxy;
|
||||
class GMPCrashHelper;
|
||||
class MediaDecoderReader;
|
||||
class TaskQueue;
|
||||
class VideoFrameContainer;
|
||||
|
@ -67,6 +68,8 @@ struct MOZ_STACK_CLASS MediaDecoderReaderInit
|
|||
AbstractMediaDecoder* const mDecoder;
|
||||
MediaResource* mResource = nullptr;
|
||||
VideoFrameContainer* mVideoFrameContainer = nullptr;
|
||||
already_AddRefed<layers::KnowsCompositor> mKnowsCompositor;
|
||||
already_AddRefed<GMPCrashHelper> mCrashHelper;
|
||||
|
||||
explicit MediaDecoderReaderInit(AbstractMediaDecoder* aDecoder)
|
||||
: mDecoder(aDecoder)
|
||||
|
@ -110,7 +113,7 @@ public:
|
|||
|
||||
// The caller must ensure that Shutdown() is called before aDecoder is
|
||||
// destroyed.
|
||||
explicit MediaDecoderReader(const MediaDecoderReaderInit& aInit);
|
||||
explicit MediaDecoderReader(MediaDecoderReaderInit& aInit);
|
||||
|
||||
// Initializes the reader, returns NS_OK on success, or NS_ERROR_FAILURE
|
||||
// on failure.
|
||||
|
@ -134,6 +137,8 @@ public:
|
|||
|
||||
void UpdateDuration(const media::TimeUnit& aDuration);
|
||||
|
||||
virtual void UpdateCompositor(already_AddRefed<layers::KnowsCompositor>) {}
|
||||
|
||||
// Resets all state related to decoding, emptying all buffers etc.
|
||||
// Cancels all pending Request*Data() request callbacks, rejects any
|
||||
// outstanding seek promises, and flushes the decode pipeline. The
|
||||
|
@ -270,6 +275,11 @@ public:
|
|||
return mOnTrackWaitingForKey;
|
||||
}
|
||||
|
||||
MediaEventSource<nsTArray<uint8_t>, nsString>& OnEncrypted()
|
||||
{
|
||||
return mOnEncrypted;
|
||||
}
|
||||
|
||||
// Switch the video decoder to NullDecoderModule. It might takes effective
|
||||
// since a few samples later depends on how much demuxed samples are already
|
||||
// queued in the original video decoder.
|
||||
|
@ -332,6 +342,8 @@ protected:
|
|||
// Notify if we are waiting for a decryption key.
|
||||
MediaEventProducer<TrackInfo::TrackType> mOnTrackWaitingForKey;
|
||||
|
||||
MediaEventProducer<nsTArray<uint8_t>, nsString> mOnEncrypted;
|
||||
|
||||
RefPtr<MediaResource> mResource;
|
||||
|
||||
private:
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "AutoTaskQueue.h"
|
||||
#include "Layers.h"
|
||||
#include "MediaData.h"
|
||||
#include "MediaDecoderOwner.h"
|
||||
#include "MediaInfo.h"
|
||||
#include "MediaResource.h"
|
||||
#include "VideoFrameContainer.h"
|
||||
|
@ -22,8 +23,6 @@
|
|||
#include "mozilla/SyncRunnable.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
#include "mozilla/Unused.h"
|
||||
#include "mozilla/dom/HTMLMediaElement.h"
|
||||
#include "mozilla/layers/ShadowLayers.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsPrintfCString.h"
|
||||
#include "nsSize.h"
|
||||
|
@ -1094,7 +1093,7 @@ MediaFormatReader::DemuxerProxy::NotifyDataArrived()
|
|||
});
|
||||
}
|
||||
|
||||
MediaFormatReader::MediaFormatReader(const MediaDecoderReaderInit& aInit,
|
||||
MediaFormatReader::MediaFormatReader(MediaDecoderReaderInit& aInit,
|
||||
MediaDataDemuxer* aDemuxer)
|
||||
: MediaDecoderReader(aInit)
|
||||
, mAudio(this, MediaData::AUDIO_DATA,
|
||||
|
@ -1106,21 +1105,17 @@ MediaFormatReader::MediaFormatReader(const MediaDecoderReaderInit& aInit,
|
|||
, mPendingNotifyDataArrived(false)
|
||||
, mLastReportedNumDecodedFrames(0)
|
||||
, mPreviousDecodedKeyframeTime_us(sNoPreviousDecodedKeyframe)
|
||||
, mKnowsCompositor(aInit.mKnowsCompositor)
|
||||
, mInitDone(false)
|
||||
, mTrackDemuxersMayBlock(false)
|
||||
, mSeekScheduled(false)
|
||||
, mVideoFrameContainer(aInit.mVideoFrameContainer)
|
||||
, mCrashHelper(aInit.mCrashHelper)
|
||||
, mDecoderFactory(new DecoderFactory(this))
|
||||
, mShutdownPromisePool(new ShutdownPromisePool())
|
||||
{
|
||||
MOZ_ASSERT(aDemuxer);
|
||||
MOZ_COUNT_CTOR(MediaFormatReader);
|
||||
|
||||
AbstractMediaDecoder* decoder = aInit.mDecoder;
|
||||
if (decoder && decoder->CompositorUpdatedEvent()) {
|
||||
mCompositorUpdatedListener = decoder->CompositorUpdatedEvent()->Connect(
|
||||
mTaskQueue, this, &MediaFormatReader::NotifyCompositorUpdated);
|
||||
}
|
||||
mOnTrackWaitingForKeyListener = OnTrackWaitingForKey().Connect(
|
||||
mTaskQueue, this, &MediaFormatReader::NotifyWaitingForKey);
|
||||
}
|
||||
|
@ -1168,7 +1163,6 @@ MediaFormatReader::Shutdown()
|
|||
mShutdownPromisePool->Track(mDemuxer->Shutdown());
|
||||
mDemuxer = nullptr;
|
||||
|
||||
mCompositorUpdatedListener.DisconnectIfExists();
|
||||
mOnTrackWaitingForKeyListener.Disconnect();
|
||||
|
||||
mShutdown = true;
|
||||
|
@ -1214,38 +1208,11 @@ MediaFormatReader::TearDownDecoders()
|
|||
return MediaDecoderReader::Shutdown();
|
||||
}
|
||||
|
||||
void
|
||||
MediaFormatReader::InitLayersBackendType()
|
||||
{
|
||||
// Extract the layer manager backend type so that platform decoders
|
||||
// can determine whether it's worthwhile using hardware accelerated
|
||||
// video decoding.
|
||||
if (!mDecoder) {
|
||||
return;
|
||||
}
|
||||
MediaDecoderOwner* owner = mDecoder->GetOwner();
|
||||
if (!owner) {
|
||||
NS_WARNING("MediaFormatReader without a decoder owner, can't get HWAccel");
|
||||
return;
|
||||
}
|
||||
|
||||
dom::HTMLMediaElement* element = owner->GetMediaElement();
|
||||
NS_ENSURE_TRUE_VOID(element);
|
||||
|
||||
RefPtr<LayerManager> layerManager =
|
||||
nsContentUtils::LayerManagerForDocument(element->OwnerDoc());
|
||||
NS_ENSURE_TRUE_VOID(layerManager);
|
||||
|
||||
mKnowsCompositor = layerManager->AsShadowForwarder();
|
||||
}
|
||||
|
||||
nsresult
|
||||
MediaFormatReader::InitInternal()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
|
||||
|
||||
InitLayersBackendType();
|
||||
|
||||
mAudio.mTaskQueue = new TaskQueue(
|
||||
GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER),
|
||||
"MFR::mAudio::mTaskQueue");
|
||||
|
@ -1254,43 +1221,9 @@ MediaFormatReader::InitInternal()
|
|||
GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER),
|
||||
"MFR::mVideo::mTaskQueue");
|
||||
|
||||
if (mDecoder) {
|
||||
// Note: GMPCrashHelper must be created on main thread, as it may use
|
||||
// weak references, which aren't threadsafe.
|
||||
mCrashHelper = mDecoder->GetCrashHelper();
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
class DispatchKeyNeededEvent : public Runnable
|
||||
{
|
||||
public:
|
||||
DispatchKeyNeededEvent(AbstractMediaDecoder* aDecoder,
|
||||
nsTArray<uint8_t>& aInitData,
|
||||
const nsString& aInitDataType)
|
||||
: Runnable("DispatchKeyNeededEvent")
|
||||
, mDecoder(aDecoder)
|
||||
, mInitData(aInitData)
|
||||
, mInitDataType(aInitDataType)
|
||||
{
|
||||
}
|
||||
NS_IMETHOD Run() override
|
||||
{
|
||||
// Note: Null check the owner, as the decoder could have been shutdown
|
||||
// since this event was dispatched.
|
||||
MediaDecoderOwner* owner = mDecoder->GetOwner();
|
||||
if (owner) {
|
||||
owner->DispatchEncrypted(mInitData, mInitDataType);
|
||||
}
|
||||
mDecoder = nullptr;
|
||||
return NS_OK;
|
||||
}
|
||||
private:
|
||||
RefPtr<AbstractMediaDecoder> mDecoder;
|
||||
nsTArray<uint8_t> mInitData;
|
||||
nsString mInitDataType;
|
||||
};
|
||||
|
||||
void
|
||||
MediaFormatReader::SetCDMProxy(CDMProxy* aProxy)
|
||||
{
|
||||
|
@ -1418,13 +1351,11 @@ MediaFormatReader::OnDemuxerInitDone(const MediaResult& aResult)
|
|||
}
|
||||
|
||||
UniquePtr<EncryptionInfo> crypto = mDemuxer->GetCrypto();
|
||||
if (mDecoder && crypto && crypto->IsEncrypted()) {
|
||||
if (crypto && crypto->IsEncrypted()) {
|
||||
// Try and dispatch 'encrypted'. Won't go if ready state still HAVE_NOTHING.
|
||||
for (uint32_t i = 0; i < crypto->mInitDatas.Length(); i++) {
|
||||
nsCOMPtr<nsIRunnable> r =
|
||||
new DispatchKeyNeededEvent(mDecoder, crypto->mInitDatas[i].mInitData,
|
||||
crypto->mInitDatas[i].mType);
|
||||
mDecoder->AbstractMainThread()->Dispatch(r.forget());
|
||||
mOnEncrypted.Notify(crypto->mInitDatas[i].mInitData,
|
||||
crypto->mInitDatas[i].mType);
|
||||
}
|
||||
mInfo.mCrypto = *crypto;
|
||||
}
|
||||
|
@ -3144,6 +3075,14 @@ MediaFormatReader::SetVideoNullDecode(bool aIsNullDecode)
|
|||
return SetNullDecode(TrackType::kVideoTrack, aIsNullDecode);
|
||||
}
|
||||
|
||||
void
|
||||
MediaFormatReader::UpdateCompositor(
|
||||
already_AddRefed<layers::KnowsCompositor> aCompositor)
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
mKnowsCompositor = aCompositor;
|
||||
}
|
||||
|
||||
void
|
||||
MediaFormatReader::SetNullDecode(TrackType aTrack, bool aIsNullDecode)
|
||||
{
|
||||
|
|
|
@ -29,7 +29,7 @@ class MediaFormatReader final : public MediaDecoderReader
|
|||
typedef MozPromise<bool, MediaResult, /* IsExclusive = */ true> NotifyDataArrivedPromise;
|
||||
|
||||
public:
|
||||
MediaFormatReader(const MediaDecoderReaderInit& aInit, MediaDataDemuxer* aDemuxer);
|
||||
MediaFormatReader(MediaDecoderReaderInit& aInit, MediaDataDemuxer* aDemuxer);
|
||||
|
||||
virtual ~MediaFormatReader();
|
||||
|
||||
|
@ -79,6 +79,8 @@ public:
|
|||
|
||||
void SetVideoNullDecode(bool aIsNullDecode) override;
|
||||
|
||||
void UpdateCompositor(already_AddRefed<layers::KnowsCompositor>) override;
|
||||
|
||||
private:
|
||||
nsresult InitInternal() override;
|
||||
|
||||
|
@ -520,11 +522,6 @@ private:
|
|||
void OnVideoSeekFailed(const MediaResult& aError);
|
||||
bool mSeekScheduled;
|
||||
|
||||
void NotifyCompositorUpdated(RefPtr<layers::KnowsCompositor> aKnowsCompositor)
|
||||
{
|
||||
mKnowsCompositor = aKnowsCompositor.forget();
|
||||
}
|
||||
|
||||
void DoAudioSeek();
|
||||
void OnAudioSeekCompleted(media::TimeUnit aTime);
|
||||
void OnAudioSeekFailed(const MediaResult& aError);
|
||||
|
@ -550,7 +547,6 @@ private:
|
|||
class ShutdownPromisePool;
|
||||
UniquePtr<ShutdownPromisePool> mShutdownPromisePool;
|
||||
|
||||
MediaEventListener mCompositorUpdatedListener;
|
||||
MediaEventListener mOnTrackWaitingForKeyListener;
|
||||
|
||||
void OnFirstDemuxCompleted(TrackInfo::TrackType aType,
|
||||
|
|
|
@ -26,7 +26,7 @@ typedef mozilla::layers::Image Image;
|
|||
typedef mozilla::layers::PlanarYCbCrImage PlanarYCbCrImage;
|
||||
|
||||
AndroidMediaReader::AndroidMediaReader(const MediaContainerType& aContainerType,
|
||||
const MediaDecoderReaderInit& aInit) :
|
||||
MediaDecoderReaderInit& aInit) :
|
||||
MediaDecoderReader(aInit),
|
||||
mType(aContainerType),
|
||||
mPlugin(nullptr),
|
||||
|
|
|
@ -38,7 +38,7 @@ class AndroidMediaReader : public MediaDecoderReader
|
|||
MozPromiseRequestHolder<MediaDecoderReader::VideoDataPromise> mSeekRequest;
|
||||
public:
|
||||
AndroidMediaReader(const MediaContainerType& aContainerType,
|
||||
const MediaDecoderReaderInit& aInit);
|
||||
MediaDecoderReaderInit& aInit);
|
||||
|
||||
nsresult ResetDecode(TrackSet aTracks = TrackSet(TrackInfo::kAudioTrack,
|
||||
TrackInfo::kVideoTrack)) override;
|
||||
|
|
|
@ -27,6 +27,7 @@ MediaDecoderStateMachine*
|
|||
FlacDecoder::CreateStateMachine()
|
||||
{
|
||||
MediaDecoderReaderInit init(this);
|
||||
init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
|
||||
mReader = new MediaFormatReader(init, new FlacDemuxer(mResource));
|
||||
return new MediaDecoderStateMachine(this, mReader);
|
||||
}
|
||||
|
|
|
@ -34,6 +34,8 @@ MediaDecoderStateMachine* MP4Decoder::CreateStateMachine()
|
|||
{
|
||||
MediaDecoderReaderInit init(this);
|
||||
init.mVideoFrameContainer = GetVideoFrameContainer();
|
||||
init.mKnowsCompositor = GetCompositor();
|
||||
init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
|
||||
mReader = new MediaFormatReader(init, new MP4Demuxer(mResource));
|
||||
return new MediaDecoderStateMachine(this, mReader);
|
||||
}
|
||||
|
|
|
@ -30,6 +30,8 @@ HLSDecoder::CreateStateMachine()
|
|||
MOZ_ASSERT(resourceWrapper);
|
||||
MediaDecoderReaderInit init(this);
|
||||
init.mVideoFrameContainer = GetVideoFrameContainer();
|
||||
init.mKnowsCompositor = GetCompositor();
|
||||
init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
|
||||
mReader =
|
||||
new MediaFormatReader(init, new HLSDemuxer(resourceWrapper->GetPlayerId()));
|
||||
|
||||
|
|
|
@ -41,6 +41,8 @@ MediaSourceDecoder::CreateStateMachine()
|
|||
mDemuxer = new MediaSourceDemuxer(AbstractMainThread());
|
||||
MediaDecoderReaderInit init(this);
|
||||
init.mVideoFrameContainer = GetVideoFrameContainer();
|
||||
init.mKnowsCompositor = GetCompositor();
|
||||
init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
|
||||
mReader = new MediaFormatReader(init, mDemuxer);
|
||||
return new MediaDecoderStateMachine(this, mReader);
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ MP3Decoder::Clone(MediaDecoderInit& aInit)
|
|||
MediaDecoderStateMachine*
|
||||
MP3Decoder::CreateStateMachine() {
|
||||
MediaDecoderReaderInit init(this);
|
||||
init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
|
||||
mReader = new MediaFormatReader(init, new MP3Demuxer(mResource));
|
||||
return new MediaDecoderStateMachine(this, mReader);
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@ MediaDecoderStateMachine* OggDecoder::CreateStateMachine()
|
|||
RefPtr<OggDemuxer> demuxer = new OggDemuxer(mResource);
|
||||
MediaDecoderReaderInit init(this);
|
||||
init.mVideoFrameContainer = GetVideoFrameContainer();
|
||||
init.mKnowsCompositor = GetCompositor();
|
||||
init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
|
||||
mReader = new MediaFormatReader(init, demuxer);
|
||||
demuxer->SetChainingEvents(&mReader->TimedMetadataProducer(),
|
||||
&mReader->MediaNotSeekableProducer());
|
||||
|
|
|
@ -23,6 +23,7 @@ MediaDecoderStateMachine*
|
|||
WaveDecoder::CreateStateMachine()
|
||||
{
|
||||
MediaDecoderReaderInit init(this);
|
||||
init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
|
||||
mReader = new MediaFormatReader(init, new WAVDemuxer(mResource));
|
||||
return new MediaDecoderStateMachine(this, mReader);
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@ MediaDecoderStateMachine* WebMDecoder::CreateStateMachine()
|
|||
{
|
||||
MediaDecoderReaderInit init(this);
|
||||
init.mVideoFrameContainer = GetVideoFrameContainer();
|
||||
init.mKnowsCompositor = GetCompositor();
|
||||
init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
|
||||
mReader = new MediaFormatReader(init, new WebMDemuxer(mResource));
|
||||
return new MediaDecoderStateMachine(this, mReader);
|
||||
}
|
||||
|
|
|
@ -461,6 +461,26 @@ private:
|
|||
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
|
||||
};
|
||||
|
||||
/**
|
||||
* A class to disable interrupt callback temporary.
|
||||
*/
|
||||
class MOZ_RAII AutoDisableJSInterruptCallback
|
||||
{
|
||||
public:
|
||||
explicit AutoDisableJSInterruptCallback(JSContext* aCx)
|
||||
: mCx(aCx)
|
||||
, mOld(JS_DisableInterruptCallback(aCx))
|
||||
{ }
|
||||
|
||||
~AutoDisableJSInterruptCallback() {
|
||||
JS_ResetInterruptCallback(mCx, mOld);
|
||||
}
|
||||
|
||||
private:
|
||||
JSContext* mCx;
|
||||
bool mOld;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_dom_ScriptSettings_h
|
||||
|
|
|
@ -79,4 +79,4 @@ to make sure that mozjs_sys also has its Cargo.lock file updated if needed, henc
|
|||
the need to run the cargo update command in js/src as well. Hopefully this will
|
||||
be resolved soon.
|
||||
|
||||
Latest Commit: 519e51986308fc11d6ba6771f1c11ea6a3133921
|
||||
Latest Commit: 479ae6475a18527206a2534c2b8a5bfb8b06bd6e
|
||||
|
|
|
@ -390,8 +390,6 @@ DeviceManagerDx::CreateCompositorDevice(FeatureState& d3d11)
|
|||
mCompositorDevice->SetExceptionMode(0);
|
||||
}
|
||||
|
||||
//#define BREAK_ON_D3D_ERROR
|
||||
|
||||
bool
|
||||
DeviceManagerDx::CreateDevice(IDXGIAdapter* aAdapter,
|
||||
D3D_DRIVER_TYPE aDriverType,
|
||||
|
@ -399,9 +397,9 @@ DeviceManagerDx::CreateDevice(IDXGIAdapter* aAdapter,
|
|||
HRESULT& aResOut,
|
||||
RefPtr<ID3D11Device>& aOutDevice)
|
||||
{
|
||||
#ifdef BREAK_ON_D3D_ERROR
|
||||
aFlags |= D3D11_CREATE_DEVICE_DEBUG;
|
||||
#endif
|
||||
if (gfxPrefs::Direct3D11EnableDebugLayer() || gfxPrefs::Direct3D11BreakOnError()) {
|
||||
aFlags |= D3D11_CREATE_DEVICE_DEBUG;
|
||||
}
|
||||
|
||||
MOZ_SEH_TRY {
|
||||
aResOut = sD3D11CreateDeviceFn(
|
||||
|
@ -413,24 +411,24 @@ DeviceManagerDx::CreateDevice(IDXGIAdapter* aAdapter,
|
|||
return false;
|
||||
}
|
||||
|
||||
#ifdef BREAK_ON_D3D_ERROR
|
||||
do {
|
||||
if (!aOutDevice)
|
||||
break;
|
||||
if (gfxPrefs::Direct3D11BreakOnError()) {
|
||||
do {
|
||||
if (!aOutDevice)
|
||||
break;
|
||||
|
||||
RefPtr<ID3D11Debug> debug;
|
||||
if(!SUCCEEDED( aOutDevice->QueryInterface(__uuidof(ID3D11Debug), getter_AddRefs(debug)) ))
|
||||
break;
|
||||
RefPtr<ID3D11Debug> debug;
|
||||
if (!SUCCEEDED(aOutDevice->QueryInterface(__uuidof(ID3D11Debug), getter_AddRefs(debug))))
|
||||
break;
|
||||
|
||||
RefPtr<ID3D11InfoQueue> infoQueue;
|
||||
if(!SUCCEEDED( debug->QueryInterface(__uuidof(ID3D11InfoQueue), getter_AddRefs(infoQueue)) ))
|
||||
break;
|
||||
RefPtr<ID3D11InfoQueue> infoQueue;
|
||||
if (!SUCCEEDED(debug->QueryInterface(__uuidof(ID3D11InfoQueue), getter_AddRefs(infoQueue))))
|
||||
break;
|
||||
|
||||
infoQueue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_CORRUPTION, true);
|
||||
infoQueue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_ERROR, true);
|
||||
infoQueue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_WARNING, true);
|
||||
} while (false);
|
||||
#endif
|
||||
infoQueue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_CORRUPTION, true);
|
||||
infoQueue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_ERROR, true);
|
||||
infoQueue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_WARNING, true);
|
||||
} while (false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -437,6 +437,8 @@ private:
|
|||
DECL_GFX_PREF(Live, "gfx.direct3d11.reuse-decoder-device", Direct3D11ReuseDecoderDevice, int32_t, -1);
|
||||
DECL_GFX_PREF(Live, "gfx.direct3d11.allow-intel-mutex", Direct3D11AllowIntelMutex, bool, true);
|
||||
DECL_GFX_PREF(Live, "gfx.direct3d11.use-double-buffering", Direct3D11UseDoubleBuffering, bool, false);
|
||||
DECL_GFX_PREF(Once, "gfx.direct3d11.enable-debug-layer", Direct3D11EnableDebugLayer, bool, false);
|
||||
DECL_GFX_PREF(Once, "gfx.direct3d11.break-on-error", Direct3D11BreakOnError, bool, false);
|
||||
DECL_GFX_PREF(Live, "gfx.downloadable_fonts.keep_variation_tables", KeepVariationTables, bool, false);
|
||||
DECL_GFX_PREF(Live, "gfx.downloadable_fonts.otl_validation", ValidateOTLTables, bool, true);
|
||||
DECL_GFX_PREF(Live, "gfx.draw-color-bars", CompositorDrawColorBars, bool, false);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "webrender"
|
||||
version = "0.47.0"
|
||||
version = "0.48.0"
|
||||
authors = ["Glenn Watson <gw@intuitionlibrary.com>"]
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/servo/webrender"
|
||||
|
|
|
@ -50,5 +50,9 @@ fn main() {
|
|||
}
|
||||
}
|
||||
|
||||
// Sort the file list so that the shaders.rs file is filled
|
||||
// deterministically.
|
||||
glsl_files.sort_by(|a, b| a.file_name().cmp(&b.file_name()));
|
||||
|
||||
write_shaders(glsl_files, &shaders_file);
|
||||
}
|
||||
|
|
|
@ -290,7 +290,6 @@ fn body(api: &RenderApi,
|
|||
font_key,
|
||||
ColorF::new(1.0, 1.0, 0.0, 1.0),
|
||||
Au::from_px(32),
|
||||
0.0,
|
||||
None);
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,15 @@ float gauss(float x, float sigma) {
|
|||
|
||||
void main(void) {
|
||||
vec4 cache_sample = texture(sCacheRGBA8, vUv);
|
||||
|
||||
// TODO(gw): The gauss function gets NaNs when blur radius
|
||||
// is zero. In the future, detect this earlier
|
||||
// and skip the blur passes completely.
|
||||
if (vBlurRadius == 0) {
|
||||
oFragColor = cache_sample;
|
||||
return;
|
||||
}
|
||||
|
||||
vec4 color = vec4(cache_sample.rgb, 1.0) * (cache_sample.a * gauss(0.0, vSigma));
|
||||
|
||||
for (int i=1 ; i < vBlurRadius ; ++i) {
|
||||
|
|
|
@ -21,7 +21,7 @@ void main(void) {
|
|||
// the glyph offset, relative to its primitive bounding rect.
|
||||
vec2 size = res.uv_rect.zw - res.uv_rect.xy;
|
||||
vec2 local_pos = glyph.offset + vec2(res.offset.x, -res.offset.y) / uDevicePixelRatio;
|
||||
vec2 origin = prim.task.screen_space_origin + uDevicePixelRatio * (local_pos - prim.local_rect.p0);
|
||||
vec2 origin = prim.task.render_target_origin + uDevicePixelRatio * (local_pos - prim.local_rect.p0);
|
||||
vec4 local_rect = vec4(origin, size);
|
||||
|
||||
vec2 texture_size = vec2(textureSize(sColor0, 0));
|
||||
|
|
|
@ -719,12 +719,8 @@ TransformVertexInfo write_transform_vertex(RectWithSize instance_rect,
|
|||
|
||||
vec4 layer_pos = get_layer_pos(device_pos / uDevicePixelRatio, layer);
|
||||
|
||||
/// Compute the snapping offset.
|
||||
vec2 snap_offset = compute_snap_offset(layer_pos.xy / layer_pos.w,
|
||||
local_clip_rect, layer, snap_rect);
|
||||
|
||||
// Apply offsets for the render task to get correct screen location.
|
||||
vec2 final_pos = device_pos + snap_offset -
|
||||
vec2 final_pos = device_pos - //Note: `snap_rect` is not used
|
||||
task.screen_space_origin +
|
||||
task.render_target_origin;
|
||||
|
||||
|
|
|
@ -357,6 +357,7 @@ struct Texture {
|
|||
filter: TextureFilter,
|
||||
mode: RenderTargetMode,
|
||||
fbo_ids: Vec<FBOId>,
|
||||
depth_rb: Option<RBOId>,
|
||||
}
|
||||
|
||||
impl Drop for Texture {
|
||||
|
@ -480,6 +481,9 @@ pub struct VAOId(gl::GLuint);
|
|||
#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
|
||||
pub struct FBOId(gl::GLuint);
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
|
||||
pub struct RBOId(gl::GLuint);
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
|
||||
pub struct VBOId(gl::GLuint);
|
||||
|
||||
|
@ -1104,6 +1108,7 @@ impl Device {
|
|||
filter: TextureFilter::Nearest,
|
||||
mode: RenderTargetMode::None,
|
||||
fbo_ids: vec![],
|
||||
depth_rb: None,
|
||||
};
|
||||
|
||||
debug_assert!(self.textures.contains_key(&texture_id) == false);
|
||||
|
@ -1229,7 +1234,8 @@ impl Device {
|
|||
|
||||
match layer_count {
|
||||
Some(layer_count) => {
|
||||
debug_assert!(layer_count > 0);
|
||||
assert!(layer_count > 0);
|
||||
assert_eq!(texture_id.target, gl::TEXTURE_2D_ARRAY);
|
||||
|
||||
// If we have enough layers allocated already, just use them.
|
||||
// TODO(gw): Probably worth removing some after a while if
|
||||
|
@ -1243,58 +1249,63 @@ impl Device {
|
|||
let type_ = gl_type_for_texture_format(texture.format);
|
||||
|
||||
self.gl.tex_image_3d(texture_id.target,
|
||||
0,
|
||||
internal_format as gl::GLint,
|
||||
texture.width as gl::GLint,
|
||||
texture.height as gl::GLint,
|
||||
layer_count,
|
||||
0,
|
||||
gl_format,
|
||||
type_,
|
||||
None);
|
||||
0,
|
||||
internal_format as gl::GLint,
|
||||
texture.width as gl::GLint,
|
||||
texture.height as gl::GLint,
|
||||
layer_count,
|
||||
0,
|
||||
gl_format,
|
||||
type_,
|
||||
None);
|
||||
|
||||
let needed_layer_count = layer_count - current_layer_count;
|
||||
let new_fbos = self.gl.gen_framebuffers(needed_layer_count);
|
||||
texture.fbo_ids.extend(new_fbos.into_iter().map(|id| FBOId(id)));
|
||||
|
||||
for (fbo_index, fbo_id) in texture.fbo_ids.iter().enumerate() {
|
||||
self.gl.bind_framebuffer(gl::FRAMEBUFFER, fbo_id.0);
|
||||
self.gl.framebuffer_texture_layer(gl::FRAMEBUFFER,
|
||||
gl::COLOR_ATTACHMENT0,
|
||||
texture_id.name,
|
||||
0,
|
||||
fbo_index as gl::GLint);
|
||||
|
||||
// TODO(gw): Share depth render buffer between FBOs to
|
||||
// save memory!
|
||||
// TODO(gw): Free these renderbuffers on exit!
|
||||
let depth_rb = if let Some(rbo) = texture.depth_rb {
|
||||
rbo.0
|
||||
} else {
|
||||
let renderbuffer_ids = self.gl.gen_renderbuffers(1);
|
||||
let depth_rb = renderbuffer_ids[0];
|
||||
self.gl.bind_renderbuffer(gl::RENDERBUFFER, depth_rb);
|
||||
self.gl.renderbuffer_storage(gl::RENDERBUFFER,
|
||||
gl::DEPTH_COMPONENT24,
|
||||
texture.width as gl::GLsizei,
|
||||
texture.height as gl::GLsizei);
|
||||
gl::DEPTH_COMPONENT24,
|
||||
texture.width as gl::GLsizei,
|
||||
texture.height as gl::GLsizei);
|
||||
texture.depth_rb = Some(RBOId(depth_rb));
|
||||
depth_rb
|
||||
};
|
||||
|
||||
for (fbo_index, fbo_id) in texture.fbo_ids.iter().enumerate() {
|
||||
self.gl.bind_framebuffer(gl::FRAMEBUFFER, fbo_id.0);
|
||||
self.gl.framebuffer_texture_layer(gl::FRAMEBUFFER,
|
||||
gl::COLOR_ATTACHMENT0,
|
||||
texture_id.name,
|
||||
0,
|
||||
fbo_index as gl::GLint);
|
||||
self.gl.framebuffer_renderbuffer(gl::FRAMEBUFFER,
|
||||
gl::DEPTH_ATTACHMENT,
|
||||
gl::RENDERBUFFER,
|
||||
depth_rb);
|
||||
gl::DEPTH_ATTACHMENT,
|
||||
gl::RENDERBUFFER,
|
||||
depth_rb);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
debug_assert!(texture.fbo_ids.len() == 0 || texture.fbo_ids.len() == 1);
|
||||
if texture.fbo_ids.is_empty() {
|
||||
let new_fbo = self.gl.gen_framebuffers(1)[0];
|
||||
assert!(texture_id.target != gl::TEXTURE_2D_ARRAY);
|
||||
|
||||
let new_fbo = self.gl.gen_framebuffers(1)[0];
|
||||
self.gl.bind_framebuffer(gl::FRAMEBUFFER, new_fbo);
|
||||
|
||||
self.gl.framebuffer_texture_2d(gl::FRAMEBUFFER,
|
||||
gl::COLOR_ATTACHMENT0,
|
||||
texture_id.target,
|
||||
texture_id.name,
|
||||
0);
|
||||
gl::COLOR_ATTACHMENT0,
|
||||
texture_id.target,
|
||||
texture_id.name,
|
||||
0);
|
||||
|
||||
texture.fbo_ids.push(FBOId(new_fbo));
|
||||
} else {
|
||||
assert_eq!(texture.fbo_ids.len(), 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1396,15 +1407,18 @@ impl Device {
|
|||
type_,
|
||||
None);
|
||||
|
||||
if let Some(RBOId(depth_rb)) = texture.depth_rb.take() {
|
||||
self.gl.delete_renderbuffers(&[depth_rb]);
|
||||
}
|
||||
|
||||
if !texture.fbo_ids.is_empty() {
|
||||
let fbo_ids: Vec<_> = texture.fbo_ids.iter().map(|&FBOId(fbo_id)| fbo_id).collect();
|
||||
let fbo_ids: Vec<_> = texture.fbo_ids.drain(..).map(|FBOId(fbo_id)| fbo_id).collect();
|
||||
self.gl.delete_framebuffers(&fbo_ids[..]);
|
||||
}
|
||||
|
||||
texture.format = ImageFormat::Invalid;
|
||||
texture.width = 0;
|
||||
texture.height = 0;
|
||||
texture.fbo_ids.clear();
|
||||
}
|
||||
|
||||
pub fn create_program(&mut self,
|
||||
|
|
|
@ -66,6 +66,10 @@ impl NestedDisplayListInfo {
|
|||
}
|
||||
|
||||
fn convert_scroll_id_to_nested(&self, id: &ClipId) -> ClipId {
|
||||
if id.pipeline_id() != self.scroll_node_id.pipeline_id() {
|
||||
return *id;
|
||||
}
|
||||
|
||||
if id.is_root_scroll_node() {
|
||||
self.scroll_node_id
|
||||
} else {
|
||||
|
@ -74,6 +78,10 @@ impl NestedDisplayListInfo {
|
|||
}
|
||||
|
||||
fn convert_clip_id_to_nested(&self, id: &ClipId) -> ClipId {
|
||||
if id.pipeline_id() != self.clip_node_id.pipeline_id() {
|
||||
return *id;
|
||||
}
|
||||
|
||||
if id.is_root_scroll_node() {
|
||||
self.clip_node_id
|
||||
} else {
|
||||
|
@ -119,7 +127,7 @@ impl<'a> FlattenContext<'a> {
|
|||
self.nested_display_list_info.pop();
|
||||
}
|
||||
|
||||
fn convert_new_id_to_neested(&self, id: &ClipId) -> ClipId {
|
||||
fn convert_new_id_to_nested(&self, id: &ClipId) -> ClipId {
|
||||
if let Some(nested_info) = self.nested_display_list_info.last() {
|
||||
nested_info.convert_id_to_nested(id)
|
||||
} else {
|
||||
|
@ -358,7 +366,7 @@ impl Frame {
|
|||
parent_id: &ClipId,
|
||||
new_clip_id: &ClipId,
|
||||
clip_region: ClipRegion) {
|
||||
let new_clip_id = context.convert_new_id_to_neested(new_clip_id);
|
||||
let new_clip_id = context.convert_new_id_to_nested(new_clip_id);
|
||||
context.builder.add_clip_node(new_clip_id,
|
||||
*parent_id,
|
||||
pipeline_id,
|
||||
|
@ -381,7 +389,7 @@ impl Frame {
|
|||
clip_region,
|
||||
&mut self.clip_scroll_tree);
|
||||
|
||||
let new_scroll_frame_id = context.convert_new_id_to_neested(new_scroll_frame_id);
|
||||
let new_scroll_frame_id = context.convert_new_id_to_nested(new_scroll_frame_id);
|
||||
context.builder.add_scroll_frame(new_scroll_frame_id,
|
||||
clip_id,
|
||||
pipeline_id,
|
||||
|
@ -483,6 +491,7 @@ impl Frame {
|
|||
pipeline_id: PipelineId,
|
||||
parent_id: ClipId,
|
||||
bounds: &LayerRect,
|
||||
local_clip: &LocalClip,
|
||||
context: &mut FlattenContext,
|
||||
reference_frame_relative_offset: LayerVector2D) {
|
||||
let pipeline = match context.scene.pipeline_map.get(&pipeline_id) {
|
||||
|
@ -495,6 +504,16 @@ impl Frame {
|
|||
None => return,
|
||||
};
|
||||
|
||||
let mut clip_region = ClipRegion::create_for_clip_node_with_local_clip(local_clip);
|
||||
clip_region.origin += reference_frame_relative_offset;
|
||||
let parent_pipeline_id = parent_id.pipeline_id();
|
||||
let clip_id = self.clip_scroll_tree.generate_new_clip_id(parent_pipeline_id);
|
||||
context.builder.add_clip_node(clip_id,
|
||||
parent_id,
|
||||
parent_pipeline_id,
|
||||
clip_region,
|
||||
&mut self.clip_scroll_tree);
|
||||
|
||||
self.pipeline_epoch_map.insert(pipeline_id, pipeline.epoch);
|
||||
|
||||
let iframe_rect = LayerRect::new(LayerPoint::zero(), bounds.size);
|
||||
|
@ -504,7 +523,7 @@ impl Frame {
|
|||
0.0);
|
||||
|
||||
let iframe_reference_frame_id =
|
||||
context.builder.push_reference_frame(Some(parent_id),
|
||||
context.builder.push_reference_frame(Some(clip_id),
|
||||
pipeline_id,
|
||||
&iframe_rect,
|
||||
&transform,
|
||||
|
@ -583,7 +602,6 @@ impl Frame {
|
|||
item.local_clip(),
|
||||
text_info.font_key,
|
||||
text_info.size,
|
||||
text_info.blur_radius,
|
||||
&text_info.color,
|
||||
item.glyphs(),
|
||||
item.display_list().get(item.glyphs()).count(),
|
||||
|
@ -666,6 +684,7 @@ impl Frame {
|
|||
self.flatten_iframe(info.pipeline_id,
|
||||
clip_and_scroll.scroll_node_id,
|
||||
&item.rect(),
|
||||
&item.local_clip(),
|
||||
context,
|
||||
reference_frame_relative_offset);
|
||||
}
|
||||
|
@ -718,6 +737,14 @@ impl Frame {
|
|||
|
||||
SpecificDisplayItem::PopStackingContext =>
|
||||
unreachable!("Should have returned in parent method."),
|
||||
SpecificDisplayItem::PushTextShadow(shadow) => {
|
||||
context.builder.push_text_shadow(shadow,
|
||||
clip_and_scroll,
|
||||
item.local_clip());
|
||||
}
|
||||
SpecificDisplayItem::PopTextShadow => {
|
||||
context.builder.pop_text_shadow();
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
|
|
@ -6,8 +6,8 @@ use api::{BorderDetails, BorderDisplayItem, BoxShadowClipMode, ClipAndScrollInfo
|
|||
use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceUintRect, DeviceUintSize};
|
||||
use api::{ExtendMode, FontKey, FontRenderMode, GlyphInstance, GlyphOptions, GradientStop};
|
||||
use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect, LayerSize};
|
||||
use api::{LayerToScrollTransform, LayerVector2D, LocalClip, PipelineId, RepeatMode, TileOffset};
|
||||
use api::{TransformStyle, WebGLContextId, WorldPixel, YuvColorSpace, YuvData};
|
||||
use api::{LayerToScrollTransform, LayerVector2D, LocalClip, PipelineId, RepeatMode, TextShadow};
|
||||
use api::{TileOffset, TransformStyle, WebGLContextId, WorldPixel, YuvColorSpace, YuvData};
|
||||
use app_units::Au;
|
||||
use frame::FrameId;
|
||||
use gpu_cache::GpuCache;
|
||||
|
@ -17,7 +17,7 @@ use plane_split::{BspSplitter, Polygon, Splitter};
|
|||
use prim_store::{GradientPrimitiveCpu, ImagePrimitiveCpu};
|
||||
use prim_store::{ImagePrimitiveKind, PrimitiveContainer, PrimitiveIndex};
|
||||
use prim_store::{PrimitiveStore, RadialGradientPrimitiveCpu};
|
||||
use prim_store::{RectanglePrimitive, TextRunPrimitiveCpu};
|
||||
use prim_store::{RectanglePrimitive, TextRunPrimitiveCpu, TextShadowPrimitiveCpu};
|
||||
use prim_store::{BoxShadowPrimitiveCpu, TexelRect, YuvImagePrimitiveCpu};
|
||||
use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
|
||||
use render_task::{AlphaRenderItem, ClipWorkItem, MaskCacheKey, RenderTask, RenderTaskIndex};
|
||||
|
@ -106,6 +106,37 @@ pub struct FrameBuilderConfig {
|
|||
pub cache_expiry_frames: u32,
|
||||
}
|
||||
|
||||
struct PendingTextShadow {
|
||||
shadow: TextShadow,
|
||||
text_primitives: Vec<TextRunPrimitiveCpu>,
|
||||
clip_and_scroll: ClipAndScrollInfo,
|
||||
local_rect: LayerRect,
|
||||
local_clip: LocalClip,
|
||||
}
|
||||
|
||||
impl PendingTextShadow {
|
||||
fn new(shadow: TextShadow,
|
||||
clip_and_scroll: ClipAndScrollInfo,
|
||||
local_clip: &LocalClip) -> PendingTextShadow {
|
||||
PendingTextShadow {
|
||||
shadow: shadow,
|
||||
text_primitives: Vec::new(),
|
||||
clip_and_scroll: clip_and_scroll,
|
||||
local_clip: local_clip.clone(),
|
||||
local_rect: LayerRect::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
fn push(&mut self,
|
||||
local_rect: LayerRect,
|
||||
primitive: &TextRunPrimitiveCpu) {
|
||||
self.text_primitives.push(primitive.clone());
|
||||
let shadow_rect = local_rect.inflate(self.shadow.blur_radius,
|
||||
self.shadow.blur_radius);
|
||||
self.local_rect = self.local_rect.union(&shadow_rect);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FrameBuilder {
|
||||
screen_size: DeviceUintSize,
|
||||
background_color: Option<ColorF>,
|
||||
|
@ -116,6 +147,7 @@ pub struct FrameBuilder {
|
|||
stacking_context_store: Vec<StackingContext>,
|
||||
clip_scroll_group_store: Vec<ClipScrollGroup>,
|
||||
packed_layers: Vec<PackedLayer>,
|
||||
pending_text_shadows: Vec<PendingTextShadow>,
|
||||
|
||||
scrollbar_prims: Vec<ScrollbarPrimitive>,
|
||||
|
||||
|
@ -144,6 +176,7 @@ impl FrameBuilder {
|
|||
clip_scroll_group_store: recycle_vec(prev.clip_scroll_group_store),
|
||||
cmds: recycle_vec(prev.cmds),
|
||||
packed_layers: recycle_vec(prev.packed_layers),
|
||||
pending_text_shadows: recycle_vec(prev.pending_text_shadows),
|
||||
scrollbar_prims: recycle_vec(prev.scrollbar_prims),
|
||||
reference_frame_stack: recycle_vec(prev.reference_frame_stack),
|
||||
stacking_context_stack: recycle_vec(prev.stacking_context_stack),
|
||||
|
@ -160,6 +193,7 @@ impl FrameBuilder {
|
|||
clip_scroll_group_store: Vec::new(),
|
||||
cmds: Vec::new(),
|
||||
packed_layers: Vec::new(),
|
||||
pending_text_shadows: Vec::new(),
|
||||
scrollbar_prims: Vec::new(),
|
||||
reference_frame_stack: Vec::new(),
|
||||
stacking_context_stack: Vec::new(),
|
||||
|
@ -287,6 +321,8 @@ impl FrameBuilder {
|
|||
pub fn pop_stacking_context(&mut self) {
|
||||
self.cmds.push(PrimitiveRunCmd::PopStackingContext);
|
||||
self.stacking_context_stack.pop();
|
||||
assert!(self.pending_text_shadows.is_empty(),
|
||||
"Found unpopped text shadows when popping stacking context!");
|
||||
}
|
||||
|
||||
pub fn push_reference_frame(&mut self,
|
||||
|
@ -392,6 +428,37 @@ impl FrameBuilder {
|
|||
self.reference_frame_stack.pop();
|
||||
}
|
||||
|
||||
pub fn push_text_shadow(&mut self,
|
||||
shadow: TextShadow,
|
||||
clip_and_scroll: ClipAndScrollInfo,
|
||||
local_clip: &LocalClip) {
|
||||
let text_shadow = PendingTextShadow::new(shadow,
|
||||
clip_and_scroll,
|
||||
local_clip);
|
||||
self.pending_text_shadows.push(text_shadow);
|
||||
}
|
||||
|
||||
pub fn pop_text_shadow(&mut self) {
|
||||
let mut text_shadow = self.pending_text_shadows
|
||||
.pop()
|
||||
.expect("Too many PopTextShadows?");
|
||||
if !text_shadow.text_primitives.is_empty() {
|
||||
let prim_cpu = TextShadowPrimitiveCpu {
|
||||
text_primitives: text_shadow.text_primitives,
|
||||
shadow: text_shadow.shadow,
|
||||
};
|
||||
|
||||
text_shadow.local_rect = text_shadow.local_rect
|
||||
.translate(&text_shadow.shadow.offset);
|
||||
|
||||
self.add_primitive(text_shadow.clip_and_scroll,
|
||||
&text_shadow.local_rect,
|
||||
&text_shadow.local_clip,
|
||||
&[],
|
||||
PrimitiveContainer::TextShadow(prim_cpu));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_solid_rectangle(&mut self,
|
||||
clip_and_scroll: ClipAndScrollInfo,
|
||||
rect: &LayerRect,
|
||||
|
@ -732,12 +799,13 @@ impl FrameBuilder {
|
|||
local_clip: &LocalClip,
|
||||
font_key: FontKey,
|
||||
size: Au,
|
||||
blur_radius: f32,
|
||||
color: &ColorF,
|
||||
glyph_range: ItemRange<GlyphInstance>,
|
||||
glyph_count: usize,
|
||||
glyph_options: Option<GlyphOptions>) {
|
||||
if color.a == 0.0 {
|
||||
let is_text_shadow = !self.pending_text_shadows.is_empty();
|
||||
|
||||
if color.a == 0.0 && !is_text_shadow {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -745,9 +813,6 @@ impl FrameBuilder {
|
|||
return
|
||||
}
|
||||
|
||||
// Expand the rectangle of the text run by the blur radius.
|
||||
let rect = rect.inflate(blur_radius, blur_radius);
|
||||
|
||||
// TODO(gw): Use a proper algorithm to select
|
||||
// whether this item should be rendered with
|
||||
// subpixel AA!
|
||||
|
@ -757,7 +822,7 @@ impl FrameBuilder {
|
|||
// subpixel text rendering, even if enabled.
|
||||
if render_mode == FontRenderMode::Subpixel {
|
||||
// text-blur shadow needs to force alpha AA.
|
||||
if blur_radius != 0.0 {
|
||||
if is_text_shadow {
|
||||
render_mode = FontRenderMode::Alpha;
|
||||
}
|
||||
|
||||
|
@ -781,7 +846,6 @@ impl FrameBuilder {
|
|||
let prim_cpu = TextRunPrimitiveCpu {
|
||||
font_key,
|
||||
logical_font_size: size,
|
||||
blur_radius,
|
||||
glyph_range,
|
||||
glyph_count,
|
||||
glyph_instances: Vec::new(),
|
||||
|
@ -790,11 +854,17 @@ impl FrameBuilder {
|
|||
glyph_options,
|
||||
};
|
||||
|
||||
self.add_primitive(clip_and_scroll,
|
||||
&rect,
|
||||
local_clip,
|
||||
&[],
|
||||
PrimitiveContainer::TextRun(prim_cpu));
|
||||
if is_text_shadow {
|
||||
for shadow in &mut self.pending_text_shadows {
|
||||
shadow.push(rect, &prim_cpu);
|
||||
}
|
||||
} else {
|
||||
self.add_primitive(clip_and_scroll,
|
||||
&rect,
|
||||
local_clip,
|
||||
&[],
|
||||
PrimitiveContainer::TextRun(prim_cpu));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fill_box_shadow_rect(&mut self,
|
||||
|
|
|
@ -261,6 +261,7 @@ impl GlyphRasterizer {
|
|||
for job in rasterized_glyphs {
|
||||
let image_id = job.result.and_then(
|
||||
|glyph| if glyph.width > 0 && glyph.height > 0 {
|
||||
assert_eq!((glyph.left.fract(), glyph.top.fract()), (0.0, 0.0));
|
||||
let image_id = texture_cache.insert(
|
||||
ImageDescriptor {
|
||||
width: glyph.width,
|
||||
|
@ -334,7 +335,7 @@ impl GlyphRequest {
|
|||
point: LayoutPoint,
|
||||
render_mode: FontRenderMode,
|
||||
glyph_options: Option<GlyphOptions>,
|
||||
) -> GlyphRequest {
|
||||
) -> Self {
|
||||
GlyphRequest {
|
||||
key: GlyphKey::new(font_key, size, color, index, point, render_mode),
|
||||
render_mode,
|
||||
|
|
|
@ -203,6 +203,7 @@ pub enum RenderTargetMode {
|
|||
LayerRenderTarget(i32), // Number of texture layers
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TextureUpdateOp {
|
||||
Create {
|
||||
width: u32,
|
||||
|
@ -238,6 +239,7 @@ pub enum TextureUpdateOp {
|
|||
Free,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TextureUpdate {
|
||||
pub id: CacheTextureId,
|
||||
pub op: TextureUpdateOp,
|
||||
|
|
|
@ -44,6 +44,14 @@ impl ClipRegion {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn create_for_clip_node_with_local_clip(local_clip: &LocalClip) -> ClipRegion {
|
||||
let complex_clips = match local_clip {
|
||||
&LocalClip::Rect(_) => Vec::new(),
|
||||
&LocalClip::RoundedRect(_, ref region) => vec![region.clone()],
|
||||
};
|
||||
ClipRegion::for_clip_node(*local_clip.clip_rect(), complex_clips, None)
|
||||
}
|
||||
|
||||
pub fn for_local_clip(local_clip: &LocalClip) -> ClipRegion {
|
||||
let complex_clips = match local_clip {
|
||||
&LocalClip::Rect(_) => Vec::new(),
|
||||
|
|
|
@ -13,11 +13,22 @@ use freetype::freetype::{FT_Library, FT_Set_Char_Size};
|
|||
use freetype::freetype::{FT_Face, FT_Long, FT_UInt, FT_F26Dot6};
|
||||
use freetype::freetype::{FT_Init_FreeType, FT_Load_Glyph, FT_Render_Glyph};
|
||||
use freetype::freetype::{FT_New_Memory_Face, FT_GlyphSlot, FT_LcdFilter};
|
||||
use freetype::freetype::{FT_Done_Face, FT_Error};
|
||||
use freetype::freetype::{FT_Done_Face, FT_Error, FT_Int32};
|
||||
|
||||
use std::{cmp, mem, ptr, slice};
|
||||
use std::collections::HashMap;
|
||||
|
||||
// This constant is not present in the freetype
|
||||
// bindings due to bindgen not handling the way
|
||||
// the macro is defined.
|
||||
const FT_LOAD_TARGET_LIGHT: FT_Int32 = 1 << 16;
|
||||
|
||||
// Default to slight hinting, which is what most
|
||||
// Linux distros use by default, and is a better
|
||||
// default than no hinting.
|
||||
// TODO(gw): Make this configurable.
|
||||
const GLYPH_LOAD_FLAGS: FT_Int32 = FT_LOAD_TARGET_LIGHT;
|
||||
|
||||
struct Face {
|
||||
face: FT_Face,
|
||||
}
|
||||
|
@ -40,14 +51,6 @@ pub struct RasterizedGlyph {
|
|||
pub bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
fn float_to_fixed(before: usize, f: f64) -> i32 {
|
||||
((1i32 << before) as f64 * f) as i32
|
||||
}
|
||||
|
||||
fn float_to_fixed_ft(f: f64) -> i32 {
|
||||
float_to_fixed(6, f)
|
||||
}
|
||||
|
||||
const SUCCESS: FT_Error = FT_Error(0);
|
||||
|
||||
impl FontContext {
|
||||
|
@ -115,14 +118,16 @@ impl FontContext {
|
|||
|
||||
debug_assert!(self.faces.contains_key(&font_key));
|
||||
let face = self.faces.get(&font_key).unwrap();
|
||||
let char_size = float_to_fixed_ft(size.to_f64_px());
|
||||
let char_size = size.to_f64_px() * 64.0 + 0.5;
|
||||
|
||||
assert_eq!(SUCCESS, unsafe {
|
||||
FT_Set_Char_Size(face.face, char_size as FT_F26Dot6, 0, 0, 0)
|
||||
});
|
||||
|
||||
let result = unsafe {
|
||||
FT_Load_Glyph(face.face, character as FT_UInt, 0)
|
||||
FT_Load_Glyph(face.face,
|
||||
character as FT_UInt,
|
||||
GLYPH_LOAD_FLAGS)
|
||||
};
|
||||
|
||||
if result == SUCCESS {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
use api::{BuiltDisplayList, ColorF, ComplexClipRegion, DeviceIntRect, DeviceIntSize, DevicePoint};
|
||||
use api::{ExtendMode, FontKey, FontRenderMode, GlyphInstance, GlyphOptions, GradientStop};
|
||||
use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect, LayerSize};
|
||||
use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect, LayerSize, TextShadow};
|
||||
use api::{LayerToWorldTransform, TileOffset, WebGLContextId, YuvColorSpace, YuvFormat};
|
||||
use api::device_length;
|
||||
use app_units::Au;
|
||||
|
@ -115,6 +115,7 @@ pub enum PrimitiveKind {
|
|||
AngleGradient,
|
||||
RadialGradient,
|
||||
BoxShadow,
|
||||
TextShadow,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
|
@ -479,11 +480,16 @@ impl RadialGradientPrimitiveCpu {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TextShadowPrimitiveCpu {
|
||||
pub text_primitives: Vec<TextRunPrimitiveCpu>,
|
||||
pub shadow: TextShadow,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TextRunPrimitiveCpu {
|
||||
pub font_key: FontKey,
|
||||
pub logical_font_size: Au,
|
||||
pub blur_radius: f32,
|
||||
pub glyph_range: ItemRange<GlyphInstance>,
|
||||
pub glyph_count: usize,
|
||||
// TODO(gw): Maybe make this an Arc for sharing with resource cache
|
||||
|
@ -493,10 +499,36 @@ pub struct TextRunPrimitiveCpu {
|
|||
pub glyph_options: Option<GlyphOptions>,
|
||||
}
|
||||
|
||||
impl ToGpuBlocks for TextRunPrimitiveCpu {
|
||||
fn write_gpu_blocks(&self, mut request: GpuDataRequest) {
|
||||
request.push(self.color);
|
||||
impl TextRunPrimitiveCpu {
|
||||
fn prepare_for_render(&mut self,
|
||||
resource_cache: &mut ResourceCache,
|
||||
device_pixel_ratio: f32,
|
||||
display_list: &BuiltDisplayList) {
|
||||
// Cache the glyph positions, if not in the cache already.
|
||||
// TODO(gw): In the future, remove `glyph_instances`
|
||||
// completely, and just reference the glyphs
|
||||
// directly from the displaty list.
|
||||
if self.glyph_instances.is_empty() {
|
||||
let src_glyphs = display_list.get(self.glyph_range);
|
||||
for src in src_glyphs {
|
||||
self.glyph_instances.push(GlyphInstance {
|
||||
index: src.index,
|
||||
point: src.point,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let font_size_dp = self.logical_font_size.scale_by(device_pixel_ratio);
|
||||
|
||||
resource_cache.request_glyphs(self.font_key,
|
||||
font_size_dp,
|
||||
self.color,
|
||||
&self.glyph_instances,
|
||||
self.render_mode,
|
||||
self.glyph_options);
|
||||
}
|
||||
|
||||
fn write_gpu_blocks(&self, request: &mut GpuDataRequest) {
|
||||
// Two glyphs are packed per GPU block.
|
||||
for glyph_chunk in self.glyph_instances.chunks(2) {
|
||||
// In the case of an odd number of glyphs, the
|
||||
|
@ -504,10 +536,24 @@ impl ToGpuBlocks for TextRunPrimitiveCpu {
|
|||
// GPU block.
|
||||
let first_glyph = glyph_chunk.first().unwrap();
|
||||
let second_glyph = glyph_chunk.last().unwrap();
|
||||
request.push([first_glyph.point.x,
|
||||
first_glyph.point.y,
|
||||
second_glyph.point.x,
|
||||
second_glyph.point.y]);
|
||||
let data = match self.render_mode {
|
||||
FontRenderMode::Mono |
|
||||
FontRenderMode::Alpha => [
|
||||
first_glyph.point.x,
|
||||
first_glyph.point.y,
|
||||
second_glyph.point.x,
|
||||
second_glyph.point.y,
|
||||
],
|
||||
// The sub-pixel offset has already been taken into account
|
||||
// by the glyph rasterizer, thus the truncating here.
|
||||
FontRenderMode::Subpixel => [
|
||||
first_glyph.point.x.trunc(),
|
||||
first_glyph.point.y.trunc(),
|
||||
second_glyph.point.x.trunc(),
|
||||
second_glyph.point.y.trunc(),
|
||||
],
|
||||
};
|
||||
request.push(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -679,6 +725,7 @@ pub enum PrimitiveContainer {
|
|||
AngleGradient(GradientPrimitiveCpu),
|
||||
RadialGradient(RadialGradientPrimitiveCpu),
|
||||
BoxShadow(BoxShadowPrimitiveCpu),
|
||||
TextShadow(TextShadowPrimitiveCpu),
|
||||
}
|
||||
|
||||
pub struct PrimitiveStore {
|
||||
|
@ -686,6 +733,7 @@ pub struct PrimitiveStore {
|
|||
pub cpu_bounding_rects: Vec<Option<DeviceIntRect>>,
|
||||
pub cpu_rectangles: Vec<RectanglePrimitive>,
|
||||
pub cpu_text_runs: Vec<TextRunPrimitiveCpu>,
|
||||
pub cpu_text_shadows: Vec<TextShadowPrimitiveCpu>,
|
||||
pub cpu_images: Vec<ImagePrimitiveCpu>,
|
||||
pub cpu_yuv_images: Vec<YuvImagePrimitiveCpu>,
|
||||
pub cpu_gradients: Vec<GradientPrimitiveCpu>,
|
||||
|
@ -702,6 +750,7 @@ impl PrimitiveStore {
|
|||
cpu_rectangles: Vec::new(),
|
||||
cpu_bounding_rects: Vec::new(),
|
||||
cpu_text_runs: Vec::new(),
|
||||
cpu_text_shadows: Vec::new(),
|
||||
cpu_images: Vec::new(),
|
||||
cpu_yuv_images: Vec::new(),
|
||||
cpu_gradients: Vec::new(),
|
||||
|
@ -717,6 +766,7 @@ impl PrimitiveStore {
|
|||
cpu_rectangles: recycle_vec(self.cpu_rectangles),
|
||||
cpu_bounding_rects: recycle_vec(self.cpu_bounding_rects),
|
||||
cpu_text_runs: recycle_vec(self.cpu_text_runs),
|
||||
cpu_text_shadows: recycle_vec(self.cpu_text_shadows),
|
||||
cpu_images: recycle_vec(self.cpu_images),
|
||||
cpu_yuv_images: recycle_vec(self.cpu_yuv_images),
|
||||
cpu_gradients: recycle_vec(self.cpu_gradients),
|
||||
|
@ -771,6 +821,23 @@ impl PrimitiveStore {
|
|||
self.cpu_text_runs.push(text_cpu);
|
||||
metadata
|
||||
}
|
||||
PrimitiveContainer::TextShadow(text_shadow) => {
|
||||
let metadata = PrimitiveMetadata {
|
||||
opacity: PrimitiveOpacity::translucent(),
|
||||
clips,
|
||||
clip_cache_info: clip_info,
|
||||
prim_kind: PrimitiveKind::TextShadow,
|
||||
cpu_prim_index: SpecificPrimitiveIndex(self.cpu_text_shadows.len()),
|
||||
gpu_location: GpuCacheHandle::new(),
|
||||
render_task: None,
|
||||
clip_task: None,
|
||||
local_rect: *local_rect,
|
||||
local_clip_rect: *local_clip_rect,
|
||||
};
|
||||
|
||||
self.cpu_text_shadows.push(text_shadow);
|
||||
metadata
|
||||
}
|
||||
PrimitiveContainer::Image(image_cpu) => {
|
||||
let metadata = PrimitiveMetadata {
|
||||
opacity: PrimitiveOpacity::translucent(),
|
||||
|
@ -997,50 +1064,34 @@ impl PrimitiveStore {
|
|||
let location = RenderTaskLocation::Dynamic(None, cache_size);
|
||||
metadata.render_task.as_mut().unwrap().location = location;
|
||||
}
|
||||
PrimitiveKind::TextRun => {
|
||||
let text = &mut self.cpu_text_runs[metadata.cpu_prim_index.0];
|
||||
|
||||
let font_size_dp = text.logical_font_size.scale_by(device_pixel_ratio);
|
||||
let src_glyphs = display_list.get(text.glyph_range);
|
||||
|
||||
// Cache the glyph positions, if not in the cache already.
|
||||
// TODO(gw): In the future, remove `glyph_instances`
|
||||
// completely, and just reference the glyphs
|
||||
// directly from the displaty list.
|
||||
if text.glyph_instances.is_empty() {
|
||||
for src in src_glyphs {
|
||||
text.glyph_instances.push(GlyphInstance {
|
||||
index: src.index,
|
||||
point: src.point,
|
||||
});
|
||||
}
|
||||
PrimitiveKind::TextShadow => {
|
||||
let shadow = &mut self.cpu_text_shadows[metadata.cpu_prim_index.0];
|
||||
for text in &mut shadow.text_primitives {
|
||||
text.prepare_for_render(resource_cache,
|
||||
device_pixel_ratio,
|
||||
display_list);
|
||||
}
|
||||
|
||||
metadata.render_task = if text.blur_radius == 0.0 {
|
||||
None
|
||||
} else {
|
||||
// This is a text-shadow element. Create a render task that will
|
||||
// render the text run to a target, and then apply a gaussian
|
||||
// blur to that text run in order to build the actual primitive
|
||||
// which will be blitted to the framebuffer.
|
||||
let cache_width = (metadata.local_rect.size.width * device_pixel_ratio).ceil() as i32;
|
||||
let cache_height = (metadata.local_rect.size.height * device_pixel_ratio).ceil() as i32;
|
||||
let cache_size = DeviceIntSize::new(cache_width, cache_height);
|
||||
let cache_key = PrimitiveCacheKey::TextShadow(prim_index);
|
||||
let blur_radius = device_length(text.blur_radius,
|
||||
device_pixel_ratio);
|
||||
Some(RenderTask::new_blur(cache_key,
|
||||
cache_size,
|
||||
blur_radius,
|
||||
prim_index))
|
||||
};
|
||||
|
||||
resource_cache.request_glyphs(text.font_key,
|
||||
font_size_dp,
|
||||
text.color,
|
||||
&text.glyph_instances,
|
||||
text.render_mode,
|
||||
text.glyph_options);
|
||||
// This is a text-shadow element. Create a render task that will
|
||||
// render the text run to a target, and then apply a gaussian
|
||||
// blur to that text run in order to build the actual primitive
|
||||
// which will be blitted to the framebuffer.
|
||||
let cache_width = (metadata.local_rect.size.width * device_pixel_ratio).ceil() as i32;
|
||||
let cache_height = (metadata.local_rect.size.height * device_pixel_ratio).ceil() as i32;
|
||||
let cache_size = DeviceIntSize::new(cache_width, cache_height);
|
||||
let cache_key = PrimitiveCacheKey::TextShadow(prim_index);
|
||||
let blur_radius = device_length(shadow.shadow.blur_radius,
|
||||
device_pixel_ratio);
|
||||
metadata.render_task = Some(RenderTask::new_blur(cache_key,
|
||||
cache_size,
|
||||
blur_radius,
|
||||
prim_index));
|
||||
}
|
||||
PrimitiveKind::TextRun => {
|
||||
let text = &mut self.cpu_text_runs[metadata.cpu_prim_index.0];
|
||||
text.prepare_for_render(resource_cache,
|
||||
device_pixel_ratio,
|
||||
display_list);
|
||||
}
|
||||
PrimitiveKind::Image => {
|
||||
let image_cpu = &mut self.cpu_images[metadata.cpu_prim_index.0];
|
||||
|
@ -1118,7 +1169,15 @@ impl PrimitiveStore {
|
|||
}
|
||||
PrimitiveKind::TextRun => {
|
||||
let text = &self.cpu_text_runs[metadata.cpu_prim_index.0];
|
||||
text.write_gpu_blocks(request);
|
||||
request.push(text.color);
|
||||
text.write_gpu_blocks(&mut request);
|
||||
}
|
||||
PrimitiveKind::TextShadow => {
|
||||
let prim = &self.cpu_text_shadows[metadata.cpu_prim_index.0];
|
||||
request.push(prim.shadow.color);
|
||||
for text in &prim.text_primitives {
|
||||
text.write_gpu_blocks(&mut request);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ use profiler::{BackendProfileCounters, GpuCacheProfileCounters, TextureCacheProf
|
|||
use record::ApiRecordingReceiver;
|
||||
use resource_cache::ResourceCache;
|
||||
use scene::Scene;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::mpsc::Sender;
|
||||
use texture_cache::TextureCache;
|
||||
|
@ -60,7 +60,6 @@ pub struct RenderBackend {
|
|||
notifier: Arc<Mutex<Option<Box<RenderNotifier>>>>,
|
||||
webrender_context_handle: Option<GLContextHandleWrapper>,
|
||||
webgl_contexts: HashMap<WebGLContextId, GLContextWrapper>,
|
||||
dirty_webgl_contexts: HashSet<WebGLContextId>,
|
||||
current_bound_webgl_context_id: Option<WebGLContextId>,
|
||||
recorder: Option<Box<ApiRecordingReceiver>>,
|
||||
main_thread_dispatcher: Arc<Mutex<Option<Box<RenderDispatcher>>>>,
|
||||
|
@ -115,7 +114,6 @@ impl RenderBackend {
|
|||
notifier,
|
||||
webrender_context_handle,
|
||||
webgl_contexts: HashMap::new(),
|
||||
dirty_webgl_contexts: HashSet::new(),
|
||||
current_bound_webgl_context_id: None,
|
||||
recorder,
|
||||
main_thread_dispatcher,
|
||||
|
@ -359,7 +357,6 @@ impl RenderBackend {
|
|||
self.resource_cache
|
||||
.add_webgl_texture(id, SourceTexture::WebGL(texture_id),
|
||||
real_size);
|
||||
self.dirty_webgl_contexts.insert(id);
|
||||
|
||||
tx.send(Ok((id, limits))).unwrap();
|
||||
},
|
||||
|
@ -384,7 +381,6 @@ impl RenderBackend {
|
|||
self.resource_cache
|
||||
.update_webgl_texture(context_id, SourceTexture::WebGL(texture_id),
|
||||
real_size);
|
||||
self.dirty_webgl_contexts.insert(context_id);
|
||||
},
|
||||
Err(msg) => {
|
||||
error!("Error resizing WebGLContext: {}", msg);
|
||||
|
@ -400,7 +396,6 @@ impl RenderBackend {
|
|||
self.current_bound_webgl_context_id = Some(context_id);
|
||||
}
|
||||
ctx.apply_command(command);
|
||||
self.dirty_webgl_contexts.insert(context_id);
|
||||
},
|
||||
|
||||
ApiMsg::VRCompositorCommand(context_id, command) => {
|
||||
|
@ -409,7 +404,6 @@ impl RenderBackend {
|
|||
self.current_bound_webgl_context_id = Some(context_id);
|
||||
}
|
||||
self.handle_vr_compositor_command(context_id, command);
|
||||
self.dirty_webgl_contexts.insert(context_id);
|
||||
}
|
||||
ApiMsg::GenerateFrame(property_bindings) => {
|
||||
profile_scope!("GenerateFrame");
|
||||
|
@ -496,18 +490,10 @@ impl RenderBackend {
|
|||
// implementations - a single flush for each webgl
|
||||
// context at the start of a render frame should
|
||||
// incur minimal cost.
|
||||
// glFlush is not enough in some GPUs.
|
||||
// glFlush doesn't guarantee the completion of the GL commands when the shared texture is sampled.
|
||||
// This leads to some graphic glitches on some demos or even nothing being rendered at all (GPU Mali-T880).
|
||||
// glFinish guarantees the completion of the commands but it may hurt performance a lot.
|
||||
// Sync Objects are the recommended way to ensure that textures are ready in OpenGL 3.0+.
|
||||
// They are more performant than glFinish and guarantee the completion of the GL commands.
|
||||
for (id, webgl_context) in &self.webgl_contexts {
|
||||
if self.dirty_webgl_contexts.remove(&id) {
|
||||
webgl_context.make_current();
|
||||
webgl_context.apply_command(WebGLCommand::FenceAndWaitSync);
|
||||
webgl_context.unbind();
|
||||
}
|
||||
for (_, webgl_context) in &self.webgl_contexts {
|
||||
webgl_context.make_current();
|
||||
webgl_context.apply_command(WebGLCommand::Flush);
|
||||
webgl_context.unbind();
|
||||
}
|
||||
|
||||
let accumulated_scale_factor = self.accumulated_scale_factor();
|
||||
|
|
|
@ -20,7 +20,7 @@ use texture_cache::{TextureCache, TextureCacheItemId};
|
|||
use api::{Epoch, FontKey, FontTemplate, GlyphKey, ImageKey, ImageRendering};
|
||||
use api::{FontRenderMode, ImageData, GlyphDimensions, WebGLContextId};
|
||||
use api::{DevicePoint, DeviceIntSize, DeviceUintRect, ImageDescriptor, ColorF};
|
||||
use api::{GlyphOptions, GlyphInstance, TileOffset, TileSize};
|
||||
use api::{GlyphOptions, GlyphInstance, SubpixelPoint, TileOffset, TileSize};
|
||||
use api::{BlobImageRenderer, BlobImageDescriptor, BlobImageError, BlobImageRequest, BlobImageData};
|
||||
use api::BlobImageResources;
|
||||
use api::{ExternalImageData, ExternalImageType, LayoutPoint};
|
||||
|
@ -43,6 +43,7 @@ pub struct CacheItem {
|
|||
pub uv_rect_handle: GpuCacheHandle,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ImageProperties {
|
||||
pub descriptor: ImageDescriptor,
|
||||
pub external_image: Option<ExternalImageData>,
|
||||
|
@ -56,6 +57,7 @@ enum State {
|
|||
QueryResources,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ImageResource {
|
||||
data: ImageData,
|
||||
descriptor: ImageDescriptor,
|
||||
|
@ -480,21 +482,21 @@ impl ResourceCache {
|
|||
glyph_options: Option<GlyphOptions>,
|
||||
mut f: F) -> SourceTexture where F: FnMut(usize, &GpuCacheHandle) {
|
||||
debug_assert_eq!(self.state, State::QueryResources);
|
||||
let mut glyph_key = GlyphRequest::new(
|
||||
let mut glyph_request = GlyphRequest::new(
|
||||
font_key,
|
||||
size,
|
||||
color,
|
||||
0,
|
||||
LayoutPoint::new(0.0, 0.0),
|
||||
LayoutPoint::zero(),
|
||||
render_mode,
|
||||
glyph_options
|
||||
);
|
||||
let mut texture_id = None;
|
||||
for (loop_index, glyph_instance) in glyph_instances.iter().enumerate() {
|
||||
glyph_key.key.index = glyph_instance.index;
|
||||
glyph_key.key.subpixel_point.set_offset(glyph_instance.point, render_mode);
|
||||
glyph_request.key.index = glyph_instance.index;
|
||||
glyph_request.key.subpixel_point = SubpixelPoint::new(glyph_instance.point, render_mode);
|
||||
|
||||
let image_id = self.cached_glyphs.get(&glyph_key, self.current_frame_id);
|
||||
let image_id = self.cached_glyphs.get(&glyph_request, self.current_frame_id);
|
||||
let cache_item = image_id.map(|image_id| self.texture_cache.get(image_id));
|
||||
if let Some(cache_item) = cache_item {
|
||||
f(loop_index, &cache_item.uv_rect_handle);
|
||||
|
|
|
@ -50,14 +50,9 @@ impl AlphaBatchHelpers for PrimitiveStore {
|
|||
match metadata.prim_kind {
|
||||
PrimitiveKind::TextRun => {
|
||||
let text_run_cpu = &self.cpu_text_runs[metadata.cpu_prim_index.0];
|
||||
if text_run_cpu.blur_radius == 0.0 {
|
||||
match text_run_cpu.render_mode {
|
||||
FontRenderMode::Subpixel => BlendMode::Subpixel(text_run_cpu.color),
|
||||
FontRenderMode::Alpha | FontRenderMode::Mono => BlendMode::Alpha,
|
||||
}
|
||||
} else {
|
||||
// Text runs drawn to blur never get drawn with subpixel AA.
|
||||
BlendMode::Alpha
|
||||
match text_run_cpu.render_mode {
|
||||
FontRenderMode::Subpixel => BlendMode::Subpixel(text_run_cpu.color),
|
||||
FontRenderMode::Alpha | FontRenderMode::Mono => BlendMode::Alpha,
|
||||
}
|
||||
}
|
||||
PrimitiveKind::Image |
|
||||
|
@ -483,26 +478,8 @@ impl AlphaRenderItem {
|
|||
}
|
||||
PrimitiveKind::TextRun => {
|
||||
let text_cpu = &ctx.prim_store.cpu_text_runs[prim_metadata.cpu_prim_index.0];
|
||||
let batch_kind = if text_cpu.blur_radius == 0.0 {
|
||||
AlphaBatchKind::TextRun
|
||||
} else {
|
||||
// Select a generic primitive shader that can blit the
|
||||
// results of the cached text blur to the framebuffer,
|
||||
// applying tile clipping etc.
|
||||
AlphaBatchKind::CacheImage
|
||||
};
|
||||
|
||||
let font_size_dp = text_cpu.logical_font_size.scale_by(ctx.device_pixel_ratio);
|
||||
|
||||
let cache_task_index = match prim_metadata.render_task {
|
||||
Some(ref task) => {
|
||||
let cache_task_id = task.id;
|
||||
render_tasks.get_task_index(&cache_task_id,
|
||||
child_pass_index).0 as i32
|
||||
}
|
||||
None => 0,
|
||||
};
|
||||
|
||||
// TODO(gw): avoid / recycle this allocation in the future.
|
||||
let mut instances = Vec::new();
|
||||
|
||||
|
@ -513,7 +490,7 @@ impl AlphaRenderItem {
|
|||
text_cpu.render_mode,
|
||||
text_cpu.glyph_options, |index, handle| {
|
||||
let uv_address = handle.as_int(gpu_cache);
|
||||
instances.push(base_instance.build(index as i32, cache_task_index, uv_address));
|
||||
instances.push(base_instance.build(index as i32, 0, uv_address));
|
||||
});
|
||||
|
||||
if texture_id != SourceTexture::Invalid {
|
||||
|
@ -521,12 +498,20 @@ impl AlphaRenderItem {
|
|||
colors: [texture_id, SourceTexture::Invalid, SourceTexture::Invalid],
|
||||
};
|
||||
|
||||
let key = AlphaBatchKey::new(batch_kind, flags, blend_mode, textures);
|
||||
let key = AlphaBatchKey::new(AlphaBatchKind::TextRun, flags, blend_mode, textures);
|
||||
let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
|
||||
|
||||
batch.add_instances(&instances);
|
||||
}
|
||||
}
|
||||
PrimitiveKind::TextShadow => {
|
||||
let cache_task_id = prim_metadata.render_task.as_ref().expect("no render task!").id;
|
||||
let cache_task_index = render_tasks.get_task_index(&cache_task_id,
|
||||
child_pass_index);
|
||||
let key = AlphaBatchKey::new(AlphaBatchKind::CacheImage, flags, blend_mode, no_textures);
|
||||
let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
|
||||
batch.add_instance(base_instance.build(0, cache_task_index.0 as i32, 0));
|
||||
}
|
||||
PrimitiveKind::AlignedGradient => {
|
||||
let gradient_cpu = &ctx.prim_store.cpu_gradients[prim_metadata.cpu_prim_index.0];
|
||||
let key = AlphaBatchKey::new(AlphaBatchKind::AlignedGradient, flags, blend_mode, no_textures);
|
||||
|
@ -1043,43 +1028,48 @@ impl RenderTarget for ColorRenderTarget {
|
|||
0); // z is disabled for rendering cache primitives
|
||||
self.box_shadow_cache_prims.push(instance.build(0, 0, 0));
|
||||
}
|
||||
PrimitiveKind::TextRun => {
|
||||
let text = &ctx.prim_store.cpu_text_runs[prim_metadata.cpu_prim_index.0];
|
||||
// We only cache text runs with a text-shadow (for now).
|
||||
debug_assert!(text.blur_radius != 0.0);
|
||||
PrimitiveKind::TextShadow => {
|
||||
let prim = &ctx.prim_store.cpu_text_shadows[prim_metadata.cpu_prim_index.0];
|
||||
|
||||
// todo(gw): avoid / recycle this allocation...
|
||||
let mut instances = Vec::new();
|
||||
let mut base_index = 0;
|
||||
|
||||
let font_size_dp = text.logical_font_size.scale_by(ctx.device_pixel_ratio);
|
||||
let task_index = render_tasks.get_task_index(&task.id, pass_index);
|
||||
|
||||
let instance = SimplePrimitiveInstance::new(prim_address,
|
||||
render_tasks.get_task_index(&task.id, pass_index),
|
||||
task_index,
|
||||
RenderTaskIndex(0),
|
||||
PackedLayerIndex(0),
|
||||
0); // z is disabled for rendering cache primitives
|
||||
|
||||
let texture_id = ctx.resource_cache.get_glyphs(text.font_key,
|
||||
font_size_dp,
|
||||
text.color,
|
||||
&text.glyph_instances,
|
||||
text.render_mode,
|
||||
text.glyph_options, |index, handle| {
|
||||
let uv_address = handle.as_int(gpu_cache);
|
||||
instances.push(instance.build(index as i32, 0, uv_address));
|
||||
});
|
||||
for text in &prim.text_primitives {
|
||||
let font_size_dp = text.logical_font_size.scale_by(ctx.device_pixel_ratio);
|
||||
|
||||
if texture_id != SourceTexture::Invalid {
|
||||
let textures = BatchTextures {
|
||||
colors: [texture_id, SourceTexture::Invalid, SourceTexture::Invalid],
|
||||
};
|
||||
let texture_id = ctx.resource_cache.get_glyphs(text.font_key,
|
||||
font_size_dp,
|
||||
text.color,
|
||||
&text.glyph_instances,
|
||||
text.render_mode,
|
||||
text.glyph_options, |index, handle| {
|
||||
let uv_address = handle.as_int(gpu_cache);
|
||||
instances.push(instance.build(base_index + index as i32, 0, uv_address));
|
||||
});
|
||||
|
||||
self.text_run_cache_prims.extend_from_slice(&instances);
|
||||
if texture_id != SourceTexture::Invalid {
|
||||
let textures = BatchTextures {
|
||||
colors: [texture_id, SourceTexture::Invalid, SourceTexture::Invalid],
|
||||
};
|
||||
|
||||
debug_assert!(textures.colors[0] != SourceTexture::Invalid);
|
||||
debug_assert!(self.text_run_textures.colors[0] == SourceTexture::Invalid ||
|
||||
self.text_run_textures.colors[0] == textures.colors[0]);
|
||||
self.text_run_textures = textures;
|
||||
self.text_run_cache_prims.extend_from_slice(&instances);
|
||||
base_index += text.glyph_instances.len() as i32;
|
||||
instances.clear();
|
||||
|
||||
debug_assert!(textures.colors[0] != SourceTexture::Invalid);
|
||||
debug_assert!(self.text_run_textures.colors[0] == SourceTexture::Invalid ||
|
||||
self.text_run_textures.colors[0] == textures.colors[0]);
|
||||
self.text_run_textures = textures;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "webrender_api"
|
||||
version = "0.47.0"
|
||||
version = "0.48.0"
|
||||
authors = ["Glenn Watson <gw@intuitionlibrary.com>"]
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/servo/webrender"
|
||||
|
|
|
@ -117,7 +117,7 @@ pub struct GLLimits([u8; 0]);
|
|||
#[cfg(not(feature = "webgl"))]
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
pub enum WebGLCommand {
|
||||
FenceAndWaitSync,
|
||||
Flush,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
|
|
|
@ -65,6 +65,8 @@ pub enum SpecificDisplayItem {
|
|||
SetGradientStops,
|
||||
PushNestedDisplayList,
|
||||
PopNestedDisplayList,
|
||||
PushTextShadow(TextShadow),
|
||||
PopTextShadow,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
|
||||
|
@ -84,7 +86,6 @@ pub struct TextDisplayItem {
|
|||
pub font_key: FontKey,
|
||||
pub size: Au,
|
||||
pub color: ColorF,
|
||||
pub blur_radius: f32,
|
||||
pub glyph_options: Option<GlyphOptions>,
|
||||
} // IMPLICIT: glyphs: Vec<GlyphInstance>
|
||||
|
||||
|
@ -224,6 +225,13 @@ pub struct BoxShadowDisplayItem {
|
|||
pub clip_mode: BoxShadowClipMode,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub struct TextShadow {
|
||||
pub offset: LayoutVector2D,
|
||||
pub color: ColorF,
|
||||
pub blur_radius: f32,
|
||||
}
|
||||
|
||||
#[repr(u32)]
|
||||
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Ord, PartialOrd)]
|
||||
pub enum ExtendMode {
|
||||
|
|
|
@ -14,7 +14,7 @@ use {GradientStop, IframeDisplayItem, ImageDisplayItem, ImageKey, ImageMask, Ima
|
|||
use {LayoutPoint, LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D, LocalClip};
|
||||
use {MixBlendMode, PipelineId, PropertyBinding, PushStackingContextDisplayItem, RadialGradient};
|
||||
use {RadialGradientDisplayItem, RectangleDisplayItem, ScrollPolicy, SpecificDisplayItem};
|
||||
use {StackingContext, TextDisplayItem, TransformStyle, WebGLContextId, WebGLDisplayItem};
|
||||
use {StackingContext, TextDisplayItem, TextShadow, TransformStyle, WebGLContextId, WebGLDisplayItem};
|
||||
use {YuvColorSpace, YuvData, YuvImageDisplayItem};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
|
@ -543,7 +543,6 @@ impl DisplayListBuilder {
|
|||
font_key: FontKey,
|
||||
color: ColorF,
|
||||
size: Au,
|
||||
blur_radius: f32,
|
||||
glyph_options: Option<GlyphOptions>) {
|
||||
// Sanity check - anything with glyphs bigger than this
|
||||
// is probably going to consume too much memory to render
|
||||
|
@ -556,7 +555,6 @@ impl DisplayListBuilder {
|
|||
color,
|
||||
font_key,
|
||||
size,
|
||||
blur_radius,
|
||||
glyph_options,
|
||||
});
|
||||
|
||||
|
@ -897,9 +895,12 @@ impl DisplayListBuilder {
|
|||
assert!(self.clip_stack.len() > 0);
|
||||
}
|
||||
|
||||
pub fn push_iframe(&mut self, rect: LayoutRect, pipeline_id: PipelineId) {
|
||||
pub fn push_iframe(&mut self,
|
||||
rect: LayoutRect,
|
||||
local_clip: Option<LocalClip>,
|
||||
pipeline_id: PipelineId) {
|
||||
let item = SpecificDisplayItem::Iframe(IframeDisplayItem { pipeline_id: pipeline_id });
|
||||
self.push_item(item, rect, None);
|
||||
self.push_item(item, rect, local_clip);
|
||||
}
|
||||
|
||||
// Don't use this function. It will go away.
|
||||
|
@ -914,6 +915,19 @@ impl DisplayListBuilder {
|
|||
self.push_new_empty_item(SpecificDisplayItem::PopNestedDisplayList);
|
||||
}
|
||||
|
||||
pub fn push_text_shadow(&mut self,
|
||||
rect: LayoutRect,
|
||||
local_clip: Option<LocalClip>,
|
||||
shadow: TextShadow) {
|
||||
self.push_item(SpecificDisplayItem::PushTextShadow(shadow),
|
||||
rect,
|
||||
local_clip);
|
||||
}
|
||||
|
||||
pub fn pop_text_shadow(&mut self) {
|
||||
self.push_new_empty_item(SpecificDisplayItem::PopTextShadow);
|
||||
}
|
||||
|
||||
pub fn finalize(self) -> (PipelineId, LayoutSize, BuiltDisplayList) {
|
||||
let end_time = precise_time_ns();
|
||||
|
||||
|
|
|
@ -149,11 +149,6 @@ impl SubpixelPoint {
|
|||
pub fn to_f64(&self) -> (f64, f64) {
|
||||
(self.x.into(), self.y.into())
|
||||
}
|
||||
|
||||
pub fn set_offset(&mut self, point: LayoutPoint, render_mode: FontRenderMode) {
|
||||
self.x = render_mode.subpixel_quantize_offset(point.x);
|
||||
self.y = render_mode.subpixel_quantize_offset(point.y);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Hash, PartialEq, Eq, Debug, Deserialize, Serialize, Ord, PartialOrd)]
|
||||
|
|
|
@ -92,7 +92,7 @@ impl ImageDescriptor {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum ImageData {
|
||||
Raw(Arc<Vec<u8>>),
|
||||
Blob(BlobImageData),
|
||||
|
|
|
@ -129,7 +129,6 @@ pub enum WebGLCommand {
|
|||
CreateVertexArray(MsgSender<Option<WebGLVertexArrayId>>),
|
||||
DeleteVertexArray(WebGLVertexArrayId),
|
||||
BindVertexArray(Option<WebGLVertexArrayId>),
|
||||
FenceAndWaitSync,
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
|
@ -389,8 +388,7 @@ impl fmt::Debug for WebGLCommand {
|
|||
GenerateMipmap(..) => "GenerateMipmap",
|
||||
CreateVertexArray(..) => "CreateVertexArray",
|
||||
DeleteVertexArray(..) => "DeleteVertexArray",
|
||||
BindVertexArray(..) => "BindVertexArray",
|
||||
FenceAndWaitSync => "FenceAndWaitSync",
|
||||
BindVertexArray(..) => "BindVertexArray"
|
||||
};
|
||||
|
||||
write!(f, "CanvasWebGLMsg::{}(..)", name)
|
||||
|
@ -633,8 +631,6 @@ impl WebGLCommand {
|
|||
ctx.gl().delete_vertex_arrays(&[id.get()]),
|
||||
WebGLCommand::BindVertexArray(id) =>
|
||||
ctx.gl().bind_vertex_array(id.map_or(0, WebGLVertexArrayId::get)),
|
||||
WebGLCommand::FenceAndWaitSync =>
|
||||
Self::fence_and_wait_sync(ctx.gl()),
|
||||
}
|
||||
|
||||
// FIXME: Use debug_assertions once tests are run with them
|
||||
|
@ -1044,13 +1040,4 @@ impl WebGLCommand {
|
|||
gl.shader_source(shader_id.get(), &[source.as_bytes()]);
|
||||
gl.compile_shader(shader_id.get());
|
||||
}
|
||||
|
||||
fn fence_and_wait_sync(gl: &gl::Gl) {
|
||||
// Call FenceSync and ClientWaitSync to ensure that textures are ready.
|
||||
let sync = gl.fence_sync(gl::SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
// SYNC_FLUSH_COMMANDS_BIT is used to automatically generate a glFlush before blocking on the sync object.
|
||||
gl.wait_sync(sync, gl::SYNC_FLUSH_COMMANDS_BIT, gl::TIMEOUT_IGNORED);
|
||||
// Release GLsync object
|
||||
gl.delete_sync(sync);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ authors = ["The Mozilla Project Developers"]
|
|||
license = "MPL-2.0"
|
||||
|
||||
[dependencies]
|
||||
webrender_api = {path = "../webrender_api", version = "0.47.0"}
|
||||
webrender_api = {path = "../webrender_api", version = "0.48.0"}
|
||||
rayon = "0.8"
|
||||
thread_profiler = "0.1.1"
|
||||
euclid = "0.15"
|
||||
|
@ -14,5 +14,5 @@ gleam = "0.4"
|
|||
|
||||
[dependencies.webrender]
|
||||
path = "../webrender"
|
||||
version = "0.47.0"
|
||||
version = "0.48.0"
|
||||
default-features = false
|
||||
|
|
|
@ -1362,7 +1362,7 @@ pub extern "C" fn wr_dp_push_iframe(state: &mut WrState,
|
|||
pipeline_id: WrPipelineId) {
|
||||
assert!(unsafe { is_in_main_thread() });
|
||||
|
||||
state.frame_builder.dl_builder.push_iframe(rect.into(), pipeline_id);
|
||||
state.frame_builder.dl_builder.push_iframe(rect.into(), None, pipeline_id);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
|
@ -1482,7 +1482,6 @@ pub extern "C" fn wr_dp_push_text(state: &mut WrState,
|
|||
font_key,
|
||||
colorf,
|
||||
Au::from_f32_px(glyph_size),
|
||||
0.0,
|
||||
glyph_options);
|
||||
}
|
||||
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче