Merge mozilla-central to inbound. a=merge CLOSED TREE

--HG--
rename : devtools/client/inspector/changes/moz.build => devtools/client/inspector/changes/utils/moz.build
extra : rebase_source : afa276396ead2cd74713b566c9b0fdc88a398ecd
This commit is contained in:
Noemi Erli 2018-10-26 18:50:07 +03:00
commit 614d3b4b95
71 changed files with 1370 additions and 1044 deletions

View File

@ -33,14 +33,8 @@ add_task(async function checkCtrlWorks() {
for (let [inputValue, expectedURL, options] of testcases) {
let promiseLoad = waitForDocLoadAndStopIt(expectedURL);
gURLBar.focus();
if (Object.keys(options).length > 0) {
gURLBar.selectionStart = gURLBar.selectionEnd =
gURLBar.inputField.value.length;
gURLBar.inputField.value = inputValue.slice(0, -1);
EventUtils.sendString(inputValue.slice(-1));
} else {
gURLBar.textValue = inputValue;
}
gURLBar.inputField.value = inputValue.slice(0, -1);
EventUtils.sendString(inputValue.slice(-1));
EventUtils.synthesizeKey("KEY_Enter", options);
await promiseLoad;
}

View File

@ -5,6 +5,11 @@
// the urlbar also shows the URLs embedded in action URIs unescaped. See bug
// 1233672.
XPCOMUtils.defineLazyModuleGetters(this, {
UrlbarMatch: "resource:///modules/UrlbarMatch.jsm",
UrlbarUtils: "resource:///modules/UrlbarUtils.jsm",
});
add_task(async function injectJSON() {
let inputStrs = [
'http://example.com/ ", "url": "bar',
@ -27,7 +32,12 @@ add_task(async function injectJSON() {
add_task(function losslessDecode() {
let urlNoScheme = "example.com/\u30a2\u30a4\u30a6\u30a8\u30aa";
let url = "http://" + urlNoScheme;
gURLBar.textValue = url;
if (Services.prefs.getBoolPref("browser.urlbar.quantumbar", true)) {
const result = new UrlbarMatch(UrlbarUtils.MATCH_TYPE.TAB_SWITCH, {url});
gURLBar.setValueFromResult(result);
} else {
gURLBar.textValue = url;
}
// Since this is directly setting textValue, it is expected to be trimmed.
Assert.equal(gURLBar.inputField.value, urlNoScheme,
"The string displayed in the textbox should not be escaped");

View File

@ -109,8 +109,9 @@ add_task(async function test_webnavigation_urlbar_typed_transitions() {
await extension.awaitMessage("ready");
gURLBar.focus();
gURLBar.textValue = "http://example.com/?q=typed";
const inputValue = "http://example.com/?q=typed";
gURLBar.inputField.value = inputValue.slice(0, -1);
EventUtils.sendString(inputValue.slice(-1));
EventUtils.synthesizeKey("VK_RETURN", {altKey: true});
await extension.awaitFinish("webNavigation.from_address_bar.typed");

View File

@ -12,6 +12,8 @@ support-files =
file_favicon_cache.png
file_favicon_thirdParty.html
file_firstPartyBasic.html
file_postMessage.html
file_postMessageSender.html
file_sharedworker.html
file_sharedworker.js
file_thirdPartyChild.audio.ogg
@ -80,5 +82,6 @@ skip-if = (verify && debug && (os == 'win'))
skip-if = verify
[browser_cacheAPI.js]
[browser_permissions.js]
[browser_postMessage.js]
[browser_sanitize.js]
[browser_windowOpenerRestriction.js]

View File

@ -0,0 +1,96 @@
/**
* Bug 1492607 - Test for assuring that postMessage cannot go across OAs.
*/
const FPD_ONE = "http://example.com";
const FPD_TWO = "http://example.org";
const TEST_BASE = "/browser/browser/components/originattributes/test/browser/";
add_task(async function setup() {
// Make sure first party isolation is enabled.
await SpecialPowers.pushPrefEnv({"set": [
["privacy.firstparty.isolate", true],
]});
});
async function runTestWithOptions(aDifferentFPD, aStarTargetOrigin, aBlockAcrossFPD) {
let testPageURL = aDifferentFPD ?
FPD_ONE + TEST_BASE + "file_postMessage.html" :
FPD_TWO + TEST_BASE + "file_postMessage.html";
// Deciding the targetOrigin according to the test setting.
let targetOrigin;
if (aStarTargetOrigin) {
targetOrigin = "*";
} else {
targetOrigin = aDifferentFPD ? FPD_ONE : FPD_TWO;
}
let senderURL = FPD_TWO + TEST_BASE + `file_postMessageSender.html?${targetOrigin}`;
// Open a tab to listen messages.
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, testPageURL);
// Use window.open() in the tab to open the sender tab. The sender tab
// will send a message through postMessage to window.opener.
let senderTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, senderURL, true);
ContentTask.spawn(tab.linkedBrowser, senderURL,
aSenderPath => {
content.open(aSenderPath, "_blank");
}
);
// Wait and get the tab of the sender tab.
let senderTab = await senderTabPromise;
// The postMessage should be blocked when the first parties are different with
// the following two cases. First, it is using a non-star target origin.
// Second, it is using the star target origin and the pref
// 'privacy.firstparty.isolate.block_post_message' is true.
let shouldBlock = aDifferentFPD && (!aStarTargetOrigin || aBlockAcrossFPD);
await ContentTask.spawn(tab.linkedBrowser, shouldBlock, async (aValue) => {
await new Promise(resolve => {
content.addEventListener("message", function eventHandler(aEvent) {
if (aEvent.data === "Self") {
if (aValue) {
is(content.document.getElementById("display").innerHTML, "",
"It should not get a message from other OA.");
} else {
is(content.document.getElementById("display").innerHTML, "Message",
"It should get a message from the same OA.");
}
content.removeEventListener("message", eventHandler);
resolve();
}
});
// Trigger the content to send a postMessage to itself.
content.document.getElementById("button").click();
});
});
BrowserTestUtils.removeTab(tab);
BrowserTestUtils.removeTab(senderTab);
}
add_task(async function runTests() {
for (let useDifferentFPD of [true, false]) {
for (let useStarTargetOrigin of [true, false]) {
for (let enableBlocking of [true, false]) {
if (enableBlocking) {
await SpecialPowers.pushPrefEnv({"set": [
["privacy.firstparty.isolate.block_post_message", true],
]});
}
await runTestWithOptions(useDifferentFPD, useStarTargetOrigin, enableBlocking);
if (enableBlocking) {
await SpecialPowers.popPrefEnv();
}
}
}
}
});

View File

@ -0,0 +1,27 @@
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Test page for window.postMessage</title>
</head>
<body>
<div id="display" style="white-space:pre; font-family:monospace; display:inline;"></div>
<input type="button" id="button" title="button"/>
<script>
const TEST_PATH = "http://example.org/browser/browser/components/originattributes/test/browser/file_postMessageSender.html";
window.onload = () => {
window.addEventListener("message", aEvent => {
if (aEvent.data === "Message") {
document.getElementById("display").innerHTML = "Message";
}
});
document.getElementById("button").onclick = () => {
window.postMessage("Self", "/");
};
};
</script>
</body>
</html>

View File

@ -0,0 +1,16 @@
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Test page for always sending a message to its opener through postMessage</title>
</head>
<body>
<script>
window.onload = () => {
// The target origin is coming from the query string.
let targetOrigin = window.location.search.substring(1);
window.opener.postMessage("Message", targetOrigin);
};
</script>
</body>
</html>

View File

@ -518,11 +518,9 @@ var PlacesUIUtils = {
*
* @param aNode
* a node, except the root node of a query.
* @param aView
* The view originating the request.
* @return true if the aNode represents a removable entry, false otherwise.
*/
canUserRemove(aNode, aView) {
canUserRemove(aNode) {
let parentNode = aNode.parent;
if (!parentNode) {
// canUserRemove doesn't accept root nodes.
@ -558,7 +556,7 @@ var PlacesUIUtils = {
return true;
// Otherwise it has to be a child of an editable folder.
return !this.isFolderReadOnly(parentNode, aView);
return !this.isFolderReadOnly(parentNode);
},
/**
@ -576,25 +574,15 @@ var PlacesUIUtils = {
*
* @param placesNode
* any folder result node.
* @param view
* The view originating the request.
* @throws if placesNode is not a folder result node or views is invalid.
* @note livemark "folders" are considered read-only (but see bug 1072833).
* @return true if placesNode is a read-only folder, false otherwise.
*/
isFolderReadOnly(placesNode, view) {
isFolderReadOnly(placesNode) {
if (typeof placesNode != "object" || !PlacesUtils.nodeIsFolder(placesNode)) {
throw new Error("invalid value for placesNode");
}
if (!view || typeof view != "object") {
throw new Error("invalid value for aView");
}
let itemId = PlacesUtils.getConcreteItemId(placesNode);
if (itemId == PlacesUtils.placesRootId ||
view.controller.hasCachedLivemarkInfo(placesNode))
return true;
return false;
return PlacesUtils.getConcreteItemId(placesNode) == PlacesUtils.placesRootId;
},
/** aItemsToOpen needs to be an array of objects of the form:
@ -646,25 +634,6 @@ var PlacesUIUtils = {
});
},
openLiveMarkNodesInTabs:
function PUIU_openLiveMarkNodesInTabs(aNode, aEvent, aView) {
let window = aView.ownerWindow;
PlacesUtils.livemarks.getLivemark({id: aNode.itemId})
.then(aLivemark => {
let urlsToOpen = [];
let nodes = aLivemark.getNodesForContainer(aNode);
for (let node of nodes) {
urlsToOpen.push({uri: node.uri, isBookmark: false});
}
if (OpenInTabsUtils.confirmOpenInTabs(urlsToOpen.length, window)) {
this._openTabset(urlsToOpen, aEvent, window);
}
}, Cu.reportError);
},
openContainerNodeInTabs:
function PUIU_openContainerInTabs(aNode, aEvent, aView) {
let window = aView.ownerWindow;

View File

@ -273,13 +273,6 @@ PlacesViewBase.prototype = {
if (!resultNode.containerOpen)
return;
if (this.controller.hasCachedLivemarkInfo(resultNode)) {
this._setEmptyPopupStatus(aPopup, false);
aPopup._built = true;
this._populateLivemarkPopup(aPopup);
return;
}
this._cleanPopup(aPopup);
let cc = resultNode.childCount;
@ -343,7 +336,6 @@ PlacesViewBase.prototype = {
element = document.createXULElement("menuseparator");
element.setAttribute("class", "small-separator");
} else {
let itemId = aPlacesNode.itemId;
if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_URI) {
element = document.createXULElement("menuitem");
element.className = "menuitem-iconic bookmark-item menuitem-with-favicon";
@ -361,18 +353,6 @@ PlacesViewBase.prototype = {
element.setAttribute("dayContainer", "true");
else if (PlacesUtils.nodeIsHost(aPlacesNode))
element.setAttribute("hostContainer", "true");
} else if (itemId != -1) {
PlacesUtils.livemarks.getLivemark({ id: itemId })
.then(aLivemark => {
element.setAttribute("livemark", "true");
if (AppConstants.platform === "macosx") {
// OS X native menubar doesn't track list-style-images since
// it doesn't have a frame (bug 733415). Thus enforce updating.
element.setAttribute("image", "");
element.removeAttribute("image");
}
this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
}, () => undefined);
}
let popup = document.createXULElement("menupopup");
@ -419,80 +399,6 @@ PlacesViewBase.prototype = {
return element;
},
_setLivemarkSiteURIMenuItem:
function PVB__setLivemarkSiteURIMenuItem(aPopup) {
let livemarkInfo = this.controller.getCachedLivemarkInfo(aPopup._placesNode);
let siteUrl = livemarkInfo && livemarkInfo.siteURI ?
livemarkInfo.siteURI.spec : null;
if (!siteUrl && aPopup._siteURIMenuitem) {
aPopup.removeChild(aPopup._siteURIMenuitem);
aPopup._siteURIMenuitem = null;
aPopup.removeChild(aPopup._siteURIMenuseparator);
aPopup._siteURIMenuseparator = null;
} else if (siteUrl && !aPopup._siteURIMenuitem) {
// Add "Open (Feed Name)" menuitem.
aPopup._siteURIMenuitem = document.createXULElement("menuitem");
aPopup._siteURIMenuitem.className = "openlivemarksite-menuitem";
if (typeof this.options.extraClasses.entry == "string") {
aPopup._siteURIMenuitem.classList.add(this.options.extraClasses.entry);
}
aPopup._siteURIMenuitem.setAttribute("targetURI", siteUrl);
aPopup._siteURIMenuitem.setAttribute("oncommand",
"openUILink(this.getAttribute('targetURI'), event, {" +
" triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({})});");
// If a user middle-clicks this item we serve the oncommand event.
// We are using checkForMiddleClick because of Bug 246720.
// Note: stopPropagation is needed to avoid serving middle-click
// with BT_onClick that would open all items in tabs.
aPopup._siteURIMenuitem.setAttribute("onclick",
"checkForMiddleClick(this, event); event.stopPropagation();");
let label =
PlacesUIUtils.getFormattedString("menuOpenLivemarkOrigin.label",
[aPopup.parentNode.getAttribute("label")]);
aPopup._siteURIMenuitem.setAttribute("label", label);
aPopup.insertBefore(aPopup._siteURIMenuitem, aPopup._startMarker);
aPopup._siteURIMenuseparator = document.createXULElement("menuseparator");
aPopup.insertBefore(aPopup._siteURIMenuseparator, aPopup._startMarker);
}
},
/**
* Add, update or remove the livemark status menuitem.
* @param aPopup
* The livemark container popup
* @param aStatus
* The livemark status
*/
_setLivemarkStatusMenuItem:
function PVB_setLivemarkStatusMenuItem(aPopup, aStatus) {
let statusMenuitem = aPopup._statusMenuitem;
if (!statusMenuitem) {
// Create the status menuitem and cache it in the popup object.
statusMenuitem = document.createXULElement("menuitem");
statusMenuitem.className = "livemarkstatus-menuitem";
if (typeof this.options.extraClasses.entry == "string") {
statusMenuitem.classList.add(this.options.extraClasses.entry);
}
statusMenuitem.setAttribute("disabled", true);
aPopup._statusMenuitem = statusMenuitem;
}
if (aStatus == Ci.mozILivemark.STATUS_LOADING ||
aStatus == Ci.mozILivemark.STATUS_FAILED) {
// Status has changed, update the cached status menuitem.
let stringId = aStatus == Ci.mozILivemark.STATUS_LOADING ?
"bookmarksLivemarkLoading" : "bookmarksLivemarkFailed";
statusMenuitem.setAttribute("label", PlacesUIUtils.getString(stringId));
if (aPopup._startMarker.nextElementSibling != statusMenuitem)
aPopup.insertBefore(statusMenuitem, aPopup._startMarker.nextElementSibling);
} else if (aPopup._statusMenuitem.parentNode == aPopup) {
// The livemark has finished loading.
aPopup.removeChild(aPopup._statusMenuitem);
}
},
toggleCutNode: function PVB_toggleCutNode(aPlacesNode, aValue) {
let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
@ -532,32 +438,7 @@ PlacesViewBase.prototype = {
elt.setAttribute("image", aPlacesNode.icon);
},
nodeAnnotationChanged:
function PVB_nodeAnnotationChanged(aPlacesNode, aAnno) {
let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
// All livemarks have a feedURI, so use it as our indicator of a livemark
// being modified.
if (aAnno == PlacesUtils.LMANNO_FEEDURI) {
let menu = elt.parentNode;
if (!menu.hasAttribute("livemark")) {
menu.setAttribute("livemark", "true");
if (AppConstants.platform === "macosx") {
// OS X native menubar doesn't track list-style-images since
// it doesn't have a frame (bug 733415). Thus enforce updating.
menu.setAttribute("image", "");
menu.removeAttribute("image");
}
}
PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId })
.then(aLivemark => {
// Controller will use this to build the meta data for the node.
this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
this.invalidateContainer(aPlacesNode);
}, () => undefined);
}
},
nodeAnnotationChanged() {},
nodeTitleChanged:
function PVB_nodeTitleChanged(aPlacesNode, aNewTitle) {
@ -602,26 +483,7 @@ PlacesViewBase.prototype = {
}
},
nodeHistoryDetailsChanged:
function PVB_nodeHistoryDetailsChanged(aPlacesNode, aTime, aCount) {
if (aPlacesNode.parent &&
this.controller.hasCachedLivemarkInfo(aPlacesNode.parent)) {
// Find the node in the parent.
let popup = this._getDOMNodeForPlacesNode(aPlacesNode.parent);
for (let child = popup._startMarker.nextElementSibling;
child != popup._endMarker;
child = child.nextElementSibling) {
if (child._placesNode && child._placesNode.uri == aPlacesNode.uri) {
if (aPlacesNode.accessCount)
child.setAttribute("visited", "true");
else
child.removeAttribute("visited");
break;
}
}
}
},
nodeHistoryDetailsChanged() { },
nodeTagsChanged() { },
nodeDateAddedChanged() { },
nodeLastModifiedChanged() { },
@ -676,66 +538,9 @@ PlacesViewBase.prototype = {
if (aNewState == Ci.nsINavHistoryContainerResultNode.STATE_OPENED ||
aNewState == Ci.nsINavHistoryContainerResultNode.STATE_CLOSED) {
this.invalidateContainer(aPlacesNode);
if (PlacesUtils.nodeIsFolder(aPlacesNode)) {
let queryOptions = PlacesUtils.asQuery(this._result.root).queryOptions;
if (queryOptions.excludeItems) {
return;
}
PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId })
.then(aLivemark => {
if (!this.controller) {
// We might have been destroyed in the interim...
return;
}
let shouldInvalidate =
!this.controller.hasCachedLivemarkInfo(aPlacesNode);
this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
if (aNewState == Ci.nsINavHistoryContainerResultNode.STATE_OPENED) {
aLivemark.registerForUpdates(aPlacesNode, this);
// Prioritize the current livemark.
aLivemark.reload();
PlacesUtils.livemarks.reloadLivemarks();
if (shouldInvalidate)
this.invalidateContainer(aPlacesNode);
} else {
aLivemark.unregisterForUpdates(aPlacesNode);
}
}, () => undefined);
}
}
},
_populateLivemarkPopup: function PVB__populateLivemarkPopup(aPopup) {
this._setLivemarkSiteURIMenuItem(aPopup);
// Show the loading status only if there are no entries yet.
if (aPopup._startMarker.nextElementSibling == aPopup._endMarker)
this._setLivemarkStatusMenuItem(aPopup, Ci.mozILivemark.STATUS_LOADING);
PlacesUtils.livemarks.getLivemark({ id: aPopup._placesNode.itemId })
.then(aLivemark => {
let placesNode = aPopup._placesNode;
if (!placesNode.containerOpen)
return;
if (aLivemark.status != Ci.mozILivemark.STATUS_LOADING)
this._setLivemarkStatusMenuItem(aPopup, aLivemark.status);
this._cleanPopup(aPopup,
this._nativeView && aPopup.parentNode.hasAttribute("open"));
let children = aLivemark.getNodesForContainer(placesNode);
for (let i = 0; i < children.length; i++) {
let child = children[i];
this.nodeInserted(placesNode, child, i);
if (child.accessCount)
this._getDOMNodeForPlacesNode(child).setAttribute("visited", true);
else
this._getDOMNodeForPlacesNode(child).removeAttribute("visited");
}
}, Cu.reportError);
},
/**
* Checks whether the popup associated with the provided element is open.
* This method may be overridden by classes that extend this base class.
@ -822,12 +627,6 @@ PlacesViewBase.prototype = {
hasMultipleURIs = numURINodes > 1;
}
let isLiveMark = false;
if (this.controller.hasCachedLivemarkInfo(aPopup._placesNode)) {
hasMultipleURIs = true;
isLiveMark = true;
}
if (!hasMultipleURIs) {
aPopup.setAttribute("singleitempopup", "true");
} else {
@ -858,15 +657,9 @@ PlacesViewBase.prototype = {
if (typeof this.options.extraClasses.footer == "string")
aPopup._endOptOpenAllInTabs.classList.add(this.options.extraClasses.footer);
if (isLiveMark) {
aPopup._endOptOpenAllInTabs.setAttribute("oncommand",
"PlacesUIUtils.openLiveMarkNodesInTabs(this.parentNode._placesNode, event, " +
"PlacesUIUtils.getViewForNode(this));");
} else {
aPopup._endOptOpenAllInTabs.setAttribute("oncommand",
"PlacesUIUtils.openContainerNodeInTabs(this.parentNode._placesNode, event, " +
"PlacesUIUtils.getViewForNode(this));");
}
aPopup._endOptOpenAllInTabs.setAttribute("oncommand",
"PlacesUIUtils.openContainerNodeInTabs(this.parentNode._placesNode, event, " +
"PlacesUIUtils.getViewForNode(this));");
aPopup._endOptOpenAllInTabs.setAttribute("onclick",
"checkForMiddleClick(this, event); event.stopPropagation();");
aPopup._endOptOpenAllInTabs.setAttribute("label",
@ -1120,12 +913,6 @@ PlacesToolbar.prototype = {
button.setAttribute("query", "true");
if (PlacesUtils.nodeIsTagQuery(aChild))
button.setAttribute("tagContainer", "true");
} else if (PlacesUtils.nodeIsFolder(aChild)) {
PlacesUtils.livemarks.getLivemark({ id: aChild.itemId })
.then(aLivemark => {
button.setAttribute("livemark", "true");
this.controller.cacheLivemarkInfo(aChild, aLivemark);
}, () => undefined);
}
let popup = document.createXULElement("menupopup");
@ -1453,18 +1240,7 @@ PlacesToolbar.prototype = {
if (elt.localName == "menupopup")
elt = elt.parentNode;
if (elt.parentNode == this._rootElt) { // Node is on the toolbar.
// All livemarks have a feedURI, so use it as our indicator.
if (aAnno == PlacesUtils.LMANNO_FEEDURI) {
elt.setAttribute("livemark", true);
PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId })
.then(aLivemark => {
this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
this.invalidateContainer(aPlacesNode);
}, Cu.reportError);
}
} else {
if (elt.parentNode != this._rootElt) { // Node is on the toolbar.
// Node is in a submenu.
PlacesViewBase.prototype.nodeAnnotationChanged.apply(this, arguments);
}
@ -1548,7 +1324,7 @@ PlacesToolbar.prototype = {
let eltRect = elt.getBoundingClientRect();
let eltIndex = Array.prototype.indexOf.call(this._rootElt.children, elt);
if (PlacesUtils.nodeIsFolder(elt._placesNode) &&
!PlacesUIUtils.isFolderReadOnly(elt._placesNode, this)) {
!PlacesUIUtils.isFolderReadOnly(elt._placesNode)) {
// This is a folder.
// If we are in the middle of it, drop inside it.
// Otherwise, drop before it, with regards to RTL mode.
@ -1864,15 +1640,11 @@ PlacesToolbar.prototype = {
let popup = aEvent.target;
let placesNode = popup._placesNode;
// Avoid handling popuphidden of inner views
if (placesNode && PlacesUIUtils.getViewForNode(popup) == this) {
// UI performance: folder queries are cheap, keep the resultnode open
// so we don't rebuild its contents whenever the popup is reopened.
// Though, we want to always close feed containers so their expiration
// status will be checked at next opening.
if (!PlacesUtils.nodeIsFolder(placesNode) ||
this.controller.hasCachedLivemarkInfo(placesNode)) {
placesNode.containerOpen = false;
}
if (placesNode && PlacesUIUtils.getViewForNode(popup) == this &&
// UI performance: folder queries are cheap, keep the resultnode open
// so we don't rebuild its contents whenever the popup is reopened.
!PlacesUtils.nodeIsFolder(placesNode)) {
placesNode.containerOpen = false;
}
let parent = popup.parentNode;
@ -1967,11 +1739,9 @@ PlacesMenu.prototype = {
// UI performance: folder queries are cheap, keep the resultnode open
// so we don't rebuild its contents whenever the popup is reopened.
// Though, we want to always close feed containers so their expiration
// status will be checked at next opening.
if (!PlacesUtils.nodeIsFolder(placesNode) ||
this.controller.hasCachedLivemarkInfo(placesNode))
if (!PlacesUtils.nodeIsFolder(placesNode)) {
placesNode.containerOpen = false;
}
// The autoopened attribute is set for folders which have been
// automatically opened when dragged over. Turn off this attribute
@ -2023,12 +1793,6 @@ PlacesPanelMenuView.prototype = {
button.setAttribute("query", "true");
if (PlacesUtils.nodeIsTagQuery(aChild))
button.setAttribute("tagContainer", "true");
} else if (PlacesUtils.nodeIsFolder(aChild)) {
PlacesUtils.livemarks.getLivemark({ id: aChild.itemId })
.then(aLivemark => {
button.setAttribute("livemark", "true");
this.controller.cacheLivemarkInfo(aChild, aLivemark);
}, () => undefined);
}
} else if (PlacesUtils.nodeIsURI(aChild)) {
button.setAttribute("scheme",
@ -2078,27 +1842,7 @@ PlacesPanelMenuView.prototype = {
this._rootElt.insertBefore(elt, this._rootElt.children[aNewIndex]);
},
nodeAnnotationChanged:
function PAMV_nodeAnnotationChanged(aPlacesNode, aAnno) {
let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
// There's no UI representation for the root node.
if (elt == this._rootElt)
return;
if (elt.parentNode != this._rootElt)
return;
// All livemarks have a feedURI, so use it as our indicator.
if (aAnno == PlacesUtils.LMANNO_FEEDURI) {
elt.setAttribute("livemark", true);
PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId })
.then(aLivemark => {
this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
this.invalidateContainer(aPlacesNode);
}, Cu.reportError);
}
},
nodeAnnotationChanged() {},
nodeTitleChanged: function PAMV_nodeTitleChanged(aPlacesNode, aNewTitle) {
let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
@ -2286,15 +2030,11 @@ this.PlacesPanelview = class extends PlacesViewBase {
let panelview = event.originalTarget;
let placesNode = panelview._placesNode;
// Avoid handling ViewHiding of inner views
if (placesNode && PlacesUIUtils.getViewForNode(panelview) == this) {
// UI performance: folder queries are cheap, keep the resultnode open
// so we don't rebuild its contents whenever the popup is reopened.
// Though, we want to always close feed containers so their expiration
// status will be checked at next opening.
if (!PlacesUtils.nodeIsFolder(placesNode) ||
this.controller.hasCachedLivemarkInfo(placesNode)) {
placesNode.containerOpen = false;
}
if (placesNode && PlacesUIUtils.getViewForNode(panelview) == this &&
// UI performance: folder queries are cheap, keep the resultnode open
// so we don't rebuild its contents whenever the popup is reopened.
!PlacesUtils.nodeIsFolder(placesNode)) {
placesNode.containerOpen = false;
}
}

View File

@ -189,7 +189,7 @@ PlacesController.prototype = {
let selectedNode = this._view.selectedNode;
return selectedNode &&
PlacesUtils.nodeIsFolder(selectedNode) &&
!PlacesUIUtils.isFolderReadOnly(selectedNode, this._view) &&
!PlacesUIUtils.isFolderReadOnly(selectedNode) &&
this._view.result.sortingMode ==
Ci.nsINavHistoryQueryOptions.SORT_BY_NONE;
}
@ -308,7 +308,7 @@ PlacesController.prototype = {
if (nodes[i] == root)
return false;
if (!PlacesUIUtils.canUserRemove(nodes[i], this._view))
if (!PlacesUIUtils.canUserRemove(nodes[i]))
return false;
}
}
@ -1242,7 +1242,7 @@ PlacesController.prototype = {
// Allow dropping into Tag containers and editable folders.
return !PlacesUtils.nodeIsTagQuery(container) &&
(!PlacesUtils.nodeIsFolder(container) ||
PlacesUIUtils.isFolderReadOnly(container, this._view));
PlacesUIUtils.isFolderReadOnly(container));
},
/**
@ -1262,7 +1262,7 @@ PlacesController.prototype = {
let parentNode = node.parent;
return parentNode != null &&
PlacesUtils.nodeIsFolder(parentNode) &&
!PlacesUIUtils.isFolderReadOnly(parentNode, this._view) &&
!PlacesUIUtils.isFolderReadOnly(parentNode) &&
!PlacesUtils.nodeIsTagQuery(parentNode);
},
};

View File

@ -110,7 +110,7 @@
let tagName = PlacesUtils.nodeIsTagQuery(elt._placesNode) ?
elt._placesNode.title : null;
if ((PlacesUtils.nodeIsFolder(elt._placesNode) &&
!PlacesUIUtils.isFolderReadOnly(elt._placesNode, this._rootView)) ||
!PlacesUIUtils.isFolderReadOnly(elt._placesNode)) ||
PlacesUtils.nodeIsTagQuery(elt._placesNode)) {
// This is a folder or a tag container.
if (eventY - eltY < eltHeight * 0.20) {

View File

@ -107,10 +107,6 @@ PlacesTreeView.prototype = {
* @return true if aContainer is a plain container, false otherwise.
*/
_isPlainContainer: function PTV__isPlainContainer(aContainer) {
// Livemarks are always plain containers.
if (this._controller.hasCachedLivemarkInfo(aContainer))
return true;
// We don't know enough about non-query containers.
if (!(aContainer instanceof Ci.nsINavHistoryQueryResultNode))
return false;
@ -343,8 +339,7 @@ PlacesTreeView.prototype = {
// Recursively do containers.
if (!this._flatList &&
curChild instanceof Ci.nsINavHistoryContainerResultNode &&
!this._controller.hasCachedLivemarkInfo(curChild)) {
curChild instanceof Ci.nsINavHistoryContainerResultNode) {
let uri = curChild.uri;
let isopen = false;
@ -831,22 +826,6 @@ PlacesTreeView.prototype = {
}
},
_populateLivemarkContainer: function PTV__populateLivemarkContainer(aNode) {
PlacesUtils.livemarks.getLivemark({ id: aNode.itemId })
.then(aLivemark => {
let placesNode = aNode;
// Need to check containerOpen since getLivemark is async.
if (!placesNode.containerOpen)
return;
let children = aLivemark.getNodesForContainer(placesNode);
for (let i = 0; i < children.length; i++) {
let child = children[i];
this.nodeInserted(placesNode, child, i);
}
}, Cu.reportError);
},
nodeTitleChanged: function PTV_nodeTitleChanged(aNode, aNewTitle) {
this._invalidateCellValue(aNode, this.COLUMN_TYPE_TITLE);
},
@ -870,19 +849,6 @@ PlacesTreeView.prototype = {
itemId: aNode.itemId,
time: aOldVisitDate}));
this._nodeDetails.set(makeNodeDetailsKey(aNode), aNode);
if (aNode.parent && this._controller.hasCachedLivemarkInfo(aNode.parent)) {
// Find the node in the parent.
let parentRow = this._flatList ? 0 : this._getRowForNode(aNode.parent);
for (let i = parentRow; i < this._rows.length; i++) {
let child = this.nodeForTreeIndex(i);
if (child.uri == aNode.uri) {
this._cellProperties.delete(child);
this._invalidateCellValue(child, this.COLUMN_TYPE_TITLE);
break;
}
}
return;
}
this._invalidateCellValue(aNode, this.COLUMN_TYPE_DATE);
this._invalidateCellValue(aNode, this.COLUMN_TYPE_VISITCOUNT);
@ -894,18 +860,7 @@ PlacesTreeView.prototype = {
nodeKeywordChanged(aNode, aNewKeyword) {},
nodeAnnotationChanged: function PTV_nodeAnnotationChanged(aNode, aAnno) {
if (aAnno == PlacesUtils.LMANNO_FEEDURI) {
PlacesUtils.livemarks.getLivemark({ id: aNode.itemId })
.then(aLivemark => {
this._controller.cacheLivemarkInfo(aNode, aLivemark);
let properties = this._cellProperties.get(aNode);
this._cellProperties.set(aNode, properties += " livemark");
// The livemark attribute is set as a cell property on the title cell.
this._invalidateCellValue(aNode, this.COLUMN_TYPE_TITLE);
}, Cu.reportError);
}
},
nodeAnnotationChanged() {},
nodeDateAddedChanged: function PTV_nodeDateAddedChanged(aNode, aNewValue) {
this._invalidateCellValue(aNode, this.COLUMN_TYPE_DATEADDED);
@ -919,32 +874,6 @@ PlacesTreeView.prototype = {
containerStateChanged:
function PTV_containerStateChanged(aNode, aOldState, aNewState) {
this.invalidateContainer(aNode);
if (PlacesUtils.nodeIsFolder(aNode) ||
(this._flatList && aNode == this._rootNode)) {
let queryOptions = PlacesUtils.asQuery(this._rootNode).queryOptions;
if (queryOptions.excludeItems) {
return;
}
if (aNode.itemId != -1) { // run when there's a valid node id
PlacesUtils.livemarks.getLivemark({ id: aNode.itemId })
.then(aLivemark => {
let shouldInvalidate =
!this._controller.hasCachedLivemarkInfo(aNode);
this._controller.cacheLivemarkInfo(aNode, aLivemark);
if (aNewState == Ci.nsINavHistoryContainerResultNode.STATE_OPENED) {
aLivemark.registerForUpdates(aNode, this);
// Prioritize the current livemark.
aLivemark.reload();
PlacesUtils.livemarks.reloadLivemarks();
if (shouldInvalidate)
this.invalidateContainer(aNode);
} else {
aLivemark.unregisterForUpdates(aNode);
}
}, () => undefined);
}
}
},
invalidateContainer: function PTV_invalidateContainer(aContainer) {
@ -1065,13 +994,6 @@ PlacesTreeView.prototype = {
}
}
if (this._controller.hasCachedLivemarkInfo(aContainer)) {
let queryOptions = PlacesUtils.asQuery(this._result.root).queryOptions;
if (!queryOptions.excludeItems) {
this._populateLivemarkContainer(aContainer);
}
}
this._tree.endUpdateBatch();
// Restore selection.
@ -1248,22 +1170,6 @@ PlacesTreeView.prototype = {
properties += " dayContainer";
else if (PlacesUtils.nodeIsHost(node))
properties += " hostContainer";
} else if (nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER ||
nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT) {
if (itemId != -1) {
if (this._controller.hasCachedLivemarkInfo(node)) {
properties += " livemark";
} else {
PlacesUtils.livemarks.getLivemark({ id: itemId })
.then(aLivemark => {
this._controller.cacheLivemarkInfo(node, aLivemark);
let livemarkProps = this._cellProperties.get(node);
this._cellProperties.set(node, livemarkProps += " livemark");
// The livemark attribute is set as a cell property on the title cell.
this._invalidateCellValue(node, this.COLUMN_TYPE_TITLE);
}, () => undefined);
}
}
}
if (itemId == -1) {
@ -1289,13 +1195,6 @@ PlacesTreeView.prototype = {
properties += " separator";
else if (PlacesUtils.nodeIsURI(node)) {
properties += " " + PlacesUIUtils.guessUrlSchemeForUI(node.uri);
if (this._controller.hasCachedLivemarkInfo(node.parent)) {
properties += " livemarkItem";
if (node.accessCount) {
properties += " visited";
}
}
}
this._cellProperties.set(node, properties);
@ -1338,14 +1237,8 @@ PlacesTreeView.prototype = {
if (this._flatList)
return true;
let node = this._rows[aRow];
if (this._controller.hasCachedLivemarkInfo(node)) {
let queryOptions = PlacesUtils.asQuery(this._result.root).queryOptions;
return queryOptions.excludeItems;
}
// All containers are listed in the rows array.
return !node.hasChildren;
return !this._rows[aRow].hasChildren;
},
isSeparator: function PTV_isSeparator(aRow) {
@ -1590,18 +1483,15 @@ PlacesTreeView.prototype = {
return;
}
// Persist containers open status, but never persist livemarks.
if (!this._controller.hasCachedLivemarkInfo(node)) {
let uri = node.uri;
let uri = node.uri;
if (uri) {
let docURI = document.documentURI;
if (uri) {
let docURI = document.documentURI;
if (node.containerOpen) {
Services.xulStore.removeValue(docURI, uri, "open");
} else {
Services.xulStore.setValue(docURI, uri, "open", "true");
}
if (node.containerOpen) {
Services.xulStore.removeValue(docURI, uri, "open");
} else {
Services.xulStore.setValue(docURI, uri, "open", "true");
}
}
@ -1716,9 +1606,8 @@ PlacesTreeView.prototype = {
}
let itemGuid = node.bookmarkGuid;
// Only bookmark-nodes are editable. Fortunately, this checks also takes
// care of livemark children.
if (itemGuid == "")
// Only bookmark-nodes are editable.
if (!itemGuid)
return false;
// The following items are also not editable, even though they are bookmark

View File

@ -547,7 +547,9 @@ var gSearchResultsPane = {
removeAllSearchTooltips() {
for (let anchorNode of this.listSearchTooltips) {
anchorNode.parentElement.classList.remove("search-tooltip-parent");
anchorNode.tooltipNode.remove();
if (anchorNode.tooltipNode) {
anchorNode.tooltipNode.remove();
}
anchorNode.tooltipNode = null;
}
this.listSearchTooltips.clear();

View File

@ -230,7 +230,16 @@ class UrlbarInput {
* @param {UrlbarMatch} result The result that was selected.
*/
resultSelected(event, result) {
// Set the input value to the target url.
this.setValueFromResult(result);
this.controller.resultSelected(event, result);
}
/**
* Called by the view when moving through results with the keyboard.
*
* @param {UrlbarMatch} result The result that was selected.
*/
setValueFromResult(result) {
let val = result.url;
let uri;
try {
@ -240,8 +249,6 @@ class UrlbarInput {
val = this.window.losslessDecodeURI(uri);
}
this.value = val;
this.controller.resultSelected(event, result);
}
// Getters and Setters below.

View File

@ -68,10 +68,6 @@ const PREF_URLBAR_DEFAULTS = new Map([
// INSERTMETHOD values.
["insertMethod", UrlbarUtils.INSERTMETHOD.MERGE_RELATED],
// Controls how URLs are matched against the user's search string. See
// mozIPlacesAutoComplete.
["matchBehavior", Ci.mozIPlacesAutoComplete.MATCH_BOUNDARY_ANYWHERE],
// Controls the composition of search results.
["matchBuckets", "suggestion:4,general:Infinity"],
@ -311,16 +307,6 @@ class Preferences {
}
return val;
}
case "matchBehavior": {
// Validate matchBehavior; default to MATCH_BOUNDARY_ANYWHERE.
let val = this._readPref(pref);
if (![Ci.mozIPlacesAutoComplete.MATCH_ANYWHERE,
Ci.mozIPlacesAutoComplete.MATCH_BOUNDARY,
Ci.mozIPlacesAutoComplete.MATCH_BEGINNING].includes(val)) {
val = Ci.mozIPlacesAutoComplete.MATCH_BOUNDARY_ANYWHERE;
}
return val;
}
}
return this._readPref(pref);
}

View File

@ -17,11 +17,6 @@ bookmarksRestoreFilterName=JSON
bookmarksRestoreFormatError=Unsupported file type.
bookmarksRestoreParseError=Unable to process the backup file.
bookmarksLivemarkLoading=Live Bookmark loading…
bookmarksLivemarkFailed=Live Bookmark feed failed to load.
menuOpenLivemarkOrigin.label=Open “%S”
sortByName=Sort %S by Name
sortByNameGeneric=Sort by Name
# LOCALIZATION NOTE (view.sortBy.1.name.label): sortBy properties are versioned.

View File

@ -26,7 +26,6 @@ browser.jar:
skin/classic/browser/notification-icons/geo.svg (notification-icons/geo.svg)
skin/classic/browser/places/allBookmarks.png (places/allBookmarks.png)
skin/classic/browser/places/editBookmark.css (places/editBookmark.css)
skin/classic/browser/places/livemark-item.png (places/livemark-item.png)
* skin/classic/browser/places/sidebar.css (places/sidebar.css)
skin/classic/browser/places/organizer.css (places/organizer.css)
skin/classic/browser/places/organizer.xml (places/organizer.xml)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 850 B

View File

@ -189,9 +189,7 @@
}
/* Workaround for native menubar inheritance */
.openintabs-menuitem,
.openlivemarksite-menuitem,
.livemarkstatus-menuitem {
.openintabs-menuitem {
list-style-image: none;
}

View File

@ -35,7 +35,6 @@ browser.jar:
skin/classic/browser/places/toolbar.png (places/toolbar.png)
skin/classic/browser/places/toolbarDropMarker.png (places/toolbarDropMarker.png)
skin/classic/browser/places/editBookmark.css (places/editBookmark.css)
skin/classic/browser/places/livemark-item.png (places/livemark-item.png)
skin/classic/browser/preferences/alwaysAsk.png (preferences/alwaysAsk.png)
skin/classic/browser/preferences/application.png (preferences/application.png)
skin/classic/browser/preferences/saveFile.png (preferences/saveFile.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 850 B

View File

@ -235,7 +235,6 @@
skin/classic/browser/places/bookmarksMenu.svg (../shared/places/bookmarksMenu.svg)
skin/classic/browser/places/bookmarksToolbar.svg (../shared/places/bookmarksToolbar.svg)
skin/classic/browser/places/folder.svg (../shared/places/folder.svg)
skin/classic/browser/places/folder-live.svg (../shared/places/folder-live.svg)
skin/classic/browser/places/folder-smart.svg (../shared/places/folder-smart.svg)
skin/classic/browser/places/history.svg (../shared/places/history.svg)
skin/classic/browser/places/tag.svg (../shared/places/tag.svg)

View File

@ -1,6 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg width="16" height="16" viewBox="0 0 16 16" fill="context-fill" fill-opacity="context-fill-opacity" xmlns="http://www.w3.org/2000/svg">
<path d="M3.5,10A2.5,2.5,0,1,0,6,12.5,2.5,2.5,0,0,0,3.5,10ZM2,1A1,1,0,0,0,2,3,10.883,10.883,0,0,1,13,14a1,1,0,0,0,2,0A12.862,12.862,0,0,0,2,1ZM2,5A1,1,0,0,0,2,7a6.926,6.926,0,0,1,7,7,1,1,0,0,0,2,0A8.9,8.9,0,0,0,2,5Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 580 B

View File

@ -15,15 +15,6 @@ treechildren::-moz-tree-image(title) {
height: 16px;
}
treechildren::-moz-tree-image(title, livemarkItem) {
list-style-image: url("chrome://browser/skin/places/livemark-item.png");
-moz-image-region: rect(0px, 16px, 16px, 0px);
}
treechildren::-moz-tree-image(title, livemarkItem, visited) {
-moz-image-region: rect(0px, 32px, 16px, 16px);
}
treechildren::-moz-tree-image(title, container),
treechildren::-moz-tree-image(title, open) {
list-style-image: url("chrome://browser/skin/places/folder.svg");
@ -36,10 +27,6 @@ treechildren::-moz-tree-image(title, separator) {
margin: 0;
}
treechildren::-moz-tree-image(container, livemark) {
list-style-image: url("chrome://browser/skin/places/folder-live.svg");
}
treechildren::-moz-tree-image(container, queryFolder_toolbar_____) {
list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.svg");
}

View File

@ -514,19 +514,6 @@ toolbar[brighttext] {
list-style-image: url("chrome://browser/skin/places/folder.svg");
}
.bookmark-item[container][livemark] {
list-style-image: url("chrome://browser/skin/places/folder-live.svg");
}
.bookmark-item[container][livemark] .bookmark-item {
list-style-image: url("chrome://browser/skin/places/livemark-item.png");
-moz-image-region: rect(0px, 16px, 16px, 0px);
}
.bookmark-item[container][livemark] .bookmark-item[visited] {
-moz-image-region: rect(0px, 32px, 16px, 16px);
}
.bookmark-item[container][query] {
list-style-image: url("chrome://browser/skin/places/folder-smart.svg");
}

View File

@ -30,7 +30,6 @@ browser.jar:
skin/classic/browser/places/editBookmark.css (places/editBookmark.css)
skin/classic/browser/places/libraryToolbar.png (places/libraryToolbar.png)
skin/classic/browser/places/allBookmarks.png (places/allBookmarks.png)
skin/classic/browser/places/livemark-item.png (places/livemark-item.png)
skin/classic/browser/preferences/alwaysAsk.png (preferences/alwaysAsk.png)
skin/classic/browser/preferences/application.png (preferences/application.png)
skin/classic/browser/preferences/saveFile.png (preferences/saveFile.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 850 B

View File

@ -19,6 +19,7 @@ using dom::URLParams;
bool OriginAttributes::sFirstPartyIsolation = false;
bool OriginAttributes::sRestrictedOpenerAccess = false;
bool OriginAttributes::sBlockPostMessageForFPI = false;
void
OriginAttributes::InitPrefs()
@ -31,6 +32,8 @@ OriginAttributes::InitPrefs()
"privacy.firstparty.isolate");
Preferences::AddBoolVarCache(&sRestrictedOpenerAccess,
"privacy.firstparty.isolate.restrict_opener_access");
Preferences::AddBoolVarCache(&sBlockPostMessageForFPI,
"privacy.firstparty.isolate.block_post_message");
}
}

View File

@ -61,6 +61,14 @@ public:
return !(*this == aOther);
}
MOZ_MUST_USE bool EqualsIgnoringFPD(const OriginAttributes& aOther) const
{
return mAppId == aOther.mAppId &&
mInIsolatedMozBrowser == aOther.mInIsolatedMozBrowser &&
mUserContextId == aOther.mUserContextId &&
mPrivateBrowsingId == aOther.mPrivateBrowsingId;
}
// Serializes/Deserializes non-default values into the suffix format, i.e.
// |!key1=value1&key2=value2|. If there are no non-default attributes, this
// returns an empty string.
@ -96,6 +104,13 @@ public:
return !sFirstPartyIsolation || sRestrictedOpenerAccess;
}
// Check whether we block the postMessage across different FPDs when the
// targetOrigin is '*'.
static inline MOZ_MUST_USE bool IsBlockPostMessageForFPI()
{
return sFirstPartyIsolation && sBlockPostMessageForFPI;
}
// returns true if the originAttributes suffix has mPrivateBrowsingId value
// different than 0.
static bool IsPrivateBrowsing(const nsACString& aOrigin);
@ -105,6 +120,7 @@ public:
private:
static bool sFirstPartyIsolation;
static bool sRestrictedOpenerAccess;
static bool sBlockPostMessageForFPI;
};
class OriginAttributesPattern : public dom::OriginAttributesPatternDictionary

View File

@ -17,7 +17,8 @@ const {
} = require("./actions/changes");
class ChangesView {
constructor(inspector) {
constructor(inspector, window) {
this.document = window.document;
this.inspector = inspector;
this.store = this.inspector.store;
this.toolbox = this.inspector.toolbox;
@ -44,9 +45,7 @@ class ChangesView {
store: this.store,
}, changesApp);
// TODO: save store and restore/replay on refresh.
// Bug 1478439 - https://bugzilla.mozilla.org/show_bug.cgi?id=1478439
this.inspector.target.once("will-navigate", this.destroy);
this.inspector.target.on("will-navigate", this.onClearChanges);
// Sync the store to the changes stored on the server. The
// syncChangesToServer() method is async, but we don't await it since
@ -86,6 +85,7 @@ class ChangesView {
this.changesFront.off("clear-changes", this.onClearChanges);
this.changesFront = null;
this.document = null;
this.inspector = null;
this.store = null;
this.toolbox = null;

View File

@ -17,10 +17,10 @@ module.exports = {
};
},
trackChange(data) {
trackChange(change) {
return {
type: TRACK_CHANGE,
data,
change,
};
},

View File

@ -0,0 +1,38 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { PureComponent } = require("devtools/client/shared/vendor/react");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
class CSSDeclaration extends PureComponent {
static get propTypes() {
return {
property: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
className: PropTypes.string,
};
}
static get defaultProps() {
return {
className: "",
};
}
render() {
const { property, value, className } = this.props;
return dom.div({ className },
dom.span({ className: "declaration-name theme-fg-color5"}, property),
":",
dom.span({ className: "declaration-value theme-fg-color1"}, value),
";"
);
}
}
module.exports = CSSDeclaration;

View File

@ -4,73 +4,114 @@
"use strict";
const { PureComponent } = require("devtools/client/shared/vendor/react");
const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const { connect } = require("devtools/client/shared/vendor/react-redux");
const CSSDeclaration = createFactory(require("./CSSDeclaration"));
class ChangesApp extends PureComponent {
static get propTypes() {
return {
// Redux state slice assigned to Track Changes feature; passed as prop by connect()
changes: PropTypes.object.isRequired,
};
}
renderMutations(remove = {}, add = {}) {
const removals = Object.entries(remove).map(([prop, value]) => {
return dom.div(
{ className: "line diff-remove"},
`${prop}: ${value};`
);
constructor(props) {
super(props);
// In the Redux store, all rules exist in a collection at the same level of nesting.
// Parent rules come before child rules. Parent/child dependencies are set
// via parameters in each rule pointing to the corresponding rule ids.
//
// To render rules, we traverse the descendant rule tree and render each child rule
// found. This means we get into situations where we can render the same rule multiple
// times: once as a child of its parent and once standalone.
//
// By keeping a log of rules previously rendered we prevent needless multi-rendering.
this.renderedRules = [];
}
renderDeclarations(remove = {}, add = {}) {
const removals = Object.entries(remove).map(([property, value]) => {
return CSSDeclaration({ className: "level diff-remove", property, value });
});
const additions = Object.entries(add).map(([prop, value]) => {
return dom.div(
{ className: "line diff-add"},
`${prop}: ${value};`
);
const additions = Object.entries(add).map(([property, value]) => {
return CSSDeclaration({ className: "level diff-add", property, value });
});
return [removals, additions];
}
renderSelectors(selectors = {}) {
return Object.keys(selectors).map(sel => {
return dom.details(
{ className: "selector", open: true },
dom.summary(
{
title: sel,
},
sel),
this.renderMutations(selectors[sel].remove, selectors[sel].add)
);
});
renderRule(ruleId, rule, rules) {
const selector = rule.selector;
if (this.renderedRules.includes(ruleId)) {
return null;
}
// Mark this rule as rendered so we don't render it again.
this.renderedRules.push(ruleId);
return dom.div(
{
className: "rule",
},
dom.div(
{
className: "level selector",
title: selector,
},
selector,
dom.span({ className: "bracket-open" }, "{")
),
// Render any nested child rules if they are present.
rule.children.length > 0 && rule.children.map(childRuleId => {
return this.renderRule(childRuleId, rules[childRuleId], rules);
}),
// Render any changed CSS declarations.
this.renderDeclarations(rule.remove, rule.add),
dom.span({ className: "level bracket-close" }, "}")
);
}
renderDiff(diff = {}) {
// Render groups of style sources: stylesheets, embedded styles and inline styles
return Object.keys(diff).map(href => {
renderDiff(changes = {}) {
// Render groups of style sources: stylesheets and element style attributes.
return Object.entries(changes).map(([sourceId, source]) => {
const href = source.href || `inline stylesheet #${source.index}`;
const rules = source.rules;
return dom.details(
{ className: "source", open: true },
{
className: "source devtools-monospace",
open: true,
},
dom.summary(
{
className: "href",
title: href,
},
href),
// Render groups of selectors
this.renderSelectors(diff[href])
// Render changed rules within this source.
Object.entries(rules).map(([ruleId, rule]) => {
return this.renderRule(ruleId, rule, rules);
})
);
});
}
render() {
// Reset log of rendered rules.
this.renderedRules = [];
return dom.div(
{
className: "theme-sidebar inspector-tabpanel",
id: "sidebar-panel-changes",
},
this.renderDiff(this.props.changes.diff)
this.renderDiff(this.props.changes)
);
}
}

View File

@ -6,4 +6,5 @@
DevToolsModules(
'ChangesApp.js',
'CSSDeclaration.js',
)

View File

@ -8,9 +8,12 @@ DIRS += [
'actions',
'components',
'reducers',
'utils',
]
DevToolsModules(
'ChangesManager.js',
'ChangesView.js',
)
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']

View File

@ -4,113 +4,229 @@
"use strict";
const { getSourceHash, getRuleHash } = require("../utils/changes-utils");
const {
RESET_CHANGES,
TRACK_CHANGE,
} = require("../actions/index");
const INITIAL_STATE = {
/**
* Diff of changes grouped by stylesheet href, then by selector, then into add/remove
* objects with CSS property names and values for corresponding changes.
*
* Structure:
*
* diff = {
* "href": {
* "selector": {
* add: {
* "property": value
* ... // more properties
* },
* remove: {
* "property": value
* ...
* }
* },
* ... // more selectors
* }
* ... // more stylesheet hrefs
* }
*/
diff: {},
};
/**
* Return a deep clone of the given state object.
*
* @param {Object} state
* @return {Object}
*/
function cloneState(state = {}) {
return Object.entries(state).reduce((sources, [sourceId, source]) => {
sources[sourceId] = {
...source,
rules: Object.entries(source.rules).reduce((rules, [ruleId, rule]) => {
rules[ruleId] = {
...rule,
children: rule.children.slice(0),
add: { ...rule.add },
remove: { ...rule.remove },
};
return rules;
}, {}),
};
return sources;
}, {});
}
/**
* Mutate the given diff object with data about a new change.
* Given information about a CSS rule and its ancestor rules (@media, @supports, etc),
* create entries in the given rules collection for each rule and assign parent/child
* dependencies.
*
* @param {Object} ruleData
* Information about a CSS rule:
* {
* selector: {String}
* CSS selector text
* ancestors: {Array}
* Flattened CSS rule tree of the rule's ancestors with the root rule
* at the beginning of the array and the leaf rule at the end.
* ruleIndex: {Array}
* Indexes of each ancestor rule within its parent rule.
* }
*
* @param {Object} rules
* Collection of rules to be mutated.
* This is a reference to the corresponding `rules` object from the state.
*
* @param {Object} diff
* Diff object from the store.
* @param {Object} change
* Data about the change: which property was added or removed, on which selector,
* in which stylesheet or whether the source is an element's inline style.
* @return {Object}
* Mutated diff object.
* Entry for the CSS rule created the given collection of rules.
*/
function updateDiff(diff = {}, change = {}) {
// Ensure expected diff structure exists
diff[change.href] = diff[change.href] || {};
diff[change.href][change.selector] = diff[change.href][change.selector] || {};
diff[change.href][change.selector].add = diff[change.href][change.selector].add || {};
diff[change.href][change.selector].remove = diff[change.href][change.selector].remove
|| {};
function createRule(ruleData, rules) {
// Append the rule data to the flattened CSS rule tree with its ancestors.
const ruleAncestry = [...ruleData.ancestors, { ...ruleData }];
// Reference to the diff data for this stylesheet/selector pair.
const ref = diff[change.href][change.selector];
return ruleAncestry
// First, generate a unique identifier for each rule.
.map((rule, index) => {
// Ensure each rule has ancestors excluding itself (expand the flattened rule tree).
rule.ancestors = ruleAncestry.slice(0, index);
// Ensure each rule has a selector text.
// For the purpose of displaying in the UI, we treat at-rules as selectors.
if (!rule.selector) {
rule.selector =
`${rule.typeName} ${(rule.conditionText || rule.name || rule.keyText)}`;
}
// Track the remove operation only if the property WAS NOT previously introduced by an
// add operation. This ensures that repeated changes of the same property show up as a
// single remove operation of the original value.
if (change.remove && change.remove.property && !ref.add[change.remove.property]) {
ref.remove[change.remove.property] = change.remove.value;
}
return getRuleHash(rule);
})
// Then, create new entries in the rules collection and assign dependencies.
.map((ruleId, index, array) => {
const { selector } = ruleAncestry[index];
const prevRuleId = array[index - 1];
const nextRuleId = array[index + 1];
if (change.add && change.add.property) {
ref.add[change.add.property] = change.add.value;
}
// Copy or create an entry for this rule.
rules[ruleId] = Object.assign({}, { selector, children: [] }, rules[ruleId]);
const propertyName = change.add && change.add.property ||
change.remove && change.remove.property;
// The next ruleId is lower in the rule tree, therefore it's a child of this rule.
if (nextRuleId && !rules[ruleId].children.includes(nextRuleId)) {
rules[ruleId].children.push(nextRuleId);
}
// Remove information about operations on the property if they cancel each other out.
if (ref.add[propertyName] === ref.remove[propertyName]) {
delete ref.add[propertyName];
delete ref.remove[propertyName];
// The previous ruleId is higher in the rule tree, therefore it's the parent.
if (prevRuleId) {
rules[ruleId].parent = prevRuleId;
}
// Remove information about the selector if there are no changes to its declarations.
if (Object.keys(ref.add).length === 0 && Object.keys(ref.remove).length === 0) {
delete diff[change.href][change.selector];
}
// Remove information about the stylesheet if there are no changes to its rules.
if (Object.keys(diff[change.href]).length === 0) {
delete diff[change.href];
}
}
ref.tag = change.tag;
return diff;
return rules[ruleId];
})
// Finally, return the last rule in the array which is the rule we set out to create.
.pop();
}
function removeRule(ruleId, rules) {
const rule = rules[ruleId];
// First, remove this rule's id from its parent's list of children
if (rule.parent && rules[rule.parent]) {
rules[rule.parent].children = rules[rule.parent].children.filter(childRuleId => {
return childRuleId !== ruleId;
});
// Remove the parent rule if it has no children left.
if (!rules[rule.parent].children.length) {
removeRule(rule.parent, rules);
}
}
delete rules[ruleId];
}
/**
* Aggregated changes grouped by sources (stylesheet/element), which contain rules,
* which contain collections of added and removed CSS declarations.
*
* Structure:
* <sourceId>: {
* type: // "stylesheet" or "element"
* href: // Stylesheet or document URL
* rules: {
* <ruleId>: {
* selector: "" // String CSS selector or CSS at-rule text
* children: [] // Array of <ruleId> for child rules of this rule.
* parent: // <ruleId> of the parent rule
* add: {
* <property>: <value> // CSS declaration
* ...
* },
* remove: {
* <property>: <value> // CSS declaration
* ...
* }
* }
* ... // more rules
* }
* }
* ... // more sources
*/
const INITIAL_STATE = {};
const reducers = {
[TRACK_CHANGE](state, { data }) {
[TRACK_CHANGE](state, { change }) {
const defaults = {
href: "",
selector: "",
tag: null,
add: null,
remove: null,
selector: null,
source: {},
ancestors: [],
add: {},
remove: {},
};
data = { ...defaults, ...data };
change = { ...defaults, ...change };
state = cloneState(state);
// Update the state in-place with data about a style change (no deep clone of state).
// TODO: redefine state as a shallow object structure after figuring how to track
// both CSS Declarations and CSS Rules and At-Rules (@media, @keyframes, etc).
// @See https://bugzilla.mozilla.org/show_bug.cgi?id=1491263
return Object.assign({}, { diff: updateDiff(state.diff, data) });
const { type, href, index } = change.source;
const { selector, ancestors, ruleIndex } = change;
const sourceId = getSourceHash(change.source);
const ruleId = getRuleHash({ selector, ancestors, ruleIndex });
// Copy or create object identifying the source (styelsheet/element) for this change.
const source = Object.assign({}, state[sourceId], { type, href, index });
// Copy or create collection of all rules ever changed in this source.
const rules = Object.assign({}, source.rules);
// Refrence or create object identifying the rule for this change.
let rule = rules[ruleId];
if (!rule) {
rule = createRule({ selector, ancestors, ruleIndex }, rules);
}
// Copy or create collection of all CSS declarations ever added to this rule.
const add = Object.assign({}, rule.add);
// Copy or create collection of all CSS declarations ever removed from this rule.
const remove = Object.assign({}, rule.remove);
if (change.remove && change.remove.property) {
// Track the remove operation only if the property was not previously introduced
// by an add operation. This ensures repeated changes of the same property
// register as a single remove operation of its original value.
if (!add[change.remove.property]) {
remove[change.remove.property] = change.remove.value;
}
// Delete any previous add operation which would be canceled out by this remove.
if (add[change.remove.property] === change.remove.value) {
delete add[change.remove.property];
}
}
if (change.add && change.add.property) {
add[change.add.property] = change.add.value;
}
const property = change.add && change.add.property ||
change.remove && change.remove.property;
// Remove tracked operations if they cancel each other out.
if (add[property] === remove[property]) {
delete add[property];
delete remove[property];
}
// Remove information about the rule if none its declarations changed.
if (!Object.keys(add).length && !Object.keys(remove).length) {
removeRule(ruleId, rules);
source.rules = { ...rules };
} else {
source.rules = { ...rules, [ruleId]: { ...rule, add, remove } };
}
// Remove information about the source if none of its rules changed.
if (!Object.keys(source.rules).length) {
delete state[sourceId];
} else {
state[sourceId] = source;
}
return state;
},
[RESET_CHANGES](state) {

View File

@ -0,0 +1,6 @@
"use strict";
module.exports = {
// Extend from the shared list of defined globals for mochitests.
"extends": "../../../../.eslintrc.mochitests.js",
};

View File

@ -0,0 +1,17 @@
[DEFAULT]
tags = devtools
subsuite = devtools
support-files =
head.js
!/devtools/client/inspector/test/head.js
!/devtools/client/inspector/test/shared-head.js
!/devtools/client/inspector/rules/test/head.js
!/devtools/client/shared/test/shared-head.js
!/devtools/client/shared/test/shared-redux-head.js
!/devtools/client/shared/test/telemetry-test-helpers.js
!/devtools/client/shared/test/test-actor.js
!/devtools/client/shared/test/test-actor-registry.js
[browser_changes_declaration_disable.js]
[browser_changes_declaration_edit_value.js]
[browser_changes_declaration_remove.js]

View File

@ -0,0 +1,47 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that toggling a CSS declaration in the Rule view is tracked.
const TEST_URI = `
<style type='text/css'>
div {
color: red;
}
</style>
<div></div>
`;
add_task(async function() {
await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
const { inspector, view: ruleView } = await openRuleView();
const { document: doc, store } = selectChangesView(inspector);
const panel = doc.querySelector("#sidebar-panel-changes");
await selectNode("div", inspector);
const rule = getRuleViewRuleEditor(ruleView, 1).rule;
const prop = rule.textProps[0];
let onTrackChange = waitUntilAction(store, "TRACK_CHANGE");
info("Disable the first declaration");
await togglePropStatus(ruleView, prop);
info("Wait for change to be tracked");
await onTrackChange;
let removedDeclarations = panel.querySelectorAll(".diff-remove");
is(removedDeclarations.length, 1, "Only one declaration was tracked as removed");
onTrackChange = waitUntilAction(store, "TRACK_CHANGE");
info("Re-enable the first declaration");
await togglePropStatus(ruleView, prop);
info("Wait for change to be tracked");
await onTrackChange;
const addedDeclarations = panel.querySelectorAll(".diff-add");
removedDeclarations = panel.querySelectorAll(".diff-remove");
is(addedDeclarations.length, 0, "No declarations were tracked as added");
is(removedDeclarations.length, 0, "No declarations were tracked as removed");
});

View File

@ -0,0 +1,95 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that editing the value of a CSS declaration in the Rule view is tracked.
const TEST_URI = `
<style type='text/css'>
div {
color: red;
}
</style>
<div></div>
`;
/*
This array will be iterated over in order and the `value` property will be used to
update the value of the first CSS declaration.
The `add` and `remove` objects hold the expected values of the tracked declarations
shown in the Changes panel. If `add` or `remove` are null, it means we don't expect
any corresponding tracked declaration to show up in the Changes panel.
*/
const VALUE_CHANGE_ITERATIONS = [
// No changes should be tracked if the value did not actually change.
{
value: "red",
add: null,
remove: null,
},
// Changing the priority flag "!important" should be tracked.
{
value: "red !important",
add: { value: "red !important" },
remove: { value: "red" },
},
// Repeated changes should still show the original value as the one removed.
{
value: "blue",
add: { value: "blue" },
remove: { value: "red" },
},
// Restoring the original value should clear tracked changes.
{
value: "red",
add: null,
remove: null,
},
];
add_task(async function() {
await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
const { inspector, view: ruleView } = await openRuleView();
const { document: doc, store } = selectChangesView(inspector);
const panel = doc.querySelector("#sidebar-panel-changes");
await selectNode("div", inspector);
const rule = getRuleViewRuleEditor(ruleView, 1).rule;
const prop = rule.textProps[0];
let onTrackChange;
let removeValue;
let addValue;
for (const { value, add, remove } of VALUE_CHANGE_ITERATIONS) {
onTrackChange = waitUntilAction(store, "TRACK_CHANGE");
info(`Change the CSS declaration value to ${value}`);
await setProperty(ruleView, prop, value);
info("Wait for the change to be tracked");
await onTrackChange;
addValue = panel.querySelector(".diff-add .declaration-value");
removeValue = panel.querySelector(".diff-remove .declaration-value");
if (add) {
is(addValue.textContent, add.value,
`Added declaration has expected value: ${add.value}`);
is(panel.querySelectorAll(".diff-add").length, 1,
"Only one declaration was tracked as added.");
} else {
ok(!addValue, `Added declaration was cleared`);
}
if (remove) {
is(removeValue.textContent, remove.value,
`Removed declaration has expected value: ${remove.value}`);
is(panel.querySelectorAll(".diff-remove").length, 1,
"Only one declaration was tracked as removed.");
} else {
ok(!removeValue, `Removed declaration was cleared`);
}
}
});

View File

@ -0,0 +1,38 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that removing a CSS declaration from a rule in the Rule view is tracked.
const TEST_URI = `
<style type='text/css'>
div {
color: red;
}
</style>
<div></div>
`;
add_task(async function() {
await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
const { inspector, view: ruleView } = await openRuleView();
const { document: doc, store } = selectChangesView(inspector);
const panel = doc.querySelector("#sidebar-panel-changes");
await selectNode("div", inspector);
const rule = getRuleViewRuleEditor(ruleView, 1).rule;
const prop = rule.textProps[0];
const onTrackChange = waitUntilAction(store, "TRACK_CHANGE");
info("Remove the first declaration");
await removeProperty(ruleView, prop);
info("Wait for change to be tracked");
await onTrackChange;
const removeName = panel.querySelector(".diff-remove .declaration-name");
const removeValue = panel.querySelector(".diff-remove .declaration-value");
is(removeName.textContent, "color", "Correct declaration name was tracked as removed");
is(removeValue.textContent, "red", "Correct declaration value was tracked as removed");
});

View File

@ -0,0 +1,31 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* eslint no-unused-vars: [2, {"vars": "local"}] */
/* import-globals-from ../../test/head.js */
/* import-globals-from ../../../inspector/rules/test/head.js */
/* import-globals-from ../../../inspector/test/shared-head.js */
/* import-globals-from ../../../shared/test/shared-redux-head.js */
"use strict";
// Load the Rule view's test/head.js to make use of its helpers.
// It loads inspector/test/head.js which itself loads inspector/test/shared-head.js
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/devtools/client/inspector/rules/test/head.js",
this);
// Load the shared Redux helpers.
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/devtools/client/shared/test/shared-redux-head.js",
this);
// Ensure the Changes panel is enabled before running the tests.
Services.prefs.setBoolPref("devtools.inspector.changes.enabled", true);
// Ensure the three-pane mode is enabled before running the tests.
Services.prefs.setBoolPref("devtools.inspector.three-pane-enabled", true);
registerCleanupFunction(() => {
Services.prefs.clearUserPref("devtools.inspector.changes.enabled");
Services.prefs.clearUserPref("devtools.inspector.three-pane-enabled");
});

View File

@ -0,0 +1,58 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
/**
* Generate a hash that uniquely identifies a stylesheet or element style attribute.
*
* @param {Object} source
* Information about a stylesheet or element style attribute:
* {
* type: {String}
* One of "stylesheet" or "element".
* index: {Number|String}
* Position of the styleshet in the list of stylesheets in the document.
* If `type` is "element", `index` is the generated selector which
* uniquely identifies the element in the document.
* href: {String|null}
* URL of the stylesheet or of the document when `type` is "element".
* If the stylesheet is inline, `href` is null.
* }
* @return {String}
*/
function getSourceHash(source) {
const { type, index, href = "inline" } = source;
return `${type}${index}${href}`;
}
/**
* Generate a hash that uniquely identifies a CSS rule.
*
* @param {Object} ruleData
* Information about a CSS rule:
* {
* selector: {String}
* CSS selector text
* ancestors: {Array}
* Flattened CSS rule tree of the rule's ancestors with the root rule
* at the beginning of the array and the leaf rule at the end.
* ruleIndex: {Array}
* Indexes of each ancestor rule within its parent rule.
* }
* @return {String}
*/
function getRuleHash(ruleData) {
const { selector = "", ancestors = [], ruleIndex } = ruleData;
const atRules = ancestors.reduce((acc, rule) => {
acc += `${rule.typeName} ${(rule.conditionText || rule.name || rule.keyText)}`;
return acc;
}, "");
return `${atRules}${selector}${ruleIndex}`;
}
module.exports.getSourceHash = getSourceHash;
module.exports.getRuleHash = getRuleHash;

View File

@ -0,0 +1,9 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DevToolsModules(
'changes-utils.js',
)

View File

@ -278,7 +278,7 @@ Inspector.prototype = {
// the ChangesActor. We want the ChangesActor to be guaranteed available before
// the user makes any changes.
this.changesFront = this.toolbox.target.getFront("changes");
await this.changesFront.allChanges();
this.changesFront.allChanges();
}
// Setup the splitter before the sidebar is displayed so, we don't miss any events.

View File

@ -120,6 +120,24 @@ function openComputedView() {
});
}
/**
* Open the toolbox, with the inspector tool visible, and the changes view
* sidebar tab selected.
*
* @return a promise that resolves when the inspector is ready and the changes
* view is visible and ready
*/
function openChangesView() {
return openInspectorSidebarTab("changesview").then(data => {
return {
toolbox: data.toolbox,
inspector: data.inspector,
testActor: data.testActor,
view: data.inspector.changesView,
};
});
}
/**
* Open the toolbox, with the inspector tool visible, and the layout view
* sidebar tab selected to display the box model view with properties.
@ -176,6 +194,18 @@ function selectComputedView(inspector) {
return inspector.getPanel("computedview").computedView;
}
/**
* Select the changes view sidebar tab on an already opened inspector panel.
*
* @param {InspectorPanel} inspector
* The opened inspector panel
* @return {ChangesView} the changes view
*/
function selectChangesView(inspector) {
inspector.sidebar.select("changesview");
return inspector.changesView;
}
/**
* Select the layout view sidebar tab on an already opened inspector panel.
*

View File

@ -8,70 +8,70 @@
--diff-add-text-color: #54983f;
--diff-remove-background-color: #fbf2f5;
--diff-remove-text-color: #bf7173;
--diff-level-offset: 15px;
}
#sidebar-panel-changes {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: auto;
}
details {
font-family: var(--monospace-font-family);
font-size: 12px;
}
summary {
outline: none;
padding: 5px 0;
-moz-user-select: none;
cursor: pointer;
}
details.source[open] {
#sidebar-panel-changes .source[open] {
padding-bottom: 10px;
}
details.source > summary {
#sidebar-panel-changes .href {
background: var(--theme-sidebar-background);
border-top: 1px solid var(--theme-splitter-color);
border-bottom: 1px solid var(--theme-splitter-color);
padding-left: 5px;
padding: 5px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
cursor: pointer;
}
details.selector > summary {
padding-left: 10px;
}
details.selector summary::after{
content: "{…}";
display: inline-block;
padding-left: 5px;
}
details.selector[open]{
margin-bottom: 5px;
}
details.selector[open] summary::after{
content: "{";
}
details.selector[open]::after{
content: "}";
display: block;
padding-left: 10px;
}
.line {
padding: 3px 5px 3px 15px;
#sidebar-panel-changes .rule .level {
padding-top: 3px;
padding-right: 5px;
padding-bottom: 3px;
padding-left: var(--diff-level-offset);
position: relative;
}
#sidebar-panel-changes .rule > .rule .level {
padding-left: calc(var(--diff-level-offset) * 2);
}
#sidebar-panel-changes .rule > .rule > .rule .level {
padding-left: calc(var(--diff-level-offset) * 3);
}
#sidebar-panel-changes .rule > .rule > .rule > .rule .level {
padding-left: calc(var(--diff-level-offset) * 4);
}
#sidebar-panel-changes .rule .selector,
#sidebar-panel-changes .rule .bracket-close {
margin-left: calc(-1 * var(--diff-level-offset) + 5px);
}
#sidebar-panel-changes .rule .bracket-open {
display: inline-block;
margin-left: 5px;
}
#sidebar-panel-changes .declaration-name {
margin-left: 10px;
}
#sidebar-panel-changes .declaration-value {
margin-left: 5px;
}
.diff-add::before,
.diff-remove::before{
position: absolute;

View File

@ -165,6 +165,9 @@ class FlexboxHighlighter extends AutoRefreshHighlighter {
this.clearCache();
this.flexData = null;
this.mainAxisDirection = null;
this.crossAxisDirection = null;
this.axes = null;
AutoRefreshHighlighter.prototype.destroy.call(this);
}
@ -301,8 +304,21 @@ class FlexboxHighlighter extends AutoRefreshHighlighter {
this.computedStyle = getComputedStyle(this.currentNode);
}
const flex = this.currentNode.getAsFlexContainer();
const oldCrossAxisDirection = this.crossAxisDirection;
this.crossAxisDirection = flex.crossAxisDirection;
const newCrossAxisDirection = this.crossAxisDirection;
const oldMainAxisDirection = this.mainAxisDirection;
this.mainAxisDirection = flex.mainAxisDirection;
const newMainAxisDirection = this.mainAxisDirection;
// Concatenate the axes to simplify conditionals.
this.axes = `${this.mainAxisDirection} ${this.crossAxisDirection}`;
const oldFlexData = this.flexData;
this.flexData = getFlexData(this.currentNode.getAsFlexContainer(), this.win);
this.flexData = getFlexData(flex, this.win);
const hasFlexDataChanged = compareFlexData(oldFlexData, this.flexData);
const oldAlignItems = this.alignItemsValue;
@ -326,7 +342,9 @@ class FlexboxHighlighter extends AutoRefreshHighlighter {
oldAlignItems !== newAlignItems ||
oldFlexDirection !== newFlexDirection ||
oldFlexWrap !== newFlexWrap ||
oldJustifyContent !== newJustifyContent;
oldJustifyContent !== newJustifyContent ||
oldCrossAxisDirection !== newCrossAxisDirection ||
oldMainAxisDirection !== newMainAxisDirection;
}
_hide() {
@ -588,50 +606,78 @@ class FlexboxHighlighter extends AutoRefreshHighlighter {
this.ctx.strokeStyle = this.color;
const { bounds } = this.currentQuads.content[0];
const isColumn = this.flexDirection.startsWith("column");
const options = { matrix: this.currentMatrix };
for (const flexLine of this.flexData.lines) {
const { crossStart, crossSize } = flexLine;
if (isColumn) {
clearRect(this.ctx, crossStart, 0, crossStart + crossSize, bounds.height,
this.currentMatrix);
switch (this.axes) {
case "horizontal-lr vertical-tb":
case "horizontal-lr vertical-bt":
case "horizontal-rl vertical-tb":
case "horizontal-rl vertical-bt":
clearRect(this.ctx, 0, crossStart, bounds.width, crossStart + crossSize,
this.currentMatrix);
// Avoid drawing the start flex line when they overlap with the flex container.
if (crossStart != 0) {
drawLine(this.ctx, crossStart, 0, crossStart, bounds.height, options);
this.ctx.stroke();
}
// Avoid drawing the start flex line when they overlap with the flex container.
if (crossStart != 0) {
drawLine(this.ctx, 0, crossStart, bounds.width, crossStart, options);
this.ctx.stroke();
}
// Avoid drawing the end flex line when they overlap with the flex container.
if (bounds.width - crossStart - crossSize >= lineWidth) {
drawLine(this.ctx, crossStart + crossSize, 0, crossStart + crossSize,
bounds.height, options);
this.ctx.stroke();
}
} else {
clearRect(this.ctx, 0, crossStart, bounds.width, crossStart + crossSize,
this.currentMatrix);
// Avoid drawing the end flex line when they overlap with the flex container.
if (bounds.height - crossStart - crossSize >= lineWidth) {
drawLine(this.ctx, 0, crossStart + crossSize, bounds.width,
crossStart + crossSize, options);
this.ctx.stroke();
}
break;
case "vertical-tb horizontal-lr":
case "vertical-bt horizontal-rl":
clearRect(this.ctx, crossStart, 0, crossStart + crossSize, bounds.height,
this.currentMatrix);
// Avoid drawing the start flex line when they overlap with the flex container.
if (crossStart != 0) {
drawLine(this.ctx, 0, crossStart, bounds.width, crossStart, options);
this.ctx.stroke();
}
// Avoid drawing the start flex line when they overlap with the flex container.
if (crossStart != 0) {
drawLine(this.ctx, crossStart, 0, crossStart, bounds.height, options);
this.ctx.stroke();
}
// Avoid drawing the end flex line when they overlap with the flex container.
if (bounds.height - crossStart - crossSize >= lineWidth) {
drawLine(this.ctx, 0, crossStart + crossSize, bounds.width,
crossStart + crossSize, options);
this.ctx.stroke();
}
// Avoid drawing the end flex line when they overlap with the flex container.
if (bounds.width - crossStart - crossSize >= lineWidth) {
drawLine(this.ctx, crossStart + crossSize, 0, crossStart + crossSize,
bounds.height, options);
this.ctx.stroke();
}
break;
case "vertical-bt horizontal-lr":
case "vertical-tb horizontal-rl":
clearRect(this.ctx, bounds.width - crossStart, 0,
bounds.width - crossStart - crossSize, bounds.height, this.currentMatrix);
// Avoid drawing the start flex line when they overlap with the flex container.
if (crossStart != 0) {
drawLine(this.ctx, bounds.width - crossStart, 0, bounds.width - crossStart,
bounds.height, options);
this.ctx.stroke();
}
// Avoid drawing the end flex line when they overlap with the flex container.
if (bounds.width - crossStart - crossSize >= lineWidth) {
drawLine(this.ctx, bounds.width - crossStart - crossSize, 0,
bounds.width - crossStart - crossSize, bounds.height, options);
this.ctx.stroke();
}
break;
}
}
this.ctx.restore();
}
/**
* Clear the whole alignment container along the main axis for each flex item.
*/
renderJustifyContent() {
if (!this.flexData || !this.currentQuads.content || !this.currentQuads.content[0]) {
return;
@ -639,11 +685,12 @@ class FlexboxHighlighter extends AutoRefreshHighlighter {
const zoom = getCurrentZoom(this.win);
const { bounds } = this.currentQuads.content[0];
const isColumn = this.flexDirection.startsWith("column");
// Draw a justify content pattern over the whole flex container.
this.drawJustifyContent(0, 0, bounds.width, bounds.height, this.currentMatrix);
for (const flexLine of this.flexData.lines) {
const { crossStart, crossSize } = flexLine;
let mainStart = 0;
for (const flexItem of flexLine.items) {
const quads = flexItem.quads;
@ -658,23 +705,27 @@ class FlexboxHighlighter extends AutoRefreshHighlighter {
const right = Math.round(flexItemBounds.right / zoom - bounds.left);
const bottom = Math.round(flexItemBounds.bottom / zoom - bounds.top);
if (isColumn) {
this.drawJustifyContent(crossStart, mainStart, crossStart + crossSize, top);
mainStart = bottom;
} else {
this.drawJustifyContent(mainStart, crossStart, left, crossStart + crossSize);
mainStart = right;
// Clear a rectangular are covering the alignment container.
switch (this.axes) {
case "horizontal-lr vertical-tb":
case "horizontal-lr vertical-bt":
case "horizontal-rl vertical-tb":
case "horizontal-rl vertical-bt":
clearRect(this.ctx, left, Math.round(crossStart) + 2, right,
Math.round(crossStart + crossSize) - 2, this.currentMatrix);
break;
case "vertical-tb horizontal-lr":
case "vertical-bt horizontal-rl":
clearRect(this.ctx, Math.round(crossStart) + 1, top,
Math.round(crossStart + crossSize), bottom, this.currentMatrix);
break;
case "vertical-bt horizontal-lr":
case "vertical-tb horizontal-rl":
clearRect(this.ctx, Math.round(bounds.width - crossStart - crossSize) + 1,
top, Math.round(bounds.width - crossStart), bottom, this.currentMatrix);
break;
}
}
// Draw the last justify-content area after the last flex item.
if (isColumn) {
this.drawJustifyContent(crossStart, mainStart, crossStart + crossSize,
bounds.height);
} else {
this.drawJustifyContent(mainStart, crossStart, bounds.width,
crossStart + crossSize);
}
}
}

View File

@ -27,7 +27,10 @@ loader.lazyRequireGetter(this, "UPDATE_PRESERVING_RULES",
"devtools/server/actors/stylesheets", true);
loader.lazyRequireGetter(this, "UPDATE_GENERAL",
"devtools/server/actors/stylesheets", true);
loader.lazyRequireGetter(this, "findCssSelector", "devtools/shared/inspector/css-logic", true);
loader.lazyRequireGetter(this, "findCssSelector",
"devtools/shared/inspector/css-logic", true);
loader.lazyRequireGetter(this, "CSSRuleTypeName",
"devtools/shared/inspector/css-logic", true);
loader.lazyGetter(this, "PSEUDO_ELEMENTS", () => {
return InspectorUtils.getCSSPseudoElementNames();
@ -986,13 +989,13 @@ var StyleRuleActor = protocol.ActorClassWithSpec(styleRuleSpec, {
if (CSSRule.isInstance(item)) {
this.type = item.type;
this.rawRule = item;
this._computeRuleIndex();
if ((this.type === CSSRule.STYLE_RULE ||
this.type === CSSRule.KEYFRAME_RULE) &&
this.rawRule.parentStyleSheet) {
this.line = InspectorUtils.getRelativeRuleLine(this.rawRule);
this.column = InspectorUtils.getRuleColumn(this.rawRule);
this._parentSheet = this.rawRule.parentStyleSheet;
this._computeRuleIndex();
this.sheetActor = this.pageStyle._sheetRef(this._parentSheet);
this.sheetActor.on("style-applied", this._onStyleApplied);
}
@ -1049,6 +1052,25 @@ var StyleRuleActor = protocol.ActorClassWithSpec(styleRuleSpec, {
this._parentSheet.href !== "about:PreferenceStyleSheet");
},
/**
* Return an array with StyleRuleActor instances for each of this rule's ancestor rules
* (@media, @supports, @keyframes, etc) obtained by recursively reading rule.parentRule.
* If the rule has no ancestors, return an empty array.
*
* @return {Array}
*/
get ancestorRules() {
const ancestors = [];
let rule = this.rawRule;
while (rule.parentRule) {
ancestors.push(this.pageStyle._styleRef(rule.parentRule));
rule = rule.parentRule;
}
return ancestors;
},
getDocument: function(sheet) {
if (sheet.ownerNode) {
return sheet.ownerNode.nodeType == sheet.ownerNode.DOCUMENT_NODE ?
@ -1186,10 +1208,10 @@ var StyleRuleActor = protocol.ActorClassWithSpec(styleRuleSpec, {
const result = [];
while (rule) {
let cssRules;
let cssRules = [];
if (rule.parentRule) {
cssRules = rule.parentRule.cssRules;
} else {
} else if (rule.parentStyleSheet) {
cssRules = rule.parentStyleSheet.cssRules;
}
@ -1467,28 +1489,69 @@ var StyleRuleActor = protocol.ActorClassWithSpec(styleRuleSpec, {
* Data about a modification to a rule. @see |modifyProperties()|
*/
logChange(change) {
const prevValue = this._declarations[change.index]
? this._declarations[change.index].value
: null;
const prevName = this._declarations[change.index]
? this._declarations[change.index].name
: null;
// Destructure properties from the previous CSS declaration at this index, if any,
// to new variable names to indicate the previous state.
let {
value: prevValue,
name: prevName,
priority: prevPriority,
commentOffsets,
} = this._declarations[change.index] || {};
// A declaration is disabled if it has a `commentOffsets` array.
// Here we type coerce the value to a boolean with double-bang (!!)
const prevDisabled = !!commentOffsets;
// Append the "!important" string if defined in the previous priority flag.
prevValue = (prevValue && prevPriority) ? `${prevValue} !important` : prevValue;
// Metadata about a change.
const data = {};
data.type = change.type;
data.selector = this.rawRule.selectorText;
// Collect information about the rule's ancestors (@media, @supports, @keyframes).
// Used to show context for this change in the UI and to match the rule for undo/redo.
data.ancestors = this.ancestorRules.map(rule => {
return {
// Rule type as number defined by CSSRule.type (ex: 4, 7, 12)
// @see https://developer.mozilla.org/en-US/docs/Web/API/CSSRule
type: rule.rawRule.type,
// Rule type as human-readable string (ex: "@media", "@supports", "@keyframes")
typeName: CSSRuleTypeName[rule.rawRule.type],
// Conditions of @media and @supports rules (ex: "min-width: 1em")
conditionText: rule.rawRule.conditionText,
// Name of @keyframes rule; refrenced by the animation-name CSS property.
name: rule.rawRule.name,
// Selector of individual @keyframe rule within a @keyframes rule (ex: 0%, 100%).
keyText: rule.rawRule.keyText,
// Array with the indexes of this rule and its ancestors within the CSS rule tree.
ruleIndex: rule._ruleIndex,
};
});
// For inline style changes, generate a unique selector and pass the node tag.
// For changes in element style attributes, generate a unique selector.
if (this.type === ELEMENT_STYLE) {
data.tag = this.rawNode.tagName;
data.href = "inline";
// findCssSelector() fails on XUL documents. Catch and silently ignore that error.
try {
data.selector = findCssSelector(this.rawNode);
} catch (err) {}
data.source = {
type: "element",
// Used to differentiate between elements which match the same generated selector
// but live in different documents (ex: host document and iframe).
href: this.rawNode.baseURI,
// Element style attributes don't have a rule index; use the generated selector.
index: data.selector,
};
data.ruleIndex = 0;
} else {
data.href = this._parentSheet.href || "inline stylesheet";
data.selector = (this.type === CSSRule.KEYFRAME_RULE)
? this.rawRule.keyText
: this.rawRule.selectorText;
data.source = {
type: "stylesheet",
href: this.sheetActor.href,
index: this.sheetActor.styleSheetIndex,
};
// Used to differentiate between changes to rules with identical selectors.
data.ruleIndex = this._ruleIndex;
}
switch (change.type) {
@ -1497,14 +1560,26 @@ var StyleRuleActor = protocol.ActorClassWithSpec(styleRuleSpec, {
// Otherwise, a new declaration is being created or the value of an existing
// declaration is being updated. In that case, use the provided `change.name`.
const name = change.newName ? change.newName : change.name;
// Reuse the previous value when the property is being renamed.
const value = change.newName ? prevValue : change.value;
// Append the "!important" string if defined in the incoming priority flag.
const newValue = change.priority ? `${change.value} !important` : change.value;
// Reuse the previous value string, when the property is renamed.
// Otherwise, use the incoming value string.
const value = change.newName ? prevValue : newValue;
data.add = { property: name, value };
// If there is a previous value, log its removal together with the previous
// property name. Using the previous name handles the case for renaming a property
// and is harmless when updating an existing value (the name stays the same).
data.remove = prevValue ? { property: prevName, value: prevValue } : null;
// When toggling a declaration from OFF to ON, if not renaming the property,
// do not mark the previous declaration for removal, otherwise the add and
// remove operations will cancel each other out when tracked. Tracked changes
// have no context of "disabled", only "add" or remove, like diffs.
if (prevDisabled && !change.newName && prevValue === newValue) {
data.remove = null;
}
break;
case "remove":
@ -1513,14 +1588,6 @@ var StyleRuleActor = protocol.ActorClassWithSpec(styleRuleSpec, {
break;
}
// Do not track non-changes. This can occur when typing a value in the Rule view
// inline editor, then committing it by pressing the Enter key.
if (data.add && data.remove &&
data.add.property === data.remove.property &&
data.add.value === data.remove.value) {
return;
}
TrackChangeEmitter.trackChange(data);
},

View File

@ -875,6 +875,7 @@ RuleRewriter.prototype = {
setPropertyEnabled: function(index, name, isEnabled) {
this.completeInitialization(index);
const decl = this.decl;
const priority = decl.priority;
let copyOffset = decl.offsets[1];
if (isEnabled) {
// Enable it. First see if the comment start can be deleted.
@ -918,9 +919,9 @@ RuleRewriter.prototype = {
this.completeCopying(copyOffset);
if (isEnabled) {
this.modifications.push({ type: "set", index, name, value: decl.value });
this.modifications.push({ type: "set", index, name, value: decl.value, priority });
} else {
this.modifications.push({ type: "remove", index, name });
this.modifications.push({ type: "remove", index, name, priority });
}
},

View File

@ -82,6 +82,26 @@ exports.STATUS = {
UNKNOWN: -1,
};
/**
* Mapping of CSSRule type value to CSSRule type name.
* @see https://developer.mozilla.org/en-US/docs/Web/API/CSSRule
*/
exports.CSSRuleTypeName = {
1: "", // Regular CSS style rule has no name
3: "@import",
4: "@media",
5: "@font-face",
6: "@page",
7: "@keyframes",
8: "@keyframe",
10: "@namespace",
11: "@counter-style",
12: "@supports",
13: "@document",
14: "@font-feature-values",
15: "@viewport",
};
/**
* Lookup a l10n string in the shared styleinspector string bundle.
*

View File

@ -5784,6 +5784,31 @@ nsGlobalWindowOuter::PostMessageMozOuter(JSContext* aCx, JS::Handle<JS::Value> a
if (NS_WARN_IF(!providedPrincipal)) {
return;
}
} else {
// We still need to check the originAttributes if the target origin is '*'.
// But we will ingore the FPD here since the FPDs are possible to be different.
auto principal = BasePrincipal::Cast(GetPrincipal());
NS_ENSURE_TRUE_VOID(principal);
OriginAttributes targetAttrs = principal->OriginAttributesRef();
OriginAttributes sourceAttrs = aSubjectPrincipal.OriginAttributesRef();
// We have to exempt the check of OA if the subject prioncipal is a system
// principal since there are many tests try to post messages to content from
// chrome with a mismatch OA. For example, using the ContentTask.spawn() to
// post a message into a private browsing window. The injected code in
// ContentTask.spawn() will be executed under the system principal and the
// OA of the system principal mismatches with the OA of a private browsing
// window.
MOZ_DIAGNOSTIC_ASSERT(aSubjectPrincipal.GetIsSystemPrincipal() ||
sourceAttrs.EqualsIgnoringFPD(targetAttrs));
// If 'privacy.firstparty.isolate.block_post_message' is true, we will block
// postMessage across different first party domains.
if (OriginAttributes::IsBlockPostMessageForFPI() &&
!aSubjectPrincipal.GetIsSystemPrincipal() &&
sourceAttrs.mFirstPartyDomain != targetAttrs.mFirstPartyDomain) {
return;
}
}
// Create and asynchronously dispatch a runnable which will handle actual DOM

View File

@ -1294,7 +1294,8 @@ impl AlphaBatchBuilder {
for (segment_index, (segment, segment_data)) in segment_desc.segments
.iter()
.zip(segment_data.iter())
.enumerate() {
.enumerate()
{
self.add_segment_to_batch(
segment,
segment_data,
@ -1315,7 +1316,8 @@ impl AlphaBatchBuilder {
// between all segments.
for (segment_index, segment) in segment_desc.segments
.iter()
.enumerate() {
.enumerate()
{
self.add_segment_to_batch(
segment,
segment_data,
@ -1333,6 +1335,7 @@ impl AlphaBatchBuilder {
}
(None, SegmentDataKind::Shared(ref segment_data)) => {
// No segments, and thus no per-segment instance data.
// Note: the blend mode already takes opacity into account
let batch_key = BatchKey {
blend_mode: non_segmented_blend_mode,
kind: BatchKind::Brush(params.batch_kind),

View File

@ -1932,7 +1932,8 @@ impl PrimitiveStore {
prim_instance.clipped_world_rect = Some(pic_state.map_pic_to_world.bounds);
} else {
if prim.local_rect.size.width <= 0.0 ||
prim.local_rect.size.height <= 0.0 {
prim.local_rect.size.height <= 0.0
{
if cfg!(debug_assertions) && is_chased {
println!("\tculled for zero local rectangle");
}
@ -1979,6 +1980,9 @@ impl PrimitiveStore {
let clip_chain = match clip_chain {
Some(clip_chain) => clip_chain,
None => {
if cfg!(debug_assertions) && is_chased {
println!("\tunable to build the clip chain, skipping");
}
prim_instance.clipped_world_rect = None;
return false;
}
@ -2085,8 +2089,8 @@ impl PrimitiveStore {
let prim_index = prim_instance.prim_index;
let is_chased = Some(prim_index) == self.chase_id;
if is_chased {
println!("\tpreparing prim {:?} in pipeline {:?}",
if cfg!(debug_assertions) && is_chased {
println!("\tpreparing {:?} in {:?}",
prim_instance.prim_index, pic_context.pipeline_id);
}
@ -2579,7 +2583,7 @@ impl PrimitiveInstance {
self.prepared_frame_id = frame_state.render_tasks.frame_id();
}
match *prim_details {
self.opacity = match *prim_details {
PrimitiveDetails::TextRun(ref mut text) => {
// The transform only makes sense for screen space rasterization
let transform = prim_context.spatial_node.world_content_transform.to_transform();
@ -2591,6 +2595,7 @@ impl PrimitiveInstance {
display_list,
frame_state,
);
PrimitiveOpacity::translucent()
}
PrimitiveDetails::Brush(ref mut brush) => {
match brush.kind {
@ -2620,13 +2625,6 @@ impl PrimitiveInstance {
frame_state.gpu_cache.invalidate(&mut self.gpu_location);
}
// Update opacity for this primitive to ensure the correct
// batching parameters are used.
self.opacity.is_opaque =
image_properties.descriptor.is_opaque &&
opacity_binding.current == 1.0 &&
color.a == 1.0;
if *tile_spacing != LayoutSize::zero() && !is_tiled {
*source = ImageSource::Cache {
// Size in device-pixels we need to allocate in render task cache.
@ -2648,6 +2646,7 @@ impl PrimitiveInstance {
}
let mut request_source_image = false;
let mut is_opaque = image_properties.descriptor.is_opaque;
// Every frame, for cached items, we need to request the render
// task cache item. The closure will be invoked on the first
@ -2666,9 +2665,7 @@ impl PrimitiveInstance {
size.width += padding.horizontal();
size.height += padding.vertical();
if padding != DeviceIntSideOffsets::zero() {
self.opacity.is_opaque = false;
}
is_opaque &= padding == DeviceIntSideOffsets::zero();
let image_cache_key = ImageCacheKey {
request,
@ -2807,11 +2804,18 @@ impl PrimitiveInstance {
frame_state.gpu_cache,
);
}
if is_opaque {
PrimitiveOpacity::from_alpha(opacity_binding.current * color.a)
} else {
PrimitiveOpacity::translucent()
}
} else {
PrimitiveOpacity::opaque()
}
}
BrushKind::LineDecoration { ref mut handle, style, orientation, wavy_line_thickness, .. } => {
BrushKind::LineDecoration { color, ref mut handle, style, orientation, wavy_line_thickness } => {
// Work out the device pixel size to be used to cache this line decoration.
let size = get_line_decoration_sizes(
&prim_local_rect.size,
orientation,
@ -2819,6 +2823,10 @@ impl PrimitiveInstance {
wavy_line_thickness,
);
if cfg!(debug_assertions) && is_chased {
println!("\tline decoration opaque={}, sizes={:?}", self.opacity.is_opaque, size);
}
if let Some((inline_size, block_size)) = size {
let size = match orientation {
LineOrientation::Horizontal => LayoutSize::new(inline_size, block_size),
@ -2887,10 +2895,15 @@ impl PrimitiveInstance {
}
));
}
match style {
LineStyle::Solid => PrimitiveOpacity::from_alpha(color.a),
LineStyle::Dotted |
LineStyle::Dashed |
LineStyle::Wavy => PrimitiveOpacity::translucent(),
}
}
BrushKind::YuvImage { format, yuv_key, image_rendering, .. } => {
self.opacity = PrimitiveOpacity::opaque();
let channel_num = format.get_plane_num();
debug_assert!(channel_num <= 3);
for channel in 0 .. channel_num {
@ -2903,6 +2916,8 @@ impl PrimitiveInstance {
frame_state.gpu_cache,
);
}
PrimitiveOpacity::opaque()
}
BrushKind::Border { ref mut source, .. } => {
match *source {
@ -2912,15 +2927,15 @@ impl PrimitiveInstance {
.get_image_properties(request.key);
if let Some(image_properties) = image_properties {
// Update opacity for this primitive to ensure the correct
// batching parameters are used.
self.opacity.is_opaque =
image_properties.descriptor.is_opaque;
frame_state.resource_cache.request_image(
request,
frame_state.gpu_cache,
);
PrimitiveOpacity {
is_opaque: image_properties.descriptor.is_opaque,
}
} else {
PrimitiveOpacity::opaque()
}
}
BorderSource::Border { ref border, ref widths, ref mut segments, .. } => {
@ -2963,6 +2978,9 @@ impl PrimitiveInstance {
}
));
}
// Shouldn't matter, since the segment opacity is used instead
PrimitiveOpacity::translucent()
}
}
}
@ -3015,6 +3033,9 @@ impl PrimitiveInstance {
},
);
}
//TODO: can we make it opaque in some cases?
PrimitiveOpacity::translucent()
}
BrushKind::LinearGradient {
stops_range,
@ -3029,19 +3050,6 @@ impl PrimitiveInstance {
ref mut visible_tiles,
..
} => {
// If the coverage of the gradient extends to or beyond
// the primitive rect, then the opacity can be determined
// by the colors of the stops. If we have tiling / spacing
// then we just assume the gradient is translucent for now.
// (In the future we could consider segmenting in some cases).
let stride = stretch_size + tile_spacing;
self.opacity = if stride.width >= prim_local_rect.size.width &&
stride.height >= prim_local_rect.size.height {
stops_opacity
} else {
PrimitiveOpacity::translucent()
};
build_gradient_stops_request(
stops_handle,
stops_range,
@ -3078,6 +3086,19 @@ impl PrimitiveInstance {
}
);
}
// If the coverage of the gradient extends to or beyond
// the primitive rect, then the opacity can be determined
// by the colors of the stops. If we have tiling / spacing
// then we just assume the gradient is translucent for now.
// (In the future we could consider segmenting in some cases).
let stride = stretch_size + tile_spacing;
if stride.width >= prim_local_rect.size.width &&
stride.height >= prim_local_rect.size.height {
stops_opacity
} else {
PrimitiveOpacity::translucent()
}
}
BrushKind::Picture { pic_index, .. } => {
let pic = &mut pictures[pic_index.0];
@ -3101,6 +3122,8 @@ impl PrimitiveInstance {
} else {
self.clipped_world_rect = None;
}
PrimitiveOpacity::translucent()
}
BrushKind::Solid { ref color, ref mut opacity_binding, .. } => {
// If the opacity changed, invalidate the GPU cache so that
@ -3110,12 +3133,12 @@ impl PrimitiveInstance {
if opacity_binding.update(frame_context.scene_properties) {
frame_state.gpu_cache.invalidate(&mut self.gpu_location);
}
self.opacity = PrimitiveOpacity::from_alpha(opacity_binding.current * color.a);
PrimitiveOpacity::from_alpha(opacity_binding.current * color.a)
}
BrushKind::Clear => {}
BrushKind::Clear => PrimitiveOpacity::opaque(),
}
}
}
};
if is_tiled {
// we already requested each tile's gpu data.

View File

@ -52,7 +52,7 @@ fn render_task_sanity_check(size: &DeviceIntSize) {
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct RenderTaskId(pub u32, FrameId); // TODO(gw): Make private when using GPU cache!
#[derive(Debug, Copy, Clone)]
#[derive(Debug, Copy, Clone, PartialEq)]
#[repr(C)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]

View File

@ -1 +1 @@
925dd051a36f1c1cd02e7f635f7e439ab2804f15
2537e5f27c2ce7b64a93498c7569a870c190feda

View File

@ -6,15 +6,6 @@ ac_add_options --target=aarch64-linux-android
ac_add_options --with-branding=mobile/android/branding/nightly
export AR="$topsrcdir/clang/bin/llvm-ar"
export NM="$topsrcdir/clang/bin/llvm-nm"
export RANLIB="$topsrcdir/clang/bin/llvm-ranlib"
# Enable LTO if the NDK is available.
if [ -z "$NO_NDK" ]; then
ac_add_options --enable-lto
fi
export MOZILLA_OFFICIAL=1
export MOZ_TELEMETRY_REPORTING=1
export MOZ_ANDROID_POCKET=1

View File

@ -16,13 +16,4 @@ export MOZ_TELEMETRY_REPORTING=1
export MOZ_ANDROID_MMA=1
export MOZ_ANDROID_POCKET=1
export AR="$topsrcdir/clang/bin/llvm-ar"
export NM="$topsrcdir/clang/bin/llvm-nm"
export RANLIB="$topsrcdir/clang/bin/llvm-ranlib"
# Enable LTO if the NDK is available.
if [ -z "$NO_NDK" ]; then
ac_add_options --enable-lto
fi
. "$topsrcdir/mobile/android/config/mozconfigs/common.override"

View File

@ -14,13 +14,4 @@ export MOZILLA_OFFICIAL=1
export MOZ_TELEMETRY_REPORTING=1
export MOZ_ANDROID_POCKET=1
export AR="$topsrcdir/clang/bin/llvm-ar"
export NM="$topsrcdir/clang/bin/llvm-nm"
export RANLIB="$topsrcdir/clang/bin/llvm-ranlib"
# Enable LTO if the NDK is available.
if [ -z "$NO_NDK" ]; then
ac_add_options --enable-lto
fi
. "$topsrcdir/mobile/android/config/mozconfigs/common.override"

View File

@ -180,8 +180,10 @@ var DownloadNotifications = {
};
function getCookieFromDownload(download) {
// Arbitrary value used to truncate long Data URLs. See bug 1497526
const maxUrlLength = 1024;
return download.target.path +
download.source.url +
download.source.url.slice(-maxUrlLength) +
download.startTime;
}

View File

@ -224,6 +224,8 @@ interface nsIRequest : nsISupports
/**
* When set, this flag indicates that caches of network connections,
* particularly HTTP persistent connections, should not be used.
* Use this together with LOAD_INITIAL_DOCUMENT_URI as otherwise it has no
* effect.
*/
const unsigned long LOAD_FRESH_CONNECTION = 1 << 15;
};

View File

@ -43,8 +43,8 @@ then
fi
set -x
else
# enable locale cache
export MBSDIFF_HOOK="/home/worker/bin/mbsdiff_hook.sh -c /tmp/fs-cache"
# disable caching
export MBSDIFF_HOOK=
fi
if [ ! -z "$FILENAME_TEMPLATE" ]; then

View File

@ -1105,8 +1105,8 @@ var PlacesUtils = {
*/
validatePageInfo(pageInfo, validateVisits = true) {
return this.validateItemProperties("PageInfo", PAGEINFO_VALIDATORS, pageInfo,
{ url: { requiredIf: b => { typeof b.guid != "string"; } },
guid: { requiredIf: b => { typeof b.url != "string"; } },
{ url: { requiredIf: b => !b.guid },
guid: { requiredIf: b => !b.url },
visits: { requiredIf: b => validateVisits },
});
},

View File

@ -446,55 +446,6 @@ namespace places {
return findInString(aToken, aSourceString, eFindOnBoundary);
}
/* static */
bool
MatchAutoCompleteFunction::findBeginning(const nsDependentCSubstring &aToken,
const nsACString &aSourceString)
{
MOZ_ASSERT(!aToken.IsEmpty(), "Don't search for an empty token!");
// We can't use StringBeginsWith here, unfortunately. Although it will
// happily take a case-insensitive UTF8 comparator, it eventually calls
// nsACString::Equals, which checks that the two strings contain the same
// number of bytes before calling the comparator. Two characters may be
// case-insensitively equal while taking up different numbers of bytes, so
// this is not what we want.
const_char_iterator tokenStart(aToken.BeginReading()),
tokenEnd(aToken.EndReading()),
sourceStart(aSourceString.BeginReading()),
sourceEnd(aSourceString.EndReading());
bool dummy;
while (sourceStart < sourceEnd &&
CaseInsensitiveUTF8CharsEqual(sourceStart, tokenStart,
sourceEnd, tokenEnd,
&sourceStart, &tokenStart, &dummy)) {
// We found the token!
if (tokenStart >= tokenEnd) {
return true;
}
}
// We don't need to check CaseInsensitiveUTF8CharsEqual's error condition
// (stored in |dummy|), since the function will return false if it
// encounters an error.
return false;
}
/* static */
bool
MatchAutoCompleteFunction::findBeginningCaseSensitive(
const nsDependentCSubstring &aToken,
const nsACString &aSourceString)
{
MOZ_ASSERT(!aToken.IsEmpty(), "Don't search for an empty token!");
return StringBeginsWith(aSourceString, aToken);
}
/* static */
MatchAutoCompleteFunction::searchFunctionPtr
MatchAutoCompleteFunction::getSearchFunction(int32_t aBehavior)
@ -503,10 +454,6 @@ namespace places {
case mozIPlacesAutoComplete::MATCH_ANYWHERE:
case mozIPlacesAutoComplete::MATCH_ANYWHERE_UNMODIFIED:
return findAnywhere;
case mozIPlacesAutoComplete::MATCH_BEGINNING:
return findBeginning;
case mozIPlacesAutoComplete::MATCH_BEGINNING_CASE_SENSITIVE:
return findBeginningCaseSensitive;
case mozIPlacesAutoComplete::MATCH_BOUNDARY:
default:
return findOnBoundary;

View File

@ -10,16 +10,6 @@
const MS_PER_DAY = 86400000; // 24 * 60 * 60 * 1000
// Match type constants.
// These indicate what type of search function we should be using.
const {
MATCH_ANYWHERE,
MATCH_BOUNDARY_ANYWHERE,
MATCH_BOUNDARY,
MATCH_BEGINNING,
MATCH_BEGINNING_CASE_SENSITIVE,
} = Ci.mozIPlacesAutoComplete;
// AutoComplete query type constants.
// Describes the various types of queries that we can process rows for.
const QUERYTYPE_FILTERED = 0;
@ -576,7 +566,7 @@ function Search(searchString, searchParam, autocompleteListener,
this._searchString = Services.textToSubURI.unEscapeURIForUI("UTF-8", suffix);
this._strippedPrefix = prefix.toLowerCase();
this._matchBehavior = UrlbarPrefs.get("matchBehavior");
this._matchBehavior = Ci.mozIPlacesAutoComplete.MATCH_BOUNDARY;
// Set the default behavior for this search.
this._behavior = this._searchString ? UrlbarPrefs.get("defaultBehavior")
: UrlbarPrefs.get("emptySearchDefaultBehavior");
@ -982,14 +972,12 @@ Search.prototype = {
this._matchAboutPages();
// If we do not have enough results, and our match type is
// MATCH_BOUNDARY_ANYWHERE, search again with MATCH_ANYWHERE to get more
// results.
// If we do not have enough matches search again with MATCH_ANYWHERE, to
// get more matches.
let count = this._counts[UrlbarUtils.MATCH_GROUP.GENERAL] +
this._counts[UrlbarUtils.MATCH_GROUP.HEURISTIC];
if (this._matchBehavior == MATCH_BOUNDARY_ANYWHERE &&
count < UrlbarPrefs.get("maxRichResults")) {
this._matchBehavior = MATCH_ANYWHERE;
if (count < UrlbarPrefs.get("maxRichResults")) {
this._matchBehavior = Ci.mozIPlacesAutoComplete.MATCH_ANYWHERE;
for (let [query, params] of [ this._adaptiveQuery,
this._searchQuery ]) {
await conn.executeCached(query, params, this._onResultRow.bind(this));

View File

@ -19,6 +19,9 @@ interface mozIPlacesAutoComplete : nsISupports
//////////////////////////////////////////////////////////////////////////////
//// Matching Constants
// A few of these are not used in Firefox, but are still referenced in
// comm-central.
/**
* Match anywhere in each searchable term.
*/

View File

@ -18,6 +18,13 @@ add_task(async function test_error_cases() {
/Error: PageInfo: Input should be/,
"passing a null as pageInfo should throw an Error"
);
Assert.throws(
() => PlacesUtils.history.update({
description: "Test description",
}),
/Error: PageInfo: The following properties were expected: url, guid/,
"not included a url or a guid should throw"
);
Assert.throws(
() => PlacesUtils.history.update({url: "not a valid url string"}),
/Error: PageInfo: Invalid value for property/,
@ -155,7 +162,7 @@ add_task(async function test_previewImageURL_change_saved() {
let guid = await PlacesTestUtils.fieldInDB(TEST_URL, "guid");
previewImageURL = IMAGE_URL;
await PlacesUtils.history.update({ url: TEST_URL, guid, previewImageURL });
await PlacesUtils.history.update({ guid, previewImageURL });
previewImageURLInDB = await PlacesTestUtils.fieldInDB(TEST_URL, "preview_image_url");
Assert.equal(previewImageURL, previewImageURLInDB, "previewImageURL should be updated via GUID as expected");

View File

@ -319,13 +319,15 @@ var addBookmark = async function(aBookmarkObj) {
if (aBookmarkObj.keyword) {
await PlacesUtils.keywords.insert({ keyword: aBookmarkObj.keyword,
url: aBookmarkObj.uri.spec ? aBookmarkObj.uri.spec : aBookmarkObj.uri,
url: aBookmarkObj.uri instanceof Ci.nsIURI ? aBookmarkObj.uri.spec : aBookmarkObj.uri,
postData: aBookmarkObj.postData,
});
}
if (aBookmarkObj.tags) {
PlacesUtils.tagging.tagURI(aBookmarkObj.uri, aBookmarkObj.tags);
let uri = aBookmarkObj.uri instanceof Ci.nsIURI ?
aBookmarkObj.uri : Services.io.newURI(aBookmarkObj.uri);
PlacesUtils.tagging.tagURI(uri, aBookmarkObj.tags);
}
};

View File

@ -1,54 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Test bug 451760 which allows matching only at the beginning of urls or
* titles to simulate Firefox 2 functionality.
*/
add_task(async function test_match_beginning() {
Services.prefs.setBoolPref("browser.urlbar.autoFill", false);
let uri1 = NetUtil.newURI("http://x.com/y");
let uri2 = NetUtil.newURI("https://y.com/x");
await PlacesTestUtils.addVisits([
{ uri: uri1, title: "a b" },
{ uri: uri2, title: "b a" },
]);
info("Match at the beginning of titles");
Services.prefs.setIntPref("browser.urlbar.matchBehavior", 3);
await check_autocomplete({
search: "a",
matches: [ { uri: uri1, title: "a b" } ],
});
info("Match at the beginning of titles");
await check_autocomplete({
search: "b",
matches: [ { uri: uri2, title: "b a" } ],
});
info("Match at the beginning of urls");
await check_autocomplete({
search: "x",
matches: [ { uri: uri1, title: "a b" } ],
});
info("Match at the beginning of urls");
await check_autocomplete({
search: "y",
matches: [ { uri: uri2, title: "b a" } ],
});
info("Sanity check that matching anywhere finds more");
Services.prefs.setIntPref("browser.urlbar.matchBehavior", 1);
await check_autocomplete({
search: "a",
matches: [ { uri: uri1, title: "a b" },
{ uri: uri2, title: "b a" } ],
});
await cleanup();
});

View File

@ -3,15 +3,12 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Test bug 393678 to make sure matches against the url, title, tags are only
* made on word boundaries instead of in the middle of words.
* Test to make sure matches against the url, title, tags are first made on word
* boundaries, instead of in the middle of words, and later are extended to the
* whole words. For this test it is critical to check sorting of the matches.
*
* Make sure we don't try matching one after a CamelCase because the upper-case
* isn't really a word boundary. (bug 429498)
*
* Bug 429531 provides switching between "must match on word boundary" and "can
* match," so leverage "must match" pref for checking word boundary logic and
* make sure "can match" matches anywhere.
*/
var katakana = ["\u30a8", "\u30c9"]; // E, Do
@ -21,155 +18,179 @@ add_task(async function test_escape() {
Services.prefs.setBoolPref("browser.urlbar.autoFill.searchEngines", false);
Services.prefs.setBoolPref("browser.urlbar.autoFill", false);
let uri1 = NetUtil.newURI("http://matchme/");
let uri2 = NetUtil.newURI("http://dontmatchme/");
let uri3 = NetUtil.newURI("http://title/1");
let uri4 = NetUtil.newURI("http://title/2");
let uri5 = NetUtil.newURI("http://tag/1");
let uri6 = NetUtil.newURI("http://tag/2");
let uri7 = NetUtil.newURI("http://crazytitle/");
let uri8 = NetUtil.newURI("http://katakana/");
let uri9 = NetUtil.newURI("http://ideograph/");
let uri10 = NetUtil.newURI("http://camel/pleaseMatchMe/");
await PlacesTestUtils.addVisits([
{ uri: uri1, title: "title1" },
{ uri: uri2, title: "title1" },
{ uri: uri3, title: "matchme2" },
{ uri: uri4, title: "dontmatchme3" },
{ uri: uri5, title: "title1" },
{ uri: uri6, title: "title1" },
{ uri: uri7, title: "!@#$%^&*()_+{}|:<>?word" },
{ uri: uri8, title: katakana.join("") },
{ uri: uri9, title: ideograph.join("") },
{ uri: uri10, title: "title1" },
{ uri: "http://matchme/", title: "title1" },
{ uri: "http://dontmatchme/", title: "title1" },
{ uri: "http://title/1", title: "matchme2" },
{ uri: "http://title/2", title: "dontmatchme3" },
{ uri: "http://tag/1", title: "title1" },
{ uri: "http://tag/2", title: "title1" },
{ uri: "http://crazytitle/", title: "!@#$%^&*()_+{}|:<>?word" },
{ uri: "http://katakana/", title: katakana.join("") },
{ uri: "http://ideograph/", title: ideograph.join("") },
{ uri: "http://camel/pleaseMatchMe/", title: "title1" },
]);
await addBookmark( { uri: uri5, title: "title1", tags: [ "matchme2" ] } );
await addBookmark( { uri: uri6, title: "title1", tags: [ "dontmatchme3" ] } );
// match only on word boundaries
Services.prefs.setIntPref("browser.urlbar.matchBehavior", 2);
await addBookmark( { uri: "http://tag/1", title: "title1", tags: [ "matchme2" ] } );
await addBookmark( { uri: "http://tag/2", title: "title1", tags: [ "dontmatchme3" ] } );
info("Match 'match' at the beginning or after / or on a CamelCase");
await check_autocomplete({
search: "match",
matches: [ { uri: uri1, title: "title1" },
{ uri: uri3, title: "matchme2" },
{ uri: uri5, title: "title1", tags: [ "matchme2" ], style: [ "bookmark-tag" ] },
{ uri: uri10, title: "title1" } ],
checkSorting: true,
matches: [
{ uri: "http://tag/1", title: "title1", tags: [ "matchme2" ], style: [ "bookmark-tag" ] },
{ uri: "http://camel/pleaseMatchMe/", title: "title1" },
{ uri: "http://title/1", title: "matchme2" },
{ uri: "http://matchme/", title: "title1" },
{ uri: "http://tag/2", title: "title1", tags: [ "dontmatchme3" ], style: [ "bookmark-tag" ] },
{ uri: "http://title/2", title: "dontmatchme3" },
{ uri: "http://dontmatchme/", title: "title1" },
],
});
info("Match 'dont' at the beginning or after /");
await check_autocomplete({
search: "dont",
matches: [ { uri: uri2, title: "title1" },
{ uri: uri4, title: "dontmatchme3" },
{ uri: uri6, title: "title1", tags: [ "dontmatchme3" ], style: [ "bookmark-tag" ] } ],
checkSorting: true,
matches: [
{ uri: "http://tag/2", title: "title1", tags: [ "dontmatchme3" ], style: [ "bookmark-tag" ] },
{ uri: "http://title/2", title: "dontmatchme3" },
{ uri: "http://dontmatchme/", title: "title1" },
],
});
info("Match 'match' at the beginning or after / or on a CamelCase");
await check_autocomplete({
search: "2",
matches: [ { uri: uri3, title: "matchme2" },
{ uri: uri4, title: "dontmatchme3" },
{ uri: uri5, title: "title1", tags: [ "matchme2" ], style: [ "bookmark-tag" ] },
{ uri: uri6, title: "title1", tags: [ "dontmatchme3" ], style: [ "bookmark-tag" ] } ],
checkSorting: true,
matches: [
{ uri: "http://tag/2", title: "title1", tags: [ "dontmatchme3" ], style: [ "bookmark-tag" ] },
{ uri: "http://tag/1", title: "title1", tags: [ "matchme2" ], style: [ "bookmark-tag" ] },
{ uri: "http://title/2", title: "dontmatchme3" },
{ uri: "http://title/1", title: "matchme2" },
],
});
info("Match 't' at the beginning or after /");
await check_autocomplete({
search: "t",
matches: [ { uri: uri1, title: "title1" },
{ uri: uri2, title: "title1" },
{ uri: uri3, title: "matchme2" },
{ uri: uri4, title: "dontmatchme3" },
{ uri: uri5, title: "title1", tags: [ "matchme2" ], style: [ "bookmark-tag" ] },
{ uri: uri6, title: "title1", tags: [ "dontmatchme3" ], style: [ "bookmark-tag" ] },
{ uri: uri10, title: "title1" } ],
checkSorting: true,
matches: [
{ uri: "http://tag/2", title: "title1", tags: [ "dontmatchme3" ], style: [ "bookmark-tag" ] },
{ uri: "http://tag/1", title: "title1", tags: [ "matchme2" ], style: [ "bookmark-tag" ] },
{ uri: "http://camel/pleaseMatchMe/", title: "title1" },
{ uri: "http://title/2", title: "dontmatchme3" },
{ uri: "http://title/1", title: "matchme2" },
{ uri: "http://dontmatchme/", title: "title1" },
{ uri: "http://matchme/", title: "title1" },
{ uri: "http://katakana/", title: katakana.join("") },
{ uri: "http://crazytitle/", title: "!@#$%^&*()_+{}|:<>?word" },
],
});
info("Match 'word' after many consecutive word boundaries");
await check_autocomplete({
search: "word",
matches: [ { uri: uri7, title: "!@#$%^&*()_+{}|:<>?word" } ],
checkSorting: true,
matches: [
{ uri: "http://crazytitle/", title: "!@#$%^&*()_+{}|:<>?word" },
],
});
info("Match a word boundary '/' for everything");
await check_autocomplete({
search: "/",
matches: [ { uri: uri1, title: "title1" },
{ uri: uri2, title: "title1" },
{ uri: uri3, title: "matchme2" },
{ uri: uri4, title: "dontmatchme3" },
{ uri: uri5, title: "title1", tags: [ "matchme2" ], style: [ "bookmark-tag" ] },
{ uri: uri6, title: "title1", tags: [ "dontmatchme3" ], style: [ "bookmark-tag" ] },
{ uri: uri7, title: "!@#$%^&*()_+{}|:<>?word" },
{ uri: uri8, title: katakana.join("") },
{ uri: uri9, title: ideograph.join("") },
{ uri: uri10, title: "title1" } ],
checkSorting: true,
matches: [
{ uri: "http://tag/2", title: "title1", tags: [ "dontmatchme3" ], style: [ "bookmark-tag" ] },
{ uri: "http://tag/1", title: "title1", tags: [ "matchme2" ], style: [ "bookmark-tag" ] },
{ uri: "http://camel/pleaseMatchMe/", title: "title1" },
{ uri: "http://ideograph/", title: ideograph.join("") },
{ uri: "http://katakana/", title: katakana.join("") },
{ uri: "http://crazytitle/", title: "!@#$%^&*()_+{}|:<>?word" },
{ uri: "http://title/2", title: "dontmatchme3" },
{ uri: "http://title/1", title: "matchme2" },
{ uri: "http://dontmatchme/", title: "title1" },
{ uri: "http://matchme/", title: "title1" },
],
});
info("Match word boundaries '()_+' that are among word boundaries");
await check_autocomplete({
search: "()_+",
matches: [ { uri: uri7, title: "!@#$%^&*()_+{}|:<>?word" } ],
checkSorting: true,
matches: [
{ uri: "http://crazytitle/", title: "!@#$%^&*()_+{}|:<>?word" },
],
});
info("Katakana characters form a string, so match the beginning");
await check_autocomplete({
search: katakana[0],
matches: [ { uri: uri8, title: katakana.join("") } ],
checkSorting: true,
matches: [
{ uri: "http://katakana/", title: katakana.join("") },
],
});
/*
do_print("Middle of a katakana word shouldn't be matched");
yield check_autocomplete({
info("Middle of a katakana word shouldn't be matched");
await check_autocomplete({
search: katakana[1],
matches: [ ]
matches: [ ],
});
*/
info("Ideographs are treated as words so 'nin' is one word");
await check_autocomplete({
search: ideograph[0],
matches: [ { uri: uri9, title: ideograph.join("") } ],
checkSorting: true,
matches: [ { uri: "http://ideograph/", title: ideograph.join("") } ],
});
info("Ideographs are treated as words so 'ten' is another word");
await check_autocomplete({
search: ideograph[1],
matches: [ { uri: uri9, title: ideograph.join("") } ],
checkSorting: true,
matches: [ { uri: "http://ideograph/", title: ideograph.join("") } ],
});
info("Ideographs are treated as words so 'do' is yet another word");
await check_autocomplete({
search: ideograph[2],
matches: [ { uri: uri9, title: ideograph.join("") } ],
checkSorting: true,
matches: [ { uri: "http://ideograph/", title: ideograph.join("") } ],
});
info("Extra negative assert that we don't match in the middle");
info("Match in the middle. Should just be sorted by frecency.");
await check_autocomplete({
search: "ch",
matches: [ ],
checkSorting: true,
matches: [
{ uri: "http://tag/2", title: "title1", tags: [ "dontmatchme3" ], style: [ "bookmark-tag" ] },
{ uri: "http://tag/1", title: "title1", tags: [ "matchme2" ], style: [ "bookmark-tag" ] },
{ uri: "http://camel/pleaseMatchMe/", title: "title1" },
{ uri: "http://title/2", title: "dontmatchme3" },
{ uri: "http://title/1", title: "matchme2" },
{ uri: "http://dontmatchme/", title: "title1" },
{ uri: "http://matchme/", title: "title1" },
],
});
info("Don't match one character after a camel-case word boundary (bug 429498)");
// Also this test should just be sorted by frecency.
info("Don't match one character after a camel-case word boundary (bug 429498). Should just be sorted by frecency.");
await check_autocomplete({
search: "atch",
matches: [ ],
});
// match against word boundaries and anywhere
Services.prefs.setIntPref("browser.urlbar.matchBehavior", 1);
await check_autocomplete({
search: "tch",
matches: [ { uri: uri1, title: "title1" },
{ uri: uri2, title: "title1" },
{ uri: uri3, title: "matchme2" },
{ uri: uri4, title: "dontmatchme3" },
{ uri: uri5, title: "title1", tags: [ "matchme2" ], style: [ "bookmark-tag" ] },
{ uri: uri6, title: "title1", tags: [ "dontmatchme3" ], style: [ "bookmark-tag" ] },
{ uri: uri10, title: "title1" } ],
checkSorting: true,
matches: [
{ uri: "http://tag/2", title: "title1", tags: [ "dontmatchme3" ], style: [ "bookmark-tag" ] },
{ uri: "http://tag/1", title: "title1", tags: [ "matchme2" ], style: [ "bookmark-tag" ] },
{ uri: "http://camel/pleaseMatchMe/", title: "title1" },
{ uri: "http://title/2", title: "dontmatchme3" },
{ uri: "http://title/1", title: "matchme2" },
{ uri: "http://dontmatchme/", title: "title1" },
{ uri: "http://matchme/", title: "title1" },
],
});
await cleanup();

View File

@ -40,7 +40,6 @@ support-files =
[test_keyword_search.js]
[test_keyword_search_actions.js]
[test_keywords.js]
[test_match_beginning.js]
[test_multi_word_search.js]
[test_PlacesSearchAutocompleteProvider.js]
skip-if = appname == "thunderbird"