mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-22 17:55:50 +00:00
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:
commit
614d3b4b95
@ -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;
|
||||
}
|
||||
|
@ -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");
|
||||
|
@ -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");
|
||||
|
@ -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]
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
@ -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>
|
@ -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>
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
},
|
||||
};
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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 |
@ -189,9 +189,7 @@
|
||||
}
|
||||
|
||||
/* Workaround for native menubar inheritance */
|
||||
.openintabs-menuitem,
|
||||
.openlivemarksite-menuitem,
|
||||
.livemarkstatus-menuitem {
|
||||
.openintabs-menuitem {
|
||||
list-style-image: none;
|
||||
}
|
||||
|
||||
|
@ -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 |
@ -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)
|
||||
|
@ -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 |
@ -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");
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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 |
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -17,10 +17,10 @@ module.exports = {
|
||||
};
|
||||
},
|
||||
|
||||
trackChange(data) {
|
||||
trackChange(change) {
|
||||
return {
|
||||
type: TRACK_CHANGE,
|
||||
data,
|
||||
change,
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -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;
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -6,4 +6,5 @@
|
||||
|
||||
DevToolsModules(
|
||||
'ChangesApp.js',
|
||||
'CSSDeclaration.js',
|
||||
)
|
||||
|
@ -8,9 +8,12 @@ DIRS += [
|
||||
'actions',
|
||||
'components',
|
||||
'reducers',
|
||||
'utils',
|
||||
]
|
||||
|
||||
DevToolsModules(
|
||||
'ChangesManager.js',
|
||||
'ChangesView.js',
|
||||
)
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
|
||||
|
@ -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) {
|
||||
|
6
devtools/client/inspector/changes/test/.eslintrc.js
Normal file
6
devtools/client/inspector/changes/test/.eslintrc.js
Normal file
@ -0,0 +1,6 @@
|
||||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
// Extend from the shared list of defined globals for mochitests.
|
||||
"extends": "../../../../.eslintrc.mochitests.js",
|
||||
};
|
17
devtools/client/inspector/changes/test/browser.ini
Normal file
17
devtools/client/inspector/changes/test/browser.ini
Normal 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]
|
@ -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");
|
||||
});
|
@ -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`);
|
||||
}
|
||||
}
|
||||
});
|
@ -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");
|
||||
});
|
31
devtools/client/inspector/changes/test/head.js
Normal file
31
devtools/client/inspector/changes/test/head.js
Normal 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");
|
||||
});
|
58
devtools/client/inspector/changes/utils/changes-utils.js
Normal file
58
devtools/client/inspector/changes/utils/changes-utils.js
Normal 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;
|
9
devtools/client/inspector/changes/utils/moz.build
Normal file
9
devtools/client/inspector/changes/utils/moz.build
Normal 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',
|
||||
)
|
@ -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.
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
},
|
||||
|
||||
|
@ -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 });
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
|
@ -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.
|
||||
|
@ -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))]
|
||||
|
@ -1 +1 @@
|
||||
925dd051a36f1c1cd02e7f635f7e439ab2804f15
|
||||
2537e5f27c2ce7b64a93498c7569a870c190feda
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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 },
|
||||
});
|
||||
},
|
||||
|
@ -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;
|
||||
|
@ -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));
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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();
|
||||
});
|
@ -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();
|
||||
|
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user