mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-07 18:04:46 +00:00
Merge m-c to graphics
MozReview-Commit-ID: QN682uyISF
This commit is contained in:
commit
8f54e2114e
@ -154,6 +154,7 @@ AccessibleNode::Get(JSContext* aCX, const nsAString& aAttribute,
|
||||
{
|
||||
if (!mIntl) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIPersistentProperties> attrs = mIntl->Attributes();
|
||||
@ -163,6 +164,7 @@ AccessibleNode::Get(JSContext* aCX, const nsAString& aAttribute,
|
||||
JS::Rooted<JS::Value> jsval(aCX);
|
||||
if (!ToJSValue(aCX, value, &jsval)) {
|
||||
aRv.Throw(NS_ERROR_UNEXPECTED);
|
||||
return;
|
||||
}
|
||||
|
||||
aValue.set(jsval);
|
||||
|
@ -392,7 +392,7 @@ function* testSubtreeRule(browser, target, rule, expected) {
|
||||
yield ContentTask.spawn(browser, target.id, id => {
|
||||
let elm = content.document.getElementById(id);
|
||||
while (elm.firstChild) {
|
||||
elm.removeChild(elm.firstChild);
|
||||
elm.firstChild.remove();
|
||||
}
|
||||
});
|
||||
yield updateAccessibleIfNeeded(onEvent, target);
|
||||
|
@ -157,7 +157,7 @@ addAccessibleTask(`
|
||||
yield ContentTask.spawn(browser, {}, () => {
|
||||
// Remove HTML element.
|
||||
let docNode = content.document.getElementById('iframe').contentDocument;
|
||||
docNode.removeChild(docNode.firstChild);
|
||||
docNode.firstChild.remove();
|
||||
});
|
||||
let event = yield reorderEventPromise;
|
||||
|
||||
@ -235,7 +235,7 @@ addAccessibleTask(`
|
||||
let docEl =
|
||||
content.document.getElementById('iframe').contentDocument.documentElement;
|
||||
// Remove aftermath of this test before next test starts.
|
||||
docEl.removeChild(docEl.firstChild);
|
||||
docEl.firstChild.remove();
|
||||
});
|
||||
// Make sure reorder event was fired and that the input was removed.
|
||||
yield reorderEventPromise;
|
||||
|
@ -56,7 +56,7 @@ addAccessibleTask('<select id="select"></select>', function*(browser, accDoc) {
|
||||
// Remove grouping from combobox
|
||||
yield ContentTask.spawn(browser, {}, () => {
|
||||
let contentSelect = content.document.getElementById('select');
|
||||
contentSelect.removeChild(contentSelect.firstChild);
|
||||
contentSelect.firstChild.remove();
|
||||
});
|
||||
yield onEvent;
|
||||
|
||||
|
@ -414,8 +414,8 @@
|
||||
var l1 = doc.firstChild;
|
||||
this.target = l1.firstChild;
|
||||
var l2 = doc.lastChild;
|
||||
l1.DOMNode.removeChild(l1.DOMNode.firstChild);
|
||||
l2.DOMNode.removeChild(l2.DOMNode.firstChild);
|
||||
l1.DOMNode.firstChild.remove();
|
||||
l2.DOMNode.firstChild.remove();
|
||||
}
|
||||
|
||||
this.check = function hideHideNDestroyDoc_check()
|
||||
|
@ -51,7 +51,7 @@
|
||||
this.invoke = function removeChildSpan_invoke()
|
||||
{
|
||||
// remove HTML span, a first child of the node
|
||||
this.DOMNode.removeChild(this.DOMNode.firstChild);
|
||||
this.DOMNode.firstChild.remove();
|
||||
}
|
||||
|
||||
this.getID = function removeChildSpan_getID()
|
||||
@ -162,7 +162,7 @@
|
||||
this.DOMNode.removeChild(this.DOMNode.lastChild);
|
||||
} else {
|
||||
while (this.DOMNode.firstChild)
|
||||
this.DOMNode.removeChild(this.DOMNode.firstChild);
|
||||
this.DOMNode.firstChild.remove();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,16 +68,16 @@
|
||||
new ExpectedEditState({
|
||||
editing: true,
|
||||
multiline: true,
|
||||
atStart: false,
|
||||
atEnd: true
|
||||
atStart: true,
|
||||
atEnd: false
|
||||
}),
|
||||
new ExpectedCursorChange(
|
||||
['Please refrain from Mayoneggs during this salmonella scare.',
|
||||
{string: 'textarea'}]),
|
||||
new ExpectedTextSelectionChanged(59, 59)
|
||||
new ExpectedTextSelectionChanged(0, 0)
|
||||
],
|
||||
[ContentMessages.activateCurrent(10),
|
||||
new ExpectedTextCaretChanged(10, 59),
|
||||
new ExpectedTextCaretChanged(0, 10),
|
||||
new ExpectedEditState({ editing: true,
|
||||
multiline: true,
|
||||
atStart: false,
|
||||
|
@ -316,7 +316,7 @@ function testNameForSubtreeRule(aElm, aRule)
|
||||
waitForEvent(EVENT_REORDER, aElm, gTestIterator.iterateNext, gTestIterator);
|
||||
|
||||
while (aElm.firstChild)
|
||||
aElm.removeChild(aElm.firstChild);
|
||||
aElm.firstChild.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -107,6 +107,7 @@
|
||||
// (per spec, min/maxlength validity is affected by interactive edits)
|
||||
var mininp = document.getElementById("minlength");
|
||||
mininp.focus();
|
||||
mininp.setSelectionRange(mininp.value.length, mininp.value.length);
|
||||
synthesizeKey("VK_BACK_SPACE", {});
|
||||
ok(!mininp.validity.valid,
|
||||
"input should be invalid after interactive edits");
|
||||
|
@ -266,7 +266,7 @@
|
||||
|
||||
// Remove HTML element.
|
||||
var docNode = getDocNode(aID);
|
||||
docNode.removeChild(docNode.firstChild);
|
||||
docNode.firstChild.remove();
|
||||
}
|
||||
|
||||
this.getID = function removeHTMLFromIFrameDoc_getID()
|
||||
|
@ -64,7 +64,7 @@
|
||||
|
||||
this.invoke = function removeRemove_invoke()
|
||||
{
|
||||
getNode(aContainer).removeChild(getNode(aContainer).firstChild);
|
||||
getNode(aContainer).firstChild.remove();
|
||||
}
|
||||
|
||||
this.finalCheck = function removeRemove_finalCheck()
|
||||
|
@ -77,7 +77,7 @@
|
||||
this.invoke = function removeOptGroup_invoke()
|
||||
{
|
||||
this.option1Node = this.selectNode.firstChild.firstChild;
|
||||
this.selectNode.removeChild(this.selectNode.firstChild);
|
||||
this.selectNode.firstChild.remove();
|
||||
}
|
||||
|
||||
this.eventSeq = [
|
||||
|
@ -89,38 +89,6 @@ var AdbController = {
|
||||
|
||||
updateState: function() {
|
||||
this.umsActive = false;
|
||||
this.storages = navigator.getDeviceStorages('sdcard');
|
||||
this.updateStorageState(0);
|
||||
},
|
||||
|
||||
updateStorageState: function(storageIndex) {
|
||||
if (storageIndex >= this.storages.length) {
|
||||
// We've iterated through all of the storage objects, now we can
|
||||
// really do updateStateInternal.
|
||||
this.updateStateInternal();
|
||||
return;
|
||||
}
|
||||
let storage = this.storages[storageIndex];
|
||||
DEBUG && debug("Checking availability of storage: '" + storage.storageName + "'");
|
||||
|
||||
let req = storage.available();
|
||||
req.onsuccess = function(e) {
|
||||
DEBUG && debug("Storage: '" + storage.storageName + "' is '" + e.target.result + "'");
|
||||
if (e.target.result == 'shared') {
|
||||
// We've found a storage area that's being shared with the PC.
|
||||
// We can stop looking now.
|
||||
this.umsActive = true;
|
||||
this.updateStateInternal();
|
||||
return;
|
||||
}
|
||||
this.updateStorageState(storageIndex + 1);
|
||||
}.bind(this);
|
||||
req.onerror = function(e) {
|
||||
|
||||
Cu.reportError("AdbController: error querying storage availability for '" +
|
||||
this.storages[storageIndex].storageName + "' (ignoring)\n");
|
||||
this.updateStorageState(storageIndex + 1);
|
||||
}.bind(this);
|
||||
},
|
||||
|
||||
updateStateInternal: function() {
|
||||
|
@ -662,6 +662,14 @@
|
||||
<prefs/>
|
||||
<versionRange minVersion="0" maxVersion="*" severity="3"/>
|
||||
</emItem>
|
||||
<emItem blockID="89a61123-79a2-45d1-aec2-97afca0863eb" id="InternetProtection@360safe.com">
|
||||
<prefs/>
|
||||
<versionRange minVersion="0" maxVersion="5.0.0.1002" severity="3">
|
||||
<targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
|
||||
<versionRange maxVersion="*" minVersion="52.0a1"/>
|
||||
</targetApplication>
|
||||
</versionRange>
|
||||
</emItem>
|
||||
<emItem blockID="i471" id="firefox@luckyleap.net">
|
||||
<prefs/>
|
||||
<versionRange minVersion="0" maxVersion="*" severity="3"/>
|
||||
@ -865,14 +873,6 @@
|
||||
<prefs/>
|
||||
<versionRange minVersion="0" maxVersion="*" severity="3"/>
|
||||
</emItem>
|
||||
<emItem blockID="i228" id="crossriderapp5060@crossrider.com">
|
||||
<prefs/>
|
||||
<versionRange minVersion="0" maxVersion="*" severity="1"/>
|
||||
</emItem>
|
||||
<emItem blockID="i470" id="extension@FastFreeConverter.com">
|
||||
<prefs/>
|
||||
<versionRange minVersion="0" maxVersion="*" severity="3"/>
|
||||
</emItem>
|
||||
<emItem blockID="i1223" id="tmbepff@trendmicro.com">
|
||||
<prefs/>
|
||||
<versionRange minVersion="9.2" maxVersion="9.2.0.1023" severity="1"/>
|
||||
@ -1527,6 +1527,14 @@
|
||||
<prefs/>
|
||||
<versionRange minVersion="0" maxVersion="*" severity="3"/>
|
||||
</emItem>
|
||||
<emItem blockID="08addad8-2f03-4cff-a791-e6f2a1b170ed" id="WebProtection@360safe.com">
|
||||
<prefs/>
|
||||
<versionRange minVersion="0" maxVersion="*" severity="3">
|
||||
<targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
|
||||
<versionRange maxVersion="*" minVersion="52.0a1"/>
|
||||
</targetApplication>
|
||||
</versionRange>
|
||||
</emItem>
|
||||
<emItem blockID="i838" id="{87b5a11e-3b54-42d2-9102-0a7cb1f79ebf}">
|
||||
<prefs/>
|
||||
<versionRange minVersion="0" maxVersion="*" severity="3"/>
|
||||
@ -2043,6 +2051,14 @@
|
||||
<prefs/>
|
||||
<versionRange minVersion="0" maxVersion="*" severity="3"/>
|
||||
</emItem>
|
||||
<emItem blockID="i228" id="crossriderapp5060@crossrider.com">
|
||||
<prefs/>
|
||||
<versionRange minVersion="0" maxVersion="*" severity="1"/>
|
||||
</emItem>
|
||||
<emItem blockID="i470" id="extension@FastFreeConverter.com">
|
||||
<prefs/>
|
||||
<versionRange minVersion="0" maxVersion="*" severity="3"/>
|
||||
</emItem>
|
||||
</emItems>
|
||||
<pluginItems>
|
||||
<pluginItem blockID="p416">
|
||||
|
@ -241,7 +241,7 @@ var ctrlTab = {
|
||||
aPreview._tab = aTab;
|
||||
|
||||
if (aPreview.firstChild)
|
||||
aPreview.removeChild(aPreview.firstChild);
|
||||
aPreview.firstChild.remove();
|
||||
if (aTab) {
|
||||
let canvasWidth = this.canvasWidth;
|
||||
let canvasHeight = this.canvasHeight;
|
||||
|
@ -330,7 +330,7 @@ var gFxAccounts = {
|
||||
populateSendTabToDevicesMenu(devicesPopup, url, title) {
|
||||
// remove existing menu items
|
||||
while (devicesPopup.hasChildNodes()) {
|
||||
devicesPopup.removeChild(devicesPopup.firstChild);
|
||||
devicesPopup.firstChild.remove();
|
||||
}
|
||||
|
||||
const fragment = document.createDocumentFragment();
|
||||
|
@ -746,7 +746,7 @@ HistoryMenu.prototype = {
|
||||
|
||||
// remove existing menu items
|
||||
while (undoPopup.hasChildNodes())
|
||||
undoPopup.removeChild(undoPopup.firstChild);
|
||||
undoPopup.firstChild.remove();
|
||||
|
||||
// no restorable tabs, so make sure menu is disabled, and return
|
||||
if (this._getClosedTabCount() == 0) {
|
||||
@ -782,7 +782,7 @@ HistoryMenu.prototype = {
|
||||
|
||||
// remove existing menu items
|
||||
while (undoPopup.hasChildNodes())
|
||||
undoPopup.removeChild(undoPopup.firstChild);
|
||||
undoPopup.firstChild.remove();
|
||||
|
||||
// no restorable windows, so make sure menu is disabled, and return
|
||||
if (SessionStore.getClosedWindowCount() == 0) {
|
||||
|
@ -418,7 +418,7 @@ function resetPageInfo(args) {
|
||||
/* Reset Feeds Tab */
|
||||
var feedListbox = document.getElementById("feedListbox");
|
||||
while (feedListbox.firstChild)
|
||||
feedListbox.removeChild(feedListbox.firstChild);
|
||||
feedListbox.firstChild.remove();
|
||||
|
||||
/* Call registered overlay reset functions */
|
||||
onResetRegistry.forEach(function(func) { func(); });
|
||||
|
@ -306,7 +306,7 @@ function setText(id, value) {
|
||||
element.value = value;
|
||||
else {
|
||||
if (element.hasChildNodes())
|
||||
element.removeChild(element.firstChild);
|
||||
element.firstChild.remove();
|
||||
var textNode = document.createTextNode(value);
|
||||
element.appendChild(textNode);
|
||||
}
|
||||
|
@ -3702,6 +3702,7 @@
|
||||
// time elapses, we're free to put up the spinner and start
|
||||
// trying to load a different tab.
|
||||
TAB_SWITCH_TIMEOUT: 400 /* ms */,
|
||||
NEWNESS_THRESHOLD: 1000 /* ms */,
|
||||
|
||||
// When the user hasn't switched tabs for this long, we unload
|
||||
// layers for all tabs that aren't in use.
|
||||
@ -4434,11 +4435,33 @@
|
||||
|
||||
spinnerDisplayed() {
|
||||
this.assert(!this.spinnerTab);
|
||||
let browser = this.requestedTab.linkedBrowser;
|
||||
this.assert(browser.isRemoteBrowser);
|
||||
TelemetryStopwatch.start("FX_TAB_SWITCH_SPINNER_VISIBLE_MS", window);
|
||||
// We have a second, similar probe for capturing recordings of
|
||||
// when the spinner is displayed for very long periods.
|
||||
TelemetryStopwatch.start("FX_TAB_SWITCH_SPINNER_VISIBLE_LONG_MS", window);
|
||||
this.addMarker("AsyncTabSwitch:SpinnerShown");
|
||||
|
||||
// What kind of tab is about to display this spinner? We have three basic
|
||||
// kinds:
|
||||
//
|
||||
// 1) A tab that we've presented before
|
||||
// 2) A tab that we've never presented before, and it's quite new
|
||||
// 3) A tab that we've never presented before, but it's not so new
|
||||
//
|
||||
// Being "new" in this sense means being a tab that was created less than
|
||||
// NEWNESS_THRESHOLD ms ago.
|
||||
|
||||
let histogram = Services.telemetry.getHistogramById("FX_TAB_SWITCH_SPINNER_TYPE");
|
||||
if (browser.frameLoader.tabParent.hasPresented) {
|
||||
// We've presented this tab before.
|
||||
histogram.add("seen");
|
||||
} else if (Date.now() - this.requestedTab.creationTime < this.NEWNESS_THRESHOLD) {
|
||||
histogram.add("unseenNew");
|
||||
} else {
|
||||
histogram.add("unseenOld");
|
||||
}
|
||||
},
|
||||
|
||||
spinnerHidden() {
|
||||
@ -7070,6 +7093,10 @@
|
||||
if (!("_lastAccessed" in this)) {
|
||||
this.updateLastAccessed();
|
||||
}
|
||||
|
||||
if (!("_creationTime" in this)) {
|
||||
this._creationTime = Date.now();
|
||||
}
|
||||
]]></constructor>
|
||||
|
||||
<property name="_visuallySelected">
|
||||
@ -7176,6 +7203,12 @@
|
||||
|
||||
<field name="cachePosition">Infinity</field>
|
||||
|
||||
<property name="creationTime">
|
||||
<getter>
|
||||
return this._creationTime;
|
||||
</getter>
|
||||
</property>
|
||||
|
||||
<field name="mOverCloseButton">false</field>
|
||||
<property name="_overPlayingIcon" readonly="true">
|
||||
<getter><![CDATA[
|
||||
|
@ -6,6 +6,8 @@ support-files =
|
||||
[browser_allow_process_switches_despite_related_browser.js]
|
||||
[browser_tabSpinnerProbe.js]
|
||||
skip-if = !e10s # Tab spinner is e10s only.
|
||||
[browser_tabSpinnerTypeProbe.js]
|
||||
skip-if = !e10s # Tab spinner is e10s only.
|
||||
[browser_tabSwitchPrintPreview.js]
|
||||
skip-if = os == 'mac'
|
||||
[browser_navigatePinnedTab.js]
|
||||
|
187
browser/base/content/test/tabs/browser_tabSpinnerTypeProbe.js
Normal file
187
browser/base/content/test/tabs/browser_tabSpinnerTypeProbe.js
Normal file
@ -0,0 +1,187 @@
|
||||
"use strict";
|
||||
|
||||
// Keep this in sync with the order in Histograms.json for
|
||||
// FX_TAB_SWITCH_SPINNER_TYPE
|
||||
const CATEGORIES = [
|
||||
"seen",
|
||||
"unseenOld",
|
||||
"unseenNew",
|
||||
];
|
||||
|
||||
add_task(function* setup() {
|
||||
yield SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
// We can interrupt JS to paint now, which is great for
|
||||
// users, but bad for testing spinners. We temporarily
|
||||
// disable that feature for this test so that we can
|
||||
// easily get ourselves into a predictable tab spinner
|
||||
// state.
|
||||
["browser.tabs.remote.force-paint", false],
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Causes the content process for a remote <xul:browser> to run
|
||||
* some busy JS for aMs milliseconds.
|
||||
*
|
||||
* @param {<xul:browser>} browser
|
||||
* The browser that's running in the content process that we're
|
||||
* going to hang.
|
||||
* @param {int} aMs
|
||||
* The amount of time, in milliseconds, to hang the content process.
|
||||
*
|
||||
* @return {Promise}
|
||||
* Resolves once the hang is done.
|
||||
*/
|
||||
function hangContentProcess(browser, aMs) {
|
||||
return ContentTask.spawn(browser, aMs, function*(ms) {
|
||||
let then = Date.now();
|
||||
while (Date.now() - then < ms) {
|
||||
// Let's burn some CPU...
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a Telemetry histogram snapshot and makes sure
|
||||
* that the index for that value (as defined by CATEGORIES)
|
||||
* has a count of 1, and that it's the only value that
|
||||
* has been incremented.
|
||||
*
|
||||
* @param snapshot (Object)
|
||||
* The Telemetry histogram snapshot to examine.
|
||||
* @param category (String)
|
||||
* The category in CATEGORIES whose index we expect to have
|
||||
* been set to 1.
|
||||
*/
|
||||
function assertOnlyOneTypeSet(snapshot, category) {
|
||||
let categoryIndex = CATEGORIES.indexOf(category);
|
||||
Assert.equal(snapshot.counts[categoryIndex], 1,
|
||||
`Should have seen the ${category} count increment.`);
|
||||
// Use Array.prototype.reduce to sum up all of the
|
||||
// snapshot.count entries
|
||||
Assert.equal(snapshot.counts.reduce((a, b) => a + b), 1,
|
||||
"Should only be 1 collected value.");
|
||||
}
|
||||
|
||||
Assert.ok(gMultiProcessBrowser,
|
||||
"These tests only makes sense in an e10s-enabled window.");
|
||||
|
||||
let gHistogram = Services.telemetry
|
||||
.getHistogramById("FX_TAB_SWITCH_SPINNER_TYPE");
|
||||
|
||||
/**
|
||||
* This test tests that the "seen" category for the FX_TAB_SWITCH_SPINNER_TYPE
|
||||
* probe works. This means that we show a spinner for a tab that we've
|
||||
* presented before.
|
||||
*/
|
||||
add_task(function* test_seen_spinner_type_probe() {
|
||||
let originalTab = gBrowser.selectedTab;
|
||||
|
||||
yield BrowserTestUtils.withNewTab({
|
||||
gBrowser,
|
||||
url: "http://example.com",
|
||||
}, function*(browser) {
|
||||
// We'll switch away from the current tab, then hang it, and then switch
|
||||
// back to it. This should add to the "seen" type for the histogram.
|
||||
let testTab = gBrowser.selectedTab;
|
||||
yield BrowserTestUtils.switchTab(gBrowser, originalTab);
|
||||
gHistogram.clear();
|
||||
|
||||
let tabHangPromise = hangContentProcess(browser, 1000);
|
||||
let hangTabSwitch = BrowserTestUtils.switchTab(gBrowser, testTab);
|
||||
yield tabHangPromise;
|
||||
yield hangTabSwitch;
|
||||
|
||||
// Okay, we should have gotten an entry in our Histogram for that one.
|
||||
let snapshot = gHistogram.snapshot();
|
||||
assertOnlyOneTypeSet(snapshot, "seen");
|
||||
gHistogram.clear();
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* This test tests that the "unseenOld" category for the FX_TAB_SWITCH_SPINNER_TYPE
|
||||
* probe works. This means that we show a spinner for a tab that we've never
|
||||
* seen before, and enough time has passed since its creation that we consider
|
||||
* it "old" (See the NEWNESS_THRESHOLD constant in the async tabswitcher for
|
||||
* the exact definition).
|
||||
*/
|
||||
add_task(function* test_unseenOld_spinner_type_probe() {
|
||||
yield BrowserTestUtils.withNewTab({
|
||||
gBrowser,
|
||||
url: "http://example.com",
|
||||
}, function*(browser) {
|
||||
const NEWNESS_THRESHOLD = gBrowser._getSwitcher().NEWNESS_THRESHOLD;
|
||||
|
||||
// First, create a new background tab, ensuring that it's in the same process
|
||||
// as the current one.
|
||||
let bgTab = gBrowser.addTab("about:blank", {
|
||||
sameProcessAsFrameLoader: browser.frameLoader,
|
||||
inBackground: true,
|
||||
});
|
||||
|
||||
yield BrowserTestUtils.browserLoaded(bgTab.linkedBrowser);
|
||||
|
||||
// Now, let's fudge with the creationTime of the background tab so that
|
||||
// it seems old. We'll also add a fudge-factor to the NEWNESS_THRESHOLD of 100ms
|
||||
// to try to avoid any potential timing issues.
|
||||
bgTab._creationTime = bgTab._creationTime - NEWNESS_THRESHOLD - 100;
|
||||
|
||||
// Okay, tab should seem sufficiently old now so that a spinner in it should
|
||||
// qualify for "unseenOld". Let's hang it and switch to it.
|
||||
gHistogram.clear();
|
||||
let tabHangPromise = hangContentProcess(browser, 1000);
|
||||
let hangTabSwitch = BrowserTestUtils.switchTab(gBrowser, bgTab);
|
||||
yield tabHangPromise;
|
||||
yield hangTabSwitch;
|
||||
|
||||
// Okay, we should have gotten an entry in our Histogram for that one.
|
||||
let snapshot = gHistogram.snapshot();
|
||||
assertOnlyOneTypeSet(snapshot, "unseenOld");
|
||||
|
||||
yield BrowserTestUtils.removeTab(bgTab);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* This test tests that the "unseenNew" category for the FX_TAB_SWITCH_SPINNER_TYPE
|
||||
* probe works. This means that we show a spinner for a tab that we've never
|
||||
* seen before, and not enough time has passed since its creation that we consider
|
||||
* it "old" (See the NEWNESS_THRESHOLD constant in the async tabswitcher for
|
||||
* the exact definition).
|
||||
*/
|
||||
add_task(function* test_unseenNew_spinner_type_probe() {
|
||||
yield BrowserTestUtils.withNewTab({
|
||||
gBrowser,
|
||||
url: "http://example.com",
|
||||
}, function*(browser) {
|
||||
// First, create a new background tab, ensuring that it's in the same process
|
||||
// as the current one.
|
||||
let bgTab = gBrowser.addTab("about:blank", {
|
||||
sameProcessAsFrameLoader: browser.frameLoader,
|
||||
inBackground: true,
|
||||
});
|
||||
|
||||
yield BrowserTestUtils.browserLoaded(bgTab.linkedBrowser);
|
||||
|
||||
// Now, let's fudge with the creationTime of the background tab so that
|
||||
// it seems very new (created 1 minute into the future).
|
||||
bgTab._creationTime = Date.now() + (60 * 1000);
|
||||
|
||||
// Okay, tab should seem sufficiently new now so that a spinner in it should
|
||||
// qualify for "unseenNew". Let's hang it and switch to it.
|
||||
gHistogram.clear();
|
||||
let tabHangPromise = hangContentProcess(browser, 1000);
|
||||
let hangTabSwitch = BrowserTestUtils.switchTab(gBrowser, bgTab);
|
||||
yield tabHangPromise;
|
||||
yield hangTabSwitch;
|
||||
|
||||
// Okay, we should have gotten an entry in our Histogram for that one.
|
||||
let snapshot = gHistogram.snapshot();
|
||||
assertOnlyOneTypeSet(snapshot, "unseenNew");
|
||||
|
||||
yield BrowserTestUtils.removeTab(bgTab);
|
||||
});
|
||||
});
|
@ -490,7 +490,7 @@ function createUserContextMenu(event, {
|
||||
useAccessKeys = true
|
||||
} = {}) {
|
||||
while (event.target.hasChildNodes()) {
|
||||
event.target.removeChild(event.target.firstChild);
|
||||
event.target.firstChild.remove();
|
||||
}
|
||||
|
||||
let bundle = document.getElementById("bundle_browser");
|
||||
|
@ -244,12 +244,12 @@ const CustomizableWidgets = [
|
||||
|
||||
let recentlyClosedTabs = doc.getElementById("PanelUI-recentlyClosedTabs");
|
||||
while (recentlyClosedTabs.firstChild) {
|
||||
recentlyClosedTabs.removeChild(recentlyClosedTabs.firstChild);
|
||||
recentlyClosedTabs.firstChild.remove();
|
||||
}
|
||||
|
||||
let recentlyClosedWindows = doc.getElementById("PanelUI-recentlyClosedWindows");
|
||||
while (recentlyClosedWindows.firstChild) {
|
||||
recentlyClosedWindows.removeChild(recentlyClosedWindows.firstChild);
|
||||
recentlyClosedWindows.firstChild.remove();
|
||||
}
|
||||
|
||||
let utils = RecentlyClosedTabsAndWindowsMenuUtils;
|
||||
|
@ -492,7 +492,7 @@ const PanelUI = {
|
||||
|
||||
// Remove all buttons from the view
|
||||
while (items.firstChild) {
|
||||
items.removeChild(items.firstChild);
|
||||
items.firstChild.remove();
|
||||
}
|
||||
|
||||
// Add the current set of menuitems of the Help menu to this view
|
||||
|
@ -106,6 +106,22 @@ add_task(function* test_on_created_navigation_target_from_mouse_click() {
|
||||
},
|
||||
});
|
||||
|
||||
info("Open link with target=\"_blank\" in a new tab using click");
|
||||
|
||||
yield runTestCase({
|
||||
extension,
|
||||
openNavTarget() {
|
||||
BrowserTestUtils.synthesizeMouseAtCenter("#test-create-new-tab-from-targetblank-click",
|
||||
{},
|
||||
tab.linkedBrowser);
|
||||
},
|
||||
expectedWebNavProps: {
|
||||
sourceTabId: expectedSourceTab.sourceTabId,
|
||||
sourceFrameId: 0,
|
||||
url: `${OPENED_PAGE}#new-tab-from-targetblank-click`,
|
||||
},
|
||||
});
|
||||
|
||||
yield BrowserTestUtils.removeTab(tab);
|
||||
|
||||
yield extension.unload();
|
||||
@ -216,6 +232,25 @@ add_task(function* test_on_created_navigation_target_from_mouse_click_subframe()
|
||||
},
|
||||
});
|
||||
|
||||
info("Open a subframe link with target=\"_blank\" in a new tab using click");
|
||||
|
||||
yield runTestCase({
|
||||
extension,
|
||||
openNavTarget() {
|
||||
BrowserTestUtils.synthesizeMouseAtCenter(function() {
|
||||
// This code runs as a framescript in the child process and it returns the
|
||||
// target link in the subframe.
|
||||
return this.content.frames[0].document // eslint-disable-line mozilla/no-cpows-in-tests
|
||||
.querySelector("#test-create-new-tab-from-targetblank-click-subframe");
|
||||
}, {}, tab.linkedBrowser);
|
||||
},
|
||||
expectedWebNavProps: {
|
||||
sourceTabId: expectedSourceTab.sourceTabId,
|
||||
sourceFrameId: expectedSourceTab.sourceTabFrames[1].frameId,
|
||||
url: `${OPENED_PAGE}#new-tab-from-targetblank-click-subframe`,
|
||||
},
|
||||
});
|
||||
|
||||
yield BrowserTestUtils.removeTab(tab);
|
||||
|
||||
yield extension.unload();
|
||||
|
@ -30,6 +30,13 @@
|
||||
Open a target page in a new window from mouse click
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="test-create-new-tab-from-targetblank-click"
|
||||
href="webNav_createdTarget.html#new-tab-from-targetblank-click"
|
||||
target="_blank">
|
||||
Open a target page in a new tab from click to link with target="_blank"
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<iframe src="webNav_createdTargetSource_subframe.html" style="width: 100%; height: 100%;">
|
||||
|
@ -30,6 +30,13 @@
|
||||
Open a target page in a new window from mouse click (subframe)
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="test-create-new-tab-from-targetblank-click-subframe"
|
||||
href="webNav_createdTarget.html#new-tab-from-targetblank-click-subframe"
|
||||
target="_blank">
|
||||
Open a target page in a new tab from click to link with target="_blank"
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -173,7 +173,7 @@ FeedWriter.prototype = {
|
||||
let element = this._document.getElementById(id);
|
||||
let textNode = text.createDocumentFragment(element);
|
||||
while (element.hasChildNodes())
|
||||
element.removeChild(element.firstChild);
|
||||
element.firstChild.remove();
|
||||
element.appendChild(textNode);
|
||||
if (text.base) {
|
||||
element.setAttributeNS(XML_NS, "base", text.base.spec);
|
||||
|
@ -185,7 +185,7 @@ var MigrationWizard = { /* exported MigrationWizard */
|
||||
|
||||
var profiles = document.getElementById("profiles");
|
||||
while (profiles.hasChildNodes())
|
||||
profiles.removeChild(profiles.firstChild);
|
||||
profiles.firstChild.remove();
|
||||
|
||||
// Note that this block is still reached even if the user chose 'From File'
|
||||
// and we canceled the dialog. When that happens, _migrator will be null.
|
||||
@ -225,7 +225,7 @@ var MigrationWizard = { /* exported MigrationWizard */
|
||||
onImportItemsPageShow() {
|
||||
var dataSources = document.getElementById("dataSources");
|
||||
while (dataSources.hasChildNodes())
|
||||
dataSources.removeChild(dataSources.firstChild);
|
||||
dataSources.firstChild.remove();
|
||||
|
||||
var items = this._migrator.getMigrateData(this._selectedProfile, this._autoMigrate);
|
||||
for (var i = 0; i < 16; ++i) {
|
||||
@ -367,7 +367,7 @@ var MigrationWizard = { /* exported MigrationWizard */
|
||||
_listItems(aID) {
|
||||
var items = document.getElementById(aID);
|
||||
while (items.hasChildNodes())
|
||||
items.removeChild(items.firstChild);
|
||||
items.firstChild.remove();
|
||||
|
||||
var itemID;
|
||||
for (var i = 0; i < 16; ++i) {
|
||||
|
@ -1011,7 +1011,7 @@ PlacesToolbar.prototype = {
|
||||
|
||||
this._openedMenuButton = null;
|
||||
while (this._rootElt.hasChildNodes()) {
|
||||
this._rootElt.removeChild(this._rootElt.firstChild);
|
||||
this._rootElt.firstChild.remove();
|
||||
}
|
||||
|
||||
let cc = this._resultNode.childCount;
|
||||
@ -1952,7 +1952,7 @@ PlacesPanelMenuView.prototype = {
|
||||
|
||||
// Container is the toolbar itself.
|
||||
while (this._rootElt.hasChildNodes()) {
|
||||
this._rootElt.removeChild(this._rootElt.firstChild);
|
||||
this._rootElt.firstChild.remove();
|
||||
}
|
||||
|
||||
for (let i = 0; i < this._resultNode.childCount; ++i) {
|
||||
|
@ -419,7 +419,7 @@ var PlacesOrganizer = {
|
||||
|
||||
// Remove existing menu items. Last item is the restoreFromFile item.
|
||||
while (restorePopup.childNodes.length > 1)
|
||||
restorePopup.removeChild(restorePopup.firstChild);
|
||||
restorePopup.firstChild.remove();
|
||||
|
||||
Task.spawn(function* () {
|
||||
let backupFiles = yield PlacesBackups.getBackupFiles();
|
||||
@ -1007,7 +1007,7 @@ var ViewMenu = {
|
||||
return endElement;
|
||||
}
|
||||
while (popup.hasChildNodes()) {
|
||||
popup.removeChild(popup.firstChild);
|
||||
popup.firstChild.remove();
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
@ -81,7 +81,7 @@ var gBlocklistManager = {
|
||||
|
||||
let blocklistsText = document.getElementById("blocklistsText");
|
||||
while (blocklistsText.hasChildNodes()) {
|
||||
blocklistsText.removeChild(blocklistsText.firstChild);
|
||||
blocklistsText.firstChild.remove();
|
||||
}
|
||||
blocklistsText.appendChild(document.createTextNode(params.introText));
|
||||
|
||||
|
@ -557,7 +557,7 @@ var gAdvancedPane = {
|
||||
|
||||
var list = document.getElementById("offlineAppsList");
|
||||
while (list.firstChild) {
|
||||
list.removeChild(list.firstChild);
|
||||
list.firstChild.remove();
|
||||
}
|
||||
|
||||
var groups;
|
||||
|
@ -97,7 +97,7 @@ var gLanguagesDialog = {
|
||||
_buildAvailableLanguageList() {
|
||||
var availableLanguagesPopup = document.getElementById("availableLanguagesPopup");
|
||||
while (availableLanguagesPopup.hasChildNodes())
|
||||
availableLanguagesPopup.removeChild(availableLanguagesPopup.firstChild);
|
||||
availableLanguagesPopup.firstChild.remove();
|
||||
|
||||
// Sort the list of languages by name
|
||||
this._availableLanguagesList.sort(function(a, b) {
|
||||
@ -119,7 +119,7 @@ var gLanguagesDialog = {
|
||||
|
||||
readAcceptLanguages() {
|
||||
while (this._activeLanguages.hasChildNodes())
|
||||
this._activeLanguages.removeChild(this._activeLanguages.firstChild);
|
||||
this._activeLanguages.firstChild.remove();
|
||||
|
||||
var selectedIndex = 0;
|
||||
var preference = document.getElementById("intl.accept_languages");
|
||||
|
@ -218,7 +218,7 @@ var gPermissionManager = {
|
||||
|
||||
var permissionsText = document.getElementById("permissionsText");
|
||||
while (permissionsText.hasChildNodes())
|
||||
permissionsText.removeChild(permissionsText.firstChild);
|
||||
permissionsText.firstChild.remove();
|
||||
permissionsText.appendChild(document.createTextNode(aParams.introText));
|
||||
|
||||
document.title = aParams.windowTitle;
|
||||
|
@ -100,7 +100,7 @@ SyncedTabsDeckView.prototype = {
|
||||
|
||||
_clearChilden() {
|
||||
while (this.container.firstChild) {
|
||||
this.container.removeChild(this.container.firstChild);
|
||||
this.container.firstChild.remove();
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -169,7 +169,7 @@ TabListView.prototype = {
|
||||
_clearChilden(node) {
|
||||
let parent = node || this.container;
|
||||
while (parent.firstChild) {
|
||||
parent.removeChild(parent.firstChild);
|
||||
parent.firstChild.remove();
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -1236,7 +1236,7 @@ this.UITour = {
|
||||
// Remove all the children of the notice (rating container
|
||||
// and the flex).
|
||||
while (notice.firstChild) {
|
||||
notice.removeChild(notice.firstChild);
|
||||
notice.firstChild.remove();
|
||||
}
|
||||
|
||||
// Make sure that we have a valid URL. If we haven't, do not open the engagement page.
|
||||
|
@ -31,9 +31,9 @@
|
||||
"unpack": true
|
||||
},
|
||||
{
|
||||
"version": "clang 4.0pre/r286542",
|
||||
"size": 222604502,
|
||||
"digest": "cea6119131adb66e0b7ec5030b00922ac95e4e97249fcab9561a848ea60b7f80536c9171a07136afcb79decbcdb20099a5e7ee493013710b8ba5ae072ad40851",
|
||||
"version": "clang 5.0pre/r293859",
|
||||
"size": 309009013,
|
||||
"digest": "cd3ed31acefd185f441632158dde73538c62bab7ebf2a8ec630985ab345938ec522983721ddb1bead1de22d5ac1571d50a958ae002364d739f2a78c6e7244222",
|
||||
"algorithm": "sha512",
|
||||
"filename": "clang.tar.bz2",
|
||||
"unpack": true
|
||||
|
@ -32,9 +32,9 @@
|
||||
"unpack": true
|
||||
},
|
||||
{
|
||||
"version": "clang 4.0pre/r286542",
|
||||
"size": 226755339,
|
||||
"digest": "3c598607c36e70788ca7dbdf0d835f9e44fbcaa7b1ed77ef9971d743a5a230bebc0ccd2bcdf97f63ed4546d1b83f4c3556f35c30589c755aaaefbd674f750e22",
|
||||
"version": "clang 5.0pre/r293859",
|
||||
"size": 313862839,
|
||||
"digest": "44dee70d525ea93952af27f943d1cc773311970c31d971d2bc2e3437cce0c899f3a03ddd8e42e86f1b4fd9ab1c4bc1767cdb0406eb4b3934ae4fc272dab830dc",
|
||||
"algorithm": "sha512",
|
||||
"filename": "clang.tar.bz2",
|
||||
"unpack": true
|
||||
|
@ -76,6 +76,7 @@ FormAutofillParent.prototype = {
|
||||
this._profileStore.initialize();
|
||||
|
||||
Services.obs.addObserver(this, "advanced-pane-loaded", false);
|
||||
Services.ppmm.addMessageListener("FormAutofill:SaveProfile", this);
|
||||
|
||||
// Observing the pref and storage changes
|
||||
Services.prefs.addObserver(ENABLED_PREF, this, false);
|
||||
@ -178,9 +179,18 @@ FormAutofillParent.prototype = {
|
||||
*/
|
||||
receiveMessage({name, data, target}) {
|
||||
switch (name) {
|
||||
case "FormAutofill:GetProfiles":
|
||||
case "FormAutofill:GetProfiles": {
|
||||
this._getProfiles(data, target);
|
||||
break;
|
||||
}
|
||||
case "FormAutofill:SaveProfile": {
|
||||
if (data.guid) {
|
||||
this.getProfileStore().update(data.guid, data.profile);
|
||||
} else {
|
||||
this.getProfileStore().add(data.profile);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -207,6 +217,7 @@ FormAutofillParent.prototype = {
|
||||
}
|
||||
|
||||
Services.ppmm.removeMessageListener("FormAutofill:GetProfiles", this);
|
||||
Services.ppmm.removeMessageListener("FormAutofill:SaveProfile", this);
|
||||
Services.obs.removeObserver(this, "advanced-pane-loaded");
|
||||
Services.prefs.removeObserver(ENABLED_PREF, this);
|
||||
},
|
||||
|
@ -15,6 +15,11 @@ const PREF_AUTOFILL_ENABLED = "browser.formautofill.enabled";
|
||||
const BUNDLE_URI = "chrome://formautofill/locale/formautofill.properties";
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://formautofill/FormAutofillUtils.jsm");
|
||||
|
||||
this.log = null;
|
||||
FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]);
|
||||
|
||||
function FormAutofillPreferences() {
|
||||
this.bundle = Services.strings.createBundle(BUNDLE_URI);
|
||||
@ -46,6 +51,26 @@ FormAutofillPreferences.prototype = {
|
||||
* @returns {XULElement}
|
||||
*/
|
||||
init(document) {
|
||||
this.createPreferenceGroup(document);
|
||||
this.attachEventListeners();
|
||||
|
||||
return this.refs.formAutofillGroup;
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove event listeners and the preference group.
|
||||
*/
|
||||
uninit() {
|
||||
this.detachEventListeners();
|
||||
this.refs.formAutofillGroup.remove();
|
||||
},
|
||||
|
||||
/**
|
||||
* Create Form Autofill preference group
|
||||
*
|
||||
* @param {XULDocument} document
|
||||
*/
|
||||
createPreferenceGroup(document) {
|
||||
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
|
||||
let formAutofillGroup = document.createElementNS(XUL_NS, "groupbox");
|
||||
@ -85,18 +110,6 @@ FormAutofillPreferences.prototype = {
|
||||
hbox.appendChild(enabledCheckbox);
|
||||
hbox.appendChild(spacer);
|
||||
hbox.appendChild(savedProfilesBtn);
|
||||
|
||||
this.attachEventListeners();
|
||||
|
||||
return formAutofillGroup;
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove event listeners and the preference group.
|
||||
*/
|
||||
uninit() {
|
||||
this.detachEventListeners();
|
||||
this.refs.formAutofillGroup.remove();
|
||||
},
|
||||
|
||||
/**
|
||||
|
4
browser/extensions/formautofill/bootstrap.js
vendored
4
browser/extensions/formautofill/bootstrap.js
vendored
@ -31,8 +31,8 @@ function insertStyleSheet(domWindow, url) {
|
||||
}
|
||||
|
||||
let windowListener = {
|
||||
onOpenWindow(aWindow) {
|
||||
let domWindow = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
|
||||
onOpenWindow(window) {
|
||||
let domWindow = window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
|
||||
|
||||
domWindow.addEventListener("load", function onWindowLoaded() {
|
||||
insertStyleSheet(domWindow, STYLESHEET_URI);
|
||||
|
92
browser/extensions/formautofill/content/editProfile.css
Normal file
92
browser/extensions/formautofill/content/editProfile.css
Normal file
@ -0,0 +1,92 @@
|
||||
/* 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/. */
|
||||
|
||||
body {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
form,
|
||||
label,
|
||||
div {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
form {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
label {
|
||||
margin: 0 0 0.5em;
|
||||
}
|
||||
|
||||
label > span {
|
||||
flex: 0 0 8em;
|
||||
padding-inline-end: 0.5em;
|
||||
align-self: center;
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
input,
|
||||
select {
|
||||
flex: 1 0 auto;
|
||||
width: 9em;
|
||||
}
|
||||
|
||||
option {
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: none;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 3px 2em;
|
||||
}
|
||||
|
||||
#country-container {
|
||||
width: 15em;
|
||||
}
|
||||
|
||||
#first-name-container,
|
||||
#middle-name-container,
|
||||
#address-level1-container,
|
||||
#postal-code-container,
|
||||
#country-container {
|
||||
flex: 0 1 50%;
|
||||
}
|
||||
|
||||
#last-name-container,
|
||||
#organization-container,
|
||||
#street-address-container,
|
||||
#address-level2-container,
|
||||
#email-container,
|
||||
#tel-container,
|
||||
#controls-container {
|
||||
flex: 0 1 100%;
|
||||
}
|
||||
|
||||
#controls-container {
|
||||
justify-content: end;
|
||||
}
|
||||
|
||||
#last-name,
|
||||
#organization,
|
||||
#address-level2,
|
||||
#tel{
|
||||
flex: 0 0 auto;
|
||||
width: calc(50% - 10em);
|
||||
}
|
||||
|
||||
#street-address,
|
||||
#email {
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
#first-name-container,
|
||||
#middle-name-container,
|
||||
#last-name-container {
|
||||
/* Hide until we support names */
|
||||
display: none;
|
||||
}
|
140
browser/extensions/formautofill/content/editProfile.js
Normal file
140
browser/extensions/formautofill/content/editProfile.js
Normal file
@ -0,0 +1,140 @@
|
||||
/* 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 {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://formautofill/FormAutofillUtils.jsm");
|
||||
|
||||
function EditDialog(profile) {
|
||||
this._profile = profile;
|
||||
window.addEventListener("DOMContentLoaded", this, {once: true});
|
||||
}
|
||||
|
||||
EditDialog.prototype = {
|
||||
init() {
|
||||
this.refs = {
|
||||
controlsContainer: document.getElementById("controls-container"),
|
||||
cancel: document.getElementById("cancel"),
|
||||
save: document.getElementById("save"),
|
||||
};
|
||||
this.attachEventListeners();
|
||||
},
|
||||
|
||||
/**
|
||||
* Asks FormAutofillParent to save or update a profile.
|
||||
* @param {object} data
|
||||
* {
|
||||
* {string} guid [optional]
|
||||
* {object} profile
|
||||
* }
|
||||
*/
|
||||
saveProfile(data) {
|
||||
Services.cpmm.sendAsyncMessage("FormAutofill:SaveProfile", data);
|
||||
},
|
||||
|
||||
/**
|
||||
* Fill the form with a profile object.
|
||||
* @param {object} profile
|
||||
*/
|
||||
loadInitialValues(profile) {
|
||||
for (let field in profile) {
|
||||
let input = document.getElementById(field);
|
||||
if (input) {
|
||||
input.value = profile[field];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get inputs from the form.
|
||||
* @returns {object}
|
||||
*/
|
||||
buildProfileObject() {
|
||||
return Array.from(document.forms[0].elements).reduce((obj, input) => {
|
||||
if (input.value) {
|
||||
obj[input.id] = input.value;
|
||||
}
|
||||
return obj;
|
||||
}, {});
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle events
|
||||
*
|
||||
* @param {DOMEvent} event
|
||||
*/
|
||||
handleEvent(event) {
|
||||
switch (event.type) {
|
||||
case "DOMContentLoaded": {
|
||||
this.init();
|
||||
if (this._profile) {
|
||||
this.loadInitialValues(this._profile);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "click": {
|
||||
this.handleClick(event);
|
||||
break;
|
||||
}
|
||||
case "input": {
|
||||
// Toggle disabled attribute on the save button based on
|
||||
// whether the form is filled or empty.
|
||||
if (Object.keys(this.buildProfileObject()).length == 0) {
|
||||
this.refs.save.setAttribute("disabled", true);
|
||||
} else {
|
||||
this.refs.save.removeAttribute("disabled");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle click events
|
||||
*
|
||||
* @param {DOMEvent} event
|
||||
*/
|
||||
handleClick(event) {
|
||||
if (event.target == this.refs.cancel) {
|
||||
this.detachEventListeners();
|
||||
window.close();
|
||||
}
|
||||
if (event.target == this.refs.save) {
|
||||
if (this._profile) {
|
||||
this.saveProfile({
|
||||
guid: this._profile.guid,
|
||||
profile: this.buildProfileObject(),
|
||||
});
|
||||
} else {
|
||||
this.saveProfile({
|
||||
profile: this.buildProfileObject(),
|
||||
});
|
||||
}
|
||||
this.detachEventListeners();
|
||||
window.close();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Attach event listener
|
||||
*/
|
||||
attachEventListeners() {
|
||||
this.refs.controlsContainer.addEventListener("click", this);
|
||||
document.addEventListener("input", this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove event listener
|
||||
*/
|
||||
detachEventListeners() {
|
||||
this.refs.controlsContainer.removeEventListener("click", this);
|
||||
document.removeEventListener("input", this);
|
||||
},
|
||||
};
|
||||
|
||||
// Pass in argument from openDialog
|
||||
new EditDialog(window.arguments[0]);
|
67
browser/extensions/formautofill/content/editProfile.xhtml
Normal file
67
browser/extensions/formautofill/content/editProfile.xhtml
Normal file
@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>Profile Autofill - Edit Profile</title>
|
||||
<link rel="stylesheet" href="chrome://formautofill/content/editProfile.css" />
|
||||
<script src="chrome://formautofill/content/editProfile.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<form>
|
||||
<label id="first-name-container">
|
||||
<span>First Name</span>
|
||||
<input id="first-name" type="text"/>
|
||||
</label>
|
||||
<label id="middle-name-container">
|
||||
<span>Middle Name</span>
|
||||
<input id="middle-name" type="text"/>
|
||||
</label>
|
||||
<label id="last-name-container">
|
||||
<span>Last Name</span>
|
||||
<input id="last-name" type="text"/>
|
||||
</label>
|
||||
<label id="organization-container">
|
||||
<span>Company</span>
|
||||
<input id="organization" type="text"/>
|
||||
</label>
|
||||
<label id="street-address-container">
|
||||
<span>Street Address</span>
|
||||
<textarea id="street-address"/>
|
||||
</label>
|
||||
<label id="address-level2-container">
|
||||
<span>City/Town</span>
|
||||
<input id="address-level2" type="text"/>
|
||||
</label>
|
||||
<label id="address-level1-container">
|
||||
<span>State/Province</span>
|
||||
<input id="address-level1" type="text"/>
|
||||
</label>
|
||||
<label id="postal-code-container">
|
||||
<span>Zip/Postal</span>
|
||||
<input id="postal-code" type="text"/>
|
||||
</label>
|
||||
<label id="country-container">
|
||||
<span>Country</span>
|
||||
<select id="country">
|
||||
<option/>
|
||||
<option value="US">United States</option>
|
||||
</select>
|
||||
</label>
|
||||
<label id="email-container">
|
||||
<span>Email</span>
|
||||
<input id="email" type="email"/>
|
||||
</label>
|
||||
<label id="tel-container">
|
||||
<span>Phone</span>
|
||||
<input id="tel" type="tel"/>
|
||||
</label>
|
||||
</form>
|
||||
<div id="controls-container">
|
||||
<button id="cancel">Cancel</button>
|
||||
<button id="save" disabled="disabled">Save</button>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -43,6 +43,7 @@ customAPIs = {
|
||||
('PPB_PDF', 'SetAccessibilityPageInfo', 'text_runs'): { 'arraySize': 'page_info->text_run_count' },
|
||||
('PPB_PDF', 'SetAccessibilityPageInfo', 'chars'): { 'arraySize': 'page_info->char_count' },
|
||||
('PPB_TCPSocket_Private', 'Write', 'buffer'): { 'array': True, 'arrayType': 'uint8_t', 'arraySize': 'bytes_to_write' },
|
||||
('PPP_Printing_Dev', 'PrintPages', 'page_ranges'): { 'array': True, 'arraySize': None },
|
||||
}
|
||||
def getCustom(interface, member, param):
|
||||
def matches(pattern, value):
|
||||
@ -335,7 +336,10 @@ class RPCGen(object):
|
||||
for param in callnode.GetListOf('Param'):
|
||||
mode = self.cgen.GetParamMode(param)
|
||||
ptype, pname, parray, pspec = self.cgen.GetComponents(param, release, "store")
|
||||
out += ' ' + self.cgen.Compose(ptype, pname, parray, pspec, '', func_as_ptr=True,
|
||||
prefix = ''
|
||||
if getCustom(iface.GetName(), node.GetName(), param.GetName()).get("array"):
|
||||
prefix = '*'
|
||||
out += ' ' + self.cgen.Compose(ptype, pname, parray, pspec, prefix, func_as_ptr=True,
|
||||
include_name=True, unsized_as_ptr=True) + ';\n'
|
||||
if mode == 'out':
|
||||
if len(parray) > 0:
|
||||
@ -359,7 +363,8 @@ class RPCGen(object):
|
||||
mode = self.cgen.GetParamMode(param)
|
||||
ntype, mode = self.cgen.GetRootTypeMode(param, release, mode)
|
||||
ptype, pname, parray, pspec = self.cgen.GetComponents(param, release, mode)
|
||||
if mode == 'out' or ntype == 'Struct' or (mode == 'constptr_in' and ntype == 'TypeValue'):
|
||||
isArray = getCustom(iface.GetName(), node.GetName(), param.GetName()).get("array")
|
||||
if (mode == 'out' or ntype == 'Struct' or (mode == 'constptr_in' and ntype == 'TypeValue')) and not isArray:
|
||||
pname = '&' + pname
|
||||
pname = '(' + self.cgen.Compose(ptype, pname, parray, pspec, '', func_as_ptr=True,
|
||||
include_name=False, unsized_as_ptr=True) + ')' + pname
|
||||
|
@ -237,74 +237,6 @@ inline bool PP_ToBool(PP_Bool b) {
|
||||
|
||||
#endif /* __cplusplus */
|
||||
|
||||
/* pp_point.idl */
|
||||
/**
|
||||
* @addtogroup Structs
|
||||
* @{
|
||||
*/
|
||||
/**
|
||||
* The PP_Point structure defines the integer x and y coordinates of a point.
|
||||
*/
|
||||
struct PP_Point {
|
||||
/**
|
||||
* This value represents the horizontal coordinate of a point, starting with 0
|
||||
* as the left-most coordinate.
|
||||
*/
|
||||
int32_t x;
|
||||
/**
|
||||
* This value represents the vertical coordinate of a point, starting with 0
|
||||
* as the top-most coordinate.
|
||||
*/
|
||||
int32_t y;
|
||||
};
|
||||
PP_COMPILE_ASSERT_STRUCT_SIZE_IN_BYTES(PP_Point, 8);
|
||||
|
||||
/**
|
||||
* The PP_FloatPoint structure defines the floating-point x and y coordinates
|
||||
* of a point.
|
||||
*/
|
||||
struct PP_FloatPoint {
|
||||
float x;
|
||||
float y;
|
||||
};
|
||||
PP_COMPILE_ASSERT_STRUCT_SIZE_IN_BYTES(PP_FloatPoint, 8);
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @addtogroup Functions
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* PP_MakePoint() creates a <code>PP_Point</code> given the x and y coordinates
|
||||
* as int32_t values.
|
||||
*
|
||||
* @param[in] x An int32_t value representing a horizontal coordinate of a
|
||||
* point, starting with 0 as the left-most coordinate.
|
||||
* @param[in] y An int32_t value representing a vertical coordinate of a point,
|
||||
* starting with 0 as the top-most coordinate.
|
||||
*
|
||||
* @return A <code>PP_Point</code> structure.
|
||||
*/
|
||||
PP_INLINE struct PP_Point PP_MakePoint(int32_t x, int32_t y) {
|
||||
struct PP_Point ret;
|
||||
ret.x = x;
|
||||
ret.y = y;
|
||||
return ret;
|
||||
}
|
||||
|
||||
PP_INLINE struct PP_FloatPoint PP_MakeFloatPoint(float x, float y) {
|
||||
struct PP_FloatPoint ret;
|
||||
ret.x = x;
|
||||
ret.y = y;
|
||||
return ret;
|
||||
}
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
/* pp_size.idl */
|
||||
/**
|
||||
* @addtogroup Structs
|
||||
@ -373,6 +305,74 @@ PP_INLINE struct PP_FloatSize PP_MakeFloatSize(float w, float h) {
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
/* pp_point.idl */
|
||||
/**
|
||||
* @addtogroup Structs
|
||||
* @{
|
||||
*/
|
||||
/**
|
||||
* The PP_Point structure defines the integer x and y coordinates of a point.
|
||||
*/
|
||||
struct PP_Point {
|
||||
/**
|
||||
* This value represents the horizontal coordinate of a point, starting with 0
|
||||
* as the left-most coordinate.
|
||||
*/
|
||||
int32_t x;
|
||||
/**
|
||||
* This value represents the vertical coordinate of a point, starting with 0
|
||||
* as the top-most coordinate.
|
||||
*/
|
||||
int32_t y;
|
||||
};
|
||||
PP_COMPILE_ASSERT_STRUCT_SIZE_IN_BYTES(PP_Point, 8);
|
||||
|
||||
/**
|
||||
* The PP_FloatPoint structure defines the floating-point x and y coordinates
|
||||
* of a point.
|
||||
*/
|
||||
struct PP_FloatPoint {
|
||||
float x;
|
||||
float y;
|
||||
};
|
||||
PP_COMPILE_ASSERT_STRUCT_SIZE_IN_BYTES(PP_FloatPoint, 8);
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @addtogroup Functions
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* PP_MakePoint() creates a <code>PP_Point</code> given the x and y coordinates
|
||||
* as int32_t values.
|
||||
*
|
||||
* @param[in] x An int32_t value representing a horizontal coordinate of a
|
||||
* point, starting with 0 as the left-most coordinate.
|
||||
* @param[in] y An int32_t value representing a vertical coordinate of a point,
|
||||
* starting with 0 as the top-most coordinate.
|
||||
*
|
||||
* @return A <code>PP_Point</code> structure.
|
||||
*/
|
||||
PP_INLINE struct PP_Point PP_MakePoint(int32_t x, int32_t y) {
|
||||
struct PP_Point ret;
|
||||
ret.x = x;
|
||||
ret.y = y;
|
||||
return ret;
|
||||
}
|
||||
|
||||
PP_INLINE struct PP_FloatPoint PP_MakeFloatPoint(float x, float y) {
|
||||
struct PP_FloatPoint ret;
|
||||
ret.x = x;
|
||||
ret.y = y;
|
||||
return ret;
|
||||
}
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
/* pp_rect.idl */
|
||||
/**
|
||||
* @addtogroup Structs
|
||||
@ -49238,14 +49238,22 @@ char* Call_PPP_Printing_Dev_PrintPages(const PPP_Printing_Dev* _interface, JSONI
|
||||
PP_Instance instance;
|
||||
iterator.skip();
|
||||
FromJSON_PP_Instance(iterator, instance);
|
||||
struct PP_PrintPageNumberRange_Dev page_ranges;
|
||||
struct PP_PrintPageNumberRange_Dev *page_ranges;
|
||||
iterator.skip();
|
||||
FromJSON_PP_PrintPageNumberRange_Dev(iterator, page_ranges);
|
||||
|
||||
{
|
||||
size_t children = iterator.expectArrayAndGotoFirstItem();
|
||||
page_ranges = new struct PP_PrintPageNumberRange_Dev[children];
|
||||
for (uint32_t _n = 0; _n < children; ++_n) {
|
||||
FromJSON_PP_PrintPageNumberRange_Dev(iterator, (page_ranges)[_n]);
|
||||
}
|
||||
// FIXME Null out remaining items?
|
||||
}
|
||||
uint32_t page_range_count;
|
||||
iterator.skip();
|
||||
FromJSON_uint32_t(iterator, page_range_count);
|
||||
int32_t rval;
|
||||
rval = _interface->PrintPages((PP_Instance )instance, (const struct PP_PrintPageNumberRange_Dev* )&page_ranges, (uint32_t )page_range_count);
|
||||
rval = _interface->PrintPages((PP_Instance )instance, (const struct PP_PrintPageNumberRange_Dev* )page_ranges, (uint32_t )page_range_count);
|
||||
return strdup(ToString_PP_Resource(rval).c_str());
|
||||
}
|
||||
char* Call_PPP_Printing_Dev_End(const PPP_Printing_Dev* _interface, JSONIterator& iterator) {
|
||||
|
@ -3898,7 +3898,7 @@ var PDFAttachmentViewer = function PDFAttachmentViewerClosure() {
|
||||
this.attachments = null;
|
||||
var container = this.container;
|
||||
while (container.firstChild) {
|
||||
container.removeChild(container.firstChild);
|
||||
container.firstChild.remove();
|
||||
}
|
||||
if (!keepRenderedCapability) {
|
||||
this._renderedCapability = pdfjsLib.createPromiseCapability();
|
||||
@ -4598,7 +4598,7 @@ var PDFOutlineViewer = function PDFOutlineViewerClosure() {
|
||||
this.lastToggleIsShow = true;
|
||||
var container = this.container;
|
||||
while (container.firstChild) {
|
||||
container.removeChild(container.firstChild);
|
||||
container.firstChild.remove();
|
||||
}
|
||||
},
|
||||
_dispatchEvent: function PDFOutlineViewer_dispatchEvent(outlineCount) {
|
||||
|
@ -206,8 +206,8 @@ components/FxAccountsPush.js
|
||||
crashreporter.app/Contents/Resources/English.lproj/MainMenu.nib/classes.nib
|
||||
crashreporter.app/Contents/Resources/English.lproj/MainMenuRTL.nib/classes.nib
|
||||
# firefox/firefox-bin is bug 658850
|
||||
firefox
|
||||
firefox-bin
|
||||
@MOZ_APP_NAME@
|
||||
@MOZ_APP_NAME@-bin
|
||||
modules/FxAccountsPush.js
|
||||
modules/commonjs/index.js
|
||||
modules/commonjs/sdk/ui/button/view/events.js
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"llvm_revision": "286542",
|
||||
"llvm_revision": "293859",
|
||||
"stages": "3",
|
||||
"build_libcxx": false,
|
||||
"build_type": "Release",
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"llvm_revision": "286542",
|
||||
"llvm_revision": "293859",
|
||||
"stages": "3",
|
||||
"build_libcxx": false,
|
||||
"build_type": "Release",
|
||||
|
@ -10,6 +10,7 @@ CHECK(ExplicitImplicitChecker, "implicit-constructor")
|
||||
CHECK(ExplicitOperatorBoolChecker, "explicit-operator-bool")
|
||||
CHECK(KungFuDeathGripChecker, "kungfu-death-grip")
|
||||
CHECK(MustOverrideChecker, "must-override")
|
||||
CHECK(MustReturnFromCallerChecker, "must-return-from-caller")
|
||||
CHECK(MustUseChecker, "must-use")
|
||||
CHECK(NaNExprChecker, "nan-expr")
|
||||
CHECK(NeedsNoVTableTypeChecker, "needs-no-vtable-type")
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include "ExplicitOperatorBoolChecker.h"
|
||||
#include "KungFuDeathGripChecker.h"
|
||||
#include "MustOverrideChecker.h"
|
||||
#include "MustReturnFromCallerChecker.h"
|
||||
#include "MustUseChecker.h"
|
||||
#include "NaNExprChecker.h"
|
||||
#include "NeedsNoVTableTypeChecker.h"
|
||||
|
@ -231,6 +231,11 @@ AST_MATCHER(CXXMethodDecl, isNonVirtual) {
|
||||
const CXXMethodDecl *Decl = Node.getCanonicalDecl();
|
||||
return Decl && !Decl->isVirtual();
|
||||
}
|
||||
|
||||
AST_MATCHER(FunctionDecl, isMozMustReturnFromCaller) {
|
||||
const FunctionDecl *Decl = Node.getCanonicalDecl();
|
||||
return Decl && hasCustomAnnotation(Decl, "moz_must_return_from_caller");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
105
build/clang-plugin/MustReturnFromCallerChecker.cpp
Normal file
105
build/clang-plugin/MustReturnFromCallerChecker.cpp
Normal file
@ -0,0 +1,105 @@
|
||||
/* 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/. */
|
||||
|
||||
#include "MustReturnFromCallerChecker.h"
|
||||
#include "CustomMatchers.h"
|
||||
|
||||
void MustReturnFromCallerChecker::registerMatchers(MatchFinder* AstMatcher) {
|
||||
// Look for a call to a MOZ_MUST_RETURN_FROM_CALLER function
|
||||
AstMatcher->addMatcher(callExpr(callee(functionDecl(isMozMustReturnFromCaller())),
|
||||
anyOf(hasAncestor(lambdaExpr().bind("containing-lambda")),
|
||||
hasAncestor(functionDecl().bind("containing-func")))).bind("call"),
|
||||
this);
|
||||
}
|
||||
|
||||
void MustReturnFromCallerChecker::check(
|
||||
const MatchFinder::MatchResult& Result) {
|
||||
const auto *ContainingLambda =
|
||||
Result.Nodes.getNodeAs<LambdaExpr>("containing-lambda");
|
||||
const auto *ContainingFunc =
|
||||
Result.Nodes.getNodeAs<FunctionDecl>("containing-func");
|
||||
const auto *Call = Result.Nodes.getNodeAs<CallExpr>("call");
|
||||
|
||||
Stmt *Body = nullptr;
|
||||
if (ContainingLambda) {
|
||||
Body = ContainingLambda->getBody();
|
||||
} else if (ContainingFunc) {
|
||||
Body = ContainingFunc->getBody();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
assert(Body && "Should have a body by this point");
|
||||
|
||||
// Generate the CFG for the enclosing function or decl.
|
||||
CFG::BuildOptions Options;
|
||||
std::unique_ptr<CFG> TheCFG =
|
||||
CFG::buildCFG(nullptr, Body, Result.Context, Options);
|
||||
if (!TheCFG) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine which block in the CFG we want to look at the successors of.
|
||||
StmtToBlockMap BlockMap(TheCFG.get(), Result.Context);
|
||||
size_t CallIndex;
|
||||
const auto *Block = BlockMap.blockContainingStmt(Call, &CallIndex);
|
||||
assert(Block && "This statement should be within the CFG!");
|
||||
|
||||
if (!immediatelyReturns(Block, Result.Context, CallIndex + 1)) {
|
||||
diag(Call->getLocStart(),
|
||||
"You must immediately return after calling this function",
|
||||
DiagnosticIDs::Error);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
MustReturnFromCallerChecker::immediatelyReturns(RecurseGuard<const CFGBlock *> Block,
|
||||
ASTContext *TheContext,
|
||||
size_t FromIdx) {
|
||||
if (Block.isRepeat()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t I = FromIdx; I < Block->size(); ++I) {
|
||||
Optional<CFGStmt> S = (*Block)[I].getAs<CFGStmt>();
|
||||
if (!S) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto AfterTrivials = IgnoreTrivials(S->getStmt());
|
||||
|
||||
// If we are looking at a ConstructExpr, a DeclRefExpr or a MemberExpr it's
|
||||
// OK to use them after a call to a MOZ_MUST_RETURN_FROM_CALLER function.
|
||||
// It is also, of course, OK to look at a ReturnStmt.
|
||||
if (isa<ReturnStmt>(AfterTrivials) ||
|
||||
isa<CXXConstructExpr>(AfterTrivials) ||
|
||||
isa<DeclRefExpr>(AfterTrivials) ||
|
||||
isa<MemberExpr>(AfterTrivials)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// It's also OK to call any function or method which is annotated with
|
||||
// MOZ_MAY_CALL_AFTER_MUST_RETURN. We consider all CXXConversionDecls
|
||||
// to be MOZ_MAY_CALL_AFTER_MUST_RETURN (like operator T*()).
|
||||
if (auto CE = dyn_cast<CallExpr>(AfterTrivials)) {
|
||||
auto Callee = CE->getDirectCallee();
|
||||
if (Callee && hasCustomAnnotation(Callee, "moz_may_call_after_must_return")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Callee && isa<CXXConversionDecl>(Callee)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, this expression is problematic.
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto Succ = Block->succ_begin(); Succ != Block->succ_end(); ++Succ) {
|
||||
if (!immediatelyReturns(Block.recurse(*Succ), TheContext, 0)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
26
build/clang-plugin/MustReturnFromCallerChecker.h
Normal file
26
build/clang-plugin/MustReturnFromCallerChecker.h
Normal file
@ -0,0 +1,26 @@
|
||||
/* 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/. */
|
||||
|
||||
#ifndef MustReturnFromCallerChecker_h__
|
||||
#define MustReturnFromCallerChecker_h__
|
||||
|
||||
#include "plugin.h"
|
||||
#include "Utils.h"
|
||||
#include "RecurseGuard.h"
|
||||
#include "StmtToBlockMap.h"
|
||||
|
||||
class MustReturnFromCallerChecker : public BaseCheck {
|
||||
public:
|
||||
MustReturnFromCallerChecker(StringRef CheckName,
|
||||
ContextType *Context = nullptr)
|
||||
: BaseCheck(CheckName, Context) {}
|
||||
void registerMatchers(MatchFinder* AstMatcher) override;
|
||||
void check(const MatchFinder::MatchResult &Result) override;
|
||||
private:
|
||||
bool immediatelyReturns(RecurseGuard<const CFGBlock *> Block,
|
||||
ASTContext *TheContext,
|
||||
size_t FromIdx);
|
||||
};
|
||||
|
||||
#endif
|
63
build/clang-plugin/RecurseGuard.h
Normal file
63
build/clang-plugin/RecurseGuard.h
Normal file
@ -0,0 +1,63 @@
|
||||
/* 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/. */
|
||||
|
||||
#ifndef RecurseGuard_h__
|
||||
#define RecurseGuard_h__
|
||||
|
||||
#include "Utils.h"
|
||||
|
||||
// This class acts as a tracker for avoiding infinite recursion when traversing
|
||||
// chains in CFGs etc.
|
||||
//
|
||||
// Constructing a RecurseGuard sets up a shared backing store which tracks the
|
||||
// currently observed objects. Whenever recursing, use RecurseGuard.recurse(T)
|
||||
// to construct another RecurseGuard with the same backing store.
|
||||
//
|
||||
// The RecurseGuard object will unregister its object when it is destroyed, and
|
||||
// has a method `isRepeat()` which will return `true` if the item was already
|
||||
// seen.
|
||||
template<typename T>
|
||||
class RecurseGuard {
|
||||
public:
|
||||
RecurseGuard(T Thing) : Thing(Thing), Set(new DenseSet<T>()), Repeat(false) {
|
||||
Set->insert(Thing);
|
||||
}
|
||||
RecurseGuard(T Thing, std::shared_ptr<DenseSet<T>>& Set)
|
||||
: Thing(Thing), Set(Set), Repeat(false) {
|
||||
Repeat = !Set->insert(Thing).second;
|
||||
}
|
||||
RecurseGuard(const RecurseGuard &) = delete;
|
||||
RecurseGuard(RecurseGuard && Other)
|
||||
: Thing(Other.Thing), Set(Other.Set), Repeat(Other.Repeat) {
|
||||
Other.Repeat = true;
|
||||
}
|
||||
~RecurseGuard() {
|
||||
if (!Repeat) {
|
||||
Set->erase(Thing);
|
||||
}
|
||||
}
|
||||
|
||||
bool isRepeat() { return Repeat; }
|
||||
|
||||
T get() { return Thing; }
|
||||
|
||||
operator T() {
|
||||
return Thing;
|
||||
}
|
||||
|
||||
T operator ->() {
|
||||
return Thing;
|
||||
}
|
||||
|
||||
RecurseGuard recurse(T NewThing) {
|
||||
return RecurseGuard(NewThing, Set);
|
||||
}
|
||||
|
||||
private:
|
||||
T Thing;
|
||||
std::shared_ptr<DenseSet<T>> Set;
|
||||
bool Repeat;
|
||||
};
|
||||
|
||||
#endif // RecurseGuard_h__
|
86
build/clang-plugin/StmtToBlockMap.h
Normal file
86
build/clang-plugin/StmtToBlockMap.h
Normal file
@ -0,0 +1,86 @@
|
||||
/* 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/. */
|
||||
|
||||
#ifndef StmtToBlockMap_h__
|
||||
#define StmtToBlockMap_h__
|
||||
|
||||
#include "Utils.h"
|
||||
|
||||
// This method is copied from clang-tidy's ExprSequence.cpp.
|
||||
//
|
||||
// Returns the Stmt nodes that are parents of 'S', skipping any potential
|
||||
// intermediate non-Stmt nodes.
|
||||
//
|
||||
// In almost all cases, this function returns a single parent or no parents at
|
||||
// all.
|
||||
inline SmallVector<const Stmt *, 1> getParentStmts(const Stmt *S,
|
||||
ASTContext *Context) {
|
||||
SmallVector<const Stmt *, 1> Result;
|
||||
|
||||
ASTContext::DynTypedNodeList Parents = Context->getParents(*S);
|
||||
|
||||
SmallVector<ast_type_traits::DynTypedNode, 1> NodesToProcess(Parents.begin(),
|
||||
Parents.end());
|
||||
|
||||
while (!NodesToProcess.empty()) {
|
||||
ast_type_traits::DynTypedNode Node = NodesToProcess.back();
|
||||
NodesToProcess.pop_back();
|
||||
|
||||
if (const auto *S = Node.get<Stmt>()) {
|
||||
Result.push_back(S);
|
||||
} else {
|
||||
Parents = Context->getParents(Node);
|
||||
NodesToProcess.append(Parents.begin(), Parents.end());
|
||||
}
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
// This class is a modified version of the class from clang-tidy's ExprSequence.cpp
|
||||
//
|
||||
// Maps `Stmt`s to the `CFGBlock` that contains them. Some `Stmt`s may be
|
||||
// contained in more than one `CFGBlock`; in this case, they are mapped to the
|
||||
// innermost block (i.e. the one that is furthest from the root of the tree).
|
||||
// An optional outparameter provides the index into the block where the `Stmt`
|
||||
// was found.
|
||||
class StmtToBlockMap {
|
||||
public:
|
||||
// Initializes the map for the given `CFG`.
|
||||
StmtToBlockMap(const CFG *TheCFG, ASTContext *TheContext) : Context(TheContext) {
|
||||
for (const auto *B : *TheCFG) {
|
||||
for (size_t I = 0; I < B->size(); ++I) {
|
||||
if (Optional<CFGStmt> S = (*B)[I].getAs<CFGStmt>()) {
|
||||
Map[S->getStmt()] = std::make_pair(B, I);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the block that S is contained in. Some `Stmt`s may be contained
|
||||
// in more than one `CFGBlock`; in this case, this function returns the
|
||||
// innermost block (i.e. the one that is furthest from the root of the tree).
|
||||
//
|
||||
// The optional outparameter `Index` is set to the index into the block where
|
||||
// the `Stmt` was found.
|
||||
const CFGBlock *blockContainingStmt(const Stmt *S, size_t *Index = nullptr) const {
|
||||
while (!Map.count(S)) {
|
||||
SmallVector<const Stmt *, 1> Parents = getParentStmts(S, Context);
|
||||
if (Parents.empty())
|
||||
return nullptr;
|
||||
S = Parents[0];
|
||||
}
|
||||
|
||||
const auto &E = Map.lookup(S);
|
||||
if (Index) *Index = E.second;
|
||||
return E.first;
|
||||
}
|
||||
|
||||
private:
|
||||
ASTContext *Context;
|
||||
|
||||
llvm::DenseMap<const Stmt *, std::pair<const CFGBlock *, size_t>> Map;
|
||||
};
|
||||
|
||||
#endif // StmtToBlockMap_h__
|
@ -16,6 +16,10 @@ void TrivialCtorDtorChecker::check(
|
||||
"class %0 must have trivial constructors and destructors";
|
||||
const CXXRecordDecl *Node = Result.Nodes.getNodeAs<CXXRecordDecl>("node");
|
||||
|
||||
if (!Node->hasDefinition()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We need to accept non-constexpr trivial constructors as well. This occurs
|
||||
// when a struct contains pod members, which will not be initialized. As
|
||||
// constexpr values are initialized, the constructor is non-constexpr.
|
||||
|
@ -16,6 +16,7 @@ UNIFIED_SOURCES += [
|
||||
'KungFuDeathGripChecker.cpp',
|
||||
'MozCheckAction.cpp',
|
||||
'MustOverrideChecker.cpp',
|
||||
'MustReturnFromCallerChecker.cpp',
|
||||
'MustUseChecker.cpp',
|
||||
'NaNExprChecker.cpp',
|
||||
'NeedsNoVTableTypeChecker.cpp',
|
||||
|
@ -5,6 +5,7 @@
|
||||
#ifndef plugin_h__
|
||||
#define plugin_h__
|
||||
|
||||
#include "clang/Analysis/CFG.h"
|
||||
#include "clang/AST/ASTConsumer.h"
|
||||
#include "clang/AST/ASTContext.h"
|
||||
#include "clang/AST/RecursiveASTVisitor.h"
|
||||
|
183
build/clang-plugin/tests/TestMustReturnFromCaller.cpp
Normal file
183
build/clang-plugin/tests/TestMustReturnFromCaller.cpp
Normal file
@ -0,0 +1,183 @@
|
||||
#include <cstddef>
|
||||
|
||||
#define MOZ_MUST_RETURN_FROM_CALLER __attribute__((annotate("moz_must_return_from_caller")))
|
||||
#define MOZ_MAY_CALL_AFTER_MUST_RETURN __attribute__((annotate("moz_may_call_after_must_return")))
|
||||
|
||||
void MOZ_MUST_RETURN_FROM_CALLER Throw() {}
|
||||
void DoAnythingElse();
|
||||
int MakeAnInt();
|
||||
int MOZ_MAY_CALL_AFTER_MUST_RETURN SafeMakeInt();
|
||||
bool Condition();
|
||||
|
||||
class Foo {
|
||||
public:
|
||||
__attribute__((annotate("moz_implicit"))) Foo(std::nullptr_t);
|
||||
Foo();
|
||||
};
|
||||
|
||||
void a1() {
|
||||
Throw();
|
||||
}
|
||||
|
||||
int a2() {
|
||||
Throw(); // expected-error {{You must immediately return after calling this function}}
|
||||
return MakeAnInt();
|
||||
}
|
||||
|
||||
int a3() {
|
||||
Throw();
|
||||
return 5;
|
||||
}
|
||||
|
||||
int a4() {
|
||||
Throw(); // expected-error {{You must immediately return after calling this function}}
|
||||
return Condition() ? MakeAnInt() : MakeAnInt();
|
||||
}
|
||||
|
||||
void a5() {
|
||||
Throw(); // expected-error {{You must immediately return after calling this function}}
|
||||
DoAnythingElse();
|
||||
}
|
||||
|
||||
int a6() {
|
||||
Throw(); // expected-error {{You must immediately return after calling this function}}
|
||||
DoAnythingElse();
|
||||
return MakeAnInt();
|
||||
}
|
||||
|
||||
int a7() {
|
||||
Throw(); // expected-error {{You must immediately return after calling this function}}
|
||||
DoAnythingElse();
|
||||
return Condition() ? MakeAnInt() : MakeAnInt();
|
||||
}
|
||||
|
||||
int a8() {
|
||||
Throw();
|
||||
return SafeMakeInt();
|
||||
}
|
||||
|
||||
int a9() {
|
||||
if (Condition()) {
|
||||
Throw();
|
||||
}
|
||||
return SafeMakeInt();
|
||||
}
|
||||
|
||||
void b1() {
|
||||
if (Condition()) {
|
||||
Throw();
|
||||
}
|
||||
}
|
||||
|
||||
int b2() {
|
||||
if (Condition()) {
|
||||
Throw(); // expected-error {{You must immediately return after calling this function}}
|
||||
}
|
||||
return MakeAnInt();
|
||||
}
|
||||
|
||||
int b3() {
|
||||
if (Condition()) {
|
||||
Throw();
|
||||
}
|
||||
return 5;
|
||||
}
|
||||
|
||||
int b4() {
|
||||
if (Condition()) {
|
||||
Throw(); // expected-error {{You must immediately return after calling this function}}
|
||||
}
|
||||
return Condition() ? MakeAnInt() : MakeAnInt();
|
||||
}
|
||||
|
||||
void b5() {
|
||||
if (Condition()) {
|
||||
Throw(); // expected-error {{You must immediately return after calling this function}}
|
||||
}
|
||||
DoAnythingElse();
|
||||
}
|
||||
|
||||
void b6() {
|
||||
if (Condition()) {
|
||||
Throw(); // expected-error {{You must immediately return after calling this function}}
|
||||
DoAnythingElse();
|
||||
}
|
||||
}
|
||||
|
||||
void b7() {
|
||||
if (Condition()) {
|
||||
Throw();
|
||||
return;
|
||||
}
|
||||
DoAnythingElse();
|
||||
}
|
||||
|
||||
void b8() {
|
||||
if (Condition()) {
|
||||
Throw(); // expected-error {{You must immediately return after calling this function}}
|
||||
DoAnythingElse();
|
||||
return;
|
||||
}
|
||||
DoAnythingElse();
|
||||
}
|
||||
|
||||
void b9() {
|
||||
while (Condition()) {
|
||||
Throw(); // expected-error {{You must immediately return after calling this function}}
|
||||
}
|
||||
}
|
||||
|
||||
void b10() {
|
||||
while (Condition()) {
|
||||
Throw();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void b11() {
|
||||
Throw(); // expected-error {{You must immediately return after calling this function}}
|
||||
if (Condition()) {
|
||||
return;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void b12() {
|
||||
switch (MakeAnInt()) {
|
||||
case 1:
|
||||
break;
|
||||
default:
|
||||
Throw();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void b13() {
|
||||
if (Condition()) {
|
||||
Throw();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Foo b14() {
|
||||
if (Condition()) {
|
||||
Throw();
|
||||
return nullptr;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Foo b15() {
|
||||
if (Condition()) {
|
||||
Throw();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Foo b16() {
|
||||
if (Condition()) {
|
||||
Throw();
|
||||
}
|
||||
return Foo();
|
||||
}
|
@ -18,6 +18,7 @@ SOURCES += [
|
||||
'TestKungFuDeathGrip.cpp',
|
||||
'TestMultipleAnnotations.cpp',
|
||||
'TestMustOverride.cpp',
|
||||
'TestMustReturnFromCaller.cpp',
|
||||
'TestMustUse.cpp',
|
||||
'TestNANTestingExpr.cpp',
|
||||
'TestNANTestingExprC.c',
|
||||
|
@ -16,7 +16,14 @@ def gen_test_backend():
|
||||
config = build_obj.config_environment
|
||||
except BuildEnvironmentNotFoundException:
|
||||
print("No build detected, test metadata may be incomplete.")
|
||||
config = EmptyConfig(build_obj.topsrcdir)
|
||||
|
||||
# If 'JS_STANDALONE' is set, tests that don't require an objdir won't
|
||||
# be picked up due to bug 1345209.
|
||||
substs = EmptyConfig.default_substs
|
||||
if 'JS_STANDALONE' in substs:
|
||||
del substs['JS_STANDALONE']
|
||||
|
||||
config = EmptyConfig(build_obj.topsrcdir, substs)
|
||||
config.topobjdir = build_obj.topobjdir
|
||||
|
||||
reader = BuildReader(config)
|
||||
|
@ -89,15 +89,28 @@ option(env='OLD_CONFIGURE', nargs=1, help='Path to the old configure script')
|
||||
option(env='MOZ_CURRENT_PROJECT', nargs=1, help='Current build project')
|
||||
option(env='MOZCONFIG', nargs=1, help='Mozconfig location')
|
||||
|
||||
option('--with-external-source-dir', env='EXTERNAL_SOURCE_DIR', nargs=1,
|
||||
help='External directory containing additional build files')
|
||||
|
||||
@depends('--with-external-source-dir')
|
||||
def external_source_dir(value):
|
||||
if value:
|
||||
return value[0]
|
||||
|
||||
set_config('EXTERNAL_SOURCE_DIR', external_source_dir)
|
||||
add_old_configure_assignment('EXTERNAL_SOURCE_DIR', external_source_dir)
|
||||
|
||||
# Read user mozconfig
|
||||
# ==============================================================
|
||||
# Note: the dependency on --help is only there to always read the mozconfig,
|
||||
# even when --help is passed. Without this dependency, the function wouldn't
|
||||
# be called when --help is passed, and the mozconfig wouldn't be read.
|
||||
@depends('MOZ_CURRENT_PROJECT', 'MOZCONFIG', 'OLD_CONFIGURE',
|
||||
check_build_environment, '--help')
|
||||
check_build_environment, '--with-external-source-dir',
|
||||
'--help')
|
||||
@imports(_from='mozbuild.mozconfig', _import='MozconfigLoader')
|
||||
def mozconfig(current_project, mozconfig, old_configure, build_env, help):
|
||||
def mozconfig(current_project, mozconfig, old_configure, build_env,
|
||||
external_source_dir, help):
|
||||
if not old_configure:
|
||||
die('The OLD_CONFIGURE environment variable must be set')
|
||||
|
||||
@ -120,7 +133,10 @@ def mozconfig(current_project, mozconfig, old_configure, build_env, help):
|
||||
if os.path.dirname(os.path.abspath(old_configure[0])).endswith('/js/src'):
|
||||
return {'path': None}
|
||||
|
||||
loader = MozconfigLoader(build_env.topsrcdir)
|
||||
topsrcdir = build_env.topsrcdir
|
||||
if external_source_dir:
|
||||
topsrcdir = external_source_dir[0]
|
||||
loader = MozconfigLoader(topsrcdir)
|
||||
current_project = current_project[0] if current_project else None
|
||||
mozconfig = mozconfig[0] if mozconfig else None
|
||||
mozconfig = loader.find_mozconfig(env={'MOZCONFIG': mozconfig})
|
||||
@ -632,9 +648,6 @@ def default_project(build_env, help):
|
||||
option('--enable-project', nargs=1, default=default_project,
|
||||
help='Project to build')
|
||||
|
||||
option('--with-external-source-dir', env='EXTERNAL_SOURCE_DIR', nargs=1,
|
||||
help='External directory containing additional build files')
|
||||
|
||||
@depends('--enable-project', '--with-external-source-dir',
|
||||
check_build_environment, '--help')
|
||||
@imports(_from='os.path', _import='exists')
|
||||
@ -651,14 +664,6 @@ def include_project_configure(project, external_source_dir, build_env, help):
|
||||
die('Cannot find project %s', project[0])
|
||||
return path
|
||||
|
||||
@depends('--with-external-source-dir')
|
||||
def external_source_dir(value):
|
||||
if value:
|
||||
return value[0]
|
||||
|
||||
set_config('EXTERNAL_SOURCE_DIR', external_source_dir)
|
||||
add_old_configure_assignment('EXTERNAL_SOURCE_DIR', external_source_dir)
|
||||
|
||||
|
||||
@depends(include_project_configure, check_build_environment, '--help')
|
||||
def build_project(include_project_configure, build_env, help):
|
||||
|
@ -50,19 +50,6 @@ OriginAttributes::InitPrefs()
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
OriginAttributes::Inherit(const OriginAttributes& aAttrs)
|
||||
{
|
||||
mAppId = aAttrs.mAppId;
|
||||
mInIsolatedMozBrowser = aAttrs.mInIsolatedMozBrowser;
|
||||
|
||||
|
||||
mUserContextId = aAttrs.mUserContextId;
|
||||
|
||||
mPrivateBrowsingId = aAttrs.mPrivateBrowsingId;
|
||||
mFirstPartyDomain = aAttrs.mFirstPartyDomain;
|
||||
}
|
||||
|
||||
void
|
||||
OriginAttributes::SetFirstPartyDomain(const bool aIsTopLevelDocument,
|
||||
nsIURI* aURI)
|
||||
|
@ -40,10 +40,6 @@ public:
|
||||
: OriginAttributesDictionary(aOther)
|
||||
{}
|
||||
|
||||
// This method 'clones' the OriginAttributes ignoring the addonId value becaue
|
||||
// this is computed from the principal URI and never propagated.
|
||||
void Inherit(const OriginAttributes& aAttrs);
|
||||
|
||||
void SetFirstPartyDomain(const bool aIsTopLevelDocument, nsIURI* aURI);
|
||||
|
||||
enum {
|
||||
|
@ -48,11 +48,9 @@ nsNullPrincipal::CreateWithInheritedAttributes(nsIPrincipal* aInheritFrom)
|
||||
/* static */ already_AddRefed<nsNullPrincipal>
|
||||
nsNullPrincipal::CreateWithInheritedAttributes(nsIDocShell* aDocShell)
|
||||
{
|
||||
OriginAttributes attrs;
|
||||
attrs.Inherit(nsDocShell::Cast(aDocShell)->GetOriginAttributes());
|
||||
|
||||
RefPtr<nsNullPrincipal> nullPrin = new nsNullPrincipal();
|
||||
nsresult rv = nullPrin->Init(attrs);
|
||||
nsresult rv =
|
||||
nullPrin->Init(nsDocShell::Cast(aDocShell)->GetOriginAttributes());
|
||||
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
|
||||
return nullPrin.forget();
|
||||
}
|
||||
|
@ -379,9 +379,11 @@ nsScriptSecurityManager::GetChannelURIPrincipal(nsIChannel* aChannel,
|
||||
|
||||
// For addons loadInfo might be null.
|
||||
if (loadInfo) {
|
||||
attrs.Inherit(loadInfo->GetOriginAttributes());
|
||||
attrs = loadInfo->GetOriginAttributes();
|
||||
}
|
||||
nsCOMPtr<nsIPrincipal> prin = BasePrincipal::CreateCodebasePrincipal(uri, attrs);
|
||||
|
||||
nsCOMPtr<nsIPrincipal> prin =
|
||||
BasePrincipal::CreateCodebasePrincipal(uri, attrs);
|
||||
prin.forget(aPrincipal);
|
||||
return *aPrincipal ? NS_OK : NS_ERROR_FAILURE;
|
||||
}
|
||||
@ -1143,13 +1145,11 @@ nsScriptSecurityManager::
|
||||
{
|
||||
NS_ENSURE_STATE(aLoadContext);
|
||||
OriginAttributes docShellAttrs;
|
||||
bool result = aLoadContext->GetOriginAttributes(docShellAttrs);;
|
||||
bool result = aLoadContext->GetOriginAttributes(docShellAttrs);
|
||||
NS_ENSURE_TRUE(result, NS_ERROR_FAILURE);
|
||||
|
||||
OriginAttributes attrs;
|
||||
attrs.Inherit(docShellAttrs);
|
||||
|
||||
nsCOMPtr<nsIPrincipal> prin = BasePrincipal::CreateCodebasePrincipal(aURI, attrs);
|
||||
nsCOMPtr<nsIPrincipal> prin =
|
||||
BasePrincipal::CreateCodebasePrincipal(aURI, docShellAttrs);
|
||||
prin.forget(aPrincipal);
|
||||
return *aPrincipal ? NS_OK : NS_ERROR_FAILURE;
|
||||
}
|
||||
@ -1159,10 +1159,8 @@ nsScriptSecurityManager::GetDocShellCodebasePrincipal(nsIURI* aURI,
|
||||
nsIDocShell* aDocShell,
|
||||
nsIPrincipal** aPrincipal)
|
||||
{
|
||||
OriginAttributes attrs;
|
||||
attrs.Inherit(nsDocShell::Cast(aDocShell)->GetOriginAttributes());
|
||||
|
||||
nsCOMPtr<nsIPrincipal> prin = BasePrincipal::CreateCodebasePrincipal(aURI, attrs);
|
||||
nsCOMPtr<nsIPrincipal> prin =
|
||||
BasePrincipal::CreateCodebasePrincipal(aURI, nsDocShell::Cast(aDocShell)->GetOriginAttributes());
|
||||
prin.forget(aPrincipal);
|
||||
return *aPrincipal ? NS_OK : NS_ERROR_FAILURE;
|
||||
}
|
||||
|
@ -36082,7 +36082,7 @@ return /******/ (function(modules) { // webpackBootstrap
|
||||
|
||||
function removeChildren(e) {
|
||||
for (var count = e.childNodes.length; count > 0; --count)
|
||||
{ e.removeChild(e.firstChild) }
|
||||
{ e.firstChild.remove() }
|
||||
return e
|
||||
}
|
||||
|
||||
|
@ -576,7 +576,7 @@ ToolSidebar.prototype = {
|
||||
}
|
||||
|
||||
while (this._tabbox.tabs && this._tabbox.tabs.hasChildNodes()) {
|
||||
this._tabbox.tabs.removeChild(this._tabbox.tabs.firstChild);
|
||||
this._tabbox.tabs.firstChild.remove();
|
||||
}
|
||||
|
||||
if (this._currentTool && this._telemetry) {
|
||||
|
@ -98,13 +98,15 @@
|
||||
<div id="ruleview-command-toolbar">
|
||||
<button id="ruleview-add-rule-button" data-localization="title=inspector.addRule.tooltip" class="devtools-button"></button>
|
||||
<button id="pseudo-class-panel-toggle" data-localization="title=inspector.togglePseudo.tooltip" class="devtools-button"></button>
|
||||
<button id="class-panel-toggle" data-localization="title=inspector.classPanel.toggleClass.tooltip" class="devtools-button"></button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="pseudo-class-panel" hidden="true">
|
||||
<div id="pseudo-class-panel" class="ruleview-reveal-panel" hidden="true">
|
||||
<label><input id="pseudo-hover-toggle" type="checkbox" value=":hover" tabindex="-1" />:hover</label>
|
||||
<label><input id="pseudo-active-toggle" type="checkbox" value=":active" tabindex="-1" />:active</label>
|
||||
<label><input id="pseudo-focus-toggle" type="checkbox" value=":focus" tabindex="-1" />:focus</label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="ruleview-class-panel" class="ruleview-reveal-panel" hidden="true"></div>
|
||||
</div>
|
||||
|
||||
<div id="ruleview-container" class="ruleview">
|
||||
|
@ -1604,7 +1604,7 @@ MarkupView.prototype = {
|
||||
// this container will do double duty as the container for the single
|
||||
// text child.
|
||||
while (container.children.firstChild) {
|
||||
container.children.removeChild(container.children.firstChild);
|
||||
container.children.firstChild.remove();
|
||||
}
|
||||
|
||||
container.setInlineTextChild(container.node.inlineTextChild);
|
||||
@ -1616,7 +1616,7 @@ MarkupView.prototype = {
|
||||
|
||||
if (!container.hasChildren) {
|
||||
while (container.children.firstChild) {
|
||||
container.children.removeChild(container.children.firstChild);
|
||||
container.children.firstChild.remove();
|
||||
}
|
||||
container.childrenDirty = false;
|
||||
container.setExpanded(false);
|
||||
@ -1659,7 +1659,7 @@ MarkupView.prototype = {
|
||||
}
|
||||
|
||||
while (container.children.firstChild) {
|
||||
container.children.removeChild(container.children.firstChild);
|
||||
container.children.firstChild.remove();
|
||||
}
|
||||
|
||||
if (!(children.hasFirst && children.hasLast)) {
|
||||
|
@ -161,6 +161,7 @@ skip-if = e10s # Bug 1036409 - The last selected node isn't reselected
|
||||
[browser_markup_tag_edit_11.js]
|
||||
[browser_markup_tag_edit_12.js]
|
||||
[browser_markup_tag_edit_13-other.js]
|
||||
[browser_markup_tag_edit_avoid_refocus.js]
|
||||
[browser_markup_tag_edit_long-classname.js]
|
||||
[browser_markup_textcontent_display.js]
|
||||
[browser_markup_textcontent_edit_01.js]
|
||||
|
@ -0,0 +1,43 @@
|
||||
/* 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";
|
||||
|
||||
// Bug 1327683 - Tests that an editable attribute is not refocused
|
||||
// when the focus has been moved to an other element than the editor.
|
||||
|
||||
const TEST_URL = 'data:text/html,<body class="abcd"></body>';
|
||||
|
||||
add_task(function* () {
|
||||
let {inspector} = yield openInspectorForURL(TEST_URL);
|
||||
|
||||
yield selectNode(".abcd", inspector);
|
||||
yield clickContainer(".abcd", inspector);
|
||||
|
||||
let container = yield focusNode(".abcd", inspector);
|
||||
ok(container && container.editor, "The markup-container was found");
|
||||
|
||||
info("Listening for the markupmutation event");
|
||||
let nodeMutated = inspector.once("markupmutation");
|
||||
let attr = container.editor.attrElements.get("class").querySelector(".editable");
|
||||
|
||||
attr.focus();
|
||||
EventUtils.sendKey("return", inspector.panelWin);
|
||||
let input = inplaceEditor(attr).input;
|
||||
ok(input, "Found editable field for class attribute");
|
||||
|
||||
input.value = "class=\"wxyz\"";
|
||||
|
||||
let onFocus = once(inspector.searchBox, "focus");
|
||||
EventUtils.synthesizeMouseAtCenter(inspector.searchBox, {}, inspector.panelWin);
|
||||
|
||||
info("Wait for the focus event on search box");
|
||||
yield onFocus;
|
||||
|
||||
info("Wait for the markup-mutation event");
|
||||
yield nodeMutated;
|
||||
|
||||
is(inspector.panelDoc.activeElement, inspector.searchBox,
|
||||
"The currently focused element is the search box");
|
||||
});
|
@ -447,6 +447,13 @@ ElementEditor.prototype = {
|
||||
this._editedAttributeObserver = null;
|
||||
}
|
||||
|
||||
let activeElement = this.markup.doc.activeElement;
|
||||
if (!activeElement || !activeElement.inplaceEditor) {
|
||||
// The focus was already removed from the current inplace editor, we should not
|
||||
// refocus the editable attribute.
|
||||
return;
|
||||
}
|
||||
|
||||
let container = this.markup.getContainer(this.node);
|
||||
|
||||
let activeAttrs = [...this.attrList.childNodes]
|
||||
|
@ -17,6 +17,7 @@ const {PrefObserver} = require("devtools/client/shared/prefs");
|
||||
const ElementStyle = require("devtools/client/inspector/rules/models/element-style");
|
||||
const Rule = require("devtools/client/inspector/rules/models/rule");
|
||||
const RuleEditor = require("devtools/client/inspector/rules/views/rule-editor");
|
||||
const ClassListPreviewer = require("devtools/client/inspector/rules/views/class-list-previewer");
|
||||
const {gDevTools} = require("devtools/client/framework/devtools");
|
||||
const {getCssProperties} = require("devtools/shared/fronts/css-properties");
|
||||
const {
|
||||
@ -120,6 +121,7 @@ function CssRuleView(inspector, document, store, pageStyle) {
|
||||
this._onClearSearch = this._onClearSearch.bind(this);
|
||||
this._onTogglePseudoClassPanel = this._onTogglePseudoClassPanel.bind(this);
|
||||
this._onTogglePseudoClass = this._onTogglePseudoClass.bind(this);
|
||||
this._onToggleClassPanel = this._onToggleClassPanel.bind(this);
|
||||
|
||||
let doc = this.styleDocument;
|
||||
this.element = doc.getElementById("ruleview-container-focusable");
|
||||
@ -128,6 +130,8 @@ function CssRuleView(inspector, document, store, pageStyle) {
|
||||
this.searchClearButton = doc.getElementById("ruleview-searchinput-clear");
|
||||
this.pseudoClassPanel = doc.getElementById("pseudo-class-panel");
|
||||
this.pseudoClassToggle = doc.getElementById("pseudo-class-panel-toggle");
|
||||
this.classPanel = doc.getElementById("ruleview-class-panel");
|
||||
this.classToggle = doc.getElementById("class-panel-toggle");
|
||||
this.hoverCheckbox = doc.getElementById("pseudo-hover-toggle");
|
||||
this.activeCheckbox = doc.getElementById("pseudo-active-toggle");
|
||||
this.focusCheckbox = doc.getElementById("pseudo-focus-toggle");
|
||||
@ -146,8 +150,8 @@ function CssRuleView(inspector, document, store, pageStyle) {
|
||||
this.searchField.addEventListener("input", this._onFilterStyles);
|
||||
this.searchField.addEventListener("contextmenu", this.inspector.onTextBoxContextMenu);
|
||||
this.searchClearButton.addEventListener("click", this._onClearSearch);
|
||||
this.pseudoClassToggle.addEventListener("click",
|
||||
this._onTogglePseudoClassPanel);
|
||||
this.pseudoClassToggle.addEventListener("click", this._onTogglePseudoClassPanel);
|
||||
this.classToggle.addEventListener("click", this._onToggleClassPanel);
|
||||
this.hoverCheckbox.addEventListener("click", this._onTogglePseudoClass);
|
||||
this.activeCheckbox.addEventListener("click", this._onTogglePseudoClass);
|
||||
this.focusCheckbox.addEventListener("click", this._onTogglePseudoClass);
|
||||
@ -181,6 +185,8 @@ function CssRuleView(inspector, document, store, pageStyle) {
|
||||
|
||||
this.highlighters.addToView(this);
|
||||
|
||||
this.classListPreviewer = new ClassListPreviewer(this.inspector, this.classPanel);
|
||||
|
||||
EventEmitter.decorate(this);
|
||||
}
|
||||
|
||||
@ -673,6 +679,7 @@ CssRuleView.prototype = {
|
||||
|
||||
this.tooltips.destroy();
|
||||
this.highlighters.removeFromView(this);
|
||||
this.classListPreviewer.destroy();
|
||||
|
||||
// Remove bound listeners
|
||||
this.shortcuts.destroy();
|
||||
@ -683,8 +690,8 @@ CssRuleView.prototype = {
|
||||
this.searchField.removeEventListener("contextmenu",
|
||||
this.inspector.onTextBoxContextMenu);
|
||||
this.searchClearButton.removeEventListener("click", this._onClearSearch);
|
||||
this.pseudoClassToggle.removeEventListener("click",
|
||||
this._onTogglePseudoClassPanel);
|
||||
this.pseudoClassToggle.removeEventListener("click", this._onTogglePseudoClassPanel);
|
||||
this.classToggle.removeEventListener("click", this._onToggleClassPanel);
|
||||
this.hoverCheckbox.removeEventListener("click", this._onTogglePseudoClass);
|
||||
this.activeCheckbox.removeEventListener("click", this._onTogglePseudoClass);
|
||||
this.focusCheckbox.removeEventListener("click", this._onTogglePseudoClass);
|
||||
@ -693,6 +700,8 @@ CssRuleView.prototype = {
|
||||
this.searchClearButton = null;
|
||||
this.pseudoClassPanel = null;
|
||||
this.pseudoClassToggle = null;
|
||||
this.classPanel = null;
|
||||
this.classToggle = null;
|
||||
this.hoverCheckbox = null;
|
||||
this.activeCheckbox = null;
|
||||
this.focusCheckbox = null;
|
||||
@ -1372,18 +1381,30 @@ CssRuleView.prototype = {
|
||||
*/
|
||||
_onTogglePseudoClassPanel: function () {
|
||||
if (this.pseudoClassPanel.hidden) {
|
||||
this.pseudoClassToggle.classList.add("checked");
|
||||
this.hoverCheckbox.setAttribute("tabindex", "0");
|
||||
this.activeCheckbox.setAttribute("tabindex", "0");
|
||||
this.focusCheckbox.setAttribute("tabindex", "0");
|
||||
this.showPseudoClassPanel();
|
||||
} else {
|
||||
this.pseudoClassToggle.classList.remove("checked");
|
||||
this.hoverCheckbox.setAttribute("tabindex", "-1");
|
||||
this.activeCheckbox.setAttribute("tabindex", "-1");
|
||||
this.focusCheckbox.setAttribute("tabindex", "-1");
|
||||
this.hidePseudoClassPanel();
|
||||
}
|
||||
},
|
||||
|
||||
this.pseudoClassPanel.hidden = !this.pseudoClassPanel.hidden;
|
||||
showPseudoClassPanel: function () {
|
||||
this.hideClassPanel();
|
||||
|
||||
this.pseudoClassToggle.classList.add("checked");
|
||||
this.hoverCheckbox.setAttribute("tabindex", "0");
|
||||
this.activeCheckbox.setAttribute("tabindex", "0");
|
||||
this.focusCheckbox.setAttribute("tabindex", "0");
|
||||
|
||||
this.pseudoClassPanel.hidden = false;
|
||||
},
|
||||
|
||||
hidePseudoClassPanel: function () {
|
||||
this.pseudoClassToggle.classList.remove("checked");
|
||||
this.hoverCheckbox.setAttribute("tabindex", "-1");
|
||||
this.activeCheckbox.setAttribute("tabindex", "-1");
|
||||
this.focusCheckbox.setAttribute("tabindex", "-1");
|
||||
|
||||
this.pseudoClassPanel.hidden = true;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1395,6 +1416,32 @@ CssRuleView.prototype = {
|
||||
this.inspector.togglePseudoClass(target.value);
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the class panel button is clicked and toggles the display of the class
|
||||
* panel.
|
||||
*/
|
||||
_onToggleClassPanel: function () {
|
||||
if (this.classPanel.hidden) {
|
||||
this.showClassPanel();
|
||||
} else {
|
||||
this.hideClassPanel();
|
||||
}
|
||||
},
|
||||
|
||||
showClassPanel: function () {
|
||||
this.hidePseudoClassPanel();
|
||||
|
||||
this.classToggle.classList.add("checked");
|
||||
this.classPanel.hidden = false;
|
||||
|
||||
this.classListPreviewer.focusAddClassField();
|
||||
},
|
||||
|
||||
hideClassPanel: function () {
|
||||
this.classToggle.classList.remove("checked");
|
||||
this.classPanel.hidden = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle the keypress event in the rule view.
|
||||
*/
|
||||
|
@ -61,6 +61,13 @@ support-files =
|
||||
[browser_rules_authored_color.js]
|
||||
[browser_rules_authored_override.js]
|
||||
[browser_rules_blob_stylesheet.js]
|
||||
[browser_rules_class_panel_add.js]
|
||||
[browser_rules_class_panel_content.js]
|
||||
[browser_rules_class_panel_edit.js]
|
||||
[browser_rules_class_panel_invalid_nodes.js]
|
||||
[browser_rules_class_panel_mutation.js]
|
||||
[browser_rules_class_panel_state_preserved.js]
|
||||
[browser_rules_class_panel_toggle.js]
|
||||
[browser_rules_colorpicker-and-image-tooltip_01.js]
|
||||
[browser_rules_colorpicker-and-image-tooltip_02.js]
|
||||
[browser_rules_colorpicker-appears-on-swatch-click.js]
|
||||
|
@ -0,0 +1,91 @@
|
||||
/* vim: set ft=javascript 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 classes can be added in the class panel
|
||||
|
||||
// This array contains the list of test cases. Each test case contains these properties:
|
||||
// - {String} textEntered The text to be entered in the field
|
||||
// - {Boolean} expectNoMutation Set to true if we shouldn't wait for a DOM mutation
|
||||
// - {Array} expectedClasses The expected list of classes to be applied to the DOM and to
|
||||
// be found in the class panel
|
||||
const TEST_ARRAY = [{
|
||||
textEntered: "",
|
||||
expectNoMutation: true,
|
||||
expectedClasses: []
|
||||
}, {
|
||||
textEntered: "class",
|
||||
expectedClasses: ["class"]
|
||||
}, {
|
||||
textEntered: "class",
|
||||
expectNoMutation: true,
|
||||
expectedClasses: ["class"]
|
||||
}, {
|
||||
textEntered: "a a a a a a a a a a",
|
||||
expectedClasses: ["class", "a"]
|
||||
}, {
|
||||
textEntered: "class2 class3",
|
||||
expectedClasses: ["class", "a", "class2", "class3"]
|
||||
}, {
|
||||
textEntered: " ",
|
||||
expectNoMutation: true,
|
||||
expectedClasses: ["class", "a", "class2", "class3"]
|
||||
}, {
|
||||
textEntered: " class4",
|
||||
expectedClasses: ["class", "a", "class2", "class3", "class4"]
|
||||
}, {
|
||||
textEntered: " \t class5 \t \t\t ",
|
||||
expectedClasses: ["class", "a", "class2", "class3", "class4", "class5"]
|
||||
}];
|
||||
|
||||
add_task(function* () {
|
||||
yield addTab("data:text/html;charset=utf-8,");
|
||||
let {testActor, inspector, view} = yield openRuleView();
|
||||
|
||||
info("Open the class panel");
|
||||
view.showClassPanel();
|
||||
|
||||
const textField = inspector.panelDoc.querySelector("#ruleview-class-panel .add-class");
|
||||
ok(textField, "The input field exists in the class panel");
|
||||
|
||||
textField.focus();
|
||||
|
||||
let onMutation;
|
||||
for (let {textEntered, expectNoMutation, expectedClasses} of TEST_ARRAY) {
|
||||
if (!expectNoMutation) {
|
||||
onMutation = inspector.once("markupmutation");
|
||||
}
|
||||
|
||||
info(`Enter the test string in the field: ${textEntered}`);
|
||||
for (let key of textEntered.split("")) {
|
||||
EventUtils.synthesizeKey(key, {}, view.styleWindow);
|
||||
}
|
||||
|
||||
info("Submit the change and wait for the textfield to become empty");
|
||||
let onEmpty = waitForFieldToBeEmpty(textField);
|
||||
EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
|
||||
|
||||
if (!expectNoMutation) {
|
||||
info("Wait for the DOM to change");
|
||||
yield onMutation;
|
||||
}
|
||||
|
||||
yield onEmpty;
|
||||
|
||||
info("Check the state of the DOM node");
|
||||
let className = yield testActor.getAttribute("body", "class");
|
||||
let expectedClassName = expectedClasses.length ? expectedClasses.join(" ") : null;
|
||||
is(className, expectedClassName, "The DOM node has the right className");
|
||||
|
||||
info("Check the content of the class panel");
|
||||
checkClassPanelContent(view, expectedClasses.map(name => {
|
||||
return {name, state: true};
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
function waitForFieldToBeEmpty(textField) {
|
||||
return waitForSuccess(() => !textField.value);
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
/* vim: set ft=javascript 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 class panel shows the right content when selecting various nodes.
|
||||
|
||||
// This array contains the list of test cases. Each test case contains these properties:
|
||||
// - {String} inputClassName The className on a node
|
||||
// - {Array} expectedClasses The expected list of classes in the class panel
|
||||
const TEST_ARRAY = [{
|
||||
inputClassName: "",
|
||||
expectedClasses: []
|
||||
}, {
|
||||
inputClassName: " a a a a a a a a a",
|
||||
expectedClasses: ["a"]
|
||||
}, {
|
||||
inputClassName: "c1 c2 c3 c4 c5",
|
||||
expectedClasses: ["c1", "c2", "c3", "c4", "c5"]
|
||||
}, {
|
||||
inputClassName: "a a b b c c a a b b c c",
|
||||
expectedClasses: ["a", "b", "c"]
|
||||
}, {
|
||||
inputClassName: "ajdhfkasjhdkjashdkjghaskdgkauhkbdhvliashdlghaslidghasldgliashdglhasli",
|
||||
expectedClasses: [
|
||||
"ajdhfkasjhdkjashdkjghaskdgkauhkbdhvliashdlghaslidghasldgliashdglhasli"
|
||||
]
|
||||
}, {
|
||||
inputClassName: "c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 " +
|
||||
"c10 c11 c12 c13 c14 c15 c16 c17 c18 c19 " +
|
||||
"c20 c21 c22 c23 c24 c25 c26 c27 c28 c29 " +
|
||||
"c30 c31 c32 c33 c34 c35 c36 c37 c38 c39 " +
|
||||
"c40 c41 c42 c43 c44 c45 c46 c47 c48 c49",
|
||||
expectedClasses: ["c0", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9",
|
||||
"c10", "c11", "c12", "c13", "c14", "c15", "c16", "c17", "c18", "c19",
|
||||
"c20", "c21", "c22", "c23", "c24", "c25", "c26", "c27", "c28", "c29",
|
||||
"c30", "c31", "c32", "c33", "c34", "c35", "c36", "c37", "c38", "c39",
|
||||
"c40", "c41", "c42", "c43", "c44", "c45", "c46", "c47", "c48", "c49"]
|
||||
}, {
|
||||
inputClassName: " \n \n class1 \t class2 \t\tclass3\t",
|
||||
expectedClasses: ["class1", "class2", "class3"]
|
||||
}];
|
||||
|
||||
add_task(function* () {
|
||||
yield addTab("data:text/html;charset=utf-8,<div>");
|
||||
let {testActor, inspector, view} = yield openRuleView();
|
||||
|
||||
yield selectNode("div", inspector);
|
||||
|
||||
info("Open the class panel");
|
||||
view.showClassPanel();
|
||||
|
||||
for (let {inputClassName, expectedClasses} of TEST_ARRAY) {
|
||||
info(`Apply the '${inputClassName}' className to the node`);
|
||||
const onMutation = inspector.once("markupmutation");
|
||||
yield testActor.setAttribute("div", "class", inputClassName);
|
||||
yield onMutation;
|
||||
|
||||
info("Check the content of the class panel");
|
||||
checkClassPanelContent(view, expectedClasses.map(name => {
|
||||
return {name, state: true};
|
||||
}));
|
||||
}
|
||||
});
|
@ -0,0 +1,51 @@
|
||||
/* vim: set ft=javascript 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 classes can be toggled in the class panel
|
||||
|
||||
add_task(function* () {
|
||||
yield addTab("data:text/html;charset=utf-8,<body class='class1 class2'>");
|
||||
let {view, testActor} = yield openRuleView();
|
||||
|
||||
info("Open the class panel");
|
||||
view.showClassPanel();
|
||||
|
||||
info("Click on class1 and check that the checkbox is unchecked and the DOM is updated");
|
||||
yield toggleClassPanelCheckBox(view, "class1");
|
||||
checkClassPanelContent(view, [
|
||||
{name: "class1", state: false},
|
||||
{name: "class2", state: true}
|
||||
]);
|
||||
let newClassName = yield testActor.getAttribute("body", "class");
|
||||
is(newClassName, "class2", "The class attribute has been updated in the DOM");
|
||||
|
||||
info("Click on class2 and check the same thing");
|
||||
yield toggleClassPanelCheckBox(view, "class2");
|
||||
checkClassPanelContent(view, [
|
||||
{name: "class1", state: false},
|
||||
{name: "class2", state: false}
|
||||
]);
|
||||
newClassName = yield testActor.getAttribute("body", "class");
|
||||
is(newClassName, "", "The class attribute has been updated in the DOM");
|
||||
|
||||
info("Click on class2 and checks that the class is added again");
|
||||
yield toggleClassPanelCheckBox(view, "class2");
|
||||
checkClassPanelContent(view, [
|
||||
{name: "class1", state: false},
|
||||
{name: "class2", state: true}
|
||||
]);
|
||||
newClassName = yield testActor.getAttribute("body", "class");
|
||||
is(newClassName, "class2", "The class attribute has been updated in the DOM");
|
||||
|
||||
info("And finally, click on class1 again and checks it is added again");
|
||||
yield toggleClassPanelCheckBox(view, "class1");
|
||||
checkClassPanelContent(view, [
|
||||
{name: "class1", state: true},
|
||||
{name: "class2", state: true}
|
||||
]);
|
||||
newClassName = yield testActor.getAttribute("body", "class");
|
||||
is(newClassName, "class1 class2", "The class attribute has been updated in the DOM");
|
||||
});
|
@ -0,0 +1,50 @@
|
||||
/* vim: set ft=javascript 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 the class panel shows a message when invalid nodes are selected.
|
||||
// text nodes, pseudo-elements, DOCTYPE, comment nodes.
|
||||
|
||||
add_task(function* () {
|
||||
yield addTab(`data:text/html;charset=utf-8,
|
||||
<body>
|
||||
<style>div::after {content: "test";}</style>
|
||||
<!-- comment -->
|
||||
Some text
|
||||
<div></div>
|
||||
</body>`);
|
||||
|
||||
info("Open the class panel");
|
||||
let {inspector, view} = yield openRuleView();
|
||||
view.showClassPanel();
|
||||
|
||||
info("Selecting the DOCTYPE node");
|
||||
let {nodes} = yield inspector.walker.children(inspector.walker.rootNode);
|
||||
yield selectNode(nodes[0], inspector);
|
||||
checkMessageIsDisplayed(view);
|
||||
|
||||
info("Selecting the comment node");
|
||||
let styleNode = yield getNodeFront("style", inspector);
|
||||
let commentNode = yield inspector.walker.nextSibling(styleNode);
|
||||
yield selectNode(commentNode, inspector);
|
||||
checkMessageIsDisplayed(view);
|
||||
|
||||
info("Selecting the text node");
|
||||
let textNode = yield inspector.walker.nextSibling(commentNode);
|
||||
yield selectNode(textNode, inspector);
|
||||
checkMessageIsDisplayed(view);
|
||||
|
||||
info("Selecting the ::after pseudo-element");
|
||||
let divNode = yield getNodeFront("div", inspector);
|
||||
let pseudoElement = (yield inspector.walker.children(divNode)).nodes[0];
|
||||
yield selectNode(pseudoElement, inspector);
|
||||
checkMessageIsDisplayed(view);
|
||||
});
|
||||
|
||||
function checkMessageIsDisplayed(view) {
|
||||
ok(view.classListPreviewer.classesEl.querySelector(".no-classes"),
|
||||
"The message is displayed");
|
||||
checkClassPanelContent(view, []);
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
/* vim: set ft=javascript 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 class panel updates on markup mutations
|
||||
|
||||
add_task(function* () {
|
||||
yield addTab("data:text/html;charset=utf-8,<div class='c1 c2'>");
|
||||
let {inspector, view, testActor} = yield openRuleView();
|
||||
|
||||
yield selectNode("div", inspector);
|
||||
|
||||
info("Open the class panel");
|
||||
view.showClassPanel();
|
||||
|
||||
info("Trigger an unrelated mutation on the div (id attribute change)");
|
||||
let onMutation = view.inspector.once("markupmutation");
|
||||
yield testActor.setAttribute("div", "id", "test-id");
|
||||
yield onMutation;
|
||||
|
||||
info("Check that the panel still contains the right classes");
|
||||
checkClassPanelContent(view, [
|
||||
{name: "c1", state: true},
|
||||
{name: "c2", state: true}
|
||||
]);
|
||||
|
||||
info("Trigger a class mutation on a different, unknown, node");
|
||||
onMutation = view.inspector.once("markupmutation");
|
||||
yield testActor.setAttribute("body", "class", "test-class");
|
||||
yield onMutation;
|
||||
|
||||
info("Check that the panel still contains the right classes");
|
||||
checkClassPanelContent(view, [
|
||||
{name: "c1", state: true},
|
||||
{name: "c2", state: true}
|
||||
]);
|
||||
|
||||
info("Trigger a class mutation on the current node");
|
||||
onMutation = view.inspector.once("markupmutation");
|
||||
yield testActor.setAttribute("div", "class", "c3 c4");
|
||||
yield onMutation;
|
||||
|
||||
info("Check that the panel now contains the new classes");
|
||||
checkClassPanelContent(view, [
|
||||
{name: "c3", state: true},
|
||||
{name: "c4", state: true}
|
||||
]);
|
||||
|
||||
info("Change the state of one of the new classes");
|
||||
yield toggleClassPanelCheckBox(view, "c4");
|
||||
checkClassPanelContent(view, [
|
||||
{name: "c3", state: true},
|
||||
{name: "c4", state: false}
|
||||
]);
|
||||
|
||||
info("Select another node");
|
||||
yield selectNode("body", inspector);
|
||||
|
||||
info("Trigger a class mutation on the div");
|
||||
onMutation = view.inspector.once("markupmutation");
|
||||
yield testActor.setAttribute("div", "class", "c5 c6 c7");
|
||||
yield onMutation;
|
||||
|
||||
info("Go back to the previous node and check the content of the class panel." +
|
||||
"Even if hidden, it should have refreshed when we changed the DOM");
|
||||
yield selectNode("div", inspector);
|
||||
checkClassPanelContent(view, [
|
||||
{name: "c5", state: true},
|
||||
{name: "c6", state: true},
|
||||
{name: "c7", state: true}
|
||||
]);
|
||||
});
|
@ -0,0 +1,37 @@
|
||||
/* vim: set ft=javascript 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 class states are preserved when switching to other nodes
|
||||
|
||||
add_task(function* () {
|
||||
yield addTab("data:text/html;charset=utf-8,<body class='class1 class2 class3'><div>");
|
||||
let {inspector, view} = yield openRuleView();
|
||||
|
||||
info("Open the class panel");
|
||||
view.showClassPanel();
|
||||
|
||||
info("With the <body> selected, uncheck class2 and class3 in the panel");
|
||||
yield toggleClassPanelCheckBox(view, "class2");
|
||||
yield toggleClassPanelCheckBox(view, "class3");
|
||||
|
||||
info("Now select the <div> so the panel gets refreshed");
|
||||
yield selectNode("div", inspector);
|
||||
is(view.classPanel.querySelectorAll("[type=checkbox]").length, 0,
|
||||
"The panel content doesn't contain any checkboxes anymore");
|
||||
|
||||
info("Select the <body> again");
|
||||
yield selectNode("body", inspector);
|
||||
const checkBoxes = view.classPanel.querySelectorAll("[type=checkbox]");
|
||||
|
||||
is(checkBoxes[0].dataset.name, "class1", "The first checkbox is class1");
|
||||
is(checkBoxes[0].checked, true, "The first checkbox is still checked");
|
||||
|
||||
is(checkBoxes[1].dataset.name, "class2", "The second checkbox is class2");
|
||||
is(checkBoxes[1].checked, false, "The second checkbox is still unchecked");
|
||||
|
||||
is(checkBoxes[2].dataset.name, "class3", "The third checkbox is class3");
|
||||
is(checkBoxes[2].checked, false, "The third checkbox is still unchecked");
|
||||
});
|
@ -0,0 +1,45 @@
|
||||
/* vim: set ft=javascript 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 the class panel can be toggled.
|
||||
|
||||
add_task(function* () {
|
||||
yield addTab("data:text/html;charset=utf-8,<body class='class1 class2'>");
|
||||
let {inspector, view} = yield openRuleView();
|
||||
|
||||
info("Check that the toggle button exists");
|
||||
const button = inspector.panelDoc.querySelector("#class-panel-toggle");
|
||||
ok(button, "The class panel toggle button exists");
|
||||
is(view.classToggle, button, "The rule-view refers to the right element");
|
||||
|
||||
info("Check that the panel exists and is hidden by default");
|
||||
const panel = inspector.panelDoc.querySelector("#ruleview-class-panel");
|
||||
ok(panel, "The class panel exists");
|
||||
is(view.classPanel, panel, "The rule-view refers to the right element");
|
||||
ok(panel.hasAttribute("hidden"), "The panel is hidden");
|
||||
|
||||
info("Click on the button to show the panel");
|
||||
button.click();
|
||||
ok(!panel.hasAttribute("hidden"), "The panel is shown");
|
||||
ok(button.classList.contains("checked"), "The button is checked");
|
||||
|
||||
info("Click again to hide the panel");
|
||||
button.click();
|
||||
ok(panel.hasAttribute("hidden"), "The panel is hidden");
|
||||
ok(!button.classList.contains("checked"), "The button is unchecked");
|
||||
|
||||
info("Open the pseudo-class panel first, then the class panel");
|
||||
view.pseudoClassToggle.click();
|
||||
ok(!view.pseudoClassPanel.hasAttribute("hidden"), "The pseudo-class panel is shown");
|
||||
button.click();
|
||||
ok(!panel.hasAttribute("hidden"), "The panel is shown");
|
||||
ok(view.pseudoClassPanel.hasAttribute("hidden"), "The pseudo-class panel is hidden");
|
||||
|
||||
info("Click again on the pseudo-class button");
|
||||
view.pseudoClassToggle.click();
|
||||
ok(panel.hasAttribute("hidden"), "The panel is hidden");
|
||||
ok(!view.pseudoClassPanel.hasAttribute("hidden"), "The pseudo-class panel is shown");
|
||||
});
|
@ -506,3 +506,40 @@ function focusAndSendKey(win, key) {
|
||||
win.document.documentElement.focus();
|
||||
EventUtils.sendKey(key, win);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle one of the checkboxes inside the class-panel. Resolved after the DOM mutation
|
||||
* has been recorded.
|
||||
* @param {CssRuleView} view The rule-view instance.
|
||||
* @param {String} name The class name to find the checkbox.
|
||||
*/
|
||||
function* toggleClassPanelCheckBox(view, name) {
|
||||
info(`Clicking on checkbox for class ${name}`);
|
||||
const checkBox = [...view.classPanel.querySelectorAll("[type=checkbox]")].find(box => {
|
||||
return box.dataset.name === name;
|
||||
});
|
||||
|
||||
const onMutation = view.inspector.once("markupmutation");
|
||||
checkBox.click();
|
||||
info("Waiting for a markupmutation as a result of toggling this class");
|
||||
yield onMutation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the content of the class-panel.
|
||||
* @param {CssRuleView} view The rule-view isntance
|
||||
* @param {Array} classes The list of expected classes. Each item in this array is an
|
||||
* object with the following properties: {name: {String}, state: {Boolean}}
|
||||
*/
|
||||
function checkClassPanelContent(view, classes) {
|
||||
const checkBoxNodeList = view.classPanel.querySelectorAll("[type=checkbox]");
|
||||
is(checkBoxNodeList.length, classes.length,
|
||||
"The panel contains the expected number of checkboxes");
|
||||
|
||||
for (let i = 0; i < classes.length; i++) {
|
||||
is(checkBoxNodeList[i].dataset.name, classes[i].name,
|
||||
`Checkbox ${i} has the right class name`);
|
||||
is(checkBoxNodeList[i].checked, classes[i].state,
|
||||
`Checkbox ${i} has the right state`);
|
||||
}
|
||||
}
|
||||
|
356
devtools/client/inspector/rules/views/class-list-previewer.js
Normal file
356
devtools/client/inspector/rules/views/class-list-previewer.js
Normal file
@ -0,0 +1,356 @@
|
||||
/* 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 EventEmitter = require("devtools/shared/event-emitter");
|
||||
const {LocalizationHelper} = require("devtools/shared/l10n");
|
||||
|
||||
const L10N = new LocalizationHelper("devtools/client/locales/inspector.properties");
|
||||
|
||||
// This serves as a local cache for the classes applied to each of the node we care about
|
||||
// here.
|
||||
// The map is indexed by NodeFront. Any time a new node is selected in the inspector, an
|
||||
// entry is added here, indexed by the corresponding NodeFront.
|
||||
// The value for each entry is an array of each of the class this node has. Items of this
|
||||
// array are objects like: { name, isApplied } where the name is the class itself, and
|
||||
// isApplied is a Boolean indicating if the class is applied on the node or not.
|
||||
const CLASSES = new WeakMap();
|
||||
|
||||
/**
|
||||
* Manages the list classes per DOM elements we care about.
|
||||
* The actual list is stored in the CLASSES const, indexed by NodeFront objects.
|
||||
* The responsibility of this class is to be the source of truth for anyone who wants to
|
||||
* know which classes a given NodeFront has, and which of these are enabled and which are
|
||||
* disabled.
|
||||
* It also reacts to DOM mutations so the list of classes is up to date with what is in
|
||||
* the DOM.
|
||||
* It can also be used to enable/disable a given class, or add classes.
|
||||
*
|
||||
* @param {Inspector} inspector
|
||||
* The current inspector instance.
|
||||
*/
|
||||
function ClassListPreviewerModel(inspector) {
|
||||
EventEmitter.decorate(this);
|
||||
|
||||
this.inspector = inspector;
|
||||
|
||||
this.onMutations = this.onMutations.bind(this);
|
||||
this.inspector.on("markupmutation", this.onMutations);
|
||||
|
||||
this.classListProxyNode = this.inspector.panelDoc.createElement("div");
|
||||
}
|
||||
|
||||
ClassListPreviewerModel.prototype = {
|
||||
destroy() {
|
||||
this.inspector.off("markupmutation", this.onMutations);
|
||||
this.inspector = null;
|
||||
this.classListProxyNode = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* The current node selection (which only returns if the node is an ELEMENT_NODE type
|
||||
* since that's the only type this model can work with.)
|
||||
*/
|
||||
get currentNode() {
|
||||
if (this.inspector.selection.isElementNode() &&
|
||||
!this.inspector.selection.isPseudoElementNode()) {
|
||||
return this.inspector.selection.nodeFront;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* The class states for the current node selection. See the documentation of the CLASSES
|
||||
* constant.
|
||||
*/
|
||||
get currentClasses() {
|
||||
if (!this.currentNode) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!CLASSES.has(this.currentNode)) {
|
||||
// Use the proxy node to get a clean list of classes.
|
||||
this.classListProxyNode.className = this.currentNode.className;
|
||||
let nodeClasses = [...new Set([...this.classListProxyNode.classList])].map(name => {
|
||||
return { name, isApplied: true };
|
||||
});
|
||||
|
||||
CLASSES.set(this.currentNode, nodeClasses);
|
||||
}
|
||||
|
||||
return CLASSES.get(this.currentNode);
|
||||
},
|
||||
|
||||
/**
|
||||
* Same as currentClasses, but returns it in the form of a className string, where only
|
||||
* enabled classes are added.
|
||||
*/
|
||||
get currentClassesPreview() {
|
||||
return this.currentClasses.filter(({ isApplied }) => isApplied)
|
||||
.map(({ name }) => name)
|
||||
.join(" ");
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the state for a given class on the current node.
|
||||
*
|
||||
* @param {String} name
|
||||
* The class which state should be changed.
|
||||
* @param {Boolean} isApplied
|
||||
* True if the class should be enabled, false otherwise.
|
||||
* @return {Promise} Resolves when the change has been made in the DOM.
|
||||
*/
|
||||
setClassState(name, isApplied) {
|
||||
// Do the change in our local model.
|
||||
let nodeClasses = this.currentClasses;
|
||||
nodeClasses.find(({ name: cName }) => cName === name).isApplied = isApplied;
|
||||
|
||||
return this.applyClassState();
|
||||
},
|
||||
|
||||
/**
|
||||
* Add several classes to the current node at once.
|
||||
*
|
||||
* @param {String} classNameString
|
||||
* The string that contains all classes.
|
||||
* @return {Promise} Resolves when the change has been made in the DOM.
|
||||
*/
|
||||
addClassName(classNameString) {
|
||||
this.classListProxyNode.className = classNameString;
|
||||
return Promise.all([...new Set([...this.classListProxyNode.classList])].map(name => {
|
||||
return this.addClass(name);
|
||||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a class to the current node at once.
|
||||
*
|
||||
* @param {String} name
|
||||
* The class to be added.
|
||||
* @return {Promise} Resolves when the change has been made in the DOM.
|
||||
*/
|
||||
addClass(name) {
|
||||
// Avoid adding the same class again.
|
||||
if (this.currentClasses.some(({ name: cName }) => cName === name)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// Change the local model, so we retain the state of the existing classes.
|
||||
this.currentClasses.push({ name, isApplied: true });
|
||||
|
||||
return this.applyClassState();
|
||||
},
|
||||
|
||||
/**
|
||||
* Used internally by other functions like addClass or setClassState. Actually applies
|
||||
* the class change to the DOM.
|
||||
*
|
||||
* @return {Promise} Resolves when the change has been made in the DOM.
|
||||
*/
|
||||
applyClassState() {
|
||||
// If there is no valid inspector selection, bail out silently. No need to report an
|
||||
// error here.
|
||||
if (!this.currentNode) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// Remember which node we changed and the className we applied, so we can filter out
|
||||
// dom mutations that are caused by us in onMutations.
|
||||
this.lastStateChange = {
|
||||
node: this.currentNode,
|
||||
className: this.currentClassesPreview
|
||||
};
|
||||
|
||||
// Apply the change to the node.
|
||||
let mod = this.currentNode.startModifyingAttributes();
|
||||
mod.setAttribute("class", this.currentClassesPreview);
|
||||
return mod.apply();
|
||||
},
|
||||
|
||||
onMutations(e, mutations) {
|
||||
for (let {type, target, attributeName} of mutations) {
|
||||
// Only care if this mutation is for the class attribute.
|
||||
if (type !== "attributes" || attributeName !== "class") {
|
||||
continue;
|
||||
}
|
||||
|
||||
let isMutationForOurChange = this.lastStateChange &&
|
||||
target === this.lastStateChange.node &&
|
||||
target.className === this.lastStateChange.className;
|
||||
|
||||
if (!isMutationForOurChange) {
|
||||
CLASSES.delete(target);
|
||||
if (target === this.currentNode) {
|
||||
this.emit("current-node-class-changed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This UI widget shows a textfield and a series of checkboxes in the rule-view. It is
|
||||
* used to toggle classes on the current node selection, and add new classes.
|
||||
*
|
||||
* @param {Inspector} inspector
|
||||
* The current inspector instance.
|
||||
* @param {DomNode} containerEl
|
||||
* The element in the rule-view where the widget should go.
|
||||
*/
|
||||
function ClassListPreviewer(inspector, containerEl) {
|
||||
this.inspector = inspector;
|
||||
this.containerEl = containerEl;
|
||||
this.model = new ClassListPreviewerModel(inspector);
|
||||
|
||||
this.onNewSelection = this.onNewSelection.bind(this);
|
||||
this.onCheckBoxChanged = this.onCheckBoxChanged.bind(this);
|
||||
this.onKeyPress = this.onKeyPress.bind(this);
|
||||
this.onCurrentNodeClassChanged = this.onCurrentNodeClassChanged.bind(this);
|
||||
|
||||
// Create the add class text field.
|
||||
this.addEl = this.doc.createElement("input");
|
||||
this.addEl.classList.add("devtools-textinput");
|
||||
this.addEl.classList.add("add-class");
|
||||
this.addEl.setAttribute("placeholder",
|
||||
L10N.getStr("inspector.classPanel.newClass.placeholder"));
|
||||
this.addEl.addEventListener("keypress", this.onKeyPress);
|
||||
this.containerEl.appendChild(this.addEl);
|
||||
|
||||
// Create the class checkboxes container.
|
||||
this.classesEl = this.doc.createElement("div");
|
||||
this.classesEl.classList.add("classes");
|
||||
this.containerEl.appendChild(this.classesEl);
|
||||
|
||||
// Start listening for interesting events.
|
||||
this.inspector.selection.on("new-node-front", this.onNewSelection);
|
||||
this.containerEl.addEventListener("input", this.onCheckBoxChanged);
|
||||
this.model.on("current-node-class-changed", this.onCurrentNodeClassChanged);
|
||||
}
|
||||
|
||||
ClassListPreviewer.prototype = {
|
||||
destroy() {
|
||||
this.inspector.selection.off("new-node-front", this.onNewSelection);
|
||||
this.addEl.removeEventListener("keypress", this.onKeyPress);
|
||||
this.containerEl.removeEventListener("input", this.onCheckBoxChanged);
|
||||
|
||||
this.containerEl.innerHTML = "";
|
||||
|
||||
this.model.destroy();
|
||||
this.containerEl = null;
|
||||
this.inspector = null;
|
||||
this.addEl = null;
|
||||
this.classesEl = null;
|
||||
},
|
||||
|
||||
get doc() {
|
||||
return this.containerEl.ownerDocument;
|
||||
},
|
||||
|
||||
/**
|
||||
* Render the content of the panel. You typically don't need to call this as the panel
|
||||
* renders itself on inspector selection changes.
|
||||
*/
|
||||
render() {
|
||||
this.classesEl.innerHTML = "";
|
||||
|
||||
for (let { name, isApplied } of this.model.currentClasses) {
|
||||
let checkBox = this.renderCheckBox(name, isApplied);
|
||||
this.classesEl.appendChild(checkBox);
|
||||
}
|
||||
|
||||
if (!this.model.currentClasses.length) {
|
||||
this.classesEl.appendChild(this.renderNoClassesMessage());
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Render a single checkbox for a given classname.
|
||||
*
|
||||
* @param {String} name
|
||||
* The name of this class.
|
||||
* @param {Boolean} isApplied
|
||||
* Is this class currently applied on the DOM node.
|
||||
* @return {DOMNode} The DOM element for this checkbox.
|
||||
*/
|
||||
renderCheckBox(name, isApplied) {
|
||||
let box = this.doc.createElement("input");
|
||||
box.setAttribute("type", "checkbox");
|
||||
if (isApplied) {
|
||||
box.setAttribute("checked", "checked");
|
||||
}
|
||||
box.dataset.name = name;
|
||||
|
||||
let labelWrapper = this.doc.createElement("label");
|
||||
labelWrapper.setAttribute("title", name);
|
||||
labelWrapper.appendChild(box);
|
||||
|
||||
// A child element is required to do the ellipsis.
|
||||
let label = this.doc.createElement("span");
|
||||
label.textContent = name;
|
||||
labelWrapper.appendChild(label);
|
||||
|
||||
return labelWrapper;
|
||||
},
|
||||
|
||||
/**
|
||||
* Render the message displayed in the panel when the current element has no classes.
|
||||
*
|
||||
* @return {DOMNode} The DOM element for the message.
|
||||
*/
|
||||
renderNoClassesMessage() {
|
||||
let msg = this.doc.createElement("p");
|
||||
msg.classList.add("no-classes");
|
||||
msg.textContent = L10N.getStr("inspector.classPanel.noClasses");
|
||||
return msg;
|
||||
},
|
||||
|
||||
/**
|
||||
* Focus the add-class text field.
|
||||
*/
|
||||
focusAddClassField() {
|
||||
if (this.addEl) {
|
||||
this.addEl.focus();
|
||||
}
|
||||
},
|
||||
|
||||
onCheckBoxChanged({ target }) {
|
||||
if (!target.dataset.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.model.setClassState(target.dataset.name, target.checked).catch(e => {
|
||||
// Only log the error if the panel wasn't destroyed in the meantime.
|
||||
if (this.containerEl) {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onKeyPress(event) {
|
||||
if (event.key !== "Enter" || this.addEl.value === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
this.model.addClassName(this.addEl.value).then(() => {
|
||||
this.render();
|
||||
this.addEl.value = "";
|
||||
}).catch(e => {
|
||||
// Only log the error if the panel wasn't destroyed in the meantime.
|
||||
if (this.containerEl) {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onNewSelection() {
|
||||
this.render();
|
||||
},
|
||||
|
||||
onCurrentNodeClassChanged() {
|
||||
this.render();
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = ClassListPreviewer;
|
@ -3,6 +3,7 @@
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DevToolsModules(
|
||||
'class-list-previewer.js',
|
||||
'rule-editor.js',
|
||||
'text-property-editor.js',
|
||||
)
|
||||
|
@ -377,6 +377,19 @@ inspector.addRule.tooltip=Add new rule
|
||||
# rule view toolbar.
|
||||
inspector.togglePseudo.tooltip=Toggle pseudo-classes
|
||||
|
||||
# LOCALIZATION NOTE (inspector.classPanel.toggleClass.tooltip): This is the tooltip
|
||||
# shown when hovering over the `Toggle Class Panel` button in the
|
||||
# rule view toolbar.
|
||||
inspector.classPanel.toggleClass.tooltip=Toggle classes
|
||||
|
||||
# LOCALIZATION NOTE (inspector.classPanel.newClass.placeholder): This is the placeholder
|
||||
# shown inside the text field used to add a new class in the rule-view.
|
||||
inspector.classPanel.newClass.placeholder=Add new class
|
||||
|
||||
# LOCALIZATION NOTE (inspector.classPanel.noClasses): This is the text displayed in the
|
||||
# class panel when the current element has no classes applied.
|
||||
inspector.classPanel.noClasses=No classes on this element
|
||||
|
||||
# LOCALIZATION NOTE (inspector.noProperties): In the case where there are no CSS
|
||||
# properties to display e.g. due to search criteria this message is
|
||||
# displayed.
|
||||
|
@ -42,6 +42,7 @@ const RequestListContent = createClass({
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
displayedRequests: PropTypes.object.isRequired,
|
||||
firstRequestStartedMillis: PropTypes.number.isRequired,
|
||||
fromCache: PropTypes.bool.isRequired,
|
||||
onItemMouseDown: PropTypes.func.isRequired,
|
||||
onSecurityIconClick: PropTypes.func.isRequired,
|
||||
onSelectDelta: PropTypes.func.isRequired,
|
||||
@ -240,6 +241,7 @@ const RequestListContent = createClass({
|
||||
},
|
||||
displayedRequests.map((item, index) => RequestListItem({
|
||||
firstRequestStartedMillis,
|
||||
fromCache: item.status === "304" || item.fromCache,
|
||||
item,
|
||||
index,
|
||||
isSelected: item.id === selectedRequestId,
|
||||
|
@ -65,6 +65,7 @@ const RequestListItem = createClass({
|
||||
index: PropTypes.number.isRequired,
|
||||
isSelected: PropTypes.bool.isRequired,
|
||||
firstRequestStartedMillis: PropTypes.number.isRequired,
|
||||
fromCache: PropTypes.bool.isRequired,
|
||||
onContextMenu: PropTypes.func.isRequired,
|
||||
onFocusedNodeChange: PropTypes.func,
|
||||
onMouseDown: PropTypes.func.isRequired,
|
||||
@ -97,6 +98,7 @@ const RequestListItem = createClass({
|
||||
index,
|
||||
isSelected,
|
||||
firstRequestStartedMillis,
|
||||
fromCache,
|
||||
onContextMenu,
|
||||
onMouseDown,
|
||||
onSecurityIconClick
|
||||
@ -106,6 +108,11 @@ const RequestListItem = createClass({
|
||||
if (isSelected) {
|
||||
classList.push("selected");
|
||||
}
|
||||
|
||||
if (fromCache) {
|
||||
classList.push("fromCache");
|
||||
}
|
||||
|
||||
classList.push(index % 2 ? "odd" : "even");
|
||||
|
||||
return (
|
||||
@ -383,11 +390,11 @@ const TransferredSizeColumn = createFactory(createClass({
|
||||
},
|
||||
|
||||
render() {
|
||||
const { transferredSize, fromCache, fromServiceWorker } = this.props.item;
|
||||
const { transferredSize, fromCache, fromServiceWorker, status } = this.props.item;
|
||||
|
||||
let text;
|
||||
let className = "subitem-label";
|
||||
if (fromCache) {
|
||||
if (fromCache || status === "304") {
|
||||
text = L10N.getStr("networkMenu.sizeCached");
|
||||
className += " theme-comment";
|
||||
} else if (fromServiceWorker) {
|
||||
|
@ -82,7 +82,7 @@ const getDisplayedRequestsSummary = createSelector(
|
||||
totals.contentSize += item.contentSize;
|
||||
}
|
||||
|
||||
if (typeof item.transferredSize == "number") {
|
||||
if (typeof item.transferredSize == "number" && !item.fromCache) {
|
||||
totals.transferredSize += item.transferredSize;
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,11 @@ function onLearnMoreClick(e, url) {
|
||||
e.preventDefault();
|
||||
|
||||
let win = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
|
||||
win.openUILinkIn(url, "tab");
|
||||
if (e.button === 1) {
|
||||
win.openUILinkIn(url, "tabshifted");
|
||||
} else {
|
||||
win.openUILinkIn(url, "tab");
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MDNLink;
|
||||
|
@ -106,6 +106,7 @@ add_task(function* () {
|
||||
let queryFocus = once(query, "focus", false);
|
||||
// Bug 1195825: Due to some unexplained dark-matter with promise,
|
||||
// focus only works if delayed by one tick.
|
||||
query.setSelectionRange(query.value.length, query.value.length);
|
||||
executeSoon(() => query.focus());
|
||||
yield queryFocus;
|
||||
|
||||
@ -115,6 +116,7 @@ add_task(function* () {
|
||||
|
||||
let headers = document.getElementById("custom-headers-value");
|
||||
let headersFocus = once(headers, "focus", false);
|
||||
headers.setSelectionRange(headers.value.length, headers.value.length);
|
||||
headers.focus();
|
||||
yield headersFocus;
|
||||
|
||||
@ -129,6 +131,7 @@ add_task(function* () {
|
||||
|
||||
let postData = document.getElementById("custom-postdata-value");
|
||||
let postFocus = once(postData, "focus", false);
|
||||
postData.setSelectionRange(postData.value.length, postData.value.length);
|
||||
postData.focus();
|
||||
yield postFocus;
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
// Test that permission popups asking for user approval still appear in RDM
|
||||
const DUMMY_URL = "http://example.com/";
|
||||
const TEST_URL = `${URL_ROOT}geolocation.html`;
|
||||
const TEST_SURL = TEST_URL.replace("http://example.com", "https://example.com");
|
||||
|
||||
function waitForGeolocationPrompt(win, browser) {
|
||||
return new Promise(resolve => {
|
||||
@ -28,7 +29,7 @@ add_task(function* () {
|
||||
|
||||
// Checks if a geolocation permission doorhanger appears when openning a page
|
||||
// requesting geolocation
|
||||
yield load(browser, TEST_URL);
|
||||
yield load(browser, TEST_SURL);
|
||||
yield waitPromptPromise;
|
||||
|
||||
ok(true, "Permission doorhanger appeared without RDM enabled");
|
||||
@ -42,7 +43,7 @@ add_task(function* () {
|
||||
|
||||
// Checks if the doorhanger appeared again when reloading the geolocation
|
||||
// page inside RDM
|
||||
yield load(browser, TEST_URL);
|
||||
yield load(browser, TEST_SURL);
|
||||
yield waitPromptPromise;
|
||||
|
||||
ok(true, "Permission doorhanger appeared inside RDM");
|
||||
|
@ -1367,7 +1367,7 @@ var Scratchpad = {
|
||||
|
||||
recentFilesMenu.setAttribute("disabled", true);
|
||||
while (recentFilesPopup.hasChildNodes()) {
|
||||
recentFilesPopup.removeChild(recentFilesPopup.firstChild);
|
||||
recentFilesPopup.firstChild.remove();
|
||||
}
|
||||
|
||||
if (filePaths.length > 0) {
|
||||
|
@ -85,6 +85,7 @@
|
||||
|
||||
/* Learn More link */
|
||||
.treeTable .treeValueCell .learn-more-link {
|
||||
-moz-user-select: none;
|
||||
color: var(--theme-highlight-blue);
|
||||
cursor: pointer;
|
||||
margin: 0 5px;
|
||||
|
@ -1019,7 +1019,7 @@ OutputPanel.prototype._update = function () {
|
||||
|
||||
// Empty this._div
|
||||
while (this._div.hasChildNodes()) {
|
||||
this._div.removeChild(this._div.firstChild);
|
||||
this._div.firstChild.remove();
|
||||
}
|
||||
|
||||
if (this.displayedOutput.data != null) {
|
||||
@ -1030,7 +1030,7 @@ OutputPanel.prototype._update = function () {
|
||||
}
|
||||
|
||||
while (this._div.hasChildNodes()) {
|
||||
this._div.removeChild(this._div.firstChild);
|
||||
this._div.firstChild.remove();
|
||||
}
|
||||
|
||||
let links = node.querySelectorAll("*[href]");
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user