This commit is contained in:
Ryan VanderMeulen 2017-07-14 09:52:26 -04:00
Родитель 112117877f 019bd042fc
Коммит dec7bdc30e
299 изменённых файлов: 6534 добавлений и 3585 удалений

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

@ -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

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

@ -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);
}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше