Merge mozilla-central to inbound. a=merge CLOSED TREE
@ -256,9 +256,7 @@ nsContextMenu.prototype = {
|
||||
InlineSpellCheckerUI.initFromRemote(gContextMenuContentData.spellInfo);
|
||||
} else {
|
||||
var targetWin = this.ownerDoc.defaultView;
|
||||
var editingSession = targetWin.docShell
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIEditingSession);
|
||||
var {editingSession} = targetWin.docShell;
|
||||
|
||||
InlineSpellCheckerUI.init(editingSession.getEditorForWindow(targetWin));
|
||||
InlineSpellCheckerUI.initFromEvent(document.popupRangeParent,
|
||||
|
@ -2532,10 +2532,20 @@ window._gBrowser = {
|
||||
},
|
||||
|
||||
getTabsToTheEndFrom(aTab) {
|
||||
let tab;
|
||||
if (aTab.multiselected) {
|
||||
// In a multi-select context, pick the rightmost
|
||||
// selected tab as reference.
|
||||
let selectedTabs = this.selectedTabs;
|
||||
tab = selectedTabs[selectedTabs.length - 1];
|
||||
} else {
|
||||
tab = aTab;
|
||||
}
|
||||
|
||||
let tabsToEnd = [];
|
||||
let tabs = this.visibleTabs;
|
||||
for (let i = tabs.length - 1; i >= 0; --i) {
|
||||
if (tabs[i] == aTab || tabs[i].pinned) {
|
||||
if (tabs[i] == tab || tabs[i].pinned) {
|
||||
break;
|
||||
}
|
||||
tabsToEnd.push(tabs[i]);
|
||||
@ -2543,6 +2553,10 @@ window._gBrowser = {
|
||||
return tabsToEnd;
|
||||
},
|
||||
|
||||
/**
|
||||
* In a multi-select context, the tabs (except pinned tabs) that are located to the
|
||||
* right of the rightmost selected tab will be removed.
|
||||
*/
|
||||
removeTabsToTheEndFrom(aTab) {
|
||||
let tabs = this.getTabsToTheEndFrom(aTab);
|
||||
if (!this.warnAboutClosingTabs(tabs.length, this.closingTabsEnum.TO_END)) {
|
||||
@ -2562,7 +2576,6 @@ window._gBrowser = {
|
||||
tabsToRemove = this.visibleTabs.filter(tab => !tab.multiselected && !tab.pinned);
|
||||
} else {
|
||||
tabsToRemove = this.visibleTabs.filter(tab => tab != aTab && !tab.pinned);
|
||||
this.selectedTab = aTab;
|
||||
}
|
||||
|
||||
if (!this.warnAboutClosingTabs(tabsToRemove.length, this.closingTabsEnum.OTHER)) {
|
||||
@ -2582,27 +2595,36 @@ window._gBrowser = {
|
||||
},
|
||||
|
||||
removeTabs(tabs) {
|
||||
let tabsWithBeforeUnload = [];
|
||||
let lastToClose;
|
||||
let params = { animate: true };
|
||||
for (let tab of tabs) {
|
||||
if (tab.selected) {
|
||||
lastToClose = tab;
|
||||
} else if (this._hasBeforeUnload(tab)) {
|
||||
tabsWithBeforeUnload.push(tab);
|
||||
} else {
|
||||
this.removeTab(tab, params);
|
||||
this._clearMultiSelectionLocked = true;
|
||||
|
||||
// Guarantee that _clearMultiSelectionLocked lock gets released.
|
||||
try {
|
||||
let tabsWithBeforeUnload = [];
|
||||
let lastToClose;
|
||||
let aParams = { animate: true };
|
||||
for (let tab of tabs) {
|
||||
if (tab.selected)
|
||||
lastToClose = tab;
|
||||
else if (this._hasBeforeUnload(tab))
|
||||
tabsWithBeforeUnload.push(tab);
|
||||
else
|
||||
this.removeTab(tab, aParams);
|
||||
}
|
||||
}
|
||||
for (let tab of tabsWithBeforeUnload) {
|
||||
this.removeTab(tab, params);
|
||||
for (let tab of tabsWithBeforeUnload) {
|
||||
this.removeTab(tab, aParams);
|
||||
}
|
||||
|
||||
// Avoid changing the selected browser several times by removing it,
|
||||
// if appropriate, lastly.
|
||||
if (lastToClose) {
|
||||
this.removeTab(lastToClose, aParams);
|
||||
}
|
||||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
|
||||
// Avoid changing the selected browser several times by removing it,
|
||||
// if appropriate, lastly.
|
||||
if (lastToClose) {
|
||||
this.removeTab(lastToClose, params);
|
||||
}
|
||||
this._clearMultiSelectionLocked = false;
|
||||
this.avoidSingleSelectedTab();
|
||||
},
|
||||
|
||||
removeCurrentTab(aParams) {
|
||||
@ -3693,12 +3715,11 @@ window._gBrowser = {
|
||||
return;
|
||||
}
|
||||
|
||||
let selectedTabs = this.selectedTabs;
|
||||
if (selectedTabs.length < 2) {
|
||||
if (this.multiSelectedTabsCount < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let tab of selectedTabs) {
|
||||
for (let tab of this.selectedTabs) {
|
||||
tab.removeAttribute("multiselected");
|
||||
}
|
||||
this._multiSelectedTabsSet = new WeakSet();
|
||||
@ -3714,16 +3735,41 @@ window._gBrowser = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove the active tab from the multiselection if it's the only one left there.
|
||||
* Remove a tab from the multiselection if it's the only one left there.
|
||||
*
|
||||
* In fact, some scenario may lead to only one single tab multi-selected,
|
||||
* this is something to avoid (Chrome does the same)
|
||||
* Consider 4 tabs A,B,C,D with A having the focus
|
||||
* 1. select C with Ctrl
|
||||
* 2. Right-click on B and "Close Tabs to The Right"
|
||||
*
|
||||
* Expected result
|
||||
* C and D closing
|
||||
* A being the only multi-selected tab, selection should be cleared
|
||||
*
|
||||
*
|
||||
* Single selected tab could even happen with a none-focused tab.
|
||||
* For exemple with the menu "Close other tabs", it could happen
|
||||
* with a multi-selected pinned tab.
|
||||
* For illustration, consider 4 tabs A,B,C,D with B active
|
||||
* 1. pin A and Ctrl-select it
|
||||
* 2. Ctrl-select C
|
||||
* 3. right-click on D and click "Close Other Tabs"
|
||||
*
|
||||
* Expected result
|
||||
* B and C closing
|
||||
* A[pinned] being the only multi-selected tab, selection should be cleared.
|
||||
*/
|
||||
updateActiveTabMultiSelectState() {
|
||||
if (this.selectedTabs.length == 1) {
|
||||
avoidSingleSelectedTab() {
|
||||
if (this.multiSelectedTabsCount == 1 ) {
|
||||
this.clearMultiSelectedTabs();
|
||||
}
|
||||
},
|
||||
|
||||
switchToNextMultiSelectedTab() {
|
||||
this._clearMultiSelectionLocked = true;
|
||||
|
||||
// Guarantee that _clearMultiSelectionLocked lock gets released.
|
||||
try {
|
||||
let lastMultiSelectedTab = gBrowser.lastMultiSelectedTab;
|
||||
if (lastMultiSelectedTab != gBrowser.selectedTab) {
|
||||
@ -3737,6 +3783,7 @@ window._gBrowser = {
|
||||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
|
||||
this._clearMultiSelectionLocked = false;
|
||||
},
|
||||
|
||||
@ -5098,10 +5145,9 @@ var TabContextMenu = {
|
||||
gBrowser.getTabsToTheEndFrom(this.contextTab).length == 0;
|
||||
|
||||
// Disable "Close other Tabs" if there are no unpinned tabs.
|
||||
let unpinnedTabsToClose = gBrowser.visibleTabs.length - gBrowser._numPinnedTabs;
|
||||
if (!this.contextTab.pinned) {
|
||||
unpinnedTabsToClose--;
|
||||
}
|
||||
let unpinnedTabsToClose = multiselectionContext ?
|
||||
gBrowser.visibleTabs.filter(t => !t.multiselected && !t.pinned).length :
|
||||
gBrowser.visibleTabs.filter(t => t != this.contextTab && !t.pinned).length;
|
||||
document.getElementById("context_closeOtherTabs").disabled = unpinnedTabsToClose < 1;
|
||||
|
||||
// Only one of close_tab/close_selected_tabs should be visible
|
||||
|
@ -2086,7 +2086,7 @@
|
||||
if (this == gBrowser.selectedTab) {
|
||||
gBrowser.switchToNextMultiSelectedTab();
|
||||
}
|
||||
gBrowser.updateActiveTabMultiSelectState();
|
||||
gBrowser.avoidSingleSelectedTab();
|
||||
} else if (this != gBrowser.selectedTab) {
|
||||
for (let tab of [this, gBrowser.selectedTab]) {
|
||||
gBrowser.addToMultiSelectedTabs(tab, true);
|
||||
|
@ -23,6 +23,7 @@ support-files =
|
||||
[browser_multiselect_tabs_bookmark.js]
|
||||
[browser_multiselect_tabs_clear_selection_when_tab_switch.js]
|
||||
[browser_multiselect_tabs_close_other_tabs.js]
|
||||
[browser_multiselect_tabs_close_tabs_to_the_right.js]
|
||||
[browser_multiselect_tabs_close_using_shortcuts.js]
|
||||
[browser_multiselect_tabs_close.js]
|
||||
[browser_multiselect_tabs_move_to_new_window_contextmenu.js]
|
||||
|
@ -65,23 +65,31 @@ add_task(async function withNotAMultiSelectedTab() {
|
||||
let tab2 = await addTab();
|
||||
let tab3 = await addTab();
|
||||
let tab4 = await addTab();
|
||||
let tab5 = await addTab();
|
||||
|
||||
is(gBrowser.multiSelectedTabsCount, 0, "Zero multiselected tabs");
|
||||
|
||||
await BrowserTestUtils.switchTab(gBrowser, tab1);
|
||||
await triggerClickOn(tab2, { ctrlKey: true });
|
||||
await triggerClickOn(tab5, { ctrlKey: true });
|
||||
|
||||
let tab4Pinned = BrowserTestUtils.waitForEvent(tab4, "TabPinned");
|
||||
gBrowser.pinTab(tab4);
|
||||
await tab4Pinned;
|
||||
|
||||
let tab5Pinned = BrowserTestUtils.waitForEvent(tab5, "TabPinned");
|
||||
gBrowser.pinTab(tab5);
|
||||
await tab5Pinned;
|
||||
|
||||
ok(!initialTab.multiselected, "InitialTab is not multiselected");
|
||||
ok(tab1.multiselected, "Tab1 is multiselected");
|
||||
ok(tab2.multiselected, "Tab2 is multiselected");
|
||||
ok(!tab3.multiselected, "Tab3 is not multiselected");
|
||||
ok(!tab4.multiselected, "Tab4 is not multiselected");
|
||||
ok(tab4.pinned, "Tab4 is pinned");
|
||||
is(gBrowser.multiSelectedTabsCount, 2, "Two multiselected tabs");
|
||||
ok(tab5.multiselected, "Tab5 is multiselected");
|
||||
ok(tab5.pinned, "Tab5 is pinned");
|
||||
is(gBrowser.multiSelectedTabsCount, 3, "Three multiselected tabs");
|
||||
is(gBrowser.selectedTab, tab1, "Tab1 is the active tab");
|
||||
|
||||
let closingTabs = [tab1, tab2, tab3];
|
||||
@ -101,8 +109,10 @@ add_task(async function withNotAMultiSelectedTab() {
|
||||
ok(tab2.closing, "Tab2 is closing");
|
||||
ok(tab3.closing, "Tab3 is closing");
|
||||
ok(!tab4.closing, "Tab4 is not closing");
|
||||
is(gBrowser.multiSelectedTabsCount, 0, "Zero multiselected tabs");
|
||||
ok(!tab5.closing, "Tab5 is not closing");
|
||||
is(gBrowser.multiSelectedTabsCount, 0, "Zero multiselected tabs, selection is cleared");
|
||||
is(gBrowser.selectedTab, initialTab, "InitialTab is the active tab now");
|
||||
|
||||
BrowserTestUtils.removeTab(tab4);
|
||||
for (let tab of [tab4, tab5])
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
@ -0,0 +1,114 @@
|
||||
const PREF_MULTISELECT_TABS = "browser.tabs.multiselect";
|
||||
const PREF_WARN_ON_CLOSE = "browser.tabs.warnOnCloseOtherTabs";
|
||||
|
||||
add_task(async function setPref() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
[PREF_MULTISELECT_TABS, true],
|
||||
[PREF_WARN_ON_CLOSE, false]
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function withAMultiSelectedTab() {
|
||||
let tab1 = await addTab();
|
||||
let tab2 = await addTab();
|
||||
let tab3 = await addTab();
|
||||
let tab4 = await addTab();
|
||||
let tab5 = await addTab();
|
||||
|
||||
is(gBrowser.multiSelectedTabsCount, 0, "Zero multiselected tabs");
|
||||
|
||||
await BrowserTestUtils.switchTab(gBrowser, tab1);
|
||||
await triggerClickOn(tab3, { ctrlKey: true });
|
||||
|
||||
ok(tab1.multiselected, "Tab1 is multiselected");
|
||||
ok(!tab2.multiselected, "Tab2 is not multiselected");
|
||||
ok(tab3.multiselected, "Tab3 is multiselected");
|
||||
ok(!tab4.multiselected, "Tab4 is not multiselected");
|
||||
ok(!tab5.multiselected, "Tab5 is not multiselected");
|
||||
is(gBrowser.multiSelectedTabsCount, 2, "Two multiselected tabs");
|
||||
|
||||
let closingTabs = [tab4, tab5];
|
||||
let tabClosingPromises = [];
|
||||
for (let tab of closingTabs) {
|
||||
tabClosingPromises.push(BrowserTestUtils.waitForTabClosing(tab));
|
||||
}
|
||||
|
||||
gBrowser.removeTabsToTheEndFrom(tab1);
|
||||
|
||||
for (let promise of tabClosingPromises) {
|
||||
await promise;
|
||||
}
|
||||
|
||||
ok(!tab1.closing, "Tab1 is not closing");
|
||||
ok(!tab2.closing, "Tab2 is not closing");
|
||||
ok(!tab3.closing, "Tab3 is not closing");
|
||||
ok(tab4.closing, "Tab4 is closing");
|
||||
ok(tab5.closing, "Tab5 is closing");
|
||||
is(gBrowser.multiSelectedTabsCount, 2, "Two multiselected tabs");
|
||||
|
||||
for (let tab of [tab1, tab2, tab3])
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
||||
add_task(async function withNotAMultiSelectedTab() {
|
||||
let tab1 = await addTab();
|
||||
let tab2 = await addTab();
|
||||
let tab3 = await addTab();
|
||||
let tab4 = await addTab();
|
||||
let tab5 = await addTab();
|
||||
|
||||
is(gBrowser.multiSelectedTabsCount, 0, "Zero multiselected tabs");
|
||||
|
||||
await BrowserTestUtils.switchTab(gBrowser, tab1);
|
||||
await triggerClickOn(tab3, { ctrlKey: true });
|
||||
await triggerClickOn(tab5, { ctrlKey: true });
|
||||
|
||||
ok(tab1.multiselected, "Tab1 is multiselected");
|
||||
ok(!tab2.multiselected, "Tab2 is not multiselected");
|
||||
ok(tab3.multiselected, "Tab3 is multiselected");
|
||||
ok(!tab4.multiselected, "Tab4 is not multiselected");
|
||||
ok(tab5.multiselected, "Tab5 is multiselected");
|
||||
is(gBrowser.multiSelectedTabsCount, 3, "Three multiselected tabs");
|
||||
|
||||
let closingTabs = [tab5];
|
||||
let tabClosingPromises = [];
|
||||
for (let tab of closingTabs) {
|
||||
tabClosingPromises.push(BrowserTestUtils.waitForTabClosing(tab));
|
||||
}
|
||||
|
||||
gBrowser.removeTabsToTheEndFrom(tab4);
|
||||
|
||||
for (let promise of tabClosingPromises) {
|
||||
await promise;
|
||||
}
|
||||
|
||||
ok(!tab1.closing, "Tab1 is not closing");
|
||||
ok(!tab2.closing, "Tab2 is not closing");
|
||||
ok(!tab3.closing, "Tab3 is not closing");
|
||||
ok(!tab4.closing, "Tab4 is not closing");
|
||||
ok(tab5.closing, "Tab5 is closing");
|
||||
is(gBrowser.multiSelectedTabsCount, 2, "Selection is not cleared");
|
||||
|
||||
closingTabs = [tab3, tab4];
|
||||
tabClosingPromises = [];
|
||||
for (let tab of closingTabs) {
|
||||
tabClosingPromises.push(BrowserTestUtils.waitForTabClosing(tab));
|
||||
}
|
||||
|
||||
gBrowser.removeTabsToTheEndFrom(tab2);
|
||||
|
||||
for (let promise of tabClosingPromises) {
|
||||
await promise;
|
||||
}
|
||||
|
||||
ok(!tab1.closing, "Tab1 is not closing");
|
||||
ok(!tab2.closing, "Tab2 is not closing");
|
||||
ok(tab3.closing, "Tab3 is closing");
|
||||
ok(tab4.closing, "Tab4 is closing");
|
||||
is(gBrowser.multiSelectedTabsCount, 0, "Selection is cleared");
|
||||
|
||||
for (let tab of [tab1, tab2])
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
});
|
@ -1 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="context-fill" d="M8 15a8 8 0 0 1-8-8V3a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v4a8 8 0 0 1-8 8zm3.985-10.032a.99.99 0 0 0-.725.319L7.978 8.57 4.755 5.336A.984.984 0 0 0 4 4.968a1 1 0 0 0-.714 1.7l-.016.011 3.293 3.306.707.707a1 1 0 0 0 1.414 0l.707-.707L12.7 6.679a1 1 0 0 0-.715-1.711z"/></svg>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path fill-opacity="context-fill-opacity" fill="context-fill" d="M14.5.932h-13A1.509 1.509 0 0 0 0 2.435v4.5a8 8 0 0 0 16 0v-4.5A1.508 1.508 0 0 0 14.5.932zm-.5 6a6 6 0 0 1-12 0v-4h12zm-6.7 3.477a1 1 0 0 0 1.422 0l3.343-3.39a1 1 0 1 0-1.423-1.406L8.01 8.283 5.38 5.614a1 1 0 0 0-1.425 1.405zm.711.3z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 358 B After Width: | Height: | Size: 608 B |
@ -24,11 +24,6 @@ address-form[address-fields]:not([address-fields~='tel']) #tel-container {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
label[required] > span:first-of-type::after {
|
||||
/* The asterisk should be localized, bug 1472278 */
|
||||
content: "*";
|
||||
}
|
||||
|
||||
.error-text:not(:empty) {
|
||||
color: #fff;
|
||||
background-color: #d70022;
|
||||
|
@ -153,18 +153,7 @@ export default class AddressForm extends PaymentStateSubscriberMixin(PaymentRequ
|
||||
this.formHandler.loadRecord(record);
|
||||
|
||||
// Add validation to some address fields
|
||||
for (let formElement of this.form.elements) {
|
||||
let container = formElement.closest(`#${formElement.id}-container`);
|
||||
if (formElement.localName == "button" || !container) {
|
||||
continue;
|
||||
}
|
||||
let required = formElement.required && !formElement.disabled;
|
||||
if (required) {
|
||||
container.setAttribute("required", "true");
|
||||
} else {
|
||||
container.removeAttribute("required");
|
||||
}
|
||||
}
|
||||
this.updateRequiredState();
|
||||
|
||||
let shippingAddressErrors = request.paymentDetails.shippingAddressErrors;
|
||||
for (let [errorName, errorSelector] of Object.entries(this._errorFieldMap)) {
|
||||
@ -205,6 +194,7 @@ export default class AddressForm extends PaymentStateSubscriberMixin(PaymentRequ
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleEvent(event) {
|
||||
switch (event.type) {
|
||||
case "click": {
|
||||
@ -246,6 +236,23 @@ export default class AddressForm extends PaymentStateSubscriberMixin(PaymentRequ
|
||||
}
|
||||
}
|
||||
|
||||
updateRequiredState() {
|
||||
for (let formElement of this.form.elements) {
|
||||
let container = formElement.closest(`#${formElement.id}-container`);
|
||||
if (formElement.localName == "button" || !container) {
|
||||
continue;
|
||||
}
|
||||
let span = container.querySelector("span");
|
||||
span.setAttribute("fieldRequiredSymbol", this.dataset.fieldRequiredSymbol);
|
||||
let required = formElement.required && !formElement.disabled;
|
||||
if (required) {
|
||||
container.setAttribute("required", "true");
|
||||
} else {
|
||||
container.removeAttribute("required");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async saveRecord() {
|
||||
let record = this.formHandler.buildFormObject();
|
||||
let currentState = this.requestStore.getState();
|
||||
|
@ -181,6 +181,7 @@ export default class BasicCardForm extends PaymentStateSubscriberMixin(PaymentRe
|
||||
}
|
||||
}
|
||||
|
||||
this.updateRequiredState();
|
||||
this.updateSaveButtonState();
|
||||
}
|
||||
|
||||
@ -299,6 +300,20 @@ export default class BasicCardForm extends PaymentStateSubscriberMixin(PaymentRe
|
||||
this.saveButton.disabled = !this.form.checkValidity();
|
||||
}
|
||||
|
||||
updateRequiredState() {
|
||||
for (let formElement of this.form.elements) {
|
||||
let container = formElement.closest("label") || formElement.closest("div");
|
||||
let span = container.querySelector("span");
|
||||
span.setAttribute("fieldRequiredSymbol", this.dataset.fieldRequiredSymbol);
|
||||
let required = formElement.required && !formElement.disabled;
|
||||
if (required) {
|
||||
container.setAttribute("required", "true");
|
||||
} else {
|
||||
container.removeAttribute("required");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async saveRecord() {
|
||||
let record = this.formHandler.buildFormObject();
|
||||
let currentState = this.requestStore.getState();
|
||||
|
7
browser/components/payments/res/containers/form.css
Normal file
@ -0,0 +1,7 @@
|
||||
/* 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/. */
|
||||
|
||||
:-moz-any(label, div)[required] > span:first-of-type::after {
|
||||
content: attr(fieldRequiredSymbol);
|
||||
}
|
@ -18,7 +18,7 @@ export default class PaymentMethodPicker extends RichPicker {
|
||||
this.dropdown.setAttribute("option-type", "basic-card-option");
|
||||
this.securityCodeInput = document.createElement("input");
|
||||
this.securityCodeInput.autocomplete = "off";
|
||||
this.securityCodeInput.placeholder = "CVV"; /* XXX Bug 1473772 */
|
||||
this.securityCodeInput.placeholder = this.dataset.cvvPlaceholder;
|
||||
this.securityCodeInput.size = 3;
|
||||
this.securityCodeInput.classList.add("security-code");
|
||||
this.securityCodeInput.addEventListener("change", this);
|
||||
|
@ -11,6 +11,7 @@
|
||||
<!ENTITY viewAllItems "View All Items">
|
||||
<!ENTITY paymentSummaryTitle "Your Payment">
|
||||
<!ENTITY header.payTo "Pay to">
|
||||
<!ENTITY fieldRequiredSymbol "*">
|
||||
|
||||
<!ENTITY shippingAddressLabel "Shipping Address">
|
||||
<!ENTITY deliveryAddressLabel "Delivery Address">
|
||||
@ -33,6 +34,7 @@
|
||||
<!ENTITY billingAddress.editPage.title "Edit Billing Address">
|
||||
<!ENTITY basicCard.addPage.title "Add Credit Card">
|
||||
<!ENTITY basicCard.editPage.title "Edit Credit Card">
|
||||
<!ENTITY basicCard.cvv.placeholder "CVV&fieldRequiredSymbol;">
|
||||
<!ENTITY payer.addPage.title "Add Payer Contact">
|
||||
<!ENTITY payer.editPage.title "Edit Payer Contact">
|
||||
<!ENTITY payerLabel "Contact Information">
|
||||
@ -82,6 +84,7 @@
|
||||
<link rel="stylesheet" href="components/basic-card-option.css"/>
|
||||
<link rel="stylesheet" href="components/shipping-option.css"/>
|
||||
<link rel="stylesheet" href="components/payment-details-item.css"/>
|
||||
<link rel="stylesheet" href="containers/form.css"/>
|
||||
<link rel="stylesheet" href="containers/address-form.css"/>
|
||||
<link rel="stylesheet" href="containers/basic-card-form.css"/>
|
||||
<link rel="stylesheet" href="containers/order-details.css"/>
|
||||
@ -124,6 +127,7 @@
|
||||
<payment-method-picker selected-state-key="selectedPaymentCard"
|
||||
data-add-link-label="&basicCard.addLink.label;"
|
||||
data-edit-link-label="&basicCard.editLink.label;"
|
||||
data-cvv-placeholder="&basicCard.cvv.placeholder;"
|
||||
label="&paymentMethodsLabel;">
|
||||
</payment-method-picker>
|
||||
<address-picker class="payer-related"
|
||||
@ -162,6 +166,7 @@
|
||||
data-update-button-label="&basicCardPage.updateButton.label;"
|
||||
data-cancel-button-label="&cancelPaymentButton.label;"
|
||||
data-persist-checkbox-label="&basicCardPage.persistCheckbox.label;"
|
||||
data-field-required-symbol="&fieldRequiredSymbol;"
|
||||
hidden="hidden"></basic-card-form>
|
||||
|
||||
<address-form id="address-page"
|
||||
@ -171,6 +176,7 @@
|
||||
data-add-button-label="&addressPage.addButton.label;"
|
||||
data-update-button-label="&addressPage.updateButton.label;"
|
||||
data-persist-checkbox-label="&addressPage.persistCheckbox.label;"
|
||||
data-field-required-symbol="&fieldRequiredSymbol;"
|
||||
hidden="hidden"></address-form>
|
||||
|
||||
<completion-error-page id="completion-timeout-error" class="illustrated"
|
||||
|
@ -17,6 +17,7 @@ Test the address-form element
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="../../res/paymentRequest.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="../../res/containers/form.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="../../res/containers/address-form.css"/>
|
||||
</head>
|
||||
<body>
|
||||
@ -275,6 +276,7 @@ add_task(async function test_restricted_address_fields() {
|
||||
|
||||
add_task(async function test_field_validation() {
|
||||
let form = new AddressForm();
|
||||
form.dataset.fieldRequiredSymbol = "*";
|
||||
await form.promiseReady;
|
||||
display.appendChild(form);
|
||||
await asyncElementRendered();
|
||||
@ -298,8 +300,11 @@ add_task(async function test_field_validation() {
|
||||
for (let field of requiredFields) {
|
||||
let container = field.closest("label");
|
||||
ok(container.hasAttribute("required"), "Container should have required attribute");
|
||||
let label = field.closest("label").querySelector("span");
|
||||
is(getComputedStyle(label, "::after").content, "\"*\"", "Asterisk should be on " + field.id);
|
||||
let span = container.querySelector("span");
|
||||
is(span.getAttribute("fieldRequiredSymbol"), "*",
|
||||
"span should have asterisk as fieldRequiredSymbol");
|
||||
is(getComputedStyle(span, "::after").content, "attr(fieldRequiredSymbol)",
|
||||
"Asterisk should be on " + field.id);
|
||||
}
|
||||
|
||||
countrySelect.selectedIndex = [...countrySelect.options].findIndex(o => o.value == "US");
|
||||
|
@ -136,6 +136,38 @@ add_task(async function test_saveButton() {
|
||||
form.remove();
|
||||
});
|
||||
|
||||
add_task(async function test_requiredAttributePropagated() {
|
||||
let form = new BasicCardForm();
|
||||
await form.promiseReady;
|
||||
display.appendChild(form);
|
||||
await asyncElementRendered();
|
||||
|
||||
let requiredElements = [...form.form.elements].filter(e => e.required && !e.disabled);
|
||||
ok(requiredElements.length, "There should be at least one required element");
|
||||
for (let element of requiredElements) {
|
||||
let container = element.closest("label") || element.closest("div");
|
||||
ok(container.hasAttribute("required"), "Container should also be marked as required");
|
||||
}
|
||||
// Now test that toggling the `required` attribute will affect the container.
|
||||
let sampleRequiredElement = requiredElements[0];
|
||||
let sampleRequiredContainer = sampleRequiredElement.closest("label") ||
|
||||
sampleRequiredElement.closest("div");
|
||||
sampleRequiredElement.removeAttribute("required");
|
||||
await form.requestStore.setState({});
|
||||
await asyncElementRendered();
|
||||
ok(!sampleRequiredElement.hasAttribute("required"),
|
||||
`"required" attribute should still be removed from element (${sampleRequiredElement.id})`);
|
||||
ok(!sampleRequiredContainer.hasAttribute("required"),
|
||||
`"required" attribute should be removed from container`);
|
||||
sampleRequiredElement.setAttribute("required", "true");
|
||||
await form.requestStore.setState({});
|
||||
await asyncElementRendered();
|
||||
ok(sampleRequiredContainer.hasAttribute("required"),
|
||||
"`required` attribute is re-added to container");
|
||||
|
||||
form.remove();
|
||||
});
|
||||
|
||||
add_task(async function test_genericError() {
|
||||
let form = new BasicCardForm();
|
||||
await form.requestStore.setState({
|
||||
@ -262,6 +294,14 @@ add_task(async function test_edit() {
|
||||
is(form.saveButton.textContent, "Update", "Check label");
|
||||
checkCCForm(form, card1);
|
||||
|
||||
let requiredElements = [...form.form.elements].filter(e => e.required && !e.disabled);
|
||||
ok(requiredElements.length, "There should be at least one required element");
|
||||
for (let element of requiredElements) {
|
||||
let container = element.closest("label") || element.closest("div");
|
||||
ok(element.hasAttribute("required"), "Element should be marked as required");
|
||||
ok(container.hasAttribute("required"), "Container should also be marked as required");
|
||||
}
|
||||
|
||||
info("test future year");
|
||||
card1["cc-exp-year"] = 2100;
|
||||
|
||||
|
@ -48,9 +48,9 @@ function loadContentWindow(webNavigation, uri) {
|
||||
|
||||
async function takeScreenshot(fullWidth, fullHeight, contentWidth, contentHeight, path, url) {
|
||||
try {
|
||||
let windowlessBrowser = Services.appShell.createWindowlessBrowser(false);
|
||||
var webNavigation = windowlessBrowser.QueryInterface(Ci.nsIWebNavigation);
|
||||
let contentWindow = await loadContentWindow(webNavigation, url);
|
||||
var windowlessBrowser = Services.appShell.createWindowlessBrowser(false);
|
||||
// nsIWindowlessBrowser inherits from nsIWebNavigation.
|
||||
let contentWindow = await loadContentWindow(windowlessBrowser, url);
|
||||
contentWindow.resizeTo(contentWidth, contentHeight);
|
||||
|
||||
let canvas = contentWindow.document.createElementNS("http://www.w3.org/1999/xhtml", "html:canvas");
|
||||
@ -82,8 +82,8 @@ async function takeScreenshot(fullWidth, fullHeight, contentWidth, contentHeight
|
||||
} catch (e) {
|
||||
dump("Failure taking screenshot: " + e + "\n");
|
||||
} finally {
|
||||
if (webNavigation) {
|
||||
webNavigation.close();
|
||||
if (windowlessBrowser) {
|
||||
windowlessBrowser.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
21
browser/extensions/pocket/bootstrap.js
vendored
@ -344,10 +344,13 @@ var PocketReader = {
|
||||
if (this.hidden) {
|
||||
Services.mm.broadcastAsyncMessage("Reader:RemoveButton", { id: "pocket-button" });
|
||||
} else {
|
||||
Services.mm.broadcastAsyncMessage("Reader:AddButton",
|
||||
{ id: "pocket-button",
|
||||
title: gPocketBundle.GetStringFromName("pocket-button.tooltiptext"),
|
||||
image: "chrome://pocket/content/panels/img/pocket.svg#pocket-mark" });
|
||||
Services.mm.broadcastAsyncMessage("Reader:AddButton", {
|
||||
id: "pocket-button",
|
||||
title: gPocketBundle.GetStringFromName("pocket-button.tooltiptext"),
|
||||
image: "chrome://pocket/content/panels/img/pocket-outline.svg",
|
||||
width: 20,
|
||||
height: 20,
|
||||
});
|
||||
}
|
||||
},
|
||||
receiveMessage(message) {
|
||||
@ -357,9 +360,13 @@ var PocketReader = {
|
||||
if (this.hidden)
|
||||
break;
|
||||
message.target.messageManager.
|
||||
sendAsyncMessage("Reader:AddButton", { id: "pocket-button",
|
||||
title: gPocketBundle.GetStringFromName("pocket-button.tooltiptext"),
|
||||
image: "chrome://pocket/content/panels/img/pocket.svg#pocket-mark"});
|
||||
sendAsyncMessage("Reader:AddButton", {
|
||||
id: "pocket-button",
|
||||
title: gPocketBundle.GetStringFromName("pocket-button.tooltiptext"),
|
||||
image: "chrome://pocket/content/panels/img/pocket-outline.svg",
|
||||
width: 20,
|
||||
height: 20,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "Reader:Clicked-pocket-button": {
|
||||
|
@ -0,0 +1,12 @@
|
||||
<!-- 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/. -->
|
||||
|
||||
<!--
|
||||
This file is the same as skin/shared/pocket-outline.svg and the two should be
|
||||
kept in sync. The only reason this file exists is that it lives in the
|
||||
contentaccessible content directory so that Firefox's reader mode can use it.
|
||||
-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path fill-opacity="context-fill-opacity" fill="context-fill" d="M14.5.932h-13A1.509 1.509 0 0 0 0 2.435v4.5a8 8 0 0 0 16 0v-4.5A1.508 1.508 0 0 0 14.5.932zm-.5 6a6 6 0 0 1-12 0v-4h12zm-6.7 3.477a1 1 0 0 0 1.422 0l3.343-3.39a1 1 0 1 0-1.423-1.406L8.01 8.283 5.38 5.614a1 1 0 0 0-1.425 1.405zm.711.3z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 852 B |
@ -1,22 +0,0 @@
|
||||
<?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/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24">
|
||||
<style>
|
||||
use:not(:target) {
|
||||
display: none;
|
||||
}
|
||||
use {
|
||||
fill: #808080;
|
||||
}
|
||||
use[id$="-added"] {
|
||||
fill: #ee4056;
|
||||
}
|
||||
</style>
|
||||
<defs>
|
||||
<path id="pocket-mark-shape" d="M21.901,4.204C21.642,3.484,20.956,3,20.196,3h-0.01h-1.721H3.814C3.067,3,2.385,3.474,2.119,4.179 C2.04,4.388,2,4.606,2,4.828v6.082l0.069,1.21c0.29,2.751,1.707,5.155,3.899,6.832c0.039,0.03,0.079,0.06,0.119,0.089l0.025,0.018 c1.175,0.866,2.491,1.452,3.91,1.741C10.677,20.932,11.347,21,12.013,21c0.615,0,1.232-0.057,1.839-0.171 c0.073-0.014,0.145-0.028,0.219-0.044c0.02-0.004,0.042-0.012,0.064-0.023c1.359-0.299,2.621-0.87,3.753-1.704l0.025-0.018 c0.04-0.029,0.08-0.059,0.119-0.089c2.192-1.677,3.609-4.08,3.898-6.832L22,10.91V4.828C22,4.618,21.975,4.409,21.901,4.204z M17.667,10.539l-4.704,4.547c-0.266,0.256-0.608,0.385-0.949,0.385c-0.342,0-0.684-0.129-0.949-0.385l-4.705-4.547 c-0.547-0.528-0.565-1.403-0.04-1.954c0.524-0.551,1.392-0.569,1.939-0.041l3.756,3.63l3.755-3.63 c0.547-0.528,1.415-0.51,1.939,0.04C18.231,9.136,18.213,10.011,17.667,10.539z"/>
|
||||
</defs>
|
||||
<use id="pocket-mark" xlink:href="#pocket-mark-shape"/>
|
||||
<use id="pocket-mark-added" xlink:href="#pocket-mark-shape"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.5 KiB |
13
browser/extensions/pocket/skin/shared/pocket-outline.svg
Normal file
@ -0,0 +1,13 @@
|
||||
<!-- 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/. -->
|
||||
|
||||
<!--
|
||||
This file is the same as content/panels/img/pocket-outline.svg and the two
|
||||
should be kept in sync. The only reason that file exists is that it lives in
|
||||
the contentaccessible content directory so that Firefox's reader mode can use
|
||||
it.
|
||||
-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path fill-opacity="context-fill-opacity" fill="context-fill" d="M14.5.932h-13A1.509 1.509 0 0 0 0 2.435v4.5a8 8 0 0 0 16 0v-4.5A1.508 1.508 0 0 0 14.5.932zm-.5 6a6 6 0 0 1-12 0v-4h12zm-6.7 3.477a1 1 0 0 0 1.422 0l3.343-3.39a1 1 0 1 0-1.423-1.406L8.01 8.283 5.38 5.614a1 1 0 0 0-1.425 1.405zm.711.3z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 861 B |
@ -7,9 +7,14 @@
|
||||
fill: #fbfbfb;
|
||||
}
|
||||
|
||||
#pocket-button,
|
||||
#pageAction-panel-pocket {
|
||||
list-style-image: url("chrome://pocket-shared/skin/pocket-outline.svg");
|
||||
}
|
||||
|
||||
#appMenu-library-pocket-button,
|
||||
#pageAction-panel-pocket,
|
||||
#pocket-button {
|
||||
#pocket-button-box[open="true"] > #pocket-button,
|
||||
#pocket-button-box[pocketed="true"] > #pocket-button {
|
||||
list-style-image: url("chrome://pocket-shared/skin/pocket.svg");
|
||||
}
|
||||
|
||||
|
@ -2,5 +2,5 @@
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path fill-opacity="context-fill-opacity" fill="context-fill" d="M8 15a8 8 0 0 1-8-8V3a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v4a8 8 0 0 1-8 8zm3.985-10.032a.99.99 0 0 0-.725.319L7.978 8.57 4.755 5.336A.984.984 0 0 0 4 4.968a1 1 0 0 0-.714 1.7l-.016.011 3.293 3.306.707.707a1 1 0 0 0 1.414 0l.707-.707L12.7 6.679a1 1 0 0 0-.715-1.711z"/>
|
||||
<path fill-opacity="context-fill-opacity" fill="context-fill" fill-rule="evenodd" d="M14.496.933C15.323.933 16 1.61 16 2.436v4.497c0 4.418-3.581 8-8 8-4.418 0-8-3.582-8-8V2.436C0 1.61.677.933 1.504.933h12.992zM8.013 8.222L4.994 5.319c-.434-.418-1.125-.404-1.543.03-.418.434-.404 1.125.03 1.543l3.776 3.63c.423.406 1.09.406 1.513 0l3.775-3.63c.435-.418.448-1.109.03-1.543-.417-.434-1.108-.448-1.542-.03l-3.02 2.903z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 634 B After Width: | Height: | Size: 723 B |
@ -52,6 +52,7 @@ buildscript {
|
||||
|
||||
ext.kotlin_version = '1.2.41'
|
||||
ext.support_library_version = '26.1.0'
|
||||
ext.jacoco_version = '0.8.1'
|
||||
|
||||
if (gradle.mozconfig.substs.MOZ_ANDROID_GOOGLE_PLAY_SERVICES) {
|
||||
ext.google_play_services_version = '15.0.1'
|
||||
|
@ -66,3 +66,10 @@ def javac_version(javac):
|
||||
return version
|
||||
except subprocess.CalledProcessError as e:
|
||||
die('Failed to get javac version: %s', e.output)
|
||||
|
||||
|
||||
# Java Code Coverage
|
||||
# ========================================================
|
||||
option('--enable-java-coverage', env='MOZ_JAVA_CODE_COVERAGE', help='Enable Java code coverage')
|
||||
|
||||
set_config('MOZ_JAVA_CODE_COVERAGE', depends('--enable-java-coverage')(lambda v: bool(v)))
|
||||
|
@ -408,6 +408,23 @@ void Elf::normalize()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ElfSegment* prevLoad = nullptr;
|
||||
for (auto& it : segments) {
|
||||
if (it->getType() == PT_LOAD) {
|
||||
if (prevLoad) {
|
||||
size_t alignedPrevEnd =
|
||||
(prevLoad->getAddr() + prevLoad->getMemSize() + prevLoad->getAlign() - 1)
|
||||
& ~(prevLoad->getAlign() - 1);
|
||||
size_t alignedStart = it->getAddr() & ~(it->getAlign() - 1);
|
||||
if (alignedPrevEnd > alignedStart) {
|
||||
throw std::runtime_error("Segments overlap");
|
||||
}
|
||||
}
|
||||
prevLoad = it;
|
||||
}
|
||||
}
|
||||
|
||||
// fixup ehdr before writing
|
||||
if (ehdr->e_phnum != segments.size()) {
|
||||
ehdr->e_phnum = segments.size();
|
||||
@ -553,14 +570,6 @@ unsigned int ElfSection::getOffset()
|
||||
if ((getType() != SHT_NOBITS) && (offset & (getAddrAlign() - 1)))
|
||||
offset = (offset | (getAddrAlign() - 1)) + 1;
|
||||
|
||||
// Two subsequent sections can't be mapped in the same page in memory
|
||||
// if they aren't in the same 4K block on disk.
|
||||
if ((getType() != SHT_NOBITS) && getAddr()) {
|
||||
if (((offset >> 12) != (previous->getOffset() >> 12)) &&
|
||||
((getAddr() >> 12) == (previous->getAddr() >> 12)))
|
||||
throw std::runtime_error("Moving section would require overlapping segments");
|
||||
}
|
||||
|
||||
return (shdr.sh_offset = offset);
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include "elfxx.h"
|
||||
#include "mozilla/CheckedInt.h"
|
||||
|
||||
#define ver "0"
|
||||
#define elfhack_data ".elfhack.data.v" ver
|
||||
@ -196,12 +197,22 @@ public:
|
||||
}
|
||||
|
||||
bool isRelocatable() {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned int getEntryPoint() {
|
||||
return entry_point;
|
||||
}
|
||||
|
||||
void insertBefore(ElfSection *section, bool dirty = true) override {
|
||||
// Adjust the address so that this section is adjacent to the one it's
|
||||
// being inserted before. This avoids creating holes which subsequently
|
||||
// might lead the PHDR-adjusting code to create unnecessary additional
|
||||
// PT_LOADs.
|
||||
shdr.sh_addr = (section->getAddr() - shdr.sh_size) & ~(shdr.sh_addralign - 1);
|
||||
ElfSection::insertBefore(section, dirty);
|
||||
}
|
||||
|
||||
private:
|
||||
void add_code_section(ElfSection *section)
|
||||
{
|
||||
@ -517,6 +528,238 @@ void maybe_split_segment(Elf *elf, ElfSegment *segment, bool fill)
|
||||
}
|
||||
}
|
||||
|
||||
// EH_FRAME constants
|
||||
static const char DW_EH_PE_absptr = 0x00;
|
||||
static const char DW_EH_PE_omit = 0xff;
|
||||
|
||||
// Data size
|
||||
static const char DW_EH_PE_LEB128 = 0x01;
|
||||
static const char DW_EH_PE_data2 = 0x02;
|
||||
static const char DW_EH_PE_data4 = 0x03;
|
||||
static const char DW_EH_PE_data8 = 0x04;
|
||||
|
||||
// Data signedness
|
||||
static const char DW_EH_PE_signed = 0x08;
|
||||
|
||||
// Modifiers
|
||||
static const char DW_EH_PE_pcrel = 0x10;
|
||||
|
||||
|
||||
// Return the data size part of the encoding value
|
||||
static char encoding_data_size(char encoding)
|
||||
{
|
||||
return encoding & 0x07;
|
||||
}
|
||||
|
||||
// Advance `step` bytes in the buffer at `data` with size `size`, returning
|
||||
// the advanced buffer pointer and remaining size.
|
||||
// Returns true if step <= size.
|
||||
static bool advance_buffer(char** data, size_t* size, size_t step)
|
||||
{
|
||||
if (step > *size)
|
||||
return false;
|
||||
|
||||
*data += step;
|
||||
*size -= step;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Advance in the given buffer, skipping the full length of the variable-length
|
||||
// encoded LEB128 type in CIE/FDE data.
|
||||
static bool skip_LEB128(char** data, size_t* size)
|
||||
{
|
||||
if (!*size)
|
||||
return false;
|
||||
|
||||
while (*size && (*(*data)++ & (char)0x80)) {
|
||||
(*size)--;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Advance in the given buffer, skipping the full length of a pointer encoded
|
||||
// with the given encoding.
|
||||
static bool skip_eh_frame_pointer(char** data, size_t* size, char encoding)
|
||||
{
|
||||
switch (encoding_data_size(encoding)) {
|
||||
case DW_EH_PE_data2:
|
||||
return advance_buffer(data, size, 2);
|
||||
case DW_EH_PE_data4:
|
||||
return advance_buffer(data, size, 4);
|
||||
case DW_EH_PE_data8:
|
||||
return advance_buffer(data, size, 8);
|
||||
case DW_EH_PE_LEB128:
|
||||
return skip_LEB128(data, size);
|
||||
}
|
||||
throw std::runtime_error("unreachable");
|
||||
}
|
||||
|
||||
// Specialized implementations for adjust_eh_frame_pointer().
|
||||
template <typename T>
|
||||
static bool adjust_eh_frame_sized_pointer(char** data, size_t* size, ElfSection* eh_frame,
|
||||
unsigned int origAddr, Elf* elf)
|
||||
{
|
||||
if (*size < sizeof(T))
|
||||
return false;
|
||||
|
||||
serializable<FixedSizeData<T>> pointer(*data, *size, elf->getClass(), elf->getData());
|
||||
mozilla::CheckedInt<T> value = pointer.value;
|
||||
if (origAddr < eh_frame->getAddr()) {
|
||||
unsigned int diff = eh_frame->getAddr() - origAddr;
|
||||
value -= diff;
|
||||
} else {
|
||||
unsigned int diff = origAddr - eh_frame->getAddr();
|
||||
value += diff;
|
||||
}
|
||||
if (!value.isValid())
|
||||
throw std::runtime_error("Overflow while adjusting eh_frame");
|
||||
pointer.value = value.value();
|
||||
pointer.serialize(*data, *size, elf->getClass(), elf->getData());
|
||||
return advance_buffer(data, size, sizeof(T));
|
||||
}
|
||||
|
||||
// In the given eh_frame section, adjust the pointer with the given encoding, pointed to
|
||||
// by the given buffer (`data`, `size`), considering the eh_frame section was originally
|
||||
// at `origAddr`.
|
||||
// Also advances in the buffer.
|
||||
static bool adjust_eh_frame_pointer(char** data, size_t* size, char encoding, ElfSection* eh_frame,
|
||||
unsigned int origAddr, Elf* elf)
|
||||
{
|
||||
if ((encoding & 0x70) != DW_EH_PE_pcrel)
|
||||
return skip_eh_frame_pointer(data, size, encoding);
|
||||
|
||||
if (encoding & DW_EH_PE_signed) {
|
||||
switch (encoding_data_size(encoding)) {
|
||||
case DW_EH_PE_data2:
|
||||
return adjust_eh_frame_sized_pointer<int16_t>(data, size, eh_frame, origAddr, elf);
|
||||
case DW_EH_PE_data4:
|
||||
return adjust_eh_frame_sized_pointer<int32_t>(data, size, eh_frame, origAddr, elf);
|
||||
case DW_EH_PE_data8:
|
||||
return adjust_eh_frame_sized_pointer<int64_t>(data, size, eh_frame, origAddr, elf);
|
||||
}
|
||||
} else {
|
||||
switch (encoding_data_size(encoding)) {
|
||||
case DW_EH_PE_data2:
|
||||
return adjust_eh_frame_sized_pointer<uint16_t>(data, size, eh_frame, origAddr, elf);
|
||||
case DW_EH_PE_data4:
|
||||
return adjust_eh_frame_sized_pointer<uint32_t>(data, size, eh_frame, origAddr, elf);
|
||||
case DW_EH_PE_data8:
|
||||
return adjust_eh_frame_sized_pointer<uint64_t>(data, size, eh_frame, origAddr, elf);
|
||||
}
|
||||
}
|
||||
|
||||
throw std::runtime_error("Unsupported eh_frame pointer encoding");
|
||||
}
|
||||
|
||||
// The eh_frame section may contain "PC"-relative pointers. If we move the section,
|
||||
// those need to be adjusted. Other type of pointers are relative to sections we
|
||||
// don't touch.
|
||||
static void adjust_eh_frame(ElfSection* eh_frame, unsigned int origAddr, Elf* elf)
|
||||
{
|
||||
if (eh_frame->getAddr() == origAddr) // nothing to do;
|
||||
return;
|
||||
|
||||
char* data = const_cast<char*>(eh_frame->getData());
|
||||
size_t size = eh_frame->getSize();
|
||||
char LSDAencoding = DW_EH_PE_omit;
|
||||
char FDEencoding = DW_EH_PE_absptr;
|
||||
bool hasZ = false;
|
||||
|
||||
// Decoding of eh_frame based on https://www.airs.com/blog/archives/460
|
||||
while (size) {
|
||||
if (size < 2 * sizeof(uint32_t)) goto malformed;
|
||||
|
||||
serializable<FixedSizeData<uint32_t>> entryLength(data, size, elf->getClass(), elf->getData());
|
||||
if (!advance_buffer(&data, &size, sizeof(uint32_t))) goto malformed;
|
||||
|
||||
char* cursor = data;
|
||||
size_t length = entryLength.value;
|
||||
|
||||
serializable<FixedSizeData<uint32_t>> id(data, size, elf->getClass(), elf->getData());
|
||||
if (!advance_buffer(&cursor, &length, sizeof(uint32_t))) goto malformed;
|
||||
|
||||
if (id.value == 0) {
|
||||
// This is a Common Information Entry
|
||||
if (length < 2) goto malformed;
|
||||
// Reset LSDA and FDE encodings, and hasZ for subsequent FDEs.
|
||||
LSDAencoding = DW_EH_PE_omit;
|
||||
FDEencoding = DW_EH_PE_absptr;
|
||||
hasZ = false;
|
||||
// CIE version. Should only be 1 or 3.
|
||||
char version = *cursor++; length--;
|
||||
if (version != 1 && version != 3) {
|
||||
throw std::runtime_error("Unsupported eh_frame version");
|
||||
}
|
||||
// NUL terminated string.
|
||||
const char* augmentationString = cursor;
|
||||
size_t l = strnlen(augmentationString, length - 1);
|
||||
if (l == length - 1) goto malformed;
|
||||
if (!advance_buffer(&cursor, &length, l + 1)) goto malformed;
|
||||
// Skip code alignment factor (LEB128)
|
||||
if (!skip_LEB128(&cursor, &length)) goto malformed;
|
||||
// Skip data alignment factor (LEB128)
|
||||
if (!skip_LEB128(&cursor, &length)) goto malformed;
|
||||
// Skip return address register (single byte in CIE version 1, LEB128
|
||||
// in CIE version 3)
|
||||
if (version == 1) {
|
||||
if (!advance_buffer(&cursor, &length, 1)) goto malformed;
|
||||
} else {
|
||||
if (!skip_LEB128(&cursor, &length)) goto malformed;
|
||||
}
|
||||
// Past this, it's data driven by the contents of the augmentation string.
|
||||
for (size_t i = 0; i < l; i++) {
|
||||
if (!length) goto malformed;
|
||||
switch (augmentationString[i]) {
|
||||
case 'z':
|
||||
if (!skip_LEB128(&cursor, &length)) goto malformed;
|
||||
hasZ = true;
|
||||
break;
|
||||
case 'L':
|
||||
LSDAencoding = *cursor++;
|
||||
length--;
|
||||
break;
|
||||
case 'R':
|
||||
FDEencoding = *cursor++;
|
||||
length--;
|
||||
break;
|
||||
case 'P':
|
||||
{
|
||||
char encoding = *cursor++;
|
||||
length--;
|
||||
if (!adjust_eh_frame_pointer(&cursor, &length, encoding, eh_frame, origAddr, elf))
|
||||
goto malformed;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
goto malformed;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// This is a Frame Description Entry
|
||||
// Starting address
|
||||
if (!adjust_eh_frame_pointer(&cursor, &length, FDEencoding, eh_frame, origAddr, elf))
|
||||
goto malformed;
|
||||
|
||||
if (LSDAencoding != DW_EH_PE_omit) {
|
||||
// Skip number of bytes, same size as the starting address.
|
||||
if (!skip_eh_frame_pointer(&cursor, &length, FDEencoding)) goto malformed;
|
||||
if (hasZ) {
|
||||
if (!skip_LEB128(&cursor, &length)) goto malformed;
|
||||
}
|
||||
// pointer to the LSDA.
|
||||
if (!adjust_eh_frame_pointer(&cursor, &length, LSDAencoding, eh_frame, origAddr, elf))
|
||||
goto malformed;
|
||||
}
|
||||
}
|
||||
|
||||
data += entryLength.value; size -= entryLength.value;
|
||||
}
|
||||
return;
|
||||
|
||||
malformed:
|
||||
throw std::runtime_error("malformed .eh_frame");
|
||||
}
|
||||
|
||||
template <typename Rel_Type>
|
||||
int do_relocation_section(Elf *elf, unsigned int rel_type, unsigned int rel_type2, bool force, bool fill)
|
||||
{
|
||||
@ -575,7 +818,8 @@ int do_relocation_section(Elf *elf, unsigned int rel_type, unsigned int rel_type
|
||||
std::vector<Rel_Type> new_rels;
|
||||
Elf_RelHack relhack_entry;
|
||||
relhack_entry.r_offset = relhack_entry.r_info = 0;
|
||||
size_t init_array_reloc = 0;
|
||||
std::vector<Rel_Type> init_array_relocs;
|
||||
size_t init_array_insert = 0;
|
||||
for (typename std::vector<Rel_Type>::iterator i = section->rels.begin();
|
||||
i != section->rels.end(); ++i) {
|
||||
// We don't need to keep R_*_NONE relocations
|
||||
@ -612,14 +856,11 @@ int do_relocation_section(Elf *elf, unsigned int rel_type, unsigned int rel_type
|
||||
}
|
||||
}
|
||||
}
|
||||
// Keep track of the relocation associated with the first init_array entry.
|
||||
if (init_array && i->r_offset == init_array->getAddr()) {
|
||||
if (init_array_reloc) {
|
||||
fprintf(stderr, "Found multiple relocations for the first init_array entry. Skipping\n");
|
||||
return -1;
|
||||
}
|
||||
new_rels.push_back(*i);
|
||||
init_array_reloc = new_rels.size();
|
||||
// Keep track of the relocations associated with the init_array section.
|
||||
if (init_array && i->r_offset >= init_array->getAddr() &&
|
||||
i->r_offset < init_array->getAddr() + init_array->getSize()) {
|
||||
init_array_relocs.push_back(*i);
|
||||
init_array_insert = new_rels.size();
|
||||
} else if (!(loc.getSection()->getFlags() & SHF_WRITE) || (ELF32_R_TYPE(i->r_info) != rel_type)) {
|
||||
// Don't pack relocations happening in non writable sections.
|
||||
// Our injected code is likely not to be allowed to write there.
|
||||
@ -656,49 +897,75 @@ int do_relocation_section(Elf *elf, unsigned int rel_type, unsigned int rel_type
|
||||
relhack_entry.r_offset = relhack_entry.r_info = 0;
|
||||
relhack->push_back(relhack_entry);
|
||||
|
||||
if (init_array && !init_array_reloc) {
|
||||
if (init_array) {
|
||||
// Some linkers create a DT_INIT_ARRAY section that, for all purposes,
|
||||
// is empty: it only contains 0x0 or 0xffffffff pointers with no relocations.
|
||||
// In some other cases, there can be null pointers with no relocations in
|
||||
// the middle of the section. Example: crtend_so.o in the Android NDK contains
|
||||
// a sized .init_array with a null pointer and no relocation, which ends up
|
||||
// in all Android libraries, and in some cases it ends up in the middle of
|
||||
// the final .init_array section.
|
||||
// If we have such a reusable slot at the beginning of .init_array, we just
|
||||
// use it. It we have one in the middle of .init_array, we slide its content
|
||||
// to move the "hole" at the beginning and use it there (we need our injected
|
||||
// code to run before any other).
|
||||
// Otherwise, replace the first entry and keep the original pointer.
|
||||
std::sort(init_array_relocs.begin(), init_array_relocs.end(),
|
||||
[](Rel_Type& a, Rel_Type& b) { return a.r_offset < b.r_offset; });
|
||||
size_t expected = init_array->getAddr();
|
||||
const size_t zero = 0;
|
||||
const size_t all = SIZE_MAX;
|
||||
const char *data = init_array->getData();
|
||||
size_t length = Elf_Addr::size(elf->getClass());
|
||||
bool empty = true;
|
||||
for (size_t off = 0; off < init_array->getSize(); off += length) {
|
||||
if (memcmp(data + off, &zero, length) &&
|
||||
memcmp(data + off, &all, length)) {
|
||||
empty = false;
|
||||
size_t off = 0;
|
||||
for (; off < init_array_relocs.size(); off++) {
|
||||
auto& r = init_array_relocs[off];
|
||||
if (r.r_offset >= expected + length &&
|
||||
(memcmp(data + off * length, &zero, length) == 0 ||
|
||||
memcmp(data + off * length, &all, length) == 0)) {
|
||||
// We found a hole, move the preceding entries.
|
||||
while (off) {
|
||||
auto& p = init_array_relocs[--off];
|
||||
if (ELF32_R_TYPE(p.r_info) == rel_type) {
|
||||
unsigned int addend = get_addend(&p, elf);
|
||||
p.r_offset += length;
|
||||
set_relative_reloc(&p, elf, addend);
|
||||
} else {
|
||||
fprintf(stderr, "Unsupported relocation type in DT_INIT_ARRAY. Skipping\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
expected = r.r_offset + length;
|
||||
}
|
||||
// If we encounter such an empty DT_INIT_ARRAY section, we add a
|
||||
// relocation for its first entry to point to our init. Code further
|
||||
// below will take care of actually setting the right r_info and
|
||||
// r_addend for the relocation, as if we had a normal DT_INIT_ARRAY
|
||||
// section.
|
||||
if (empty) {
|
||||
new_rels.emplace_back();
|
||||
init_array_reloc = new_rels.size();
|
||||
Rel_Type *rel = &new_rels[init_array_reloc - 1];
|
||||
rel->r_offset = init_array->getAddr();
|
||||
|
||||
if (off == 0) {
|
||||
// We either found a hole above, and can now use the first entry,
|
||||
// or the init_array section is effectively empty (see further above)
|
||||
// and we also can use the first entry.
|
||||
// Either way, code further below will take care of actually setting
|
||||
// the right r_info and r_added for the relocation.
|
||||
Rel_Type rel;
|
||||
rel.r_offset = init_array->getAddr();
|
||||
init_array_relocs.insert(init_array_relocs.begin(), rel);
|
||||
} else {
|
||||
fprintf(stderr, "Didn't find relocation for DT_INIT_ARRAY's first entry. Skipping\n");
|
||||
return -1;
|
||||
}
|
||||
} else if (init_array) {
|
||||
Rel_Type *rel = &new_rels[init_array_reloc - 1];
|
||||
unsigned int addend = get_addend(rel, elf);
|
||||
// Use relocated value of DT_INIT_ARRAY's first entry for the
|
||||
// function to be called by the injected code.
|
||||
if (ELF32_R_TYPE(rel->r_info) == rel_type) {
|
||||
original_init = addend;
|
||||
} else if (ELF32_R_TYPE(rel->r_info) == rel_type2) {
|
||||
ElfSymtab_Section *symtab = (ElfSymtab_Section *)section->getLink();
|
||||
original_init = symtab->syms[ELF32_R_SYM(rel->r_info)].value.getValue() + addend;
|
||||
} else {
|
||||
fprintf(stderr, "Unsupported relocation type for DT_INIT_ARRAY's first entry. Skipping\n");
|
||||
return -1;
|
||||
// Use relocated value of DT_INIT_ARRAY's first entry for the
|
||||
// function to be called by the injected code.
|
||||
auto& rel = init_array_relocs[0];
|
||||
unsigned int addend = get_addend(&rel, elf);
|
||||
if (ELF32_R_TYPE(rel.r_info) == rel_type) {
|
||||
original_init = addend;
|
||||
} else if (ELF32_R_TYPE(rel.r_info) == rel_type2) {
|
||||
ElfSymtab_Section *symtab = (ElfSymtab_Section *)section->getLink();
|
||||
original_init = symtab->syms[ELF32_R_SYM(rel.r_info)].value.getValue() + addend;
|
||||
} else {
|
||||
fprintf(stderr, "Unsupported relocation type for DT_INIT_ARRAY's first entry. Skipping\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
new_rels.insert(std::next(new_rels.begin(), init_array_insert), init_array_relocs.begin(), init_array_relocs.end());
|
||||
}
|
||||
|
||||
unsigned int mprotect_cb = 0;
|
||||
@ -779,6 +1046,8 @@ int do_relocation_section(Elf *elf, unsigned int rel_type, unsigned int rel_type
|
||||
}
|
||||
}
|
||||
|
||||
size_t old_size = section->getSize();
|
||||
|
||||
section->rels.assign(new_rels.begin(), new_rels.end());
|
||||
section->shrink(new_rels.size() * section->getEntSize());
|
||||
|
||||
@ -800,20 +1069,48 @@ int do_relocation_section(Elf *elf, unsigned int rel_type, unsigned int rel_type
|
||||
return -1;
|
||||
}
|
||||
|
||||
unsigned int old_exec = first_executable->getOffset();
|
||||
|
||||
relhack->insertBefore(section);
|
||||
relhackcode->insertBefore(first_executable);
|
||||
|
||||
// Trying to get first_executable->getOffset() now may throw if the new
|
||||
// layout would require it to move, so we look at the end of the relhack
|
||||
// code section instead, comparing it to where the first executable
|
||||
// section used to start.
|
||||
if (relhackcode->getOffset() + relhackcode->getSize() >= old_exec) {
|
||||
// Don't try further if we can't gain from the relocation section size change.
|
||||
size_t align = first_executable->getSegmentByType(PT_LOAD)->getAlign();
|
||||
size_t new_size = relhack->getSize() + relhackcode->getSize();
|
||||
if (!force && (new_size >= old_size || old_size - new_size < align)) {
|
||||
fprintf(stderr, "No gain. Skipping\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// .eh_frame/.eh_frame_hdr may be between the relocation sections and the
|
||||
// executable sections. When that happens, we may end up creating a separate
|
||||
// PT_LOAD for just both of them because they are not considered relocatable.
|
||||
// But they are, in fact, kind of relocatable, albeit with some manual work.
|
||||
// Which we'll do here.
|
||||
ElfSegment* eh_frame_segment = elf->getSegmentByType(PT_GNU_EH_FRAME);
|
||||
ElfSection* eh_frame_hdr = eh_frame_segment ? eh_frame_segment->getFirstSection() : nullptr;
|
||||
// The .eh_frame section usually follows the eh_frame_hdr section.
|
||||
ElfSection* eh_frame = eh_frame_hdr ? eh_frame_hdr->getNext() : nullptr;
|
||||
if (eh_frame_hdr && !eh_frame) {
|
||||
throw std::runtime_error("Expected to find an .eh_frame section after .eh_frame_hdr");
|
||||
}
|
||||
if (eh_frame && strcmp(eh_frame->getName(), ".eh_frame") == 0) {
|
||||
// The distance between both sections needs to be preserved because eh_frame_hdr
|
||||
// contains relative offsets to eh_frame. Well, they could be relocated too, but
|
||||
// it's not worth the effort for the few number of bytes this would save.
|
||||
size_t distance = eh_frame->getAddr() - eh_frame_hdr->getAddr();
|
||||
ElfSection* previous = eh_frame_hdr->getPrevious();
|
||||
eh_frame_hdr->getShdr().sh_addr =
|
||||
(previous->getAddr() + previous->getSize() + eh_frame_hdr->getAddrAlign() - 1)
|
||||
& ~(eh_frame_hdr->getAddrAlign() - 1);
|
||||
unsigned int origAddr = eh_frame->getAddr();
|
||||
eh_frame->getShdr().sh_addr =
|
||||
(eh_frame_hdr->getAddr() + eh_frame_hdr->getSize() + eh_frame->getAddrAlign() - 1)
|
||||
& ~(eh_frame->getAddrAlign() - 1);
|
||||
// Re-adjust the eh_frame_hdr address to keep the original distance.
|
||||
eh_frame_hdr->getShdr().sh_addr = eh_frame->getAddr() - distance;
|
||||
eh_frame_hdr->markDirty();
|
||||
adjust_eh_frame(eh_frame, origAddr, elf);
|
||||
}
|
||||
|
||||
// Adjust PT_LOAD segments
|
||||
for (ElfSegment *segment = elf->getSegmentByType(PT_LOAD); segment;
|
||||
segment = elf->getSegmentByType(PT_LOAD, segment)) {
|
||||
@ -827,9 +1124,9 @@ int do_relocation_section(Elf *elf, unsigned int rel_type, unsigned int rel_type
|
||||
// Adjust the first DT_INIT_ARRAY entry to point at the injected code
|
||||
// by transforming its relocation into a relative one pointing to the
|
||||
// address of the injected code.
|
||||
Rel_Type *rel = §ion->rels[init_array_reloc - 1];
|
||||
Rel_Type *rel = §ion->rels[init_array_insert];
|
||||
rel->r_info = ELF32_R_INFO(0, rel_type); // Set as a relative relocation
|
||||
set_relative_reloc(§ion->rels[init_array_reloc - 1], elf, init->getValue());
|
||||
set_relative_reloc(rel, elf, init->getValue());
|
||||
} else if (!dyn->setValueForType(DT_INIT, init)) {
|
||||
fprintf(stderr, "Can't grow .dynamic section to set DT_INIT. Skipping\n");
|
||||
return -1;
|
||||
|
@ -49,6 +49,21 @@ class Elf;
|
||||
class ElfDynamic_Section;
|
||||
class ElfStrtab_Section;
|
||||
|
||||
template <typename X>
|
||||
class FixedSizeData {
|
||||
public:
|
||||
struct Wrapper {
|
||||
X value;
|
||||
};
|
||||
typedef Wrapper Type32;
|
||||
typedef Wrapper Type64;
|
||||
|
||||
template <class endian, typename R, typename T>
|
||||
static void swap(T &t, R &r) {
|
||||
r.value = endian::swap(t.value);
|
||||
}
|
||||
};
|
||||
|
||||
class Elf_Ehdr_Traits {
|
||||
public:
|
||||
typedef Elf32_Ehdr Type32;
|
||||
@ -383,7 +398,7 @@ public:
|
||||
insertInSegments(section->segments);
|
||||
}
|
||||
|
||||
void insertBefore(ElfSection *section, bool dirty = true) {
|
||||
virtual void insertBefore(ElfSection *section, bool dirty = true) {
|
||||
if (previous != nullptr)
|
||||
previous->next = next;
|
||||
if (next != nullptr)
|
||||
|
@ -16,6 +16,7 @@ if not CONFIG['CROSS_COMPILE']:
|
||||
|
||||
for f in CONFIG['OS_CFLAGS']:
|
||||
if f.startswith('-flto'):
|
||||
SOURCES['dummy.c'].flags += ['-fno-lto']
|
||||
SOURCES['test-array.c'].flags += ['-fno-lto']
|
||||
SOURCES['test-ctors.c'].flags += ['-fno-lto']
|
||||
|
||||
|
@ -16,9 +16,6 @@ if [ -e "$topsrcdir/gcc/bin/ld" ]; then
|
||||
export CXX="$CXX -B $topsrcdir/gcc/bin"
|
||||
fi
|
||||
|
||||
# Until Bug 1423822 is resolved
|
||||
ac_add_options --disable-elf-hack
|
||||
|
||||
ac_add_options --enable-lto
|
||||
|
||||
. "$topsrcdir/build/unix/mozconfig.stdcxx"
|
||||
|
@ -270,6 +270,7 @@ subsuite = clipboard
|
||||
[browser_webconsole_context_menu_object_in_sidebar.js]
|
||||
[browser_webconsole_context_menu_open_url.js]
|
||||
[browser_webconsole_context_menu_store_as_global.js]
|
||||
[browser_webconsole_cors_errors.js]
|
||||
[browser_webconsole_csp_ignore_reflected_xss_message.js]
|
||||
[browser_webconsole_csp_violation.js]
|
||||
[browser_webconsole_cspro.js]
|
||||
|
@ -0,0 +1,180 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Ensure that the different CORS error are logged to the console with the appropriate
|
||||
// "Learn more" link.
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_URI = "http://example.com/browser/devtools/client/webconsole/test/mochitest/test-network-request.html";
|
||||
const BASE_CORS_ERROR_URL = "https://developer.mozilla.org/docs/Web/HTTP/CORS/Errors/";
|
||||
const BASE_CORS_ERROR_URL_PARAMS = new URLSearchParams({
|
||||
utm_source: "devtools",
|
||||
utm_medium: "firefox-cors-errors",
|
||||
utm_campaign: "default",
|
||||
});
|
||||
|
||||
add_task(async function() {
|
||||
await pushPref("devtools.webconsole.filter.netxhr", true);
|
||||
|
||||
const hud = await openNewTabAndConsole(TEST_URI);
|
||||
|
||||
let onCorsMessage;
|
||||
let message;
|
||||
|
||||
info(`Setting "content.cors.disable" to true to test CORSDisabled message`);
|
||||
await pushPref("content.cors.disable", true);
|
||||
onCorsMessage = waitForMessage(hud, "Reason: CORS disabled");
|
||||
makeFaultyCorsCall("CORSDisabled");
|
||||
message = await onCorsMessage;
|
||||
await checkCorsMessage(message, "CORSDisabled");
|
||||
await pushPref("content.cors.disable", false);
|
||||
|
||||
info("Test CORSPreflightDidNotSucceed");
|
||||
onCorsMessage = waitForMessage(hud, `CORS preflight channel did not succeed`);
|
||||
makeFaultyCorsCall("CORSPreflightDidNotSucceed");
|
||||
message = await onCorsMessage;
|
||||
await checkCorsMessage(message, "CORSPreflightDidNotSucceed");
|
||||
|
||||
info("Test CORS did not succeed");
|
||||
onCorsMessage = waitForMessage(hud, "Reason: CORS request did not succeed");
|
||||
makeFaultyCorsCall("CORSDidNotSucceed");
|
||||
message = await onCorsMessage;
|
||||
await checkCorsMessage(message, "CORSDidNotSucceed");
|
||||
|
||||
info("Test CORSExternalRedirectNotAllowed");
|
||||
onCorsMessage = waitForMessage(hud,
|
||||
"Reason: CORS request external redirect not allowed");
|
||||
makeFaultyCorsCall("CORSExternalRedirectNotAllowed");
|
||||
message = await onCorsMessage;
|
||||
await checkCorsMessage(message, "CORSExternalRedirectNotAllowed");
|
||||
|
||||
info("Test CORSMissingAllowOrigin");
|
||||
onCorsMessage = waitForMessage(hud,
|
||||
`Reason: CORS header ${quote("Access-Control-Allow-Origin")} missing`);
|
||||
makeFaultyCorsCall("CORSMissingAllowOrigin");
|
||||
message = await onCorsMessage;
|
||||
await checkCorsMessage(message, "CORSMissingAllowOrigin");
|
||||
|
||||
info("Test CORSMultipleAllowOriginNotAllowed");
|
||||
onCorsMessage = waitForMessage(hud,
|
||||
`Reason: Multiple CORS header ${quote("Access-Control-Allow-Origin")} not allowed`);
|
||||
makeFaultyCorsCall("CORSMultipleAllowOriginNotAllowed");
|
||||
message = await onCorsMessage;
|
||||
await checkCorsMessage(message, "CORSMultipleAllowOriginNotAllowed");
|
||||
|
||||
info("Test CORSAllowOriginNotMatchingOrigin");
|
||||
onCorsMessage = waitForMessage(hud, `Reason: CORS header ` +
|
||||
`${quote("Access-Control-Allow-Origin")} does not match ${quote("mochi.test")}`);
|
||||
makeFaultyCorsCall("CORSAllowOriginNotMatchingOrigin");
|
||||
message = await onCorsMessage;
|
||||
await checkCorsMessage(message, "CORSAllowOriginNotMatchingOrigin");
|
||||
|
||||
info("Test CORSNotSupportingCredentials");
|
||||
onCorsMessage = waitForMessage(hud, `Reason: Credential is not supported if the CORS ` +
|
||||
`header ${quote("Access-Control-Allow-Origin")} is ${quote("*")}`);
|
||||
makeFaultyCorsCall("CORSNotSupportingCredentials");
|
||||
message = await onCorsMessage;
|
||||
await checkCorsMessage(message, "CORSNotSupportingCredentials");
|
||||
|
||||
info("Test CORSMethodNotFound");
|
||||
onCorsMessage = waitForMessage(hud, `Reason: Did not find method in CORS header ` +
|
||||
`${quote("Access-Control-Allow-Methods")}`);
|
||||
makeFaultyCorsCall("CORSMethodNotFound");
|
||||
message = await onCorsMessage;
|
||||
await checkCorsMessage(message, "CORSMethodNotFound");
|
||||
|
||||
info("Test CORSMissingAllowCredentials");
|
||||
onCorsMessage = waitForMessage(hud, `Reason: expected ${quote("true")} in CORS ` +
|
||||
`header ${quote("Access-Control-Allow-Credentials")}`);
|
||||
makeFaultyCorsCall("CORSMissingAllowCredentials");
|
||||
message = await onCorsMessage;
|
||||
await checkCorsMessage(message, "CORSMissingAllowCredentials");
|
||||
|
||||
info("Test CORSInvalidAllowMethod");
|
||||
onCorsMessage = waitForMessage(hud, `Reason: invalid token ${quote("xyz;")} in CORS ` +
|
||||
`header ${quote("Access-Control-Allow-Methods")}`);
|
||||
makeFaultyCorsCall("CORSInvalidAllowMethod");
|
||||
message = await onCorsMessage;
|
||||
await checkCorsMessage(message, "CORSInvalidAllowMethod");
|
||||
|
||||
info("Test CORSInvalidAllowHeader");
|
||||
onCorsMessage = waitForMessage(hud, `Reason: invalid token ${quote("xyz;")} in CORS ` +
|
||||
`header ${quote("Access-Control-Allow-Headers")}`);
|
||||
makeFaultyCorsCall("CORSInvalidAllowHeader");
|
||||
message = await onCorsMessage;
|
||||
await checkCorsMessage(message, "CORSInvalidAllowHeader");
|
||||
|
||||
info("Test CORSMissingAllowHeaderFromPreflight");
|
||||
onCorsMessage = waitForMessage(hud, `Reason: missing token ${quote("xyz")} in CORS ` +
|
||||
`header ${quote("Access-Control-Allow-Headers")} from CORS preflight channel`);
|
||||
makeFaultyCorsCall("CORSMissingAllowHeaderFromPreflight");
|
||||
message = await onCorsMessage;
|
||||
await checkCorsMessage(message, "CORSMissingAllowHeaderFromPreflight");
|
||||
|
||||
// See Bug 1480671.
|
||||
// XXX: how to make Origin to not be included in the request ?
|
||||
// onCorsMessage = waitForMessage(hud,
|
||||
// `Reason: CORS header ${quote("Origin")} cannot be added`);
|
||||
// makeFaultyCorsCall("CORSOriginHeaderNotAdded");
|
||||
// message = await onCorsMessage;
|
||||
// await checkCorsMessage(message, "CORSOriginHeaderNotAdded");
|
||||
|
||||
// See Bug 1480672.
|
||||
// XXX: Failing with another error: Console message: Security Error: Content at
|
||||
// http://example.com/browser/devtools/client/webconsole/test/mochitest/test-network-request.html
|
||||
// may not load or link to file:///Users/nchevobbe/Projects/mozilla-central/devtools/client/webconsole/test/mochitest/sjs_cors-test-server.sjs.
|
||||
// info("Test CORSRequestNotHttp");
|
||||
// onCorsMessage = waitForMessage(hud, "Reason: CORS request not http");
|
||||
// const dir = getChromeDir(getResolvedURI(gTestPath));
|
||||
// dir.append("sjs_cors-test-server.sjs");
|
||||
// makeFaultyCorsCall("CORSRequestNotHttp", Services.io.newFileURI(dir).spec);
|
||||
// message = await onCorsMessage;
|
||||
// await checkCorsMessage(message, "CORSRequestNotHttp");
|
||||
});
|
||||
|
||||
async function checkCorsMessage(message, category) {
|
||||
const node = message.node;
|
||||
ok(node.classList.contains("warn"), "The cors message has the expected classname");
|
||||
const learnMoreLink = node.querySelector(".learn-more-link");
|
||||
ok(learnMoreLink, "There is a Learn more link displayed");
|
||||
const linkSimulation = await simulateLinkClick(learnMoreLink);
|
||||
is(linkSimulation.link, getCategoryUrl(category),
|
||||
"Click on the link opens the expected page");
|
||||
}
|
||||
|
||||
function makeFaultyCorsCall(errorCategory, corsUrl) {
|
||||
ContentTask.spawn(gBrowser.selectedBrowser, [errorCategory, corsUrl],
|
||||
([category, url]) => {
|
||||
if (!url) {
|
||||
const baseUrl =
|
||||
"http://mochi.test:8888/browser/devtools/client/webconsole/test/mochitest";
|
||||
url = `${baseUrl}/sjs_cors-test-server.sjs?corsErrorCategory=${category}`;
|
||||
}
|
||||
|
||||
// Preflight request are not made for GET requests, so let's do a PUT.
|
||||
const method = "PUT";
|
||||
const options = { method };
|
||||
if (category === "CORSNotSupportingCredentials"
|
||||
|| category === "CORSMissingAllowCredentials"
|
||||
) {
|
||||
options.credentials = "include";
|
||||
}
|
||||
|
||||
if (category === "CORSMissingAllowHeaderFromPreflight") {
|
||||
options.headers = new content.Headers({"xyz": true});
|
||||
}
|
||||
|
||||
content.fetch(url, options);
|
||||
});
|
||||
}
|
||||
|
||||
function quote(str) {
|
||||
const openingQuote = String.fromCharCode(8216);
|
||||
const closingQuote = String.fromCharCode(8217);
|
||||
return `${openingQuote}${str}${closingQuote}`;
|
||||
}
|
||||
|
||||
function getCategoryUrl(category) {
|
||||
return `${BASE_CORS_ERROR_URL}${category}?${BASE_CORS_ERROR_URL_PARAMS}`;
|
||||
}
|
@ -1,17 +1,150 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
function handleRequest(request, response) {
|
||||
response.setStatusLine(request.httpVersion, 200, "Och Aye");
|
||||
"use strict";
|
||||
|
||||
function handleRequest(request, response) {
|
||||
const params = new Map(
|
||||
request.queryString
|
||||
.replace("?", "")
|
||||
.split("&")
|
||||
.map(s => s.split("="))
|
||||
);
|
||||
|
||||
if (!params.has("corsErrorCategory")) {
|
||||
response.setStatusLine(request.httpVersion, 200, "Och Aye");
|
||||
setCacheHeaders(response);
|
||||
response.setHeader("Access-Control-Allow-Origin", "*", false);
|
||||
response.setHeader("Access-Control-Allow-Headers", "content-type", false);
|
||||
response.setHeader("Content-Type", "text/plain; charset=utf-8", false);
|
||||
response.write("Access-Control-Allow-Origin: *");
|
||||
return;
|
||||
}
|
||||
|
||||
const category = params.get("corsErrorCategory");
|
||||
switch (category) {
|
||||
case "CORSDidNotSucceed":
|
||||
corsDidNotSucceed(request, response);
|
||||
break;
|
||||
case "CORSExternalRedirectNotAllowed":
|
||||
corsExternalRedirectNotAllowed(request, response);
|
||||
break;
|
||||
case "CORSMissingAllowOrigin":
|
||||
corsMissingAllowOrigin(request, response);
|
||||
break;
|
||||
case "CORSMultipleAllowOriginNotAllowed":
|
||||
corsMultipleOriginNotAllowed(request, response);
|
||||
break;
|
||||
case "CORSAllowOriginNotMatchingOrigin":
|
||||
corsAllowOriginNotMatchingOrigin(request, response);
|
||||
break;
|
||||
case "CORSNotSupportingCredentials":
|
||||
corsNotSupportingCredentials(request, response);
|
||||
break;
|
||||
case "CORSMethodNotFound":
|
||||
corsMethodNotFound(request, response);
|
||||
break;
|
||||
case "CORSMissingAllowCredentials":
|
||||
corsMissingAllowCredentials(request, response);
|
||||
break;
|
||||
case "CORSPreflightDidNotSucceed":
|
||||
corsPreflightDidNotSucceed(request, response);
|
||||
break;
|
||||
case "CORSInvalidAllowMethod":
|
||||
corsInvalidAllowMethod(request, response);
|
||||
break;
|
||||
case "CORSInvalidAllowHeader":
|
||||
corsInvalidAllowHeader(request, response);
|
||||
break;
|
||||
case "CORSMissingAllowHeaderFromPreflight":
|
||||
corsMissingAllowHeaderFromPreflight(request, response);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function corsDidNotSucceed(request, response) {
|
||||
setCacheHeaders(response);
|
||||
response.setStatusLine(request.httpVersion, 301, "Moved Permanently");
|
||||
response.setHeader("Location", "http://example.com");
|
||||
}
|
||||
|
||||
function corsExternalRedirectNotAllowed(request, response) {
|
||||
response.setStatusLine(request.httpVersion, 301, "Moved Permanently");
|
||||
response.setHeader("Access-Control-Allow-Origin", "*", false);
|
||||
response.setHeader("Access-Control-Allow-Headers", "content-type", false);
|
||||
response.setHeader("Location", "http://redirect.test/");
|
||||
}
|
||||
|
||||
function corsMissingAllowOrigin(request, response) {
|
||||
setCacheHeaders(response);
|
||||
response.setStatusLine(request.httpVersion, 200, "corsMissingAllowOrigin");
|
||||
}
|
||||
|
||||
function corsMultipleOriginNotAllowed(request, response) {
|
||||
// We can't set the same header twice with response.setHeader, so we need to seizePower
|
||||
// and write the response manually.
|
||||
response.seizePower();
|
||||
response.write("HTTP/1.0 200 OK\r\n");
|
||||
response.write("Content-Type: text/plain\r\n");
|
||||
response.write("Access-Control-Allow-Origin: *\r\n");
|
||||
response.write("Access-Control-Allow-Origin: mochi.test\r\n");
|
||||
response.write("\r\n");
|
||||
response.finish();
|
||||
setCacheHeaders(response);
|
||||
}
|
||||
|
||||
function corsAllowOriginNotMatchingOrigin(request, response) {
|
||||
response.setStatusLine(request.httpVersion, 200, "corsAllowOriginNotMatchingOrigin");
|
||||
response.setHeader("Access-Control-Allow-Origin", "mochi.test");
|
||||
}
|
||||
|
||||
function corsNotSupportingCredentials(request, response) {
|
||||
response.setStatusLine(request.httpVersion, 200, "corsNotSupportingCredentials");
|
||||
response.setHeader("Access-Control-Allow-Origin", "*");
|
||||
}
|
||||
|
||||
function corsMethodNotFound(request, response) {
|
||||
response.setStatusLine(request.httpVersion, 200, "corsMethodNotFound");
|
||||
response.setHeader("Access-Control-Allow-Origin", "*");
|
||||
// Will make the request fail since it is a "PUT".
|
||||
response.setHeader("Access-Control-Allow-Methods", "POST");
|
||||
}
|
||||
|
||||
function corsMissingAllowCredentials(request, response) {
|
||||
response.setStatusLine(request.httpVersion, 200, "corsMissingAllowCredentials");
|
||||
// Need to set an explicit origin (i.e. not "*") to make the request fail.
|
||||
response.setHeader("Access-Control-Allow-Origin", "http://example.com");
|
||||
}
|
||||
|
||||
function corsPreflightDidNotSucceed(request, response) {
|
||||
const isPreflight = request.method == "OPTIONS";
|
||||
if (isPreflight) {
|
||||
response.setStatusLine(request.httpVersion, 500, "Preflight fail");
|
||||
response.setHeader("Access-Control-Allow-Origin", "*");
|
||||
}
|
||||
}
|
||||
|
||||
function corsInvalidAllowMethod(request, response) {
|
||||
response.setStatusLine(request.httpVersion, 200, "corsInvalidAllowMethod");
|
||||
response.setHeader("Access-Control-Allow-Origin", "*");
|
||||
response.setHeader("Access-Control-Allow-Methods", "xyz;");
|
||||
}
|
||||
|
||||
function corsInvalidAllowHeader(request, response) {
|
||||
response.setStatusLine(request.httpVersion, 200, "corsInvalidAllowHeader");
|
||||
response.setHeader("Access-Control-Allow-Origin", "*");
|
||||
response.setHeader("Access-Control-Allow-Methods", "PUT");
|
||||
response.setHeader("Access-Control-Allow-Headers", "xyz;");
|
||||
}
|
||||
|
||||
function corsMissingAllowHeaderFromPreflight(request, response) {
|
||||
response.setStatusLine(request.httpVersion, 200, "corsMissingAllowHeaderFromPreflight");
|
||||
response.setHeader("Access-Control-Allow-Origin", "*");
|
||||
response.setHeader("Access-Control-Allow-Methods", "PUT");
|
||||
}
|
||||
|
||||
function setCacheHeaders(response) {
|
||||
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
response.setHeader("Pragma", "no-cache");
|
||||
response.setHeader("Expires", "0");
|
||||
|
||||
response.setHeader("Access-Control-Allow-Origin", "*", false);
|
||||
response.setHeader("Access-Control-Allow-Headers", "content-type", false);
|
||||
|
||||
response.setHeader("Content-Type", "text/plain; charset=utf-8", false);
|
||||
|
||||
response.write("Access-Control-Allow-Origin: *");
|
||||
}
|
||||
|
@ -9,9 +9,10 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const baseURL = "https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/";
|
||||
const baseErrorURL = "https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/";
|
||||
const params =
|
||||
"?utm_source=mozilla&utm_medium=firefox-console-errors&utm_campaign=default";
|
||||
|
||||
const ErrorDocs = {
|
||||
JSMSG_READ_ONLY: "Read-only",
|
||||
JSMSG_BAD_ARRAY_LENGTH: "Invalid_array_length",
|
||||
@ -108,6 +109,27 @@ const ErrorCategories = {
|
||||
"source map": SOURCE_MAP_LEARN_MORE,
|
||||
};
|
||||
|
||||
const baseCorsErrorUrl = "https://developer.mozilla.org/docs/Web/HTTP/CORS/Errors/";
|
||||
const corsParams =
|
||||
"?utm_source=devtools&utm_medium=firefox-cors-errors&utm_campaign=default";
|
||||
const CorsErrorDocs = {
|
||||
CORSDisabled: "CORSDisabled",
|
||||
CORSDidNotSucceed: "CORSDidNotSucceed",
|
||||
CORSOriginHeaderNotAdded: "CORSOriginHeaderNotAdded",
|
||||
CORSExternalRedirectNotAllowed: "CORSExternalRedirectNotAllowed",
|
||||
CORSRequestNotHttp: "CORSRequestNotHttp",
|
||||
CORSMissingAllowOrigin: "CORSMissingAllowOrigin",
|
||||
CORSMultipleAllowOriginNotAllowed: "CORSMultipleAllowOriginNotAllowed",
|
||||
CORSAllowOriginNotMatchingOrigin: "CORSAllowOriginNotMatchingOrigin",
|
||||
CORSNotSupportingCredentials: "CORSNotSupportingCredentials",
|
||||
CORSMethodNotFound: "CORSMethodNotFound",
|
||||
CORSMissingAllowCredentials: "CORSMissingAllowCredentials",
|
||||
CORSPreflightDidNotSucceed: "CORSPreflightDidNotSucceed",
|
||||
CORSInvalidAllowMethod: "CORSInvalidAllowMethod",
|
||||
CORSInvalidAllowHeader: "CORSInvalidAllowHeader",
|
||||
CORSMissingAllowHeaderFromPreflight: "CORSMissingAllowHeaderFromPreflight",
|
||||
};
|
||||
|
||||
exports.GetURL = (error) => {
|
||||
if (!error) {
|
||||
return undefined;
|
||||
@ -115,7 +137,12 @@ exports.GetURL = (error) => {
|
||||
|
||||
const doc = ErrorDocs[error.errorMessageName];
|
||||
if (doc) {
|
||||
return baseURL + doc + params;
|
||||
return baseErrorURL + doc + params;
|
||||
}
|
||||
|
||||
const corsDoc = CorsErrorDocs[error.category];
|
||||
if (corsDoc) {
|
||||
return baseCorsErrorUrl + corsDoc + corsParams;
|
||||
}
|
||||
|
||||
const categoryURL = ErrorCategories[error.category];
|
||||
|
@ -54,8 +54,7 @@ function createFakeAddonWindow({addonId} = {}) {
|
||||
const principal = Services.scriptSecurityManager
|
||||
.createCodebasePrincipal(baseURI, {});
|
||||
const chromeWebNav = Services.appShell.createWindowlessBrowser(true);
|
||||
const docShell = chromeWebNav.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDocShell);
|
||||
const { docShell } = chromeWebNav;
|
||||
docShell.createAboutBlankContentViewer(principal);
|
||||
const addonWindow = docShell.contentViewer.DOMDocument.defaultView;
|
||||
|
||||
|
@ -619,11 +619,6 @@ nsDocShell::GetInterface(const nsIID& aIID, void** aSink)
|
||||
*aSink = mFind;
|
||||
NS_ADDREF((nsISupports*)*aSink);
|
||||
return NS_OK;
|
||||
} else if (aIID.Equals(NS_GET_IID(nsIEditingSession))) {
|
||||
nsCOMPtr<nsIEditingSession> es;
|
||||
GetEditingSession(getter_AddRefs(es));
|
||||
es.forget(aSink);
|
||||
return *aSink ? NS_OK : NS_NOINTERFACE;
|
||||
} else if (aIID.Equals(NS_GET_IID(nsISelectionDisplay))) {
|
||||
nsIPresShell* shell = GetPresShell();
|
||||
if (shell) {
|
||||
|
@ -51,14 +51,12 @@ function test() {
|
||||
}
|
||||
}
|
||||
|
||||
var systemPrincipal = Cc["@mozilla.org/systemprincipal;1"].
|
||||
createInstance(Ci.nsIPrincipal);
|
||||
var webNav = Cc["@mozilla.org/appshell/appShellService;1"].
|
||||
getService(Ci.nsIAppShellService).
|
||||
createWindowlessBrowser(true);
|
||||
var docShell = webNav.
|
||||
QueryInterface(Ci.nsIInterfaceRequestor).
|
||||
getInterface(Ci.nsIDocShell);
|
||||
var systemPrincipal = Cc["@mozilla.org/systemprincipal;1"]
|
||||
.getService(Ci.nsIPrincipal);
|
||||
var webNav = Cc["@mozilla.org/appshell/appShellService;1"]
|
||||
.getService(Ci.nsIAppShellService)
|
||||
.createWindowlessBrowser(true);
|
||||
var docShell = webNav.docShell;
|
||||
docShell.createAboutBlankContentViewer(systemPrincipal);
|
||||
var win = docShell.contentViewer.DOMDocument.defaultView;
|
||||
|
||||
|
@ -33,7 +33,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=846906
|
||||
var interfaceRequestor = windowlessBrowser.QueryInterface(Ci.nsIInterfaceRequestor);
|
||||
ok(interfaceRequestor, "Should be able to query interface requestor interface");
|
||||
|
||||
var docShell = interfaceRequestor.getInterface(Ci.nsIDocShell);
|
||||
var docShell = windowlessBrowser.docShell;
|
||||
ok(docShell, "Should be able to get doc shell interface");
|
||||
|
||||
var document = webNavigation.document;
|
||||
|
@ -71,10 +71,9 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1342989
|
||||
|
||||
var webNav = Cc["@mozilla.org/appshell/appShellService;1"].
|
||||
getService(Ci.nsIAppShellService).createWindowlessBrowser(true);
|
||||
let docShell = webNav.QueryInterface(Ci.nsIInterfaceRequestor).
|
||||
getInterface(Ci.nsIDocShell);
|
||||
let docShell = webNav.docShell;
|
||||
docShell.createAboutBlankContentViewer(
|
||||
Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal));
|
||||
Cc["@mozilla.org/systemprincipal;1"].getService(Ci.nsIPrincipal));
|
||||
|
||||
progressListener.add(docShell, function(success) {
|
||||
webNav.close();
|
||||
|
@ -6,10 +6,9 @@ ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
add_task(async function() {
|
||||
let webNav = Services.appShell.createWindowlessBrowser(false);
|
||||
|
||||
let loadContext = webNav.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsILoadContext);
|
||||
let docShell = webNav.docShell;
|
||||
|
||||
let docShell = webNav.getInterface(Ci.nsIDocShell);
|
||||
let loadContext = docShell.QueryInterface(Ci.nsILoadContext);
|
||||
|
||||
equal(loadContext.usePrivateBrowsing, false, "Should start out in non-private mode");
|
||||
|
||||
|
@ -12,10 +12,10 @@
|
||||
#include "mozilla/Logging.h"
|
||||
#include "nsContentPermissionHelper.h"
|
||||
|
||||
extern mozilla::LazyLogModule gMediaElementLog;
|
||||
extern mozilla::LazyLogModule gAutoplayPermissionLog;
|
||||
|
||||
#define PLAY_REQUEST_LOG(msg, ...) \
|
||||
MOZ_LOG(gMediaElementLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
|
||||
MOZ_LOG(gAutoplayPermissionLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
|
@ -9,10 +9,10 @@
|
||||
|
||||
#include "mozilla/Logging.h"
|
||||
|
||||
extern mozilla::LazyLogModule gMediaElementLog;
|
||||
extern mozilla::LazyLogModule gAutoplayPermissionLog;
|
||||
|
||||
#define PLAY_REQUEST_LOG(msg, ...) \
|
||||
MOZ_LOG(gMediaElementLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
|
||||
MOZ_LOG(gAutoplayPermissionLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
|
@ -127,6 +127,10 @@
|
||||
mozilla::LazyLogModule gMediaElementLog("nsMediaElement");
|
||||
static mozilla::LazyLogModule gMediaElementEventsLog("nsMediaElementEvents");
|
||||
|
||||
extern mozilla::LazyLogModule gAutoplayPermissionLog;
|
||||
#define AUTOPLAY_LOG(msg, ...) \
|
||||
MOZ_LOG(gAutoplayPermissionLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
|
||||
|
||||
#define LOG(type, msg) MOZ_LOG(gMediaElementLog, type, msg)
|
||||
#define LOG_EVENT(type, msg) MOZ_LOG(gMediaElementEventsLog, type, msg)
|
||||
|
||||
@ -3068,6 +3072,7 @@ HTMLMediaElement::PauseIfShouldNotBePlaying()
|
||||
return;
|
||||
}
|
||||
if (AutoplayPolicy::IsAllowedToPlay(*this) != nsIAutoplay::ALLOWED) {
|
||||
AUTOPLAY_LOG("pause because not allowed to play, element=%p", this);
|
||||
ErrorResult rv;
|
||||
Pause(rv);
|
||||
OwnerDoc()->SetDocTreeHadPlayRevoked();
|
||||
@ -4103,7 +4108,7 @@ HTMLMediaElement::Play(ErrorResult& aRv)
|
||||
break;
|
||||
}
|
||||
case nsIAutoplay::BLOCKED: {
|
||||
LOG(LogLevel::Debug, ("%p play not blocked.", this));
|
||||
AUTOPLAY_LOG("%p play blocked.", this);
|
||||
promise->MaybeReject(NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR);
|
||||
if (StaticPrefs::MediaBlockEventEnabled()) {
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("blocked"));
|
||||
@ -4128,8 +4133,7 @@ HTMLMediaElement::EnsureAutoplayRequested(bool aHandlingUserInput)
|
||||
// Await for the previous request to be approved or denied. This
|
||||
// play request's promise will be fulfilled with all other pending
|
||||
// promises when the permission prompt is resolved.
|
||||
LOG(LogLevel::Debug,
|
||||
("%p EnsureAutoplayRequested() existing request, bailing.", this));
|
||||
AUTOPLAY_LOG("%p EnsureAutoplayRequested() existing request, bailing.", this);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -4146,19 +4150,17 @@ HTMLMediaElement::EnsureAutoplayRequested(bool aHandlingUserInput)
|
||||
[ self, handlingUserInput = aHandlingUserInput, request ](
|
||||
bool aApproved) {
|
||||
self->mAutoplayPermissionRequest.Complete();
|
||||
LOG(LogLevel::Debug,
|
||||
("%p Autoplay request approved request=%p",
|
||||
self.get(),
|
||||
request.get()));
|
||||
AUTOPLAY_LOG("%p Autoplay request approved request=%p",
|
||||
self.get(),
|
||||
request.get());
|
||||
self->PlayInternal(handlingUserInput);
|
||||
self->UpdateCustomPolicyAfterPlayed();
|
||||
},
|
||||
[self, request](nsresult aError) {
|
||||
self->mAutoplayPermissionRequest.Complete();
|
||||
LOG(LogLevel::Debug,
|
||||
("%p Autoplay request denied request=%p",
|
||||
self.get(),
|
||||
request.get()));
|
||||
AUTOPLAY_LOG("%p Autoplay request denied request=%p",
|
||||
self.get(),
|
||||
request.get());
|
||||
LOG(LogLevel::Debug, ("%s rejecting play promimses", __func__));
|
||||
self->AsyncRejectPendingPlayPromises(
|
||||
NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR);
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "AutoplayPolicy.h"
|
||||
|
||||
#include "mozilla/EventStateManager.h"
|
||||
#include "mozilla/Logging.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/dom/AudioContext.h"
|
||||
#include "mozilla/AutoplayPermissionManager.h"
|
||||
@ -20,6 +21,26 @@
|
||||
#include "nsIDocShellTreeItem.h"
|
||||
#include "nsPIDOMWindow.h"
|
||||
|
||||
mozilla::LazyLogModule gAutoplayPermissionLog("Autoplay");
|
||||
|
||||
#define AUTOPLAY_LOG(msg, ...) \
|
||||
MOZ_LOG(gAutoplayPermissionLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
|
||||
|
||||
static const char*
|
||||
AllowAutoplayToStr(const uint32_t state)
|
||||
{
|
||||
switch (state) {
|
||||
case nsIAutoplay::ALLOWED:
|
||||
return "allowed";
|
||||
case nsIAutoplay::BLOCKED:
|
||||
return "blocked";
|
||||
case nsIAutoplay::PROMPT:
|
||||
return "prompt";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
@ -62,17 +83,17 @@ IsWindowAllowedToPlay(nsPIDOMWindowInner* aWindow)
|
||||
nsIDocument* approver = ApproverDocOf(*aWindow->GetExtantDoc());
|
||||
if (nsContentUtils::IsExactSitePermAllow(approver->NodePrincipal(),
|
||||
"autoplay-media")) {
|
||||
// Autoplay permission has been granted already.
|
||||
AUTOPLAY_LOG("Allow autoplay as document has autoplay permission.");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (approver->HasBeenUserGestureActivated()) {
|
||||
// Document has been activated by user gesture.
|
||||
AUTOPLAY_LOG("Allow autoplay as document activated by user gesture.");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (approver->IsExtensionPage()) {
|
||||
// Always allow extension page to autoplay.
|
||||
AUTOPLAY_LOG("Allow autoplay as in extension document.");
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -108,10 +129,23 @@ DefaultAutoplayBehaviour()
|
||||
static bool
|
||||
IsMediaElementAllowedToPlay(const HTMLMediaElement& aElement)
|
||||
{
|
||||
return ((aElement.Volume() == 0.0 || aElement.Muted()) &&
|
||||
Preferences::GetBool("media.autoplay.allow-muted", true)) ||
|
||||
IsWindowAllowedToPlay(aElement.OwnerDoc()->GetInnerWindow()) ||
|
||||
(aElement.OwnerDoc()->MediaDocumentKind() == nsIDocument::MediaDocumentKind::Video);
|
||||
if ((aElement.Volume() == 0.0 || aElement.Muted()) &&
|
||||
Preferences::GetBool("media.autoplay.allow-muted", true)) {
|
||||
AUTOPLAY_LOG("Allow muted media %p to autoplay.", &aElement);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (IsWindowAllowedToPlay(aElement.OwnerDoc()->GetInnerWindow())) {
|
||||
AUTOPLAY_LOG("Autoplay allowed as activated/whitelisted window, media %p.", &aElement);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (aElement.OwnerDoc()->MediaDocumentKind() == nsIDocument::MediaDocumentKind::Video) {
|
||||
AUTOPLAY_LOG("Allow video document %p to autoplay\n", &aElement);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
@ -134,11 +168,12 @@ AutoplayPolicy::IsAllowedToPlay(const HTMLMediaElement& aElement)
|
||||
? nsIAutoplay::ALLOWED : nsIAutoplay::BLOCKED;
|
||||
}
|
||||
|
||||
if (IsMediaElementAllowedToPlay(aElement)) {
|
||||
return nsIAutoplay::ALLOWED;
|
||||
}
|
||||
const uint32_t result = IsMediaElementAllowedToPlay(aElement) ?
|
||||
nsIAutoplay::ALLOWED : autoplayDefault;
|
||||
|
||||
return autoplayDefault;
|
||||
AUTOPLAY_LOG("IsAllowedToPlay, mediaElement=%p, isAllowToPlay=%s",
|
||||
&aElement, AllowAutoplayToStr(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
|
@ -69,8 +69,7 @@ function test()
|
||||
.createCodebasePrincipal(baseURI, {});
|
||||
|
||||
let chromeWebNav = Services.appShell.createWindowlessBrowser(true);
|
||||
let interfaceRequestor = chromeWebNav.QueryInterface(Ci.nsIInterfaceRequestor);
|
||||
let docShell = interfaceRequestor.getInterface(Ci.nsIDocShell);
|
||||
let docShell = chromeWebNav.docShell;
|
||||
docShell.createAboutBlankContentViewer(principal);
|
||||
|
||||
info("fake webextension docShell created");
|
||||
|
@ -1710,6 +1710,7 @@ WebRenderBridgeParent::ClearResources()
|
||||
RefPtr<WebRenderImageHost> host = entry.second;
|
||||
host->ClearWrBridge();
|
||||
mAsyncImageManager->RemoveAsyncImagePipeline(pipelineId, txn);
|
||||
txn.RemovePipeline(pipelineId);
|
||||
}
|
||||
mAsyncCompositables.clear();
|
||||
for (const auto& entry : mSharedSurfaceIds) {
|
||||
|
@ -4,8 +4,7 @@ add_task(async function test_windowlessBrowserTroubleshootCrash() {
|
||||
let webNav = Services.appShell.createWindowlessBrowser(false);
|
||||
|
||||
let onLoaded = new Promise((resolve, reject) => {
|
||||
let docShell = webNav.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDocShell);
|
||||
let docShell = webNav.docShell;
|
||||
let listener = {
|
||||
observe(contentWindow, topic, data) {
|
||||
let observedDocShell = contentWindow.docShell
|
||||
|
@ -5,7 +5,7 @@
|
||||
#include "Hal.h"
|
||||
#include "HalLog.h"
|
||||
|
||||
#include <Windows.h>
|
||||
#include <windows.h>
|
||||
|
||||
using namespace mozilla::hal;
|
||||
|
||||
|
@ -18,8 +18,7 @@ add_task(async function() {
|
||||
|
||||
let webnav = Services.appShell.createWindowlessBrowser(false);
|
||||
|
||||
let docShell = webnav.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDocShell);
|
||||
let docShell = webnav.docShell;
|
||||
|
||||
docShell.createAboutBlankContentViewer(principal);
|
||||
|
||||
|
@ -14,8 +14,7 @@ function getWindowlessBrowser(url) {
|
||||
|
||||
let webnav = Services.appShell.createWindowlessBrowser(false);
|
||||
|
||||
let docShell = webnav.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDocShell);
|
||||
let docShell = webnav.docShell;
|
||||
|
||||
docShell.createAboutBlankContentViewer(principal);
|
||||
|
||||
|
@ -7,8 +7,7 @@ ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
add_task(async function() {
|
||||
let webnav = Services.appShell.createWindowlessBrowser(false);
|
||||
|
||||
let docShell = webnav.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDocShell);
|
||||
let docShell = webnav.docShell;
|
||||
|
||||
docShell.createAboutBlankContentViewer(null);
|
||||
|
||||
|
@ -34,6 +34,9 @@ ac_add_options --target=arm-linux-androideabi
|
||||
|
||||
ac_add_options --with-branding=mobile/android/branding/nightly
|
||||
|
||||
# Pull code coverage dependencies too.
|
||||
ac_add_options --enable-java-coverage
|
||||
|
||||
export MOZILLA_OFFICIAL=1
|
||||
export MOZ_TELEMETRY_REPORTING=1
|
||||
export MOZ_ANDROID_MMA=1
|
||||
|
29
mobile/android/config/mozconfigs/android-api-16/debug-ccov
Normal file
@ -0,0 +1,29 @@
|
||||
. "$topsrcdir/build/mozconfig.artifact.automation"
|
||||
|
||||
NO_CACHE=1
|
||||
NO_NDK=1
|
||||
|
||||
. "$topsrcdir/mobile/android/config/mozconfigs/common"
|
||||
|
||||
# Global options
|
||||
ac_add_options --enable-debug
|
||||
ac_add_options --enable-java-coverage
|
||||
|
||||
# Android
|
||||
# Warning: Before increasing the with-android-min-sdk value, please note several places in and out
|
||||
# of tree have to be changed. Otherwise, places like Treeherder or archive.mozilla.org will
|
||||
# advertise a bad API level. This may confuse people. As an example, please look at bug 1384482.
|
||||
# If you think you can't handle the whole set of changes, please reach out to the Release
|
||||
# Engineering team.
|
||||
ac_add_options --with-android-min-sdk=16
|
||||
ac_add_options --target=arm-linux-androideabi
|
||||
|
||||
. "$topsrcdir/mobile/android/config/mozconfigs/android-api-16/nightly"
|
||||
|
||||
. "$topsrcdir/build/mozconfig.artifact"
|
||||
|
||||
ac_add_options --enable-artifact-build-symbols
|
||||
|
||||
ac_add_options --with-branding=mobile/android/branding/nightly
|
||||
|
||||
. "$topsrcdir/mobile/android/config/mozconfigs/common.override"
|
@ -165,6 +165,11 @@
|
||||
# See: http://stackoverflow.com/questions/22441366/note-android-support-v4-text-icucompatics-cant-find-dynamically-referenced-cl
|
||||
-dontnote android.support.**
|
||||
|
||||
# Don't warn when classes referenced by JaCoCo are missing when running the build from android-dependencies.
|
||||
-dontwarn java.lang.instrument.**
|
||||
-dontwarn java.lang.management.**
|
||||
-dontwarn javax.management.**
|
||||
|
||||
-include "adjust-keeps.cfg"
|
||||
|
||||
-include "leakcanary-keeps.cfg"
|
||||
|
@ -21,6 +21,7 @@ Contents:
|
||||
shutdown
|
||||
push
|
||||
gradle
|
||||
testcoverage
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
117
mobile/android/docs/testcoverage.rst
Normal file
@ -0,0 +1,117 @@
|
||||
.. -*- Mode: rst; fill-column: 80; -*-
|
||||
|
||||
========================================================
|
||||
Collecting code coverage information for Android tests
|
||||
========================================================
|
||||
|
||||
The Android-specific test suites are outlined on MDN_. A more elaborate description can be found on
|
||||
the Wiki_. This page describes how collecting code coverage information is implemented for these
|
||||
test suites.
|
||||
|
||||
.. _MDN: https://developer.mozilla.org/en-US/docs/Mozilla/Android-specific_test_suites
|
||||
.. _WIKI: https://wiki.mozilla.org/Mobile/Fennec/Android/Testing
|
||||
|
||||
Collecting and exporting code coverage information
|
||||
==================================================
|
||||
|
||||
Relevant tools and libraries
|
||||
----------------------------
|
||||
|
||||
JaCoCo_ is the tool used to gather code coverage data. JaCoCo uses class file
|
||||
instrumentation to record execution coverage data. It has two operating modes:
|
||||
|
||||
- `online instrumentation`_: Class files are instrumented on-the-fly using a so
|
||||
called Java agent. The JaCoCo agent collects execution information and dumps
|
||||
it on request or when the JVM exits. This method is used for test suites
|
||||
which run on the JVM (generally, `*UnitTest` Gradle tasks).
|
||||
- `offline instrumentation`_: At runtime the pre-instrumented classes needs be
|
||||
on the classpath instead of the original classes. In addition,
|
||||
`jacocoagent.jar` must be put on the classpath. This method is used for test
|
||||
suites with run on the Android emulator (generally, `*AndroidTest` Gradle
|
||||
tasks, including `robocop`).
|
||||
|
||||
JaCoCo is integrated with Gradle in two ways: a Gradle plugin (activated in the
|
||||
``build.gradle`` file as a top-level ``apply plugin: 'jacoco'``), and an
|
||||
Android-specific Gradle plugin, activated with ``android { buildTypes { debug {
|
||||
testCoverageEnabled true } } }``. These two methods of activating JaCoCo have a
|
||||
different syntax of choosing the JaCoCo tool version, and both should be
|
||||
configured to use the same version. See the
|
||||
``mobile/android/geckoview/build.gradle`` file for example usage.
|
||||
|
||||
grcov_ is a tool implemented in Rust that transforms code coverage reports
|
||||
between formats. Among others, it supports reading JaCoCo XML reports.
|
||||
|
||||
.. _JaCoCo: https://www.eclemma.org/jacoco/
|
||||
.. _online instrumentation: https://www.jacoco.org/jacoco/trunk/doc/agent.html
|
||||
.. _offline instrumentation: https://www.jacoco.org/jacoco/trunk/doc/offline.html
|
||||
.. _grcov: https://github.com/mozilla/grcov/
|
||||
|
||||
Generating the coverage report artifacts
|
||||
----------------------------------------
|
||||
|
||||
All tasks that output code coverage information do so by exporting an artifact
|
||||
named ``code-coverage-grcov.zip`` which contains a single file named
|
||||
``grcov_lcov_output.info``. The ``grcov_lcov_output.info`` file should contain
|
||||
coverage information in the lcov format. The artifact, once uploaded, is picked
|
||||
up and indexed by ActiveData_.
|
||||
|
||||
The code that generates the ``code-coverage-grcov.zip`` artifact after a
|
||||
generally resides in the ``CodeCoverageMixin`` class, in the module
|
||||
``testing/mozharness/mozharness/mozilla/testing/codecoverage.py``. This class is
|
||||
responsible for downloading ``grcov`` along with other artifacts from the build
|
||||
job. It is also responsible for running ``grcov`` after the tests are finished,
|
||||
to convert and merge the coverage reports.
|
||||
|
||||
.. _ActiveData: https://wiki.mozilla.org/EngineeringProductivity/Projects/ActiveData
|
||||
|
||||
Code coverage for android-test
|
||||
===============================
|
||||
|
||||
The `android-test` suite is a JUnit test suite that runs locally on the
|
||||
host's JVM. It can be run with ``mach android test``. The test suite is
|
||||
implemented as a build task, defined at
|
||||
``taskcluster/ci/build/android-stuff.yml``.
|
||||
|
||||
To collect code coverage from this suite, a duplicate build task is defined,
|
||||
called `android-test-ccov`. It can be run with ``mach android test-ccov``.
|
||||
|
||||
The mach subcommand is responsible for downloading and running `grcov`,
|
||||
instead of the ``CodeCoverageMixin`` class. This is because the
|
||||
`android-test-ccov` task is a build task (not a test task), so it doesn't use
|
||||
mozharness to run the tests.
|
||||
|
||||
|
||||
Code coverage for geckoview-junit
|
||||
==================================
|
||||
|
||||
The geckoview-junit_ tests are on-device Android JUnit tests written for
|
||||
GeckoView_. The tests are implemented with ``mochitest``. The automation
|
||||
entry point is ``testing/mozharness/scripts/android_emulator_unittest.py``,
|
||||
which then calls ``testing/mochitest/runjunit.py``. This is an out-of-tree
|
||||
task, so a source tree clone is unavailable.
|
||||
|
||||
To generate the coverage report, we need three things:
|
||||
|
||||
- The classfiles before instrumentation. These are archived as a public
|
||||
artifact during build time, and downloaded on the test machine during testing
|
||||
time;
|
||||
- The coverage.ec file with coverage counters. This is generated while running
|
||||
the tests on the emulator, then downloaded with ``adb pull``;
|
||||
- ``jacoco-cli``, a command-line package JaCoCo component that takes the
|
||||
classfiles and the coverage counters as input and generates XML reports as
|
||||
output.
|
||||
|
||||
The ``mach android archive-geckoview-coverage-artifacts`` command archives the
|
||||
class files and exports the ``jacoco-cli`` jar file after the build is done.
|
||||
These files are later saved as public artifacts of the build.
|
||||
|
||||
To enable offline instrumentation for the test suites, the mozconfig flag
|
||||
``--enable-java-coverage`` should be set. When the flag is checked both during
|
||||
build and test time. During test time, the flag instructs ``CodeCoverageMixin``
|
||||
to download the coverage artifacts from the build task before the tests run,
|
||||
and generate and export the reports after testing is finished. The flag also
|
||||
instructs the ``runjunit.py`` script to insert the arguments ``-e coverage
|
||||
true`` to ``am instrument``.
|
||||
|
||||
.. _GeckoView: https://wiki.mozilla.org/Mobile/GeckoView
|
||||
.. _geckoview-junit: https://developer.mozilla.org/en-US/docs/Mozilla/Geckoview-Junit_Tests
|
@ -372,3 +372,71 @@ apply from: "${topsrcdir}/mobile/android/gradle/jacoco_dependencies.gradle"
|
||||
if (project.hasProperty('enable_code_coverage')) {
|
||||
apply from: "${topsrcdir}/mobile/android/gradle/jacoco_for_junit.gradle"
|
||||
}
|
||||
|
||||
// Set up code coverage for tests on emulators.
|
||||
if (mozconfig.substs.MOZ_JAVA_CODE_COVERAGE) {
|
||||
apply plugin: "jacoco"
|
||||
jacoco {
|
||||
toolVersion = "${project.jacoco_version}"
|
||||
}
|
||||
|
||||
android {
|
||||
jacoco {
|
||||
version = "$jacoco_version"
|
||||
}
|
||||
buildTypes {
|
||||
debug {
|
||||
testCoverageEnabled true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configurations {
|
||||
// This configuration is used for dependencies that are not needed at compilation or
|
||||
// runtime, but need to be exported as artifacts of the build for usage on the testing
|
||||
// machines.
|
||||
coverageDependency
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// This is required both in the instrumented application classes and the test classes,
|
||||
// so `api` has to be used instead of `androidTestImplementation`.
|
||||
api "org.jacoco:org.jacoco.agent:$jacoco_version:runtime"
|
||||
|
||||
coverageDependency ("org.jacoco:org.jacoco.cli:$jacoco_version:nodeps") {
|
||||
exclude group: 'org.ow2.asm', module: '*'
|
||||
}
|
||||
}
|
||||
|
||||
// This task is used by `mach android archive-geckoview-coverage-artifacts`.
|
||||
task copyCoverageDependencies(type: Copy) {
|
||||
from(configurations.coverageDependency) {
|
||||
include 'org.jacoco.cli-*-nodeps.jar'
|
||||
rename { _ -> 'target.jacoco-cli.jar' }
|
||||
}
|
||||
into "$buildDir/coverage"
|
||||
}
|
||||
|
||||
// Generate tasks to archive compiled classfiles for later use with JaCoCo report generation.
|
||||
// One of these tasks is used by `mach android archive-geckoview-coverage-artifacts`.
|
||||
android.libraryVariants.all { variant ->
|
||||
def name = variant.name
|
||||
def compileTask = tasks.getByName("compile${name.capitalize()}JavaWithJavac")
|
||||
task "archiveClassfiles${name.capitalize()}"(type: Zip, dependsOn: compileTask) {
|
||||
description = "Archive compiled classfiles for $name in order to export them as code coverage artifacts."
|
||||
def fileFilter = ['**/androidTest/**',
|
||||
'**/test/**',
|
||||
'**/R.class',
|
||||
'**/R$*.class',
|
||||
'**/BuildConfig.*',
|
||||
'**/Manifest*.*',
|
||||
'**/*Test*.*',
|
||||
'android/**/*.*']
|
||||
from fileTree(dir: compileTask.destinationDir, excludes: fileFilter)
|
||||
destinationDir = file("${buildDir}/coverage")
|
||||
// Note: This task assumes only one variant of archiveClassfiles* will be used.
|
||||
// Running multiple variants of this task will overwrite the output archive.
|
||||
archiveName = 'target.geckoview_classfiles.zip'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -237,6 +237,18 @@ def gradle_android_archive_geckoview_tasks(build_config):
|
||||
set_config('GRADLE_ANDROID_ARCHIVE_GECKOVIEW_TASKS', gradle_android_archive_geckoview_tasks)
|
||||
|
||||
|
||||
@depends(gradle_android_build_config)
|
||||
def gradle_android_archive_geckoview_coverage_artifacts_tasks(build_config):
|
||||
'''Gradle tasks run by |mach android archive-geckoview-coverage-artifacts|.'''
|
||||
return [
|
||||
'geckoview:archiveClassfiles{geckoview.variant.name}'.format(geckoview=build_config.geckoview),
|
||||
'geckoview:copyCoverageDependencies',
|
||||
]
|
||||
|
||||
set_config('GRADLE_ANDROID_ARCHIVE_GECKOVIEW_COVERAGE_ARTIFACTS_TASKS',
|
||||
gradle_android_archive_geckoview_coverage_artifacts_tasks)
|
||||
|
||||
|
||||
@depends(
|
||||
gradle_android_app_tasks,
|
||||
gradle_android_test_tasks,
|
||||
@ -247,6 +259,7 @@ set_config('GRADLE_ANDROID_ARCHIVE_GECKOVIEW_TASKS', gradle_android_archive_geck
|
||||
gradle_android_generate_sdk_bindings_tasks,
|
||||
gradle_android_generate_generated_jni_wrappers_tasks,
|
||||
gradle_android_generate_fennec_jni_wrappers_tasks,
|
||||
gradle_android_archive_geckoview_coverage_artifacts_tasks,
|
||||
)
|
||||
@imports(_from='itertools', _import='imap')
|
||||
@imports(_from='itertools', _import='chain')
|
||||
|
@ -3,11 +3,9 @@
|
||||
* 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/. */
|
||||
|
||||
project.ext.jacoco_version = "0.7.8"
|
||||
|
||||
dependencies {
|
||||
testImplementation "org.jacoco:org.jacoco.agent:${project.jacoco_version}"
|
||||
testImplementation "org.jacoco:org.jacoco.ant:${project.jacoco_version}"
|
||||
testImplementation "org.jacoco:org.jacoco.core:${project.jacoco_version}"
|
||||
testImplementation "org.jacoco:org.jacoco.report:${project.jacoco_version}"
|
||||
testImplementation "org.jacoco:org.jacoco.agent:$jacoco_version"
|
||||
testImplementation "org.jacoco:org.jacoco.ant:$jacoco_version"
|
||||
testImplementation "org.jacoco:org.jacoco.core:$jacoco_version"
|
||||
testImplementation "org.jacoco:org.jacoco.report:$jacoco_version"
|
||||
}
|
||||
|
@ -207,7 +207,7 @@ class MachCommands(MachCommandBase):
|
||||
|
||||
@SubCommand('android', 'test-ccov',
|
||||
"""Run Android local unit tests in order to get a code coverage report.
|
||||
See https://developer.mozilla.org/en-US/docs/Mozilla/Android-specific_test_suites#android-test""") # NOQA: E501
|
||||
See https://firefox-source-docs.mozilla.org/mobile/android/fennec/testcoverage.html""") # NOQA: E501
|
||||
@CommandArgument('args', nargs=argparse.REMAINDER)
|
||||
def android_test_ccov(self, args):
|
||||
enable_ccov = '-Penable_code_coverage'
|
||||
@ -413,6 +413,16 @@ class MachCommands(MachCommandBase):
|
||||
|
||||
return 0
|
||||
|
||||
@SubCommand('android', 'archive-geckoview-coverage-artifacts',
|
||||
"""Archive compiled geckoview classfiles to be used later in generating code
|
||||
coverage reports. See https://firefox-source-docs.mozilla.org/mobile/android/fennec/testcoverage.html""") # NOQA: E501
|
||||
@CommandArgument('args', nargs=argparse.REMAINDER)
|
||||
def android_archive_geckoview_classfiles(self, args):
|
||||
self.gradle(self.substs['GRADLE_ANDROID_ARCHIVE_GECKOVIEW_COVERAGE_ARTIFACTS_TASKS'] +
|
||||
["--continue"] + args, verbose=True)
|
||||
|
||||
return 0
|
||||
|
||||
@SubCommand('android', 'archive-geckoview',
|
||||
"""Create GeckoView archives.
|
||||
See http://firefox-source-docs.mozilla.org/build/buildsystem/toolchains.html#firefox-for-android-with-gradle""") # NOQA: E501
|
||||
|
@ -2,7 +2,7 @@
|
||||
* 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 <Windows.h>
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
* 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 <Windows.h>
|
||||
#include <windows.h>
|
||||
|
||||
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD aReason, LPVOID)
|
||||
{
|
||||
|
@ -2,7 +2,7 @@
|
||||
* 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 <Windows.h>
|
||||
#include <windows.h>
|
||||
#include <winternl.h>
|
||||
#include "gtest/gtest.h"
|
||||
#include "nsReadableUtils.h"
|
||||
|
@ -512,6 +512,7 @@ class TestChecksConfigure(unittest.TestCase):
|
||||
'JAR': jar,
|
||||
'JARSIGNER': jarsigner,
|
||||
'KEYTOOL': keytool,
|
||||
'MOZ_JAVA_CODE_COVERAGE': False,
|
||||
})
|
||||
self.assertEqual(out, textwrap.dedent('''\
|
||||
checking for java... %s
|
||||
@ -555,6 +556,7 @@ class TestChecksConfigure(unittest.TestCase):
|
||||
'JAR': alt_jar,
|
||||
'JARSIGNER': alt_jarsigner,
|
||||
'KEYTOOL': alt_keytool,
|
||||
'MOZ_JAVA_CODE_COVERAGE': False,
|
||||
})
|
||||
self.assertEqual(out, textwrap.dedent('''\
|
||||
checking for java... %s
|
||||
@ -584,6 +586,7 @@ class TestChecksConfigure(unittest.TestCase):
|
||||
'JAR': alt_jar,
|
||||
'JARSIGNER': alt_jarsigner,
|
||||
'KEYTOOL': alt_keytool,
|
||||
'MOZ_JAVA_CODE_COVERAGE': False,
|
||||
})
|
||||
self.assertEqual(out, textwrap.dedent('''\
|
||||
checking for java... %s
|
||||
@ -614,6 +617,7 @@ class TestChecksConfigure(unittest.TestCase):
|
||||
'JAR': alt_jar,
|
||||
'JARSIGNER': alt_jarsigner,
|
||||
'KEYTOOL': alt_keytool,
|
||||
'MOZ_JAVA_CODE_COVERAGE': False,
|
||||
})
|
||||
self.assertEqual(out, textwrap.dedent('''\
|
||||
checking for java... %s
|
||||
@ -626,6 +630,26 @@ class TestChecksConfigure(unittest.TestCase):
|
||||
''' % (alt_java, alt_javah, alt_jar, alt_jarsigner,
|
||||
alt_keytool, alt_javac)))
|
||||
|
||||
# --enable-java-coverage should set MOZ_JAVA_CODE_COVERAGE.
|
||||
config, out, status = self.get_result(
|
||||
args=['--enable-java-coverage'],
|
||||
includes=includes,
|
||||
extra_paths=paths,
|
||||
environ={
|
||||
'PATH': mozpath.dirname(java),
|
||||
'JAVA_HOME': mozpath.dirname(mozpath.dirname(java)),
|
||||
})
|
||||
self.assertEqual(status, 0)
|
||||
self.assertEqual(config, {
|
||||
'JAVA': java,
|
||||
'JAVAH': javah,
|
||||
'JAVAC': javac,
|
||||
'JAR': jar,
|
||||
'JARSIGNER': jarsigner,
|
||||
'KEYTOOL': keytool,
|
||||
'MOZ_JAVA_CODE_COVERAGE': True,
|
||||
})
|
||||
|
||||
def mock_old_javac(_, args):
|
||||
if len(args) == 1 and args[0] == '-version':
|
||||
return 0, '1.6.9', ''
|
||||
|
@ -32,6 +32,7 @@ const TOPICS = [
|
||||
"fxaccounts:onlogin", // Defined in FxAccountsCommon, pulling it is expensive.
|
||||
"fxaccounts:onlogout",
|
||||
"fxaccounts:profilechange",
|
||||
"fxaccounts:statechange",
|
||||
];
|
||||
|
||||
const ON_UPDATE = "sync-ui-state:update";
|
||||
|
@ -48,6 +48,62 @@ android-api-16/debug:
|
||||
- linux64-sccache
|
||||
- linux64-node
|
||||
|
||||
android-api-16-ccov/debug:
|
||||
description: "Android 4.0 api-16+ Debug Coverage"
|
||||
index:
|
||||
product: mobile
|
||||
job-name: android-api-16-ccov-debug
|
||||
treeherder:
|
||||
platform: android-4-0-armv7-api16-ccov/debug
|
||||
symbol: B
|
||||
worker-type: aws-provisioner-v1/gecko-{level}-b-android
|
||||
worker:
|
||||
docker-image: {in-tree: android-build}
|
||||
max-run-time: 7200
|
||||
env:
|
||||
GRADLE_USER_HOME: "/builds/worker/workspace/build/src/mobile/android/gradle/dotgradle-offline"
|
||||
TOOLTOOL_MANIFEST: "mobile/android/config/tooltool-manifests/android/releng.manifest"
|
||||
artifacts:
|
||||
- name: public/android/R
|
||||
path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/app/R
|
||||
type: directory
|
||||
- name: public/android/maven
|
||||
path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview/maven/
|
||||
type: directory
|
||||
- name: public/build/geckoview-androidTest.apk
|
||||
path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview/outputs/apk/androidTest/officialWithGeckoBinariesNoMinApi/debug/geckoview-official-withGeckoBinaries-noMinApi-debug-androidTest.apk
|
||||
type: file
|
||||
- name: public/build/geckoview_example.apk
|
||||
path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview_example/outputs/apk/officialWithGeckoBinariesNoMinApi/debug/geckoview_example-official-withGeckoBinaries-noMinApi-debug.apk
|
||||
type: file
|
||||
- name: public/build/target.geckoview_classfiles.zip
|
||||
path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview/coverage/target.geckoview_classfiles.zip
|
||||
type: file
|
||||
- name: public/build/target.jacoco-cli.jar
|
||||
path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview/coverage/target.jacoco-cli.jar
|
||||
type: file
|
||||
- name: public/build
|
||||
path: /builds/worker/artifacts/
|
||||
type: directory
|
||||
run:
|
||||
using: mozharness
|
||||
actions: [get-secrets build multi-l10n update]
|
||||
config:
|
||||
- builds/releng_base_android_64_builds.py
|
||||
script: "mozharness/scripts/fx_desktop_build.py"
|
||||
secrets: true
|
||||
custom-build-variant-cfg: api-16-debug-ccov
|
||||
tooltool-downloads: internal
|
||||
toolchains:
|
||||
- android-gradle-dependencies
|
||||
- android-ndk-linux
|
||||
- android-sdk-linux
|
||||
- linux64-clang
|
||||
- linux64-rust-android
|
||||
- linux64-rust-size
|
||||
- linux64-sccache
|
||||
- linux64-node
|
||||
|
||||
android-x86/opt:
|
||||
description: "Android 4.2 x86 Opt"
|
||||
index:
|
||||
|
@ -100,10 +100,10 @@ jobs:
|
||||
run:
|
||||
using: debian-package
|
||||
tarball:
|
||||
url: https://www.mercurial-scm.org/release/mercurial-4.5.2.tar.gz
|
||||
sha256: a44a9ffd1c9502a4f97298a6bbcb8a79fc8192424c760c67f17b45c12114e390
|
||||
url: https://www.mercurial-scm.org/release/mercurial-4.7.tar.gz
|
||||
sha256: 098cb1437f77fb7f75dc2f008742933c729ec0b63cfa3e4e2f0a8fbc3d0d349f
|
||||
pre-build-command: >-
|
||||
cp -r contrib/debian debian &&
|
||||
cp -r contrib/packaging/debian debian &&
|
||||
sed -i -e "s/__VERSION__/$(awk -F\" '$2 {print $2}' mercurial/__version__.py)-1.deb7moz1/" \
|
||||
-e "s/__DATE__/$(date --rfc-2822)/" \
|
||||
-e "s/__CODENAME__/wheezy/" debian/changelog
|
||||
@ -116,10 +116,10 @@ jobs:
|
||||
using: debian-package
|
||||
dist: stretch
|
||||
tarball:
|
||||
url: https://www.mercurial-scm.org/release/mercurial-4.5.2.tar.gz
|
||||
sha256: a44a9ffd1c9502a4f97298a6bbcb8a79fc8192424c760c67f17b45c12114e390
|
||||
url: https://www.mercurial-scm.org/release/mercurial-4.7.tar.gz
|
||||
sha256: 098cb1437f77fb7f75dc2f008742933c729ec0b63cfa3e4e2f0a8fbc3d0d349f
|
||||
pre-build-command: >-
|
||||
cp -r contrib/debian debian &&
|
||||
cp -r contrib/packaging/debian debian &&
|
||||
sed -i -e "s/__VERSION__/$(awk -F\" '$2 {print $2}' mercurial/__version__.py)-1.deb9moz1/" \
|
||||
-e "s/__DATE__/$(date --rfc-2822)/" \
|
||||
-e "s/__CODENAME__/stretch/" debian/changelog
|
||||
|
@ -33,6 +33,7 @@ geckoview-junit:
|
||||
max-run-time: 3600
|
||||
chunks:
|
||||
by-test-platform:
|
||||
android-em-4.3-arm7-api-16-ccov/debug: 4
|
||||
android-em-4.3-arm7-api-16/debug: 4
|
||||
android-em-4.3-arm7-api-16/opt: 2
|
||||
default: 1
|
||||
|
@ -322,6 +322,11 @@ android-em-4.3-arm7-api-16/debug:
|
||||
- android-common-tests
|
||||
- android-gradle-tests
|
||||
|
||||
android-em-4.3-arm7-api-16-ccov/debug:
|
||||
build-platform: android-api-16-ccov/debug
|
||||
test-sets:
|
||||
- android-ccov-tests
|
||||
|
||||
android-em-4.3-arm7-api-16/opt:
|
||||
build-platform: android-api-16/opt
|
||||
test-sets:
|
||||
|
@ -391,6 +391,9 @@ android-x86-kvm-tests:
|
||||
- reftest
|
||||
- test-verify
|
||||
|
||||
android-ccov-tests:
|
||||
- geckoview-junit
|
||||
|
||||
devtools-tests:
|
||||
- mochitest-devtools-chrome
|
||||
|
||||
|
@ -454,6 +454,7 @@ linux64-android-gradle-dependencies:
|
||||
toolchains:
|
||||
# Aliases aren't allowed for toolchains depending on toolchains.
|
||||
- linux64-android-sdk-linux-repack
|
||||
- linux64-node
|
||||
|
||||
linux64-rust-1.27:
|
||||
description: "rust repack"
|
||||
@ -494,8 +495,7 @@ linux64-rust-1.28:
|
||||
using: toolchain-script
|
||||
script: repack_rust.py
|
||||
arguments: [
|
||||
# 1.28.0-beta.6
|
||||
'--channel', 'beta-2018-06-30',
|
||||
'--channel', '1.28.0',
|
||||
'--host', 'x86_64-unknown-linux-gnu',
|
||||
'--target', 'x86_64-unknown-linux-gnu',
|
||||
'--target', 'i686-unknown-linux-gnu',
|
||||
@ -542,8 +542,7 @@ linux64-rust-macos-1.28:
|
||||
using: toolchain-script
|
||||
script: repack_rust.py
|
||||
arguments: [
|
||||
# 1.28.0-beta.6
|
||||
'--channel', 'beta-2018-06-30',
|
||||
'--channel', '1.28.0',
|
||||
'--host', 'x86_64-unknown-linux-gnu',
|
||||
'--target', 'x86_64-unknown-linux-gnu',
|
||||
'--target', 'x86_64-apple-darwin',
|
||||
@ -567,8 +566,7 @@ linux64-rust-android-1.28:
|
||||
using: toolchain-script
|
||||
script: repack_rust.py
|
||||
arguments: [
|
||||
# 1.28.0-beta.6
|
||||
'--channel', 'beta-2018-06-30',
|
||||
'--channel', '1.28.0',
|
||||
'--host', 'x86_64-unknown-linux-gnu',
|
||||
'--target', 'x86_64-unknown-linux-gnu',
|
||||
'--target', 'armv7-linux-androideabi',
|
||||
|
@ -135,8 +135,7 @@ win64-rust-1.28:
|
||||
using: toolchain-script
|
||||
script: repack_rust.py
|
||||
arguments: [
|
||||
# 1.28.0-beta.6
|
||||
'--channel', 'beta-2018-06-30',
|
||||
'--channel', '1.28.0',
|
||||
'--host', 'x86_64-pc-windows-msvc',
|
||||
'--target', 'x86_64-pc-windows-msvc',
|
||||
'--target', 'i686-pc-windows-msvc',
|
||||
@ -202,8 +201,7 @@ win32-rust-1.28:
|
||||
using: toolchain-script
|
||||
script: repack_rust.py
|
||||
arguments: [
|
||||
# 1.28.0-beta.6
|
||||
'--channel', 'beta-2018-06-30',
|
||||
'--channel', '1.28.0',
|
||||
'--host', 'i686-pc-windows-msvc',
|
||||
'--target', 'i686-pc-windows-msvc',
|
||||
]
|
||||
@ -227,12 +225,12 @@ mingw32-rust-1.28:
|
||||
using: toolchain-script
|
||||
script: repack_rust.py
|
||||
arguments: [
|
||||
# 1.28.0-beta.6
|
||||
'--channel', 'beta-2018-06-30',
|
||||
'--host', 'i686-unknown-linux-gnu',
|
||||
'--target', 'i686-pc-windows-gnu',
|
||||
'--target', 'x86_64-unknown-linux-gnu',
|
||||
'--channel', '1.28.0',
|
||||
'--host', 'x86_64-unknown-linux-gnu',
|
||||
'--target', 'i686-unknown-linux-gnu',
|
||||
'--target', 'i686-pc-windows-gnu',
|
||||
'--target', 'x86_64-pc-windows-gnu',
|
||||
'--target', 'x86_64-unknown-linux-gnu',
|
||||
]
|
||||
toolchain-alias: mingw32-rust
|
||||
toolchain-artifact: public/build/rustc.tar.xz
|
||||
|
@ -16,13 +16,13 @@ if [ -f /etc/lsb-release ]; then
|
||||
if [ "${DISTRIB_ID}" = "Ubuntu" ] && [[ "${DISTRIB_RELEASE}" = "16.04" || "${DISTRIB_RELEASE}" = "17.10" || "${DISTRIB_RELEASE}" = "18.04" ]]
|
||||
then
|
||||
HG_DEB=1
|
||||
HG_DIGEST=e58ecb78fb6856161f8af1b7a50a024f1d6aa6efe50e18666aae0368ee1a44eead2f0c52e5088304b4c0e89dd74a058128e9bd184efab0142275a228aa8e0f45
|
||||
HG_SIZE=193382
|
||||
HG_FILENAME=mercurial_4.5.2_amd64.deb
|
||||
HG_DIGEST=21a5ca8170bdb05527b04cbc93f00ecef39680a2c7b80aa625f9add8b7dba0a7bff0ad58e0ba40b1956ae310f681d04302d033e398eca0e001dce10d74d0dbdc
|
||||
HG_SIZE=250748
|
||||
HG_FILENAME=mercurial_4.7_amd64.deb
|
||||
|
||||
HG_COMMON_DIGEST=b69d94c91ad78a26318e3bbd2f0fda7eb3f3295755a727cde677bf143312c5dfc27ac47e800772d624ea88c6f2576fa2f585c1b8b9930ba83ddc8356661627b8
|
||||
HG_COMMON_SIZE=2141554
|
||||
HG_COMMON_FILENAME=mercurial-common_4.5.2_all.deb
|
||||
HG_COMMON_DIGEST=521e0c150b142d0bbb69fa100b96bac5bee7a108605eea1c484e8544540dcf764efffab2e3a9ad640f56674ad85c1b47647e3d504c96f7567d7d7c21299c2c25
|
||||
HG_COMMON_SIZE=2315590
|
||||
HG_COMMON_FILENAME=mercurial-common_4.7_all.deb
|
||||
elif [ "${DISTRIB_ID}" = "Ubuntu" ] && [ "${DISTRIB_RELEASE}" = "12.04" ]
|
||||
then
|
||||
echo "Ubuntu 12.04 not supported"
|
||||
@ -106,15 +106,15 @@ elif [ -n "${PIP_PATH}" ]; then
|
||||
tooltool_fetch <<EOF
|
||||
[
|
||||
{
|
||||
"size": 5779915,
|
||||
"digest": "f70e40cba72b7955f0ecec9c1f53ffffac26f206188617cb182e22ce4f43dc8b970ce46d12c516ef88480c3fa076a59afcddd736dffb642d8e23befaf45b4941",
|
||||
"size": 6476268,
|
||||
"digest": "a08dfc4e296b5d162097769ab38ab85b7c5de16710bce0b6dce2a39f56cb517455c0ed634f689d07e9bd082fb7641501b7da51963844aee7ab28233cf721dec8",
|
||||
"algorithm": "sha512",
|
||||
"filename": "mercurial-4.5.2.tar.gz"
|
||||
"filename": "mercurial-4.7.tar.gz"
|
||||
}
|
||||
]
|
||||
EOF
|
||||
|
||||
${PIP_PATH} install mercurial-4.5.2.tar.gz
|
||||
${PIP_PATH} install mercurial-4.7.tar.gz
|
||||
else
|
||||
echo "Do not know how to install Mercurial on this OS"
|
||||
exit 1
|
||||
|
@ -560,6 +560,7 @@ def set_treeherder_machine_platform(config, tests):
|
||||
# The build names for Android platforms have partially evolved over the
|
||||
# years and need to be translated.
|
||||
'android-api-16/debug': 'android-em-4-3-armv7-api16/debug',
|
||||
'android-api-16-ccov/debug': 'android-em-4-3-armv7-api16-ccov/debug',
|
||||
'android-api-16/opt': 'android-em-4-3-armv7-api16/opt',
|
||||
'android-x86/opt': 'android-em-4-2-x86/opt',
|
||||
'android-api-16-gradle/opt': 'android-api-16-gradle/opt',
|
||||
@ -731,6 +732,12 @@ def enable_code_coverage(config, tests):
|
||||
if 'opt' in test['build-platform'] or 'fuzzing' in test['build-platform']:
|
||||
test['run-on-projects'] = []
|
||||
continue
|
||||
# Skip this transform for android code coverage builds.
|
||||
if 'android' in test['build-platform']:
|
||||
test.setdefault('fetches', {}).setdefault('fetch', []).append('grcov-linux-x86_64')
|
||||
test['mozharness'].setdefault('extra-options', []).append('--java-code-coverage')
|
||||
yield test
|
||||
continue
|
||||
test['mozharness'].setdefault('extra-options', []).append('--code-coverage')
|
||||
test['instance-size'] = 'xlarge'
|
||||
# Ensure we always run on the projects defined by the build, unless the test
|
||||
|
@ -51,6 +51,11 @@ class JUnitTestRunner(MochitestDesktop):
|
||||
self.log.debug("options=%s" % vars(options))
|
||||
update_mozinfo()
|
||||
self.remote_profile = posixpath.join(self.device.test_root, 'junit-profile')
|
||||
|
||||
if self.options.coverage and not self.options.coverage_output_path:
|
||||
raise Exception("--coverage-output-path is required when using --enable-coverage")
|
||||
self.remote_coverage_output_path = posixpath.join(self.device.test_root,
|
||||
'junit-coverage.ec')
|
||||
self.server_init()
|
||||
|
||||
self.cleanup()
|
||||
@ -145,6 +150,10 @@ class JUnitTestRunner(MochitestDesktop):
|
||||
for f in test_filters:
|
||||
# filter can be class-name or 'class-name#method-name' (single test)
|
||||
cmd = cmd + " -e class %s" % f
|
||||
# enable code coverage reports
|
||||
if self.options.coverage:
|
||||
cmd = cmd + " -e coverage true"
|
||||
cmd = cmd + " -e coverageFile %s" % self.remote_coverage_output_path
|
||||
# environment
|
||||
env = {}
|
||||
env["MOZ_CRASHREPORTER"] = "1"
|
||||
@ -262,6 +271,9 @@ class JUnitTestRunner(MochitestDesktop):
|
||||
if self.check_for_crashes():
|
||||
self.fail_count = 1
|
||||
|
||||
if self.options.coverage:
|
||||
self.device.pull(self.remote_coverage_output_path, self.options.coverage_output_path)
|
||||
|
||||
return 1 if self.fail_count else 0
|
||||
|
||||
def check_for_crashes(self):
|
||||
@ -364,6 +376,17 @@ class JunitArgumentParser(argparse.ArgumentParser):
|
||||
dest="thisChunk",
|
||||
default=None,
|
||||
help="If running tests by chunks, the chunk number to run.")
|
||||
self.add_argument("--enable-coverage",
|
||||
action="store_true",
|
||||
dest="coverage",
|
||||
default=False,
|
||||
help="Enable code coverage collection.")
|
||||
self.add_argument("--coverage-output-path",
|
||||
action="store",
|
||||
type=str,
|
||||
dest="coverage_output_path",
|
||||
default=None,
|
||||
help="If collecting code coverage, save the report file to this path.")
|
||||
# Additional options for server.
|
||||
self.add_argument("--certificate-path",
|
||||
action="store",
|
||||
|
@ -0,0 +1,15 @@
|
||||
config = {
|
||||
'base_name': 'Android armv7 api-16+ %(branch)s debug coverage',
|
||||
'stage_platform': 'android-api-16-debug-ccov',
|
||||
'src_mozconfig': 'mobile/android/config/mozconfigs/android-api-16/debug-ccov',
|
||||
'multi_locale_config_platform': 'android',
|
||||
'debug_build': True,
|
||||
'postflight_build_mach_commands': [
|
||||
['android',
|
||||
'archive-geckoview',
|
||||
],
|
||||
['android',
|
||||
'archive-geckoview-coverage-artifacts',
|
||||
],
|
||||
],
|
||||
}
|
@ -49,7 +49,7 @@ except ImportError:
|
||||
# Causes worker to purge caches on process exit and for task to retry.
|
||||
EXIT_PURGE_CACHE = 72
|
||||
|
||||
testedwith = '3.7 3.8 3.9 4.0 4.1 4.2 4.3 4.4 4.5'
|
||||
testedwith = '3.7 3.8 3.9 4.0 4.1 4.2 4.3 4.4 4.5 4.6 4.7'
|
||||
minimumhgversion = '3.7'
|
||||
|
||||
cmdtable = {}
|
||||
@ -86,6 +86,17 @@ def getsparse():
|
||||
return sparse
|
||||
|
||||
|
||||
def supported_hg():
|
||||
'''Returns True if the Mercurial version is supported for robustcheckout'''
|
||||
return util.versiontuple(n=2) in (
|
||||
(4, 3),
|
||||
(4, 4),
|
||||
(4, 5),
|
||||
(4, 6),
|
||||
(4, 7),
|
||||
)
|
||||
|
||||
|
||||
if os.name == 'nt':
|
||||
import ctypes
|
||||
|
||||
@ -228,7 +239,7 @@ def robustcheckout(ui, url, dest, upstream=None, revision=None, branch=None,
|
||||
# However, given that sparse has performance implications, we want to fail
|
||||
# fast if we can't satisfy the desired checkout request.
|
||||
if sparseprofile:
|
||||
if util.versiontuple(n=2) not in ((4, 3), (4, 4), (4, 5)):
|
||||
if not supported_hg():
|
||||
raise error.Abort('sparse profile support only available for '
|
||||
'Mercurial versions greater than 4.3 (using %s)' % util.version())
|
||||
|
||||
@ -575,16 +586,21 @@ def _docheckout(ui, url, dest, upstream, revision, branch, purge, sharebase,
|
||||
# We only pull if we are using symbolic names or the requested revision
|
||||
# doesn't exist.
|
||||
havewantedrev = False
|
||||
if revision and revision in repo:
|
||||
ctx = repo[revision]
|
||||
|
||||
if not ctx.hex().startswith(revision):
|
||||
raise error.Abort('--revision argument is ambiguous',
|
||||
hint='must be the first 12+ characters of a '
|
||||
'SHA-1 fragment')
|
||||
if revision:
|
||||
try:
|
||||
ctx = scmutil.revsingle(repo, revision)
|
||||
except error.RepoLookupError:
|
||||
ctx = None
|
||||
|
||||
checkoutrevision = ctx.hex()
|
||||
havewantedrev = True
|
||||
if ctx:
|
||||
if not ctx.hex().startswith(revision):
|
||||
raise error.Abort('--revision argument is ambiguous',
|
||||
hint='must be the first 12+ characters of a '
|
||||
'SHA-1 fragment')
|
||||
|
||||
checkoutrevision = ctx.hex()
|
||||
havewantedrev = True
|
||||
|
||||
if not havewantedrev:
|
||||
ui.write('(pulling to obtain %s)\n' % (revision or branch,))
|
||||
@ -635,7 +651,7 @@ def _docheckout(ui, url, dest, upstream, revision, branch, purge, sharebase,
|
||||
try:
|
||||
old_sparse_fn = getattr(repo.dirstate, '_sparsematchfn', None)
|
||||
if old_sparse_fn is not None:
|
||||
assert util.versiontuple(n=2) in ((4, 3), (4, 4), (4, 5))
|
||||
assert supported_hg(), 'Mercurial version not supported (must be 4.3+)'
|
||||
repo.dirstate._sparsematchfn = lambda: matchmod.always(repo.root, '')
|
||||
|
||||
with timeit('purge'):
|
||||
|
@ -428,6 +428,7 @@ class BuildOptionParser(object):
|
||||
'api-16': 'builds/releng_sub_%s_configs/%s_api_16.py',
|
||||
'api-16-artifact': 'builds/releng_sub_%s_configs/%s_api_16_artifact.py',
|
||||
'api-16-debug': 'builds/releng_sub_%s_configs/%s_api_16_debug.py',
|
||||
'api-16-debug-ccov': 'builds/releng_sub_%s_configs/%s_api_16_debug_ccov.py',
|
||||
'api-16-debug-artifact': 'builds/releng_sub_%s_configs/%s_api_16_debug_artifact.py',
|
||||
'api-16-gradle': 'builds/releng_sub_%s_configs/%s_api_16_gradle.py',
|
||||
'api-16-gradle-artifact': 'builds/releng_sub_%s_configs/%s_api_16_gradle_artifact.py',
|
||||
|
@ -45,6 +45,12 @@ code_coverage_config_options = [
|
||||
"default": False,
|
||||
"help": "Whether JSDebugger code coverage should be run."
|
||||
}],
|
||||
[["--java-code-coverage"],
|
||||
{"action": "store_true",
|
||||
"dest": "java_code_coverage",
|
||||
"default": False,
|
||||
"help": "Whether Java code coverage should be run."
|
||||
}],
|
||||
]
|
||||
|
||||
|
||||
@ -89,11 +95,14 @@ class CodeCoverageMixin(SingleTestMixin):
|
||||
except (AttributeError, KeyError, TypeError):
|
||||
return False
|
||||
|
||||
@PostScriptAction('download-and-extract')
|
||||
def setup_coverage_tools(self, action, success=None):
|
||||
if not self.code_coverage_enabled:
|
||||
return
|
||||
@property
|
||||
def java_code_coverage_enabled(self):
|
||||
try:
|
||||
return bool(self.config.get('java_code_coverage'))
|
||||
except (AttributeError, KeyError, TypeError):
|
||||
return False
|
||||
|
||||
def _setup_cpp_js_coverage_tools(self):
|
||||
if mozinfo.os == 'linux' or mozinfo.os == 'mac':
|
||||
self.prefix = '/builds/worker/workspace/build/src/'
|
||||
strip_count = self.prefix.count('/')
|
||||
@ -107,11 +116,34 @@ class CodeCoverageMixin(SingleTestMixin):
|
||||
|
||||
os.environ['GCOV_PREFIX_STRIP'] = str(strip_count)
|
||||
|
||||
# Install grcov on the test machine
|
||||
# Get the path to the build machines gcno files.
|
||||
self.url_to_gcno = self.query_build_dir_url('target.code-coverage-gcno.zip')
|
||||
self.url_to_chrome_map = self.query_build_dir_url('chrome-map.json')
|
||||
# Download the gcno archive from the build machine.
|
||||
url_to_gcno = self.query_build_dir_url('target.code-coverage-gcno.zip')
|
||||
self.download_file(url_to_gcno, parent_dir=self.grcov_dir)
|
||||
|
||||
# Download the chrome-map.json file from the build machine.
|
||||
url_to_chrome_map = self.query_build_dir_url('chrome-map.json')
|
||||
self.download_file(url_to_chrome_map, parent_dir=self.grcov_dir)
|
||||
|
||||
def _setup_java_coverage_tools(self):
|
||||
# Download and extract jacoco-cli from the build task.
|
||||
url_to_jacoco = self.query_build_dir_url('target.jacoco-cli.jar')
|
||||
self.jacoco_jar = os.path.join(tempfile.mkdtemp(), 'target.jacoco-cli.jar')
|
||||
self.download_file(url_to_jacoco, self.jacoco_jar)
|
||||
|
||||
# Download and extract class files from the build task.
|
||||
self.classfiles_dir = tempfile.mkdtemp()
|
||||
url_to_classfiles = self.query_build_dir_url('target.geckoview_classfiles.zip')
|
||||
classfiles_zip_path = os.path.join(self.classfiles_dir, 'target.geckoview_classfiles.zip')
|
||||
self.download_file(url_to_classfiles, classfiles_zip_path)
|
||||
with zipfile.ZipFile(classfiles_zip_path, 'r') as z:
|
||||
z.extractall(self.classfiles_dir)
|
||||
os.remove(classfiles_zip_path)
|
||||
|
||||
# Create the directory where the emulator coverage file will be placed.
|
||||
self.java_coverage_output_path = os.path.join(tempfile.mkdtemp(),
|
||||
'junit-coverage.ec')
|
||||
|
||||
def _download_grcov(self):
|
||||
fetches_dir = os.environ.get('MOZ_FETCHES_DIR')
|
||||
if fetches_dir and os.path.isfile(os.path.join(fetches_dir, 'grcov')):
|
||||
self.grcov_dir = fetches_dir
|
||||
@ -130,11 +162,18 @@ class CodeCoverageMixin(SingleTestMixin):
|
||||
tar.extractall(self.grcov_dir)
|
||||
os.remove(os.path.join(self.grcov_dir, filename))
|
||||
|
||||
# Download the gcno archive from the build machine.
|
||||
self.download_file(self.url_to_gcno, parent_dir=self.grcov_dir)
|
||||
@PostScriptAction('download-and-extract')
|
||||
def setup_coverage_tools(self, action, success=None):
|
||||
if not self.code_coverage_enabled and not self.java_code_coverage_enabled:
|
||||
return
|
||||
|
||||
# Download the chrome-map.json file from the build machine.
|
||||
self.download_file(self.url_to_chrome_map, parent_dir=self.grcov_dir)
|
||||
self._download_grcov()
|
||||
|
||||
if self.code_coverage_enabled:
|
||||
self._setup_cpp_js_coverage_tools()
|
||||
|
||||
if self.java_code_coverage_enabled:
|
||||
self._setup_java_coverage_tools()
|
||||
|
||||
@PostScriptAction('download-and-extract')
|
||||
def find_tests_for_coverage(self, action, success=None):
|
||||
@ -440,6 +479,49 @@ class CodeCoverageMixin(SingleTestMixin):
|
||||
|
||||
shutil.rmtree(self.grcov_dir)
|
||||
|
||||
@PostScriptAction('run-tests')
|
||||
def process_java_coverage_data(self, action, success=None):
|
||||
'''
|
||||
Run JaCoCo on the coverage.ec file in order to get a XML report.
|
||||
After that, run grcov on the XML report to get a lcov report.
|
||||
Finally, archive the lcov file and upload it, as process_coverage_data is doing.
|
||||
'''
|
||||
if not self.java_code_coverage_enabled:
|
||||
return
|
||||
|
||||
# If the emulator became unresponsive, the task has failed and we don't
|
||||
# have the coverage report file, so stop running this function and
|
||||
# allow the task to be retried automatically.
|
||||
if not success and not os.path.exists(self.java_coverage_output_path):
|
||||
return
|
||||
|
||||
dirs = self.query_abs_dirs()
|
||||
xml_path = tempfile.mkdtemp()
|
||||
jacoco_command = ['java', '-jar', self.jacoco_jar, 'report',
|
||||
self.java_coverage_output_path,
|
||||
'--classfiles', self.classfiles_dir,
|
||||
'--name', 'geckoview-junit',
|
||||
'--xml', os.path.join(xml_path, 'geckoview-junit.xml')]
|
||||
self.run_command(jacoco_command, halt_on_failure=True)
|
||||
|
||||
grcov_command = [
|
||||
os.path.join(self.grcov_dir, 'grcov'),
|
||||
'-t', 'lcov',
|
||||
xml_path,
|
||||
]
|
||||
tmp_output_file, _ = self.get_output_from_command(
|
||||
grcov_command,
|
||||
silent=True,
|
||||
save_tmpfiles=True,
|
||||
return_type='files',
|
||||
throw_exception=True,
|
||||
)
|
||||
|
||||
if not self.ccov_upload_disabled:
|
||||
grcov_zip_path = os.path.join(dirs['abs_blob_upload_dir'], 'code-coverage-grcov.zip')
|
||||
with zipfile.ZipFile(grcov_zip_path, 'w', zipfile.ZIP_DEFLATED) as z:
|
||||
z.write(tmp_output_file, 'grcov_lcov_output.info')
|
||||
|
||||
|
||||
def rm_baseline_cov(baseline_coverage, test_coverage):
|
||||
'''
|
||||
|
@ -26,7 +26,10 @@ from mozharness.base.script import BaseScript, PreScriptAction, PostScriptAction
|
||||
from mozharness.mozilla.automation import TBPL_RETRY, EXIT_STATUS_DICT
|
||||
from mozharness.mozilla.mozbase import MozbaseMixin
|
||||
from mozharness.mozilla.testing.testbase import TestingMixin, testing_config_options
|
||||
from mozharness.mozilla.testing.codecoverage import CodeCoverageMixin
|
||||
from mozharness.mozilla.testing.codecoverage import (
|
||||
CodeCoverageMixin,
|
||||
code_coverage_config_options
|
||||
)
|
||||
|
||||
|
||||
class AndroidEmulatorTest(TestingMixin, BaseScript, MozbaseMixin, CodeCoverageMixin):
|
||||
@ -77,7 +80,8 @@ class AndroidEmulatorTest(TestingMixin, BaseScript, MozbaseMixin, CodeCoverageMi
|
||||
"default": "info",
|
||||
"help": "Set log level (debug|info|warning|error|critical|fatal)",
|
||||
}
|
||||
]] + copy.deepcopy(testing_config_options)
|
||||
]] + copy.deepcopy(testing_config_options) + \
|
||||
copy.deepcopy(code_coverage_config_options)
|
||||
|
||||
app_name = None
|
||||
|
||||
@ -487,6 +491,10 @@ class AndroidEmulatorTest(TestingMixin, BaseScript, MozbaseMixin, CodeCoverageMi
|
||||
None,
|
||||
try_tests))
|
||||
|
||||
if self.java_code_coverage_enabled:
|
||||
cmd.extend(['--enable-coverage',
|
||||
'--coverage-output-path', self.java_coverage_output_path])
|
||||
|
||||
return cmd
|
||||
|
||||
def _get_repo_url(self, path):
|
||||
|
@ -1077,14 +1077,12 @@ class HiddenXULWindow {
|
||||
// The windowless browser is a thin wrapper around a docShell that keeps
|
||||
// its related resources alive. It implements nsIWebNavigation and
|
||||
// forwards its methods to the underlying docShell, but cannot act as a
|
||||
// docShell itself. Calling `getInterface(nsIDocShell)` gives us the
|
||||
// docShell itself. Getting .docShell gives us the
|
||||
// underlying docShell, and `QueryInterface(nsIWebNavigation)` gives us
|
||||
// access to the webNav methods that are already available on the
|
||||
// windowless browser, but contrary to appearances, they are not the same
|
||||
// object.
|
||||
this.chromeShell = this._windowlessBrowser
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDocShell)
|
||||
this.chromeShell = this._windowlessBrowser.docShell
|
||||
.QueryInterface(Ci.nsIWebNavigation);
|
||||
|
||||
if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
|
||||
|
@ -118,9 +118,8 @@ class ContentPage {
|
||||
|
||||
let system = Services.scriptSecurityManager.getSystemPrincipal();
|
||||
|
||||
let chromeShell = this.windowlessBrowser.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDocShell)
|
||||
.QueryInterface(Ci.nsIWebNavigation);
|
||||
let chromeShell = this.windowlessBrowser.docShell
|
||||
.QueryInterface(Ci.nsIWebNavigation);
|
||||
|
||||
chromeShell.createAboutBlankContentViewer(system);
|
||||
chromeShell.useGlobalHistory = false;
|
||||
|
@ -67,10 +67,10 @@ public:
|
||||
void UnregisterContentScript(const WebExtensionContentScript& script,
|
||||
ErrorResult& aRv);
|
||||
|
||||
bool CanAccessURI(const URLInfo& aURI, bool aExplicit = false) const
|
||||
bool CanAccessURI(const URLInfo& aURI, bool aExplicit = false, bool aCheckRestricted = true) const
|
||||
{
|
||||
return (!IsRestrictedURI(aURI) &&
|
||||
mHostPermissions && mHostPermissions->Matches(aURI, aExplicit));
|
||||
return (!aCheckRestricted || !IsRestrictedURI(aURI)) &&
|
||||
mHostPermissions && mHostPermissions->Matches(aURI, aExplicit);
|
||||
}
|
||||
|
||||
bool IsPathWebAccessible(const nsAString& aPath) const
|
||||
|
@ -708,9 +708,7 @@ this.downloads = class extends ExtensionAPI {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let chromeWebNav = Services.appShell.createWindowlessBrowser(true);
|
||||
chromeWebNav
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDocShell)
|
||||
chromeWebNav.docShell
|
||||
.createAboutBlankContentViewer(Services.scriptSecurityManager.getSystemPrincipal());
|
||||
|
||||
let img = chromeWebNav.document.createElement("img");
|
||||
|
@ -808,8 +808,7 @@ function loadImage(img, data) {
|
||||
|
||||
add_task(async function test_getFileIcon() {
|
||||
let webNav = Services.appShell.createWindowlessBrowser(false);
|
||||
let docShell = webNav.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDocShell);
|
||||
let docShell = webNav.docShell;
|
||||
|
||||
let system = Services.scriptSecurityManager.getSystemPrincipal();
|
||||
docShell.createAboutBlankContentViewer(system);
|
||||
|
@ -8,10 +8,10 @@ XPCOMUtils.defineLazyServiceGetter(this, "gProxyService",
|
||||
|
||||
const TRANSPARENT_PROXY_RESOLVES_HOST = Ci.nsIProxyInfo.TRANSPARENT_PROXY_RESOLVES_HOST;
|
||||
|
||||
function getProxyInfo() {
|
||||
function getProxyInfo(url = "http://www.mozilla.org/") {
|
||||
return new Promise((resolve, reject) => {
|
||||
let channel = NetUtil.newChannel({
|
||||
uri: "http://www.mozilla.org/",
|
||||
uri: url,
|
||||
loadUsingSystemPrincipal: true,
|
||||
});
|
||||
|
||||
@ -176,13 +176,14 @@ async function getExtension(expectedProxyInfo) {
|
||||
|
||||
add_task(async function test_passthrough() {
|
||||
let ext1 = await getExtension(null);
|
||||
let ext2 = await getExtension({host: "1.2.3.4", port: 8888, type: "http"});
|
||||
let ext2 = await getExtension({host: "1.2.3.4", port: 8888, type: "https"});
|
||||
|
||||
let proxyInfo = await getProxyInfo();
|
||||
// Also use a restricted url to test the ability to proxy those.
|
||||
let proxyInfo = await getProxyInfo("https://addons.mozilla.org/");
|
||||
|
||||
equal(proxyInfo.host, "1.2.3.4", `second extension won`);
|
||||
equal(proxyInfo.port, "8888", `second extension won`);
|
||||
equal(proxyInfo.type, "http", `second extension won`);
|
||||
equal(proxyInfo.type, "https", `second extension won`);
|
||||
|
||||
await ext2.unload();
|
||||
|
||||
|
@ -17,7 +17,7 @@ skip-if = os == "android" # Android does not use Places for history.
|
||||
skip-if = os == "android"
|
||||
[test_ext_browserSettings.js]
|
||||
[test_ext_browserSettings_homepage.js]
|
||||
skip-if = os == "android"
|
||||
skip-if = appname == "thunderbird" || os == "android"
|
||||
[test_ext_cookieBehaviors.js]
|
||||
[test_ext_cookies_samesite.js]
|
||||
[test_ext_content_security_policy.js]
|
||||
@ -31,21 +31,25 @@ skip-if = os == 'android' && debug # The generated script takes too long to load
|
||||
[test_ext_contentscript_restrictSchemes.js]
|
||||
[test_ext_contentscript_teardown.js]
|
||||
[test_ext_contextual_identities.js]
|
||||
skip-if = os == "android" # Containers are not exposed to android.
|
||||
skip-if = appname == "thunderbird" || os == "android" # Containers are not exposed to android.
|
||||
[test_ext_debugging_utils.js]
|
||||
[test_ext_dns.js]
|
||||
[test_ext_downloads.js]
|
||||
[test_ext_downloads_download.js]
|
||||
fail-if = appname == "thunderbird"
|
||||
skip-if = os == "android"
|
||||
[test_ext_downloads_misc.js]
|
||||
fail-if = appname == "thunderbird"
|
||||
skip-if = os == "android" || (os=='linux' && bits==32) # linux32: bug 1324870
|
||||
[test_ext_downloads_private.js]
|
||||
skip-if = os == "android"
|
||||
skip-if = appname == "thunderbird" || os == "android"
|
||||
[test_ext_downloads_search.js]
|
||||
fail-if = appname == "thunderbird"
|
||||
skip-if = os == "android"
|
||||
[test_ext_error_location.js]
|
||||
[test_ext_eventpage_warning.js]
|
||||
[test_ext_experiments.js]
|
||||
fail-if = appname == "thunderbird"
|
||||
[test_ext_extension.js]
|
||||
[test_ext_extensionPreferencesManager.js]
|
||||
[test_ext_extensionSettingsStore.js]
|
||||
@ -61,18 +65,22 @@ skip-if = os == "android" # checking for telemetry needs to be updated: 1384923
|
||||
skip-if = (os == "win" && !debug) #Bug 1419183 disable on Windows
|
||||
[test_ext_management_uninstall_self.js]
|
||||
[test_ext_messaging_startup.js]
|
||||
skip-if = appname == "thunderbird"
|
||||
[test_ext_onmessage_removelistener.js]
|
||||
skip-if = true # This test no longer tests what it is meant to test.
|
||||
[test_ext_permission_xhr.js]
|
||||
[test_ext_persistent_events.js]
|
||||
[test_ext_privacy.js]
|
||||
skip-if = appname == "thunderbird"
|
||||
[test_ext_privacy_disable.js]
|
||||
skip-if = appname == "thunderbird"
|
||||
[test_ext_privacy_update.js]
|
||||
[test_ext_proxy_auth.js]
|
||||
[test_ext_proxy_config.js]
|
||||
skip-if = appname == "thunderbird"
|
||||
[test_ext_proxy_onauthrequired.js]
|
||||
[test_ext_proxy_settings.js]
|
||||
skip-if = os == "android" # proxy settings are not supported on android
|
||||
skip-if = appname == "thunderbird" || os == "android" # proxy settings are not supported on android
|
||||
[test_ext_proxy_socks.js]
|
||||
[test_ext_proxy_speculative.js]
|
||||
[test_ext_proxy_startup.js]
|
||||
@ -103,9 +111,9 @@ skip-if = os == "android"
|
||||
skip-if = os == "android"
|
||||
[test_ext_storage_sync.js]
|
||||
head = head.js head_sync.js
|
||||
skip-if = os == "android"
|
||||
skip-if = appname == "thunderbird" || os == "android"
|
||||
[test_ext_storage_sync_crypto.js]
|
||||
skip-if = os == "android"
|
||||
skip-if = appname == "thunderbird" || os == "android"
|
||||
[test_ext_storage_tab.js]
|
||||
[test_ext_storage_telemetry.js]
|
||||
skip-if = os == "android" # checking for telemetry needs to be updated: 1384923
|
||||
@ -121,18 +129,20 @@ skip-if = true # Too frequent intermittent failures
|
||||
[test_ext_webRequest_permission.js]
|
||||
[test_ext_webRequest_responseBody.js]
|
||||
[test_ext_webRequest_set_cookie.js]
|
||||
skip-if = appname == "thunderbird"
|
||||
[test_ext_webRequest_startup.js]
|
||||
[test_ext_webRequest_suspend.js]
|
||||
[test_ext_webRequest_webSocket.js]
|
||||
skip-if = appname == "thunderbird"
|
||||
[test_ext_xhr_capabilities.js]
|
||||
[test_native_manifests.js]
|
||||
subprocess = true
|
||||
skip-if = os == "android"
|
||||
[test_ext_permissions.js]
|
||||
skip-if = os == "android" # Bug 1350559
|
||||
skip-if = appname == "thunderbird" || os == "android" # Bug 1350559
|
||||
[test_proxy_listener.js]
|
||||
[test_proxy_scripts.js]
|
||||
[test_proxy_scripts_results.js]
|
||||
[test_ext_brokenlinks.js]
|
||||
[test_ext_performance_counters.js]
|
||||
skip-if = os == "android"
|
||||
skip-if = appname == "thunderbird" || os == "android"
|
||||
|
@ -1,7 +1,6 @@
|
||||
[DEFAULT]
|
||||
head = head.js head_telemetry.js head_storage.js
|
||||
firefox-appdir = browser
|
||||
skip-if = appname == "thunderbird"
|
||||
dupe-manifest =
|
||||
support-files =
|
||||
data/**
|
||||
|
@ -521,13 +521,14 @@ ChannelWrapper::Matches(const dom::MozRequestFilter& aFilter,
|
||||
}
|
||||
|
||||
if (aExtension) {
|
||||
if (!aExtension->CanAccessURI(urlInfo)) {
|
||||
bool isProxy = aOptions.mIsProxy && aExtension->HasPermission(nsGkAtoms::proxy);
|
||||
// Proxies are allowed access to all urls, including restricted urls.
|
||||
if (!aExtension->CanAccessURI(urlInfo, false, !isProxy)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If this isn't the proxy phase of the request, check that the extension
|
||||
// has origin permissions for origin that originated the request.
|
||||
bool isProxy = aOptions.mIsProxy && aExtension->HasPermission(nsGkAtoms::proxy);
|
||||
if (!isProxy) {
|
||||
if (IsSystemLoad()) {
|
||||
return false;
|
||||
|
@ -212,6 +212,8 @@ AboutReader.prototype = {
|
||||
btn.title = message.data.title;
|
||||
if (message.data.text)
|
||||
btn.textContent = message.data.text;
|
||||
if (message.data.width && message.data.height)
|
||||
btn.style.backgroundSize = `${message.data.width}px ${message.data.height}px`;
|
||||
let tb = this._toolbarElement;
|
||||
tb.appendChild(btn);
|
||||
this._setupButton(message.data.id, button => {
|
||||
|
@ -59,9 +59,7 @@ find-menu appears in editor element which has had makeEditable() called but desi
|
||||
|
||||
async function onDocumentLoaded() {
|
||||
await ContentTask.spawn(gBrowser, null, async function() {
|
||||
var edsession = content.docShell
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIEditingSession);
|
||||
var edsession = content.docShell.editingSession;
|
||||
edsession.makeWindowEditable(content, "html", false, true, false);
|
||||
content.focus();
|
||||
});
|
||||
|
@ -131,7 +131,7 @@
|
||||
onget="return this.docShell.contentViewer;"/>
|
||||
<property name="editingSession"
|
||||
readonly="true"
|
||||
onget="return this.webNavigation.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIEditingSession);"/>
|
||||
onget="return this.docShell.editingSession"/>
|
||||
<property name="commandManager"
|
||||
readonly="true"
|
||||
onget="return this.webNavigation.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsICommandManager);"/>
|
||||
|
@ -104,7 +104,7 @@ HiddenFrame.prototype = {
|
||||
}
|
||||
};
|
||||
this._webProgress.addProgressListener(this._listener, Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
|
||||
let docShell = this._browser.getInterface(Ci.nsIDocShell);
|
||||
let docShell = this._browser.docShell;
|
||||
docShell.createAboutBlankContentViewer(Services.scriptSecurityManager.getSystemPrincipal());
|
||||
docShell.useGlobalHistory = false;
|
||||
this._browser.loadURI(XUL_PAGE, 0, null, null, null);
|
||||
|
@ -419,9 +419,7 @@ var SpellCheckHelper = {
|
||||
if (win) {
|
||||
var isSpellcheckable = false;
|
||||
try {
|
||||
var editingSession = win.docShell
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIEditingSession);
|
||||
var editingSession = win.docShell.editingSession;
|
||||
if (editingSession.windowIsEditable(win) &&
|
||||
this.getComputedStyle(element, "-moz-user-modify") == "read-write") {
|
||||
isSpellcheckable = true;
|
||||
|
@ -21,9 +21,7 @@ var InlineSpellCheckerContent = {
|
||||
if (!(editFlags & (SpellCheckHelper.TEXTAREA | SpellCheckHelper.INPUT))) {
|
||||
// Get the editor off the window.
|
||||
let win = event.target.ownerGlobal;
|
||||
let editingSession = win.docShell
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIEditingSession);
|
||||
let editingSession = win.docShell.editingSession;
|
||||
spellChecker = this._spellChecker =
|
||||
new InlineSpellChecker(editingSession.getEditorForWindow(win));
|
||||
} else {
|
||||
|
@ -18,6 +18,26 @@ XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
Preferences: "resource://gre/modules/Preferences.jsm",
|
||||
});
|
||||
|
||||
// The current platform as specified in the AMO API:
|
||||
// http://addons-server.readthedocs.io/en/latest/topics/api/addons.html#addon-detail-platform
|
||||
XPCOMUtils.defineLazyGetter(this, "PLATFORM", () => {
|
||||
let platform = Services.appinfo.OS;
|
||||
switch (platform) {
|
||||
case "Darwin":
|
||||
return "mac";
|
||||
|
||||
case "Linux":
|
||||
return "linux";
|
||||
|
||||
case "Android":
|
||||
return "android";
|
||||
|
||||
case "WINNT":
|
||||
return "windows";
|
||||
}
|
||||
return platform;
|
||||
});
|
||||
|
||||
var EXPORTED_SYMBOLS = [ "AddonRepository" ];
|
||||
|
||||
const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled";
|
||||
@ -582,7 +602,7 @@ var AddonRepository = {
|
||||
addon.version = String(aEntry.current_version.version);
|
||||
if (Array.isArray(aEntry.current_version.files)) {
|
||||
for (let file of aEntry.current_version.files) {
|
||||
if (file.platform == "all" || file.platform == Services.appinfo.OS.toLowerCase()) {
|
||||
if (file.platform == "all" || file.platform == PLATFORM) {
|
||||
if (file.url) {
|
||||
addon.sourceURI = NetUtil.newURI(file.url);
|
||||
}
|
||||
|
@ -59,6 +59,21 @@
|
||||
"weekly_downloads": 3333,
|
||||
"last_updated": "2010-02-01T14:04:05Z"
|
||||
},
|
||||
{
|
||||
"name": "PASS",
|
||||
"type": "extension",
|
||||
"guid": "test2@tests.mozilla.org",
|
||||
"current_version": {
|
||||
"version": "2.0",
|
||||
"files": [
|
||||
{
|
||||
"platform": "XPCShell",
|
||||
"url": "http://example.com/addons/bleah.xpi",
|
||||
"size": 1000
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "FAIL",
|
||||
"type": "extension",
|
||||
|
@ -123,6 +123,12 @@ var GET_RESULTS = [{
|
||||
weeklyDownloads: 3333,
|
||||
sourceURI: BASE_URL + INSTALL_URL2,
|
||||
updateDate: new Date(1265033045000),
|
||||
}, {
|
||||
id: "test2@tests.mozilla.org",
|
||||
type: "extension",
|
||||
version: "2.0",
|
||||
icons: {},
|
||||
sourceURI: "http://example.com/addons/bleah.xpi",
|
||||
}, {
|
||||
id: "test_AddonRepository_1@tests.mozilla.org",
|
||||
type: "theme",
|
||||
@ -137,9 +143,11 @@ var GET_TEST = {
|
||||
failedIDs: ["test1@tests.mozilla.org"],
|
||||
failedURL: "/XPCShell/1/test1%40tests.mozilla.org",
|
||||
successfulIDs: ["test1@tests.mozilla.org",
|
||||
"{00000000-1111-2222-3333-444444444444}",
|
||||
"test_AddonRepository_1@tests.mozilla.org"],
|
||||
"test2@tests.mozilla.org",
|
||||
"{00000000-1111-2222-3333-444444444444}",
|
||||
"test_AddonRepository_1@tests.mozilla.org"],
|
||||
successfulURL: "/XPCShell/1/test1%40tests.mozilla.org%2C" +
|
||||
"test2%40tests.mozilla.org%2C" +
|
||||
"%7B00000000-1111-2222-3333-444444444444%7D%2C" +
|
||||
"test_AddonRepository_1%40tests.mozilla.org"
|
||||
};
|
||||
|
@ -44,8 +44,6 @@ async function tearDownAddon(addon) {
|
||||
}
|
||||
|
||||
add_task(async function test_reloading_a_temp_addon() {
|
||||
if (AppConstants.MOZ_APP_NAME == "thunderbird")
|
||||
return;
|
||||
await promiseRestartManager();
|
||||
let xpi = AddonTestUtils.createTempXPIFile(ADDONS.webextension_1);
|
||||
const addon = await AddonManager.installTemporaryAddon(xpi);
|
||||
|
@ -763,8 +763,7 @@ add_task(async function test_replace_permanent_disabled() {
|
||||
});
|
||||
|
||||
// Tests that XPIs with a .zip extension work when loaded temporarily.
|
||||
add_task({ skip_if: () => AppConstants.MOZ_APP_NAME == "thunderbird" },
|
||||
async function test_zip_extension() {
|
||||
add_task(async function test_zip_extension() {
|
||||
let xpi = createTempWebExtensionFile({
|
||||
background() {
|
||||
/* globals browser */
|
||||
|
@ -84,7 +84,6 @@ tags = blocklist
|
||||
head =
|
||||
[test_delay_update.js]
|
||||
[test_delay_update_webextension.js]
|
||||
skip-if = appname == "thunderbird"
|
||||
tags = webextensions
|
||||
[test_dependencies.js]
|
||||
[test_dictionary.js]
|
||||
@ -95,7 +94,6 @@ tags = webextensions
|
||||
skip-if = os == "android"
|
||||
[test_error.js]
|
||||
[test_ext_management.js]
|
||||
skip-if = appname == "thunderbird"
|
||||
tags = webextensions
|
||||
[test_general.js]
|
||||
[test_getresource.js]
|
||||
@ -222,7 +220,6 @@ skip-if = os == "mac" && debug
|
||||
[test_system_update_empty.js]
|
||||
skip-if = true # Failing intermittently due to a race condition in the test, see bug 1348981
|
||||
[test_system_update_enterprisepolicy.js]
|
||||
skip-if = appname == "thunderbird"
|
||||
[test_system_update_fail.js]
|
||||
[test_system_update_newset.js]
|
||||
[test_system_update_overlapping.js]
|
||||
@ -258,25 +255,18 @@ skip-if = os == "android"
|
||||
run-sequentially = Uses global XCurProcD dir.
|
||||
[test_upgrade_incompatible.js]
|
||||
[test_webextension.js]
|
||||
skip-if = appname == "thunderbird"
|
||||
tags = webextensions
|
||||
[test_webextension_embedded.js]
|
||||
skip-if = appname == "thunderbird"
|
||||
tags = webextensions
|
||||
[test_webextension_events.js]
|
||||
skip-if = appname == "thunderbird"
|
||||
tags = webextensions
|
||||
[test_webextension_icons.js]
|
||||
skip-if = appname == "thunderbird"
|
||||
tags = webextensions
|
||||
[test_webextension_install.js]
|
||||
skip-if = appname == "thunderbird"
|
||||
tags = webextensions
|
||||
[test_webextension_install_syntax_error.js]
|
||||
skip-if = appname == "thunderbird"
|
||||
tags = webextensions
|
||||
[test_webextension_langpack.js]
|
||||
skip-if = appname == "thunderbird"
|
||||
tags = webextensions
|
||||
[test_webextension_theme.js]
|
||||
tags = webextensions
|
||||
|