зеркало из https://github.com/mozilla/gecko-dev.git
Merge inbound to m-c, a=merge
MozReview-Commit-ID: LCCoXUsCtmv
This commit is contained in:
Коммит
9359f5bf39
|
@ -305,6 +305,10 @@ endif
|
|||
update-packaging:
|
||||
$(MAKE) -C tools/update-packaging
|
||||
|
||||
.PHONY: package-generated-sources
|
||||
package-generated-sources:
|
||||
$(call py_action,package_generated_sources,'$(DIST)/$(PKG_PATH)$(GENERATED_SOURCE_FILE_PACKAGE)')
|
||||
|
||||
#XXX: this is a hack, since we don't want to clobber for MSVC
|
||||
# PGO support, but we can't do this test in client.mk
|
||||
ifneq ($(OS_ARCH)_$(GNU_CC), WINNT_)
|
||||
|
|
|
@ -326,6 +326,10 @@ toolbarpaletteitem > toolbaritem[sdkstylewidget="true"][cui-areatype="toolbar"]
|
|||
display: -moz-box;
|
||||
}
|
||||
|
||||
.webextension-browser-action > .toolbarbutton-badge-stack > .toolbarbutton-icon {
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
@media not all and (min-resolution: 1.1dppx) {
|
||||
.webextension-browser-action {
|
||||
list-style-image: var(--webextension-toolbar-image, inherit);
|
||||
|
|
|
@ -177,7 +177,7 @@ this.browserAction = class extends ExtensionAPI {
|
|||
node.onmouseover = event => this.handleEvent(event);
|
||||
node.onmouseout = event => this.handleEvent(event);
|
||||
|
||||
this.updateButton(node, this.defaults);
|
||||
this.updateButton(node, this.defaults, true);
|
||||
},
|
||||
|
||||
onViewShowing: async event => {
|
||||
|
@ -429,10 +429,9 @@ this.browserAction = class extends ExtensionAPI {
|
|||
|
||||
// Update the toolbar button |node| with the tab context data
|
||||
// in |tabData|.
|
||||
updateButton(node, tabData) {
|
||||
updateButton(node, tabData, sync = false) {
|
||||
let title = tabData.title || this.extension.name;
|
||||
|
||||
node.ownerGlobal.requestAnimationFrame(() => {
|
||||
let callback = () => {
|
||||
node.setAttribute("tooltiptext", title);
|
||||
node.setAttribute("label", title);
|
||||
|
||||
|
@ -467,7 +466,12 @@ this.browserAction = class extends ExtensionAPI {
|
|||
}
|
||||
|
||||
node.setAttribute("style", style);
|
||||
});
|
||||
};
|
||||
if (sync) {
|
||||
callback();
|
||||
} else {
|
||||
node.ownerGlobal.requestAnimationFrame(callback);
|
||||
}
|
||||
}
|
||||
|
||||
getIconData(icons) {
|
||||
|
|
|
@ -151,7 +151,9 @@
|
|||
{ "$ref": "ImageDataType" },
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": {"$ref": "ImageDataType"}
|
||||
"patternProperties": {
|
||||
"^[1-9]\\d*$": {"$ref": "ImageDataType"}
|
||||
}
|
||||
}
|
||||
],
|
||||
"optional": true,
|
||||
|
@ -162,7 +164,9 @@
|
|||
{ "type": "string" },
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": {"type": "string"}
|
||||
"patternProperties": {
|
||||
"^[1-9]\\d*$": { "type": "string" }
|
||||
}
|
||||
}
|
||||
],
|
||||
"optional": true,
|
||||
|
|
|
@ -143,7 +143,9 @@
|
|||
{ "$ref": "ImageDataType" },
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": {"$ref": "ImageDataType"}
|
||||
"patternProperties": {
|
||||
"^[1-9]\\d*$": {"$ref": "ImageDataType"}
|
||||
}
|
||||
}
|
||||
],
|
||||
"optional": true,
|
||||
|
@ -154,7 +156,9 @@
|
|||
{ "type": "string" },
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": {"type": "string"}
|
||||
"patternProperties": {
|
||||
"^[1-9]\\d*$": { "type": "string" }
|
||||
}
|
||||
}
|
||||
],
|
||||
"optional": true,
|
||||
|
|
|
@ -19,11 +19,10 @@ add_task(async function testInvalidIconSizes() {
|
|||
// helper function to run setIcon and check if it fails
|
||||
let assertSetIconThrows = function(detail, error, message) {
|
||||
detail.tabId = tabId;
|
||||
promises.push(
|
||||
browser.test.assertRejects(
|
||||
browser[api].setIcon(detail),
|
||||
/must be an integer/,
|
||||
"setIcon with invalid icon size"));
|
||||
browser.test.assertThrows(
|
||||
() => browser[api].setIcon(detail),
|
||||
/an unexpected .* property/,
|
||||
"setIcon with invalid icon size");
|
||||
};
|
||||
|
||||
let imageData = new ImageData(1, 1);
|
||||
|
|
|
@ -138,14 +138,21 @@ function needHomepageOverride(prefb) {
|
|||
function getPostUpdateOverridePage(defaultOverridePage) {
|
||||
var um = Components.classes["@mozilla.org/updates/update-manager;1"]
|
||||
.getService(Components.interfaces.nsIUpdateManager);
|
||||
try {
|
||||
// If the updates.xml file is deleted then getUpdateAt will throw.
|
||||
var update = um.getUpdateAt(0)
|
||||
// The active update should be present when this code is called. If for
|
||||
// whatever reason it isn't fallback to the latest update in the update
|
||||
// history.
|
||||
if (um.activeUpdate) {
|
||||
var update = um.activeUpdate
|
||||
.QueryInterface(Components.interfaces.nsIPropertyBag);
|
||||
} catch (e) {
|
||||
// This should never happen.
|
||||
Components.utils.reportError("Unable to find update: " + e);
|
||||
return defaultOverridePage;
|
||||
} else {
|
||||
// If the updates.xml file is deleted then getUpdateAt will throw.
|
||||
try {
|
||||
update = um.getUpdateAt(0)
|
||||
.QueryInterface(Components.interfaces.nsIPropertyBag);
|
||||
} catch (e) {
|
||||
Components.utils.reportError("Unable to find update: " + e);
|
||||
return defaultOverridePage;
|
||||
}
|
||||
}
|
||||
|
||||
let actions = update.getProperty("actions");
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
MOZ_AUTOMATION_BUILD_SYMBOLS=0
|
||||
MOZ_AUTOMATION_L10N_CHECK=0
|
||||
. "$topsrcdir/build/mozconfig.artifact.automation"
|
||||
|
||||
. "$topsrcdir/browser/config/mozconfigs/linux32/common-opt"
|
||||
. "$topsrcdir/build/mozconfig.common.override"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
MOZ_AUTOMATION_BUILD_SYMBOLS=0
|
||||
MOZ_AUTOMATION_L10N_CHECK=0
|
||||
. "$topsrcdir/build/mozconfig.artifact.automation"
|
||||
|
||||
. "$topsrcdir/build/unix/mozconfig.linux32"
|
||||
. "$topsrcdir/build/mozconfig.common.override"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
MOZ_AUTOMATION_BUILD_SYMBOLS=0
|
||||
MOZ_AUTOMATION_L10N_CHECK=0
|
||||
. "$topsrcdir/build/mozconfig.artifact.automation"
|
||||
|
||||
. "$topsrcdir/browser/config/mozconfigs/linux64/common-opt"
|
||||
. "$topsrcdir/build/mozconfig.common.override"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
MOZ_AUTOMATION_BUILD_SYMBOLS=0
|
||||
MOZ_AUTOMATION_L10N_CHECK=0
|
||||
. "$topsrcdir/build/mozconfig.artifact.automation"
|
||||
|
||||
. "$topsrcdir/build/unix/mozconfig.linux"
|
||||
. "$topsrcdir/build/mozconfig.common.override"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
MOZ_AUTOMATION_BUILD_SYMBOLS=0
|
||||
MOZ_AUTOMATION_L10N_CHECK=0
|
||||
. "$topsrcdir/build/mozconfig.artifact.automation"
|
||||
|
||||
# Needed to set SourceRepository in application.ini (used by Talos)
|
||||
export MOZILLA_OFFICIAL=1
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
MOZ_AUTOMATION_BUILD_SYMBOLS=0
|
||||
MOZ_AUTOMATION_L10N_CHECK=0
|
||||
. "$topsrcdir/build/mozconfig.artifact.automation"
|
||||
|
||||
. "$topsrcdir/build/macosx/mozconfig.common"
|
||||
. "$topsrcdir/build/mozconfig.common.override"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
MOZ_AUTOMATION_BUILD_SYMBOLS=0
|
||||
MOZ_AUTOMATION_L10N_CHECK=0
|
||||
. "$topsrcdir/build/mozconfig.artifact.automation"
|
||||
|
||||
# Needed to set SourceRepository in application.ini (used by Talos)
|
||||
export MOZILLA_OFFICIAL=1
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
MOZ_AUTOMATION_BUILD_SYMBOLS=0
|
||||
MOZ_AUTOMATION_L10N_CHECK=0
|
||||
. "$topsrcdir/build/mozconfig.artifact.automation"
|
||||
|
||||
. "$topsrcdir/browser/config/mozconfigs/common"
|
||||
. "$topsrcdir/build/mozconfig.win-common"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
MOZ_AUTOMATION_BUILD_SYMBOLS=0
|
||||
MOZ_AUTOMATION_L10N_CHECK=0
|
||||
. "$topsrcdir/build/mozconfig.artifact.automation"
|
||||
|
||||
# Needed to set SourceRepository in application.ini (used by Talos)
|
||||
export MOZILLA_OFFICIAL=1
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
MOZ_AUTOMATION_BUILD_SYMBOLS=0
|
||||
MOZ_AUTOMATION_L10N_CHECK=0
|
||||
. "$topsrcdir/build/mozconfig.artifact.automation"
|
||||
|
||||
. "$topsrcdir/browser/config/mozconfigs/win64/common-win64"
|
||||
. "$topsrcdir/browser/config/mozconfigs/common"
|
||||
|
|
|
@ -20,6 +20,7 @@ const BRAND_SHORT_NAME = Services.strings
|
|||
.createBundle("chrome://branding/locale/brand.properties")
|
||||
.GetStringFromName("brandShortName");
|
||||
const PROMPT_COUNT_PREF = "browser.onboarding.notification.prompt-count";
|
||||
const ONBOARDING_DIALOG_ID = "onboarding-overlay-dialog";
|
||||
|
||||
/**
|
||||
* Add any number of tours, key is the tourId, value should follow the format below
|
||||
|
@ -368,6 +369,7 @@ class Onboarding {
|
|||
let { body } = this._window.document;
|
||||
this._overlayIcon = this._renderOverlayButton();
|
||||
this._overlayIcon.addEventListener("click", this);
|
||||
this._overlayIcon.addEventListener("keypress", this);
|
||||
body.insertBefore(this._overlayIcon, body.firstChild);
|
||||
|
||||
this._overlay = this._renderOverlay();
|
||||
|
@ -436,6 +438,15 @@ class Onboarding {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a tour that should be selected. It is either a first tour that was not
|
||||
* yet complete or the first one in the tab list.
|
||||
*/
|
||||
get selectedTour() {
|
||||
return this._tours.find(tour => !this.isTourCompleted(tour.id)) ||
|
||||
this._tours[0];
|
||||
}
|
||||
|
||||
handleClick(target) {
|
||||
let { id, classList } = target;
|
||||
// Only containers receive pointer events in onboarding tour tab list,
|
||||
|
@ -452,8 +463,7 @@ class Onboarding {
|
|||
// Let's toggle the overlay.
|
||||
case "onboarding-overlay":
|
||||
this.toggleOverlay();
|
||||
let selectedTour = this._tours.find(tour => !this.isTourCompleted(tour.id)) || this._tours[0];
|
||||
this.gotoPage(selectedTour.id);
|
||||
this.gotoPage(this.selectedTour.id);
|
||||
break;
|
||||
case "onboarding-notification-close-btn":
|
||||
this.hideNotification();
|
||||
|
@ -477,8 +487,46 @@ class Onboarding {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap keyboard focus within the dialog and focus on first element after last
|
||||
* when moving forward or last element after first when moving backwards. Do
|
||||
* nothing if focus is moving in the middle of the list of dialog's focusable
|
||||
* elements.
|
||||
*
|
||||
* @param {DOMNode} current currently focused element
|
||||
* @param {Boolean} back direction
|
||||
* @return {DOMNode} newly focused element if any
|
||||
*/
|
||||
wrapMoveFocus(current, back) {
|
||||
let elms = [...this._dialog.querySelectorAll(
|
||||
`button, input[type="checkbox"], input[type="email"], [tabindex="0"]`)];
|
||||
let next;
|
||||
if (back) {
|
||||
if (elms.indexOf(current) === 0) {
|
||||
next = elms[elms.length - 1];
|
||||
next.focus();
|
||||
}
|
||||
} else if (elms.indexOf(current) === elms.length - 1) {
|
||||
next = elms[0];
|
||||
next.focus();
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
handleKeypress(event) {
|
||||
let { target, key } = event;
|
||||
let { target, key, shiftKey } = event;
|
||||
|
||||
if (target === this._overlayIcon) {
|
||||
if ([" ", "Enter"].includes(key)) {
|
||||
// Remember that the dialog was opened with a keyboard.
|
||||
this._overlayIcon.dataset.keyboardFocus = true;
|
||||
this.handleClick(target);
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Current focused item can be tab container if previous navigation was done
|
||||
// via mouse.
|
||||
if (target.classList.contains("onboarding-tour-item-container")) {
|
||||
|
@ -515,6 +563,16 @@ class Onboarding {
|
|||
}
|
||||
event.preventDefault();
|
||||
break;
|
||||
case "Escape":
|
||||
this.toggleOverlay();
|
||||
break;
|
||||
case "Tab":
|
||||
let next = this.wrapMoveFocus(target, shiftKey);
|
||||
// If focus was wrapped, prevent Tab key default action.
|
||||
if (next) {
|
||||
event.preventDefault();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -564,6 +622,7 @@ class Onboarding {
|
|||
|
||||
this.hideNotification();
|
||||
this._overlay.classList.toggle("onboarding-opened");
|
||||
this.toggleModal(this._overlay.classList.contains("onboarding-opened"));
|
||||
|
||||
let hiddenCheckbox = this._window.document.getElementById("onboarding-tour-hidden-checkbox");
|
||||
if (hiddenCheckbox.checked) {
|
||||
|
@ -571,6 +630,41 @@ class Onboarding {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set modal dialog state and properties for accessibility purposes.
|
||||
* @param {Boolean} opened whether the dialog is opened or closed.
|
||||
*/
|
||||
toggleModal(opened) {
|
||||
let { document: doc } = this._window;
|
||||
if (opened) {
|
||||
// Set aria-hidden to true for the rest of the document.
|
||||
[...doc.body.children].forEach(
|
||||
child => child.id !== "onboarding-overlay" &&
|
||||
child.setAttribute("aria-hidden", true));
|
||||
// When dialog is opened with the keyboard, focus on the selected or
|
||||
// first tour item.
|
||||
if (this._overlayIcon.dataset.keyboardFocus) {
|
||||
doc.getElementById(this.selectedTour.id).focus();
|
||||
} else {
|
||||
// When dialog is opened with mouse, focus on the dialog itself to avoid
|
||||
// visible keyboard focus styling.
|
||||
this._dialog.focus();
|
||||
}
|
||||
} else {
|
||||
// Remove all set aria-hidden attributes.
|
||||
[...doc.body.children].forEach(
|
||||
child => child.removeAttribute("aria-hidden"));
|
||||
// If dialog was opened with a keyboard, set the focus back on the overlay
|
||||
// button.
|
||||
if (this._overlayIcon.dataset.keyboardFocus) {
|
||||
delete this._overlayIcon.dataset.keyboardFocus;
|
||||
this._overlayIcon.focus();
|
||||
} else {
|
||||
this._window.document.activeElement.blur();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gotoPage(tourId) {
|
||||
let targetPageId = `${tourId}-page`;
|
||||
for (let page of this._tourPages) {
|
||||
|
@ -868,7 +962,7 @@ class Onboarding {
|
|||
// We use `innerHTML` for more friendly reading.
|
||||
// The security should be fine because this is not from an external input.
|
||||
div.innerHTML = `
|
||||
<div id="onboarding-overlay-dialog">
|
||||
<div role="dialog" tabindex="-1" aria-labelledby="onboarding-header">
|
||||
<header id="onboarding-header"></header>
|
||||
<nav>
|
||||
<ul id="onboarding-tour-list" role="tablist"></ul>
|
||||
|
@ -880,6 +974,9 @@ class Onboarding {
|
|||
</div>
|
||||
`;
|
||||
|
||||
this._dialog = div.querySelector(`[role="dialog"]`);
|
||||
this._dialog.id = ONBOARDING_DIALOG_ID;
|
||||
|
||||
div.querySelector("label[for='onboarding-tour-hidden-checkbox']").textContent =
|
||||
this._bundle.GetStringFromName("onboarding.hidden-checkbox-label-text");
|
||||
div.querySelector("#onboarding-header").textContent =
|
||||
|
@ -898,7 +995,7 @@ class Onboarding {
|
|||
button.setAttribute("aria-label", tooltip);
|
||||
button.id = "onboarding-overlay-button";
|
||||
button.setAttribute("aria-haspopup", true);
|
||||
button.setAttribute("aria-controls", "onboarding-overlay-dialog");
|
||||
button.setAttribute("aria-controls", `${ONBOARDING_DIALOG_ID}`);
|
||||
let img = this._window.document.createElement("img");
|
||||
img.id = "onboarding-overlay-button-icon";
|
||||
img.setAttribute("role", "presentation");
|
||||
|
@ -959,11 +1056,10 @@ class Onboarding {
|
|||
this.markTourCompletionState(tour.id);
|
||||
}
|
||||
|
||||
let dialog = this._window.document.getElementById("onboarding-overlay-dialog");
|
||||
let ul = this._window.document.getElementById("onboarding-tour-list");
|
||||
ul.appendChild(itemsFrag);
|
||||
let footer = this._window.document.getElementById("onboarding-footer");
|
||||
dialog.insertBefore(pagesFrag, footer);
|
||||
this._dialog.insertBefore(pagesFrag, footer);
|
||||
}
|
||||
|
||||
_loadCSS() {
|
||||
|
|
|
@ -63,3 +63,30 @@ add_task(async function test_onboarding_notification_bar() {
|
|||
|
||||
await BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
||||
add_task(async function test_onboarding_overlay_dialog() {
|
||||
resetOnboardingDefaultState();
|
||||
|
||||
info("Wait for onboarding overlay loaded");
|
||||
let tab = await openTab(ABOUT_HOME_URL);
|
||||
let browser = tab.linkedBrowser;
|
||||
await promiseOnboardingOverlayLoaded(browser);
|
||||
|
||||
info("Test accessibility and semantics of the dialog overlay");
|
||||
await assertModalDialog(browser, { visible: false });
|
||||
|
||||
info("Click on overlay button and check modal dialog state");
|
||||
await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button",
|
||||
{}, browser);
|
||||
await promiseOnboardingOverlayOpened(browser);
|
||||
await assertModalDialog(browser,
|
||||
{ visible: true, focusedId: "onboarding-overlay-dialog" });
|
||||
|
||||
info("Close the dialog and check modal dialog state");
|
||||
await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-close-btn",
|
||||
{}, browser);
|
||||
await promiseOnboardingOverlayClosed(browser);
|
||||
await assertModalDialog(browser, { visible: false });
|
||||
|
||||
await BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
|
|
@ -4,19 +4,29 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
function assertTourList(browser, args) {
|
||||
return ContentTask.spawn(browser, args, ({ tourId, focusedId }) => {
|
||||
let doc = content.document;
|
||||
let items = [...doc.querySelectorAll(".onboarding-tour-item")];
|
||||
items.forEach(item => is(item.getAttribute("aria-selected"),
|
||||
item.id === tourId ? "true" : "false",
|
||||
"Active item should have aria-selected set to true and inactive to false"));
|
||||
let focused = doc.getElementById(focusedId);
|
||||
is(focused, doc.activeElement, `Focus should be set on ${focusedId}`);
|
||||
function assertOverlayState(browser, args) {
|
||||
return ContentTask.spawn(browser, args, ({ tourId, focusedId, visible }) => {
|
||||
let { document: doc, window} = content;
|
||||
if (tourId) {
|
||||
let items = [...doc.querySelectorAll(".onboarding-tour-item")];
|
||||
items.forEach(item => is(item.getAttribute("aria-selected"),
|
||||
item.id === tourId ? "true" : "false",
|
||||
"Active item should have aria-selected set to true and inactive to false"));
|
||||
}
|
||||
if (focusedId) {
|
||||
let focused = doc.getElementById(focusedId);
|
||||
is(focused, doc.activeElement, `Focus should be set on ${focusedId}`);
|
||||
}
|
||||
if (visible !== undefined) {
|
||||
let overlay = doc.getElementById("onboarding-overlay");
|
||||
is(window.getComputedStyle(overlay).getPropertyValue("display"),
|
||||
visible ? "block" : "none",
|
||||
`Onboarding overlay should be ${visible ? "visible" : "invisible"}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const TEST_DATA = [
|
||||
const TOUR_LIST_TEST_DATA = [
|
||||
{ key: "VK_DOWN", expected: { tourId: TOUR_IDs[1], focusedId: TOUR_IDs[1] }},
|
||||
{ key: "VK_DOWN", expected: { tourId: TOUR_IDs[2], focusedId: TOUR_IDs[2] }},
|
||||
{ key: "VK_DOWN", expected: { tourId: TOUR_IDs[3], focusedId: TOUR_IDs[3] }},
|
||||
|
@ -32,6 +42,21 @@ const TEST_DATA = [
|
|||
{ key: " ", expected: { tourId: TOUR_IDs[2], focusedId: TOUR_IDs[2] }}
|
||||
];
|
||||
|
||||
const BUTTONS_TEST_DATA = [
|
||||
{ key: " ", expected: { focusedId: TOUR_IDs[0], visible: true }},
|
||||
{ key: "VK_ESCAPE", expected: { focusedId: "onboarding-overlay-button", visible: false }},
|
||||
{ key: "VK_RETURN", expected: { focusedId: TOUR_IDs[1], visible: true }},
|
||||
{ key: "VK_TAB", options: { shiftKey: true }, expected: { focusedId: TOUR_IDs[0], visible: true }},
|
||||
{ key: "VK_TAB", options: { shiftKey: true }, expected: { focusedId: "onboarding-overlay-close-btn", visible: true }},
|
||||
{ key: " ", expected: { focusedId: "onboarding-overlay-button", visible: false }},
|
||||
{ key: "VK_RETURN", expected: { focusedId: TOUR_IDs[1], visible: true }},
|
||||
{ key: "VK_TAB", options: { shiftKey: true }, expected: { focusedId: TOUR_IDs[0], visible: true }},
|
||||
{ key: "VK_TAB", options: { shiftKey: true }, expected: { focusedId: "onboarding-overlay-close-btn", visible: true }},
|
||||
{ key: "VK_TAB", expected: { focusedId: TOUR_IDs[0], visible: true }},
|
||||
{ key: "VK_TAB", options: { shiftKey: true }, expected: { focusedId: "onboarding-overlay-close-btn", visible: true }},
|
||||
{ key: "VK_RETURN", expected: { focusedId: "onboarding-overlay-button", visible: false }}
|
||||
];
|
||||
|
||||
add_task(async function test_tour_list_keyboard_navigation() {
|
||||
resetOnboardingDefaultState();
|
||||
|
||||
|
@ -48,14 +73,65 @@ add_task(async function test_tour_list_keyboard_navigation() {
|
|||
info("Set initial focus on the currently active tab");
|
||||
await ContentTask.spawn(tab.linkedBrowser, {}, () =>
|
||||
content.document.querySelector(".onboarding-active").focus());
|
||||
await assertTourList(tab.linkedBrowser,
|
||||
await assertOverlayState(tab.linkedBrowser,
|
||||
{ tourId: TOUR_IDs[0], focusedId: TOUR_IDs[0] });
|
||||
|
||||
for (let { key, options = {}, expected } of TEST_DATA) {
|
||||
for (let { key, options = {}, expected } of TOUR_LIST_TEST_DATA) {
|
||||
info(`Pressing ${key} to select ${expected.tourId} and have focus on ${expected.focusedId}`);
|
||||
await BrowserTestUtils.synthesizeKey(key, options, tab.linkedBrowser);
|
||||
await assertTourList(tab.linkedBrowser, expected);
|
||||
await assertOverlayState(tab.linkedBrowser, expected);
|
||||
}
|
||||
|
||||
await BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
||||
add_task(async function test_buttons_keyboard_navigation() {
|
||||
resetOnboardingDefaultState();
|
||||
|
||||
info("Wait for onboarding overlay loaded");
|
||||
let tab = await openTab(ABOUT_HOME_URL);
|
||||
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
|
||||
|
||||
info("Set keyboard focus on the onboarding overlay button");
|
||||
await ContentTask.spawn(tab.linkedBrowser, {}, () =>
|
||||
content.document.getElementById("onboarding-overlay-button").focus());
|
||||
await assertOverlayState(tab.linkedBrowser,
|
||||
{ focusedId: "onboarding-overlay-button", visible: false });
|
||||
|
||||
for (let { key, options = {}, expected } of BUTTONS_TEST_DATA) {
|
||||
info(`Pressing ${key} to have ${expected.visible ? "visible" : "invisible"} overlay and have focus on ${expected.focusedId}`);
|
||||
await BrowserTestUtils.synthesizeKey(key, options, tab.linkedBrowser);
|
||||
await assertOverlayState(tab.linkedBrowser, expected);
|
||||
}
|
||||
|
||||
await BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
||||
add_task(async function test_overlay_dialog_keyboard_navigation() {
|
||||
resetOnboardingDefaultState();
|
||||
|
||||
info("Wait for onboarding overlay loaded");
|
||||
let tab = await openTab(ABOUT_HOME_URL);
|
||||
let browser = tab.linkedBrowser;
|
||||
await promiseOnboardingOverlayLoaded(browser);
|
||||
|
||||
info("Test accessibility and semantics of the dialog overlay");
|
||||
await assertModalDialog(browser, { visible: false });
|
||||
|
||||
info("Set keyboard focus on the onboarding overlay button");
|
||||
await ContentTask.spawn(browser, {}, () =>
|
||||
content.document.getElementById("onboarding-overlay-button").focus());
|
||||
info("Open dialog with keyboard and check the dialog state");
|
||||
await BrowserTestUtils.synthesizeKey(" ", {}, browser);
|
||||
await promiseOnboardingOverlayOpened(browser);
|
||||
await assertModalDialog(browser,
|
||||
{ visible: true, keyboardFocus: true, focusedId: TOUR_IDs[0] });
|
||||
|
||||
info("Close the dialog and check modal dialog state");
|
||||
await BrowserTestUtils.synthesizeKey("VK_ESCAPE", {}, browser);
|
||||
await promiseOnboardingOverlayClosed(browser);
|
||||
await assertModalDialog(browser,
|
||||
{ visible: false, keyboardFocus: true, focusedId: "onboarding-overlay-button" });
|
||||
|
||||
await BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
|
|
@ -87,21 +87,22 @@ function promiseOnboardingOverlayLoaded(browser) {
|
|||
}
|
||||
|
||||
function promiseOnboardingOverlayOpened(browser) {
|
||||
let condition = () => {
|
||||
return ContentTask.spawn(browser, {}, function() {
|
||||
return new Promise(resolve => {
|
||||
let overlay = content.document.querySelector("#onboarding-overlay");
|
||||
if (overlay.classList.contains("onboarding-opened")) {
|
||||
resolve(true);
|
||||
return;
|
||||
}
|
||||
resolve(false);
|
||||
});
|
||||
})
|
||||
};
|
||||
return BrowserTestUtils.waitForCondition(
|
||||
condition,
|
||||
"Should open onboarding overlay",
|
||||
return BrowserTestUtils.waitForCondition(() =>
|
||||
ContentTask.spawn(browser, {}, () =>
|
||||
content.document.querySelector("#onboarding-overlay").classList.contains(
|
||||
"onboarding-opened")),
|
||||
"Should close onboarding overlay",
|
||||
100,
|
||||
30
|
||||
);
|
||||
}
|
||||
|
||||
function promiseOnboardingOverlayClosed(browser) {
|
||||
return BrowserTestUtils.waitForCondition(() =>
|
||||
ContentTask.spawn(browser, {}, () =>
|
||||
!content.document.querySelector("#onboarding-overlay").classList.contains(
|
||||
"onboarding-opened")),
|
||||
"Should close onboarding overlay",
|
||||
100,
|
||||
30
|
||||
);
|
||||
|
@ -209,9 +210,17 @@ function assertOverlaySemantics(browser) {
|
|||
return ContentTask.spawn(browser, {}, function() {
|
||||
let doc = content.document;
|
||||
|
||||
info("Checking dialog");
|
||||
let dialog = doc.getElementById("onboarding-overlay-dialog");
|
||||
is(dialog.getAttribute("role"), "dialog",
|
||||
"Dialog should have a dialog role attribute set");
|
||||
is(dialog.tabIndex, "-1", "Dialog should be focusable but not in tab order");
|
||||
is(dialog.getAttribute("aria-labelledby"), "onboarding-header",
|
||||
"Dialog should be labaled by its header");
|
||||
|
||||
info("Checking the tablist container");
|
||||
is(doc.getElementById("onboarding-tour-list").getAttribute("role"), "tablist",
|
||||
"Tour list should have a tablist role argument set");
|
||||
"Tour list should have a tablist role attribute set");
|
||||
|
||||
info("Checking each tour item that represents the tab");
|
||||
let items = [...doc.querySelectorAll(".onboarding-tour-item")];
|
||||
|
@ -222,15 +231,43 @@ function assertOverlaySemantics(browser) {
|
|||
item.classList.contains("onboarding-active") ? "true" : "false",
|
||||
"Active item should have aria-selected set to true and inactive to false");
|
||||
is(item.tabIndex, "0", "Item tab index must be set for keyboard accessibility");
|
||||
is(item.getAttribute("role"), "tab", "Item should have a tab role argument set");
|
||||
is(item.getAttribute("role"), "tab", "Item should have a tab role attribute set");
|
||||
let tourPanelId = `${item.id}-page`;
|
||||
is(item.getAttribute("aria-controls"), tourPanelId,
|
||||
"Item should have aria-controls attribute point to its tabpanel");
|
||||
let panel = doc.getElementById(tourPanelId);
|
||||
is(panel.getAttribute("role"), "tabpanel",
|
||||
"Tour panel should have a tabpanel role argument set");
|
||||
"Tour panel should have a tabpanel role attribute set");
|
||||
is(panel.getAttribute("aria-labelledby"), item.id,
|
||||
"Tour panel should have aria-labelledby attribute point to its tab");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function assertModalDialog(browser, args) {
|
||||
return ContentTask.spawn(browser, args, ({ keyboardFocus, visible, focusedId }) => {
|
||||
let doc = content.document;
|
||||
let overlayButton = doc.getElementById("onboarding-overlay-button");
|
||||
if (visible) {
|
||||
[...doc.body.children].forEach(child =>
|
||||
child.id !== "onboarding-overlay" &&
|
||||
is(child.getAttribute("aria-hidden"), "true",
|
||||
"Content should not be visible to screen reader"));
|
||||
is(focusedId ? doc.getElementById(focusedId) : doc.body,
|
||||
doc.activeElement, `Focus should be on ${focusedId || "body"}`);
|
||||
is(keyboardFocus ? "true" : undefined,
|
||||
overlayButton.dataset.keyboardFocus,
|
||||
"Overlay button focus state is saved correctly");
|
||||
} else {
|
||||
[...doc.body.children].forEach(
|
||||
child => ok(!child.hasAttribute("aria-hidden"),
|
||||
"Content should be visible to screen reader"));
|
||||
if (keyboardFocus) {
|
||||
is(overlayButton, doc.activeElement,
|
||||
"Focus should be set on overlay button");
|
||||
}
|
||||
ok(!overlayButton.dataset.keyboardFocus,
|
||||
"Overlay button focus state should be cleared");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ tier_MOZ_AUTOMATION_INSTALLER = installer
|
|||
tier_MOZ_AUTOMATION_PACKAGE = package
|
||||
tier_MOZ_AUTOMATION_PACKAGE_TESTS = package-tests
|
||||
tier_MOZ_AUTOMATION_UPDATE_PACKAGING = update-packaging
|
||||
tier_MOZ_AUTOMATION_PACKAGE_GENERATED_SOURCES = package-generated-sources
|
||||
tier_MOZ_AUTOMATION_UPLOAD_SYMBOLS = uploadsymbols
|
||||
tier_MOZ_AUTOMATION_UPLOAD = upload
|
||||
|
||||
|
@ -41,6 +42,7 @@ moz_automation_symbols = \
|
|||
MOZ_AUTOMATION_PACKAGE \
|
||||
MOZ_AUTOMATION_INSTALLER \
|
||||
MOZ_AUTOMATION_UPDATE_PACKAGING \
|
||||
MOZ_AUTOMATION_PACKAGE_GENERATED_SOURCES \
|
||||
MOZ_AUTOMATION_L10N_CHECK \
|
||||
MOZ_AUTOMATION_UPLOAD \
|
||||
$(NULL)
|
||||
|
@ -60,6 +62,7 @@ automation/upload: automation/package
|
|||
automation/upload: automation/package-tests
|
||||
automation/upload: automation/buildsymbols
|
||||
automation/upload: automation/update-packaging
|
||||
automation/upload: automation/package-generated-sources
|
||||
|
||||
# The installer and packager all run stage-package, and may conflict
|
||||
# with each other.
|
||||
|
|
|
@ -42,6 +42,7 @@ add_gcc_warning('-Wtype-limits')
|
|||
|
||||
# catches some dead code
|
||||
add_gcc_warning('-Wunreachable-code')
|
||||
check_and_add_gcc_warning('-Wunreachable-code-return')
|
||||
|
||||
# catches treating string literals as non-const
|
||||
add_gcc_warning('-Wwrite-strings', cxx_compiler)
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
# Common options for artifact builds to set automation steps.
|
||||
# This gets included before mozconfig.automation.
|
||||
|
||||
MOZ_AUTOMATION_BUILD_SYMBOLS=0
|
||||
MOZ_AUTOMATION_L10N_CHECK=0
|
||||
MOZ_AUTOMATION_PACKAGE_GENERATED_SOURCES=0
|
|
@ -15,6 +15,7 @@ mk_add_options "export MOZ_AUTOMATION_PACKAGE=${MOZ_AUTOMATION_PACKAGE-1}"
|
|||
mk_add_options "export MOZ_AUTOMATION_PACKAGE_TESTS=${MOZ_AUTOMATION_PACKAGE_TESTS-1}"
|
||||
mk_add_options "export MOZ_AUTOMATION_INSTALLER=${MOZ_AUTOMATION_INSTALLER-0}"
|
||||
mk_add_options "export MOZ_AUTOMATION_UPDATE_PACKAGING=${MOZ_AUTOMATION_UPDATE_PACKAGING-0}"
|
||||
mk_add_options "export MOZ_AUTOMATION_PACKAGE_GENERATED_SOURCES=${MOZ_AUTOMATION_PACKAGE_GENERATED_SOURCES-1}"
|
||||
mk_add_options "export MOZ_AUTOMATION_UPLOAD=${MOZ_AUTOMATION_UPLOAD-1}"
|
||||
mk_add_options "export MOZ_AUTOMATION_UPLOAD_SYMBOLS=${MOZ_AUTOMATION_UPLOAD_SYMBOLS-0}"
|
||||
|
||||
|
|
|
@ -0,0 +1,156 @@
|
|||
#!/usr/bin/env/python
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import argparse
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from contextlib import contextmanager
|
||||
import gzip
|
||||
import io
|
||||
import logging
|
||||
from mozbuild.base import MozbuildObject
|
||||
from mozbuild.generated_sources import (
|
||||
get_filename_with_digest,
|
||||
get_s3_region_and_bucket,
|
||||
)
|
||||
import os
|
||||
from Queue import Queue
|
||||
import requests
|
||||
import sys
|
||||
import tarfile
|
||||
from threading import Event, Thread
|
||||
import time
|
||||
|
||||
# Arbitrary, should probably measure this.
|
||||
NUM_WORKER_THREADS = 10
|
||||
log = logging.getLogger('upload-generated-sources')
|
||||
log.setLevel(logging.INFO)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def timed():
|
||||
'''
|
||||
Yield a function that provides the elapsed time in seconds since this
|
||||
function was called.
|
||||
'''
|
||||
start = time.time()
|
||||
def elapsed():
|
||||
return time.time() - start
|
||||
yield elapsed
|
||||
|
||||
|
||||
def gzip_compress(data):
|
||||
'''
|
||||
Apply gzip compression to `data` and return the result as a `BytesIO`.
|
||||
'''
|
||||
b = io.BytesIO()
|
||||
with gzip.GzipFile(fileobj=b, mode='w') as f:
|
||||
f.write(data)
|
||||
b.flush()
|
||||
b.seek(0)
|
||||
return b
|
||||
|
||||
|
||||
def upload_worker(queue, event, bucket, session_args):
|
||||
'''
|
||||
Get `(name, contents)` entries from `queue` and upload `contents`
|
||||
to S3 with gzip compression using `name` as the key, prefixed with
|
||||
the SHA-512 digest of `contents` as a hex string. If an exception occurs,
|
||||
set `event`.
|
||||
'''
|
||||
try:
|
||||
import boto3
|
||||
session = boto3.session.Session(**session_args)
|
||||
s3 = session.client('s3')
|
||||
while True:
|
||||
if event.is_set():
|
||||
# Some other thread hit an exception.
|
||||
return
|
||||
(name, contents) = queue.get()
|
||||
pathname = get_filename_with_digest(name, contents)
|
||||
compressed = gzip_compress(contents)
|
||||
extra_args = {
|
||||
'ContentEncoding': 'gzip',
|
||||
'ContentType': 'text/plain',
|
||||
}
|
||||
log.info('Uploading "{}" ({} bytes)'.format(pathname, len(compressed.getvalue())))
|
||||
with timed() as elapsed:
|
||||
s3.upload_fileobj(compressed, bucket, pathname, ExtraArgs=extra_args)
|
||||
log.info('Finished uploading "{}" in {:0.3f}s'.format(pathname, elapsed()))
|
||||
queue.task_done()
|
||||
except Exception:
|
||||
log.exception('Thread encountered exception:')
|
||||
event.set()
|
||||
|
||||
|
||||
def do_work(artifact, region, bucket):
|
||||
session_args = {'region_name': region}
|
||||
session = requests.Session()
|
||||
if 'TASK_ID' in os.environ:
|
||||
level = os.environ.get('MOZ_SCM_LEVEL', '1')
|
||||
secrets_url = 'http://taskcluster/secrets/v1/secret/project/releng/gecko/build/level-{}/gecko-generated-sources-upload'.format(level)
|
||||
log.info('Using AWS credentials from the secrets service: "{}"'.format(secrets_url))
|
||||
res = session.get(secrets_url)
|
||||
res.raise_for_status()
|
||||
secret = res.json()
|
||||
session_args.update(
|
||||
aws_access_key_id=secret['secret']['AWS_ACCESS_KEY_ID'],
|
||||
aws_secret_access_key=secret['secret']['AWS_SECRET_ACCESS_KEY'],
|
||||
)
|
||||
else:
|
||||
log.info('Trying to use your AWS credentials..')
|
||||
|
||||
|
||||
# First, fetch the artifact containing the sources.
|
||||
log.info('Fetching generated sources artifact: "{}"'.format(artifact))
|
||||
with timed() as elapsed:
|
||||
res = session.get(artifact)
|
||||
log.info('Fetch HTTP status: {}, {} bytes downloaded in {:0.3f}s'.format(res.status_code, len(res.content), elapsed()))
|
||||
res.raise_for_status()
|
||||
# Create a queue and worker threads for uploading.
|
||||
q = Queue()
|
||||
event = Event()
|
||||
log.info('Creating {} worker threads'.format(NUM_WORKER_THREADS))
|
||||
for i in range(NUM_WORKER_THREADS):
|
||||
t = Thread(target=upload_worker, args=(q, event, bucket, session_args))
|
||||
t.daemon = True
|
||||
t.start()
|
||||
with tarfile.open(fileobj=io.BytesIO(res.content), mode='r|gz') as tar:
|
||||
# Next, process each file.
|
||||
for entry in tar:
|
||||
if event.is_set():
|
||||
break
|
||||
log.info('Queueing "{}"'.format(entry.name))
|
||||
q.put((entry.name, tar.extractfile(entry).read()))
|
||||
# Wait until all uploads are finished.
|
||||
# We don't use q.join() here because we want to also monitor event.
|
||||
while q.unfinished_tasks:
|
||||
if event.wait(0.1):
|
||||
log.error('Worker thread encountered exception, exiting...')
|
||||
break
|
||||
|
||||
|
||||
def main(argv):
|
||||
logging.basicConfig(format='%(levelname)s - %(threadName)s - %(message)s')
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Upload generated source files in ARTIFACT to BUCKET in S3.')
|
||||
parser.add_argument('artifact',
|
||||
help='generated-sources artifact from build task')
|
||||
args = parser.parse_args(argv)
|
||||
region, bucket = get_s3_region_and_bucket()
|
||||
|
||||
config = MozbuildObject.from_environment()
|
||||
config._activate_virtualenv()
|
||||
config.virtualenv_manager.install_pip_package('boto3==1.4.4')
|
||||
|
||||
with timed() as elapsed:
|
||||
do_work(region=region, bucket=bucket, artifact=args.artifact)
|
||||
log.info('Finished in {:.03f}s'.format(elapsed()))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv[1:]))
|
|
@ -1004,12 +1004,7 @@ CssRuleView.prototype = {
|
|||
container.hidden = false;
|
||||
this.element.appendChild(container);
|
||||
|
||||
header.addEventListener("dblclick", () => {
|
||||
this._toggleContainerVisibility(twisty, container, isPseudo,
|
||||
!this.showPseudoElements);
|
||||
});
|
||||
|
||||
twisty.addEventListener("click", () => {
|
||||
header.addEventListener("click", () => {
|
||||
this._toggleContainerVisibility(twisty, container, isPseudo,
|
||||
!this.showPseudoElements);
|
||||
});
|
||||
|
|
|
@ -860,7 +860,7 @@ static const uint32_t JSCLASS_FOREGROUND_FINALIZE = 1 << (JSCLASS_HIGH_FLAGS
|
|||
// application.
|
||||
static const uint32_t JSCLASS_GLOBAL_APPLICATION_SLOTS = 5;
|
||||
static const uint32_t JSCLASS_GLOBAL_SLOT_COUNT =
|
||||
JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 37;
|
||||
JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 36;
|
||||
|
||||
#define JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(n) \
|
||||
(JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + (n)))
|
||||
|
|
|
@ -905,8 +905,14 @@ obj_watch(JSContext* cx, unsigned argc, Value* vp)
|
|||
if (!obj)
|
||||
return false;
|
||||
|
||||
if (!GlobalObject::warnOnceAboutWatch(cx, obj))
|
||||
return false;
|
||||
if (!cx->compartment()->warnedAboutObjectWatch) {
|
||||
if (!JS_ReportErrorFlagsAndNumberASCII(cx, JSREPORT_WARNING, GetErrorMessage, nullptr,
|
||||
JSMSG_OBJECT_WATCH_DEPRECATED))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
cx->compartment()->warnedAboutObjectWatch = true;
|
||||
}
|
||||
|
||||
if (args.length() <= 1) {
|
||||
ReportMissingArg(cx, args.calleev(), 1);
|
||||
|
@ -937,8 +943,14 @@ obj_unwatch(JSContext* cx, unsigned argc, Value* vp)
|
|||
if (!obj)
|
||||
return false;
|
||||
|
||||
if (!GlobalObject::warnOnceAboutWatch(cx, obj))
|
||||
return false;
|
||||
if (!cx->compartment()->warnedAboutObjectWatch) {
|
||||
if (!JS_ReportErrorFlagsAndNumberASCII(cx, JSREPORT_WARNING, GetErrorMessage, nullptr,
|
||||
JSMSG_OBJECT_WATCH_DEPRECATED))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
cx->compartment()->warnedAboutObjectWatch = true;
|
||||
}
|
||||
|
||||
RootedId id(cx);
|
||||
if (args.length() != 0) {
|
||||
|
|
|
@ -132,10 +132,8 @@ js::gc::AllocateWholeCellSet(Arena* arena)
|
|||
AutoEnterOOMUnsafeRegion oomUnsafe;
|
||||
Nursery& nursery = zone->group()->nursery();
|
||||
void* data = nursery.allocateBuffer(zone, sizeof(ArenaCellSet));
|
||||
if (!data) {
|
||||
if (!data)
|
||||
oomUnsafe.crash("Failed to allocate WholeCellSet");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (nursery.freeSpace() < ArenaCellSet::NurseryFreeThresholdBytes)
|
||||
zone->group()->storeBuffer().setAboutToOverflow(JS::gcreason::FULL_WHOLE_CELL_BUFFER);
|
||||
|
|
|
@ -1617,8 +1617,7 @@ IonBuilder::inlineConstantStringSplitString(CallInfo& callInfo)
|
|||
MDefinition* array = current->peek(-1);
|
||||
|
||||
if (!initLength) {
|
||||
if (!array->isResumePoint())
|
||||
MOZ_TRY(resumeAfter(array->toNewArray()));
|
||||
MOZ_TRY(resumeAfter(array->toNewArray()));
|
||||
return InliningStatus_Inlined;
|
||||
}
|
||||
|
||||
|
|
|
@ -4132,7 +4132,7 @@ MResumePoint::Copy(TempAllocator& alloc, MResumePoint* src)
|
|||
}
|
||||
|
||||
MResumePoint::MResumePoint(MBasicBlock* block, jsbytecode* pc, Mode mode)
|
||||
: MNode(block),
|
||||
: MNode(block, Kind::ResumePoint),
|
||||
pc_(pc),
|
||||
instruction_(nullptr),
|
||||
mode_(mode)
|
||||
|
@ -4149,7 +4149,7 @@ MResumePoint::init(TempAllocator& alloc)
|
|||
MResumePoint*
|
||||
MResumePoint::caller() const
|
||||
{
|
||||
return block_->callerResumePoint();
|
||||
return block()->callerResumePoint();
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -301,37 +301,61 @@ typedef InlineList<MUse>::iterator MUseIterator;
|
|||
class MNode : public TempObject
|
||||
{
|
||||
protected:
|
||||
MBasicBlock* block_; // Containing basic block.
|
||||
|
||||
public:
|
||||
enum Kind {
|
||||
Definition,
|
||||
enum class Kind {
|
||||
Definition = 0,
|
||||
ResumePoint
|
||||
};
|
||||
|
||||
MNode()
|
||||
: block_(nullptr)
|
||||
private:
|
||||
static const uintptr_t KindMask = 0x1;
|
||||
uintptr_t blockAndKind_;
|
||||
|
||||
Kind kind() const {
|
||||
return Kind(blockAndKind_ & KindMask);
|
||||
}
|
||||
|
||||
protected:
|
||||
explicit MNode(const MNode& other)
|
||||
: blockAndKind_(other.blockAndKind_)
|
||||
{ }
|
||||
|
||||
explicit MNode(MBasicBlock* block)
|
||||
: block_(block)
|
||||
{ }
|
||||
MNode(MBasicBlock* block, Kind kind)
|
||||
{
|
||||
setBlockAndKind(block, kind);
|
||||
}
|
||||
|
||||
virtual Kind kind() const = 0;
|
||||
void setBlockAndKind(MBasicBlock* block, Kind kind) {
|
||||
blockAndKind_ = uintptr_t(block) | uintptr_t(kind);
|
||||
MOZ_ASSERT(this->block() == block);
|
||||
}
|
||||
|
||||
MBasicBlock* definitionBlock() const {
|
||||
MOZ_ASSERT(isDefinition());
|
||||
static_assert(unsigned(Kind::Definition) == 0, "Code below relies on low bit being 0");
|
||||
return reinterpret_cast<MBasicBlock*>(blockAndKind_);
|
||||
}
|
||||
MBasicBlock* resumePointBlock() const {
|
||||
MOZ_ASSERT(isResumePoint());
|
||||
static_assert(unsigned(Kind::ResumePoint) == 1, "Code below relies on low bit being 1");
|
||||
// Use a subtraction: if the caller does block()->foo, the compiler
|
||||
// will be able to fold it with the load.
|
||||
return reinterpret_cast<MBasicBlock*>(blockAndKind_ - 1);
|
||||
}
|
||||
|
||||
public:
|
||||
// Returns the definition at a given operand.
|
||||
virtual MDefinition* getOperand(size_t index) const = 0;
|
||||
virtual size_t numOperands() const = 0;
|
||||
virtual size_t indexOf(const MUse* u) const = 0;
|
||||
|
||||
bool isDefinition() const {
|
||||
return kind() == Definition;
|
||||
return kind() == Kind::Definition;
|
||||
}
|
||||
bool isResumePoint() const {
|
||||
return kind() == ResumePoint;
|
||||
return kind() == Kind::ResumePoint;
|
||||
}
|
||||
MBasicBlock* block() const {
|
||||
return block_;
|
||||
return reinterpret_cast<MBasicBlock*>(blockAndKind_ & ~KindMask);
|
||||
}
|
||||
MBasicBlock* caller() const;
|
||||
|
||||
|
@ -515,16 +539,21 @@ class MDefinition : public MNode
|
|||
flags_ |= flags;
|
||||
}
|
||||
|
||||
// Calling isDefinition or isResumePoint on MDefinition is unnecessary.
|
||||
bool isDefinition() const = delete;
|
||||
bool isResumePoint() const = delete;
|
||||
|
||||
protected:
|
||||
void setBlock(MBasicBlock* block) {
|
||||
block_ = block;
|
||||
setBlockAndKind(block, Kind::Definition);
|
||||
}
|
||||
|
||||
static HashNumber addU32ToHash(HashNumber hash, uint32_t data);
|
||||
|
||||
public:
|
||||
MDefinition()
|
||||
: id_(0),
|
||||
: MNode(nullptr, Kind::Definition),
|
||||
id_(0),
|
||||
flags_(0),
|
||||
range_(nullptr),
|
||||
resultType_(MIRType::None),
|
||||
|
@ -535,7 +564,8 @@ class MDefinition : public MNode
|
|||
|
||||
// Copying a definition leaves the list of uses and the block empty.
|
||||
explicit MDefinition(const MDefinition& other)
|
||||
: id_(0),
|
||||
: MNode(other),
|
||||
id_(0),
|
||||
flags_(other.flags_),
|
||||
range_(other.range_),
|
||||
resultType_(other.resultType_),
|
||||
|
@ -565,6 +595,10 @@ class MDefinition : public MNode
|
|||
// be worthwhile.
|
||||
virtual bool possiblyCalls() const { return false; }
|
||||
|
||||
MBasicBlock* block() const {
|
||||
return definitionBlock();
|
||||
}
|
||||
|
||||
void setTrackedSite(const BytecodeSite* site) {
|
||||
MOZ_ASSERT(site);
|
||||
trackedSite_ = site;
|
||||
|
@ -700,12 +734,8 @@ class MDefinition : public MNode
|
|||
virtual void collectRangeInfoPreTrunc() {
|
||||
}
|
||||
|
||||
MNode::Kind kind() const override {
|
||||
return MNode::Definition;
|
||||
}
|
||||
|
||||
uint32_t id() const {
|
||||
MOZ_ASSERT(block_);
|
||||
MOZ_ASSERT(block());
|
||||
return id_;
|
||||
}
|
||||
void setId(uint32_t id) {
|
||||
|
@ -13330,6 +13360,14 @@ class MResumePoint final :
|
|||
MResumePoint(MBasicBlock* block, jsbytecode* pc, Mode mode);
|
||||
void inherit(MBasicBlock* state);
|
||||
|
||||
void setBlock(MBasicBlock* block) {
|
||||
setBlockAndKind(block, Kind::ResumePoint);
|
||||
}
|
||||
|
||||
// Calling isDefinition or isResumePoint on MResumePoint is unnecessary.
|
||||
bool isDefinition() const = delete;
|
||||
bool isResumePoint() const = delete;
|
||||
|
||||
protected:
|
||||
// Initializes operands_ to an empty array of a fixed length.
|
||||
// The array may then be filled in by inherit().
|
||||
|
@ -13354,9 +13392,10 @@ class MResumePoint final :
|
|||
const MDefinitionVector& operands);
|
||||
static MResumePoint* Copy(TempAllocator& alloc, MResumePoint* src);
|
||||
|
||||
MNode::Kind kind() const override {
|
||||
return MNode::ResumePoint;
|
||||
MBasicBlock* block() const {
|
||||
return resumePointBlock();
|
||||
}
|
||||
|
||||
size_t numAllocatedOperands() const {
|
||||
return operands_.length();
|
||||
}
|
||||
|
|
|
@ -325,7 +325,7 @@ MBasicBlock::NewWithResumePoint(MIRGraph& graph, const CompileInfo& info,
|
|||
|
||||
MOZ_ASSERT(!resumePoint->instruction());
|
||||
resumePoint->block()->discardResumePoint(resumePoint, RefType_None);
|
||||
resumePoint->block_ = block;
|
||||
resumePoint->setBlock(block);
|
||||
block->addResumePoint(resumePoint);
|
||||
block->entryResumePoint_ = resumePoint;
|
||||
|
||||
|
@ -927,9 +927,9 @@ void
|
|||
MBasicBlock::discardDef(MDefinition* at)
|
||||
{
|
||||
if (at->isPhi())
|
||||
at->block_->discardPhi(at->toPhi());
|
||||
at->block()->discardPhi(at->toPhi());
|
||||
else
|
||||
at->block_->discard(at->toInstruction());
|
||||
at->block()->discard(at->toInstruction());
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -60,6 +60,7 @@ JSCompartment::JSCompartment(Zone* zone, const JS::CompartmentOptions& options =
|
|||
warnedAboutExprClosure(false),
|
||||
warnedAboutForEach(false),
|
||||
warnedAboutLegacyGenerator(false),
|
||||
warnedAboutObjectWatch(false),
|
||||
warnedAboutStringGenericsMethods(0),
|
||||
#ifdef DEBUG
|
||||
firedOnNewGlobalObject(false),
|
||||
|
|
|
@ -625,6 +625,7 @@ struct JSCompartment
|
|||
bool warnedAboutExprClosure : 1;
|
||||
bool warnedAboutForEach : 1;
|
||||
bool warnedAboutLegacyGenerator : 1;
|
||||
bool warnedAboutObjectWatch : 1;
|
||||
uint32_t warnedAboutStringGenericsMethods;
|
||||
|
||||
#ifdef DEBUG
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
// |reftest| skip-if(!xulRuntime.shell)
|
||||
|
||||
enableLastWarning();
|
||||
|
||||
var g = newGlobal();
|
||||
g.eval("({}).watch('x', function(){})");
|
||||
|
||||
var warning = getLastWarning();
|
||||
assertEq(warning.name, "Warning");
|
||||
assertEq(warning.message.includes("watch"), true, "warning should mention watch");
|
||||
|
||||
clearLastWarning();
|
||||
|
||||
g = newGlobal();
|
||||
g.eval("({}).unwatch('x')");
|
||||
|
||||
warning = getLastWarning();
|
||||
assertEq(warning.name, "Warning");
|
||||
assertEq(warning.message.includes("watch"), true, "warning should mention watch");
|
||||
|
||||
clearLastWarning();
|
||||
|
||||
reportCompare(0, 0);
|
|
@ -543,28 +543,6 @@ GlobalObject::isRuntimeCodeGenEnabled(JSContext* cx, Handle<GlobalObject*> globa
|
|||
return !v.isFalse();
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
GlobalObject::warnOnceAbout(JSContext* cx, HandleObject obj, WarnOnceFlag flag,
|
||||
unsigned errorNumber)
|
||||
{
|
||||
Rooted<GlobalObject*> global(cx, &obj->global());
|
||||
HeapSlot& v = global->getSlotRef(WARNED_ONCE_FLAGS);
|
||||
MOZ_ASSERT_IF(!v.isUndefined(), v.toInt32());
|
||||
int32_t flags = v.isUndefined() ? 0 : v.toInt32();
|
||||
if (!(flags & flag)) {
|
||||
if (!JS_ReportErrorFlagsAndNumberASCII(cx, JSREPORT_WARNING, GetErrorMessage, nullptr,
|
||||
errorNumber))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (v.isUndefined())
|
||||
v.init(global, HeapSlot::Slot, WARNED_ONCE_FLAGS, Int32Value(flags | flag));
|
||||
else
|
||||
v.set(global, HeapSlot::Slot, WARNED_ONCE_FLAGS, Int32Value(flags | flag));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */ JSFunction*
|
||||
GlobalObject::createConstructor(JSContext* cx, Native ctor, JSAtom* nameArg, unsigned length,
|
||||
gc::AllocKind kind, const JSJitInfo* jitInfo)
|
||||
|
|
|
@ -100,7 +100,6 @@ class GlobalObject : public NativeObject
|
|||
IMPORT_ENTRY_PROTO,
|
||||
EXPORT_ENTRY_PROTO,
|
||||
REGEXP_STATICS,
|
||||
WARNED_ONCE_FLAGS,
|
||||
RUNTIME_CODEGEN_ENABLED,
|
||||
DEBUGGERS,
|
||||
INTRINSICS,
|
||||
|
@ -120,18 +119,6 @@ class GlobalObject : public NativeObject
|
|||
static_assert(JSCLASS_GLOBAL_SLOT_COUNT == RESERVED_SLOTS,
|
||||
"global object slot counts are inconsistent");
|
||||
|
||||
enum WarnOnceFlag : int32_t {
|
||||
WARN_WATCH_DEPRECATED = 1 << 0,
|
||||
};
|
||||
|
||||
// Emit the specified warning if the given slot in |obj|'s global isn't
|
||||
// true, then set the slot to true. Thus calling this method warns once
|
||||
// for each global object it's called on, and every other call does
|
||||
// nothing.
|
||||
static bool
|
||||
warnOnceAbout(JSContext* cx, HandleObject obj, WarnOnceFlag flag, unsigned errorNumber);
|
||||
|
||||
|
||||
public:
|
||||
LexicalEnvironmentObject& lexicalEnvironment() const;
|
||||
GlobalScope& emptyGlobalScope() const;
|
||||
|
@ -751,15 +738,6 @@ class GlobalObject : public NativeObject
|
|||
|
||||
static bool isRuntimeCodeGenEnabled(JSContext* cx, Handle<GlobalObject*> global);
|
||||
|
||||
// Warn about use of the deprecated watch/unwatch functions in the global
|
||||
// in which |obj| was created, if no prior warning was given.
|
||||
static bool warnOnceAboutWatch(JSContext* cx, HandleObject obj) {
|
||||
// Temporarily disabled until we've provided a watch/unwatch workaround for
|
||||
// debuggers like Firebug (bug 934669).
|
||||
//return warnOnceAbout(cx, obj, WARN_WATCH_DEPRECATED, JSMSG_OBJECT_WATCH_DEPRECATED);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool getOrCreateEval(JSContext* cx, Handle<GlobalObject*> global,
|
||||
MutableHandleObject eval);
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ MOZ_AUTOMATION_PACKAGE_TESTS=0
|
|||
MOZ_AUTOMATION_UPDATE_PACKAGING=0
|
||||
MOZ_AUTOMATION_UPLOAD=0
|
||||
MOZ_AUTOMATION_UPLOAD_SYMBOLS=0
|
||||
MOZ_AUTOMATION_PACKAGE_GENERATED_SOURCES=0
|
||||
|
||||
NO_CACHE=1
|
||||
NO_NDK=1
|
||||
|
|
|
@ -7,6 +7,7 @@ MOZ_AUTOMATION_PACKAGE_TESTS=0
|
|||
MOZ_AUTOMATION_UPDATE_PACKAGING=0
|
||||
MOZ_AUTOMATION_UPLOAD=0
|
||||
MOZ_AUTOMATION_UPLOAD_SYMBOLS=0
|
||||
MOZ_AUTOMATION_PACKAGE_GENERATED_SOURCES=0
|
||||
|
||||
NO_CACHE=1
|
||||
NO_NDK=1
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
MOZ_AUTOMATION_BUILD_SYMBOLS=0
|
||||
MOZ_AUTOMATION_L10N_CHECK=0
|
||||
. "$topsrcdir/build/mozconfig.artifact.automation"
|
||||
|
||||
NO_CACHE=1
|
||||
NO_NDK=1
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
MOZ_AUTOMATION_BUILD_SYMBOLS=0
|
||||
MOZ_AUTOMATION_L10N_CHECK=0
|
||||
. "$topsrcdir/build/mozconfig.artifact.automation"
|
||||
|
||||
NO_CACHE=1
|
||||
NO_NDK=1
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
MOZ_AUTOMATION_BUILD_SYMBOLS=0
|
||||
MOZ_AUTOMATION_L10N_CHECK=0
|
||||
. "$topsrcdir/build/mozconfig.artifact.automation"
|
||||
|
||||
NO_CACHE=1
|
||||
NO_NDK=1
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
MOZ_AUTOMATION_BUILD_SYMBOLS=0
|
||||
MOZ_AUTOMATION_L10N_CHECK=0
|
||||
. "$topsrcdir/build/mozconfig.artifact.automation"
|
||||
|
||||
NO_CACHE=1
|
||||
NO_NDK=1
|
||||
|
|
|
@ -899,6 +899,7 @@ pref("gfx.webrender.force-angle", true);
|
|||
pref("gfx.webrender.highlight-painted-layers", false);
|
||||
pref("gfx.webrender.layers-free", false);
|
||||
pref("gfx.webrender.profiler.enabled", false);
|
||||
pref("gfx.webrender.blob-images", false);
|
||||
|
||||
// Whether webrender should be used as much as possible.
|
||||
pref("gfx.webrendest.enabled", false);
|
||||
|
|
|
@ -85,8 +85,6 @@ MapGIOResult(gint code)
|
|||
default:
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
static nsresult
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
# 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/.
|
||||
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os.path
|
||||
import sys
|
||||
|
||||
import buildconfig
|
||||
from mozpack.archive import create_tar_gz_from_files
|
||||
from mozbuild.generated_sources import get_generated_sources
|
||||
|
||||
|
||||
def main(argv):
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Produce archive of generated sources')
|
||||
parser.add_argument('outputfile', help='File to write output to')
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
|
||||
files = dict(get_generated_sources())
|
||||
with open(args.outputfile, 'wb') as fh:
|
||||
create_tar_gz_from_files(fh, files, compresslevel=5)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv[1:]))
|
|
@ -0,0 +1,73 @@
|
|||
# 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/.
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
|
||||
from mozpack.files import FileFinder
|
||||
import mozpack.path as mozpath
|
||||
|
||||
|
||||
def sha512_digest(data):
|
||||
'''
|
||||
Generate the SHA-512 digest of `data` and return it as a hex string.
|
||||
'''
|
||||
return hashlib.sha512(data).hexdigest()
|
||||
|
||||
|
||||
def get_filename_with_digest(name, contents):
|
||||
'''
|
||||
Return the filename that will be used to store the generated file
|
||||
in the S3 bucket, consisting of the SHA-512 digest of `contents`
|
||||
joined with the relative path `name`.
|
||||
'''
|
||||
digest = sha512_digest(contents)
|
||||
return mozpath.join(digest, name)
|
||||
|
||||
|
||||
def get_generated_sources():
|
||||
'''
|
||||
Yield tuples of `(objdir-rel-path, file)` for generated source files
|
||||
in this objdir, where `file` is either an absolute path to the file or
|
||||
a `mozpack.File` instance.
|
||||
'''
|
||||
import buildconfig
|
||||
|
||||
# First, get the list of generated sources produced by the build backend.
|
||||
gen_sources = os.path.join(buildconfig.topobjdir, 'generated-sources.json')
|
||||
with open(gen_sources, 'rb') as f:
|
||||
data = json.load(f)
|
||||
for f in data['sources']:
|
||||
yield f, mozpath.join(buildconfig.topobjdir, f)
|
||||
# Next, return all the files in $objdir/ipc/ipdl/_ipdlheaders.
|
||||
base = 'ipc/ipdl/_ipdlheaders'
|
||||
finder = FileFinder(mozpath.join(buildconfig.topobjdir, base))
|
||||
for p, f in finder.find('**/*.h'):
|
||||
yield mozpath.join(base, p), f
|
||||
# Next, return any Rust source files that were generated into the Rust
|
||||
# object directory.
|
||||
rust_build_kind = 'debug' if buildconfig.substs.get('MOZ_DEBUG_RUST') else 'release'
|
||||
base = mozpath.join('toolkit/library',
|
||||
buildconfig.substs['RUST_TARGET'],
|
||||
rust_build_kind,
|
||||
'build')
|
||||
finder = FileFinder(mozpath.join(buildconfig.topobjdir, base))
|
||||
for p, f in finder.find('**/*.rs'):
|
||||
yield mozpath.join(base, p), f
|
||||
|
||||
|
||||
def get_s3_region_and_bucket():
|
||||
'''
|
||||
Return a tuple of (region, bucket) giving the AWS region and S3
|
||||
bucket to which generated sources should be uploaded.
|
||||
'''
|
||||
region = 'us-west-2'
|
||||
level = os.environ.get('MOZ_SCM_LEVEL', '1')
|
||||
bucket = {
|
||||
'1': 'gecko-generated-sources-l1',
|
||||
'2': 'gecko-generated-sources-l2',
|
||||
'3': 'gecko-generated-sources',
|
||||
}[level]
|
||||
return (region, bucket)
|
|
@ -82,9 +82,10 @@ if CONFIG['MOZ_GMP_SANDBOX']:
|
|||
# consistent. See also the comment in SandboxLogging.h.
|
||||
SOURCES['../chromium/base/strings/safe_sprintf.cc'].flags += ['-DNDEBUG']
|
||||
|
||||
# Keep clang from warning about intentional 'switch' fallthrough in icu_utf.cc:
|
||||
if CONFIG['CLANG_CXX']:
|
||||
# Keep clang from warning about intentional 'switch' fallthrough in icu_utf.cc:
|
||||
SOURCES['../chromium/base/third_party/icu/icu_utf.cc'].flags += ['-Wno-implicit-fallthrough']
|
||||
SOURCES['../chromium/sandbox/linux/seccomp-bpf/trap.cc'].flags += ['-Wno-unreachable-code-return']
|
||||
|
||||
if CONFIG['GNU_CXX']:
|
||||
CXXFLAGS += ['-Wno-shadow']
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
# 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/.
|
||||
|
||||
loader: taskgraph.loader.single_dep:loader
|
||||
|
||||
transforms:
|
||||
- taskgraph.transforms.upload_generated_sources:transforms
|
||||
- taskgraph.transforms.job:transforms
|
||||
- taskgraph.transforms.task:transforms
|
||||
|
||||
kind-dependencies:
|
||||
- build
|
||||
|
||||
only-for-attributes:
|
||||
- nightly
|
||||
|
||||
job-template:
|
||||
description: Upload generated source files from build
|
||||
attributes:
|
||||
nightly: true
|
||||
worker-type: aws-provisioner-v1/gecko-t-linux-xlarge
|
||||
treeherder:
|
||||
symbol: Ugs
|
||||
kind: build
|
||||
worker:
|
||||
docker-image: {in-tree: "lint"}
|
||||
max-run-time: 600
|
||||
run:
|
||||
using: run-task
|
||||
command: >
|
||||
cd /home/worker/checkouts/gecko &&
|
||||
./mach python build/upload_generated_sources.py ${ARTIFACT_URL}
|
||||
scopes:
|
||||
- secrets:get:project/releng/gecko/build/level-{level}/gecko-generated-sources-upload
|
|
@ -67,6 +67,11 @@ upload-symbols
|
|||
Upload-symbols tasks run after builds and upload the symbols files generated by
|
||||
build tasks to Socorro for later use in crash analysis.
|
||||
|
||||
upload-generated-sources
|
||||
--------------
|
||||
|
||||
Upload-generated-sources tasks run after builds and upload source files that were generated as part of the build process to an s3 bucket for later use in links from crash reports or when debugging shipped builds.
|
||||
|
||||
valgrind
|
||||
--------
|
||||
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
# 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/.
|
||||
"""
|
||||
Transform the upload-generated-files task description template,
|
||||
taskcluster/ci/upload-generated-sources/kind.yml
|
||||
into an actual task description.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
from taskgraph.transforms.base import TransformSequence
|
||||
from taskgraph.util.taskcluster import get_artifact_url
|
||||
|
||||
|
||||
transforms = TransformSequence()
|
||||
|
||||
|
||||
@transforms.add
|
||||
def add_task_info(config, jobs):
|
||||
for job in jobs:
|
||||
dep_task = job['dependent-task']
|
||||
del job['dependent-task']
|
||||
|
||||
# Add a dependency on the build task.
|
||||
job['dependencies'] = {'build': dep_task.label}
|
||||
# Label the job to match the build task it's uploading from.
|
||||
job['label'] = dep_task.label.replace("build-", "upload-generated-sources-")
|
||||
# Copy over some bits of metdata from the build task.
|
||||
dep_th = dep_task.task['extra']['treeherder']
|
||||
job.setdefault('attributes', {})
|
||||
job['attributes']['build_platform'] = dep_task.attributes.get('build_platform')
|
||||
plat = '{}/{}'.format(dep_th['machine']['platform'], dep_task.attributes.get('build_type'))
|
||||
job['treeherder']['platform'] = plat
|
||||
job['treeherder']['tier'] = dep_th['tier']
|
||||
# Add an environment variable pointing at the artifact from the build.
|
||||
artifact_url = get_artifact_url('<build>',
|
||||
'public/build/target.generated-files.tar.gz')
|
||||
job['worker'].setdefault('env', {})['ARTIFACT_URL'] = {
|
||||
'task-reference': artifact_url
|
||||
}
|
||||
|
||||
yield job
|
|
@ -405911,6 +405911,12 @@
|
|||
{}
|
||||
]
|
||||
],
|
||||
"webdriver/tests/minimize_window.py": [
|
||||
[
|
||||
"/webdriver/tests/minimize_window.py",
|
||||
{}
|
||||
]
|
||||
],
|
||||
"webdriver/tests/navigation/current_url.py": [
|
||||
[
|
||||
"/webdriver/tests/navigation/current_url.py",
|
||||
|
@ -626276,6 +626282,10 @@
|
|||
"d783d0dd370f58b264ef238d8da5cd8601dc3c7f",
|
||||
"testharness"
|
||||
],
|
||||
"webdriver/tests/minimize_window.py": [
|
||||
"99fa058dd78c2f3a2d2c34dc7096edb9ca12ac4f",
|
||||
"wdspec"
|
||||
],
|
||||
"webdriver/tests/navigation/current_url.py": [
|
||||
"cec2987258d9c807a247da9e0216b3af1f171484",
|
||||
"wdspec"
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
from tests.support.inline import inline
|
||||
from tests.support.asserts import assert_error, assert_success
|
||||
|
||||
alert_doc = inline("<script>window.alert()</script>")
|
||||
|
||||
# 10.7.4 Minimize Window
|
||||
def test_minimize_no_browsing_context(session, create_window):
|
||||
# Step 1
|
||||
session.window_handle = create_window()
|
||||
session.close()
|
||||
result = session.transport.send("POST", "session/%s/window/minimize" % session.session_id)
|
||||
assert_error(result, "no such window")
|
||||
|
||||
|
||||
def test_handle_user_prompt(session):
|
||||
# Step 2
|
||||
session.url = alert_doc
|
||||
result = session.transport.send("POST", "session/%s/window/minimize" % session.session_id)
|
||||
assert_error(result, "unexpected alert open")
|
||||
|
||||
|
||||
def test_minimize(session):
|
||||
before_size = session.window.size
|
||||
assert session.window.state == "normal"
|
||||
|
||||
# step 4
|
||||
result = session.transport.send("POST", "session/%s/window/minimize" % session.session_id)
|
||||
assert_success(result)
|
||||
|
||||
assert session.window.state == "minimized"
|
||||
|
||||
|
||||
def test_payload(session):
|
||||
before_size = session.window.size
|
||||
assert session.window.state == "normal"
|
||||
|
||||
result = session.transport.send("POST", "session/%s/window/minimize" % session.session_id)
|
||||
|
||||
# step 5
|
||||
assert result.status == 200
|
||||
assert isinstance(result.body["value"], dict)
|
||||
|
||||
resp = result.body["value"]
|
||||
assert "width" in resp
|
||||
assert "height" in resp
|
||||
assert "x" in resp
|
||||
assert "y" in resp
|
||||
assert "state" in resp
|
||||
assert isinstance(resp["width"], (int, float))
|
||||
assert isinstance(resp["height"], (int, float))
|
||||
assert isinstance(resp["x"], (int, float))
|
||||
assert isinstance(resp["y"], (int, float))
|
||||
assert isinstance(resp["state"], basestring)
|
||||
|
||||
assert session.window.state == "minimized"
|
||||
|
|
@ -483,106 +483,119 @@ this.ExtensionData = class {
|
|||
};
|
||||
}
|
||||
|
||||
parseManifest() {
|
||||
return Promise.all([
|
||||
async parseManifest() {
|
||||
let [manifest] = await Promise.all([
|
||||
this.readJSON("manifest.json"),
|
||||
Management.lazyInit(),
|
||||
]).then(([manifest]) => {
|
||||
this.manifest = manifest;
|
||||
this.rawManifest = manifest;
|
||||
]);
|
||||
|
||||
if (manifest && manifest.default_locale) {
|
||||
return this.initLocale();
|
||||
this.manifest = manifest;
|
||||
this.rawManifest = manifest;
|
||||
|
||||
if (manifest && manifest.default_locale) {
|
||||
await this.initLocale();
|
||||
}
|
||||
|
||||
let context = {
|
||||
url: this.baseURI && this.baseURI.spec,
|
||||
|
||||
principal: this.principal,
|
||||
|
||||
logError: error => {
|
||||
this.manifestWarning(error);
|
||||
},
|
||||
|
||||
preprocessors: {},
|
||||
};
|
||||
|
||||
if (this.manifest.theme) {
|
||||
let invalidProps = validateThemeManifest(Object.getOwnPropertyNames(this.manifest));
|
||||
|
||||
if (invalidProps.length) {
|
||||
let message = `Themes defined in the manifest may only contain static resources. ` +
|
||||
`If you would like to use additional properties, please use the "theme" permission instead. ` +
|
||||
`(the invalid properties found are: ${invalidProps})`;
|
||||
this.manifestError(message);
|
||||
}
|
||||
}).then(() => {
|
||||
let context = {
|
||||
url: this.baseURI && this.baseURI.spec,
|
||||
}
|
||||
|
||||
principal: this.principal,
|
||||
if (this.localeData) {
|
||||
context.preprocessors.localize = (value, context) => this.localize(value);
|
||||
}
|
||||
|
||||
logError: error => {
|
||||
this.manifestWarning(error);
|
||||
},
|
||||
let normalized = Schemas.normalize(this.manifest, "manifest.WebExtensionManifest", context);
|
||||
if (normalized.error) {
|
||||
this.manifestError(normalized.error);
|
||||
return null;
|
||||
}
|
||||
|
||||
preprocessors: {},
|
||||
};
|
||||
manifest = normalized.value;
|
||||
|
||||
if (this.manifest.theme) {
|
||||
let invalidProps = validateThemeManifest(Object.getOwnPropertyNames(this.manifest));
|
||||
let id;
|
||||
try {
|
||||
if (manifest.applications.gecko.id) {
|
||||
id = manifest.applications.gecko.id;
|
||||
}
|
||||
} catch (e) {
|
||||
// Errors are handled by the type checks above.
|
||||
}
|
||||
|
||||
if (invalidProps.length) {
|
||||
let message = `Themes defined in the manifest may only contain static resources. ` +
|
||||
`If you would like to use additional properties, please use the "theme" permission instead. ` +
|
||||
`(the invalid properties found are: ${invalidProps})`;
|
||||
this.manifestError(message);
|
||||
if (!this.id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
let apiNames = new Set();
|
||||
let dependencies = new Set();
|
||||
let originPermissions = new Set();
|
||||
let permissions = new Set();
|
||||
|
||||
for (let perm of manifest.permissions) {
|
||||
if (perm === "geckoProfiler") {
|
||||
const acceptedExtensions = Services.prefs.getStringPref("extensions.geckoProfiler.acceptedExtensionIds", "");
|
||||
if (!acceptedExtensions.split(",").includes(id)) {
|
||||
this.manifestError("Only whitelisted extensions are allowed to access the geckoProfiler.");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.localeData) {
|
||||
context.preprocessors.localize = (value, context) => this.localize(value);
|
||||
let type = classifyPermission(perm);
|
||||
if (type.origin) {
|
||||
let matcher = new MatchPattern(perm, {ignorePath: true});
|
||||
|
||||
perm = matcher.pattern;
|
||||
originPermissions.add(perm);
|
||||
} else if (type.api) {
|
||||
apiNames.add(type.api);
|
||||
}
|
||||
|
||||
let normalized = Schemas.normalize(this.manifest, "manifest.WebExtensionManifest", context);
|
||||
if (normalized.error) {
|
||||
this.manifestError(normalized.error);
|
||||
return null;
|
||||
}
|
||||
permissions.add(perm);
|
||||
}
|
||||
|
||||
let manifest = normalized.value;
|
||||
|
||||
let id;
|
||||
try {
|
||||
if (manifest.applications.gecko.id) {
|
||||
id = manifest.applications.gecko.id;
|
||||
}
|
||||
} catch (e) {
|
||||
// Errors are handled by the type checks above.
|
||||
}
|
||||
|
||||
let apiNames = new Set();
|
||||
let dependencies = new Set();
|
||||
let hostPermissions = new Set();
|
||||
let permissions = new Set();
|
||||
|
||||
for (let perm of manifest.permissions) {
|
||||
if (perm === "geckoProfiler") {
|
||||
const acceptedExtensions = Services.prefs.getStringPref("extensions.geckoProfiler.acceptedExtensionIds", "");
|
||||
if (!acceptedExtensions.split(",").includes(id)) {
|
||||
this.manifestError("Only whitelisted extensions are allowed to access the geckoProfiler.");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let type = classifyPermission(perm);
|
||||
if (type.origin) {
|
||||
let matcher = new MatchPattern(perm, {ignorePath: true});
|
||||
|
||||
perm = matcher.pattern;
|
||||
hostPermissions.add(perm);
|
||||
} else if (type.api) {
|
||||
apiNames.add(type.api);
|
||||
}
|
||||
if (this.id) {
|
||||
// An extension always gets permission to its own url.
|
||||
let matcher = new MatchPattern(this.getURL(), {ignorePath: true});
|
||||
originPermissions.add(matcher.pattern);
|
||||
|
||||
// Apply optional permissions
|
||||
let perms = await ExtensionPermissions.get(this);
|
||||
for (let perm of perms.permissions) {
|
||||
permissions.add(perm);
|
||||
}
|
||||
|
||||
// An extension always gets permission to its own url.
|
||||
if (this.id) {
|
||||
let matcher = new MatchPattern(this.getURL(), {ignorePath: true});
|
||||
hostPermissions.add(matcher.pattern);
|
||||
for (let origin of perms.origins) {
|
||||
originPermissions.add(origin);
|
||||
}
|
||||
}
|
||||
|
||||
for (let api of apiNames) {
|
||||
dependencies.add(`${api}@experiments.addons.mozilla.org`);
|
||||
}
|
||||
for (let api of apiNames) {
|
||||
dependencies.add(`${api}@experiments.addons.mozilla.org`);
|
||||
}
|
||||
|
||||
// Normalize all patterns to contain a single leading /
|
||||
let webAccessibleResources = (manifest.web_accessible_resources || [])
|
||||
.map(path => path.replace(/^\/*/, "/"));
|
||||
// Normalize all patterns to contain a single leading /
|
||||
let webAccessibleResources = (manifest.web_accessible_resources || [])
|
||||
.map(path => path.replace(/^\/*/, "/"));
|
||||
|
||||
return {apiNames, dependencies, hostPermissions, id, manifest, permissions,
|
||||
webAccessibleResources};
|
||||
});
|
||||
return {apiNames, dependencies, originPermissions, id, manifest, permissions,
|
||||
webAccessibleResources};
|
||||
}
|
||||
|
||||
// Reads the extension's |manifest.json| file, and stores its
|
||||
|
@ -608,7 +621,7 @@ this.ExtensionData = class {
|
|||
this.permissions = manifestData.permissions;
|
||||
|
||||
this.webAccessibleResources = manifestData.webAccessibleResources.map(res => new MatchGlob(res));
|
||||
this.whiteListedHosts = new MatchPatternSet(manifestData.hostPermissions);
|
||||
this.whiteListedHosts = new MatchPatternSet(manifestData.originPermissions);
|
||||
|
||||
return this.manifest;
|
||||
}
|
||||
|
@ -821,7 +834,8 @@ this.Extension = class extends ExtensionData {
|
|||
|
||||
this.id = addonData.id;
|
||||
this.version = addonData.version;
|
||||
this.baseURI = Services.io.newURI(this.getURL("")).QueryInterface(Ci.nsIURL);
|
||||
this.baseURL = this.getURL("");
|
||||
this.baseURI = Services.io.newURI(this.baseURL).QueryInterface(Ci.nsIURL);
|
||||
this.principal = this.createPrincipal();
|
||||
this.views = new Set();
|
||||
this._backgroundPageFrameLoader = null;
|
||||
|
@ -849,12 +863,14 @@ this.Extension = class extends ExtensionData {
|
|||
if (permissions.origins.length > 0) {
|
||||
let patterns = this.whiteListedHosts.patterns.map(host => host.pattern);
|
||||
|
||||
this.whiteListedHosts = new MatchPatternSet([...patterns, ...permissions.origins],
|
||||
this.whiteListedHosts = new MatchPatternSet(new Set([...patterns, ...permissions.origins]),
|
||||
{ignorePath: true});
|
||||
}
|
||||
|
||||
this.policy.permissions = Array.from(this.permissions);
|
||||
this.policy.allowedOrigins = this.whiteListedHosts;
|
||||
|
||||
this.cachePermissions();
|
||||
});
|
||||
|
||||
this.on("remove-permissions", (ignoreEvent, permissions) => {
|
||||
|
@ -871,6 +887,8 @@ this.Extension = class extends ExtensionData {
|
|||
|
||||
this.policy.permissions = Array.from(this.permissions);
|
||||
this.policy.allowedOrigins = this.whiteListedHosts;
|
||||
|
||||
this.cachePermissions();
|
||||
});
|
||||
/* eslint-enable mozilla/balanced-listeners */
|
||||
}
|
||||
|
@ -943,7 +961,17 @@ this.Extension = class extends ExtensionData {
|
|||
let uri = Services.io.newURI(url);
|
||||
|
||||
let common = this.baseURI.getCommonBaseSpec(uri);
|
||||
return common == this.baseURI.spec;
|
||||
return common == this.baseURL;
|
||||
}
|
||||
|
||||
checkLoadURL(url, options = {}) {
|
||||
// As an optimization, f the URL starts with the extension's base URL,
|
||||
// don't do any further checks. It's always allowed to load it.
|
||||
if (url.startsWith(this.baseURL)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return ExtensionUtils.checkLoadURL(url, this.principal, options);
|
||||
}
|
||||
|
||||
async promiseLocales(locale) {
|
||||
|
@ -961,9 +989,20 @@ this.Extension = class extends ExtensionData {
|
|||
});
|
||||
}
|
||||
|
||||
get manifestCacheKey() {
|
||||
return [this.id, this.version, Services.locale.getAppLocaleAsLangTag()];
|
||||
}
|
||||
|
||||
parseManifest() {
|
||||
return StartupCache.manifests.get([this.id, this.version, Services.locale.getAppLocaleAsLangTag()],
|
||||
() => super.parseManifest());
|
||||
return StartupCache.manifests.get(this.manifestCacheKey, () => super.parseManifest());
|
||||
}
|
||||
|
||||
async cachePermissions() {
|
||||
let manifestData = await this.parseManifest();
|
||||
|
||||
manifestData.originPermissions = this.whiteListedHosts.patterns.map(pat => pat.pattern);
|
||||
manifestData.permissions = this.permissions;
|
||||
return StartupCache.manifests.set(this.manifestCacheKey, manifestData);
|
||||
}
|
||||
|
||||
async loadManifest() {
|
||||
|
@ -1171,10 +1210,7 @@ this.Extension = class extends ExtensionData {
|
|||
|
||||
TelemetryStopwatch.start("WEBEXT_EXTENSION_STARTUP_MS", this);
|
||||
try {
|
||||
let [perms] = await Promise.all([
|
||||
ExtensionPermissions.get(this),
|
||||
this.loadManifest(),
|
||||
]);
|
||||
await this.loadManifest();
|
||||
|
||||
if (!this.hasShutdown) {
|
||||
await this.initLocale();
|
||||
|
@ -1190,17 +1226,6 @@ this.Extension = class extends ExtensionData {
|
|||
|
||||
GlobalManager.init(this);
|
||||
|
||||
// Apply optional permissions
|
||||
for (let perm of perms.permissions) {
|
||||
this.permissions.add(perm);
|
||||
}
|
||||
if (perms.origins.length > 0) {
|
||||
let patterns = this.whiteListedHosts.patterns.map(host => host.pattern);
|
||||
|
||||
this.whiteListedHosts = new MatchPatternSet([...patterns, ...perms.origins],
|
||||
{ignorePath: true});
|
||||
}
|
||||
|
||||
this.policy.active = false;
|
||||
this.policy = processScript.initExtension(this);
|
||||
|
||||
|
|
|
@ -338,6 +338,7 @@ class Messenger {
|
|||
return Promise.reject({message: error.message});
|
||||
}
|
||||
});
|
||||
holder = null;
|
||||
|
||||
return this.context.wrapPromise(promise, responseCallback);
|
||||
}
|
||||
|
@ -374,12 +375,16 @@ class Messenger {
|
|||
});
|
||||
|
||||
let message = holder.deserialize(this.context.cloneScope);
|
||||
holder = null;
|
||||
|
||||
sender = Cu.cloneInto(sender, this.context.cloneScope);
|
||||
sendResponse = Cu.exportFunction(sendResponse, this.context.cloneScope);
|
||||
|
||||
// Note: We intentionally do not use runSafe here so that any
|
||||
// errors are propagated to the message sender.
|
||||
let result = fire.raw(message, sender, sendResponse);
|
||||
message = null;
|
||||
|
||||
if (result instanceof this.context.cloneScope.Promise) {
|
||||
return result;
|
||||
} else if (result === true) {
|
||||
|
@ -511,6 +516,7 @@ class BrowserExtensionContent extends EventEmitter {
|
|||
this.localeData = new LocaleData(data.localeData);
|
||||
|
||||
this.manifest = data.manifest;
|
||||
this.baseURL = data.baseURL;
|
||||
this.baseURI = Services.io.newURI(data.baseURL);
|
||||
|
||||
// Only used in addon processes.
|
||||
|
@ -813,9 +819,6 @@ class ChildAPIManager {
|
|||
* @param {function(*)} [callback] The callback to be called when the function
|
||||
* completes.
|
||||
* @param {object} [options] Extra options.
|
||||
* @param {boolean} [options.noClone = false] If true, do not clone
|
||||
* the arguments into an extension sandbox before calling the API
|
||||
* method.
|
||||
* @returns {Promise|undefined} Must be void if `callback` is set, and a
|
||||
* promise otherwise. The promise is resolved when the function completes.
|
||||
*/
|
||||
|
@ -829,7 +832,6 @@ class ChildAPIManager {
|
|||
callId,
|
||||
path,
|
||||
args,
|
||||
noClone: options.noClone || false,
|
||||
});
|
||||
|
||||
return this.context.wrapPromise(deferred.promise, callback);
|
||||
|
|
|
@ -42,11 +42,10 @@ var {
|
|||
EventEmitter,
|
||||
ExtensionError,
|
||||
defineLazyGetter,
|
||||
filterStack,
|
||||
getConsole,
|
||||
getInnerWindowID,
|
||||
getUniqueId,
|
||||
runSafeSync,
|
||||
runSafeSyncWithoutClone,
|
||||
instanceOf,
|
||||
} = ExtensionUtils;
|
||||
|
||||
|
@ -250,48 +249,55 @@ class BaseContext {
|
|||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
runSafe(...args) {
|
||||
runSafe(callback, ...args) {
|
||||
return this.applySafe(callback, args);
|
||||
}
|
||||
|
||||
runSafeWithoutClone(callback, ...args) {
|
||||
return this.applySafeWithoutClone(callback, args);
|
||||
}
|
||||
|
||||
applySafe(callback, args) {
|
||||
if (this.unloaded) {
|
||||
Cu.reportError("context.runSafe called after context unloaded");
|
||||
} else if (!this.active) {
|
||||
Cu.reportError("context.runSafe called while context is inactive");
|
||||
} else {
|
||||
return runSafeSync(this, ...args);
|
||||
try {
|
||||
let {cloneScope} = this;
|
||||
args = args.map(arg => Cu.cloneInto(arg, cloneScope));
|
||||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
dump(`runSafe failure: cloning into ${this.cloneScope}: ${e}\n\n${filterStack(Error())}`);
|
||||
}
|
||||
|
||||
return this.applySafeWithoutClone(callback, args);
|
||||
}
|
||||
}
|
||||
|
||||
runSafeWithoutClone(...args) {
|
||||
applySafeWithoutClone(callback, args) {
|
||||
if (this.unloaded) {
|
||||
Cu.reportError("context.runSafeWithoutClone called after context unloaded");
|
||||
} else if (!this.active) {
|
||||
Cu.reportError("context.runSafeWithoutClone called while context is inactive");
|
||||
} else {
|
||||
return runSafeSyncWithoutClone(...args);
|
||||
try {
|
||||
return Reflect.apply(callback, null, args);
|
||||
} catch (e) {
|
||||
dump(`Extension error: ${e} ${e.fileName} ${e.lineNumber}\n[[Exception stack\n${filterStack(e)}Current stack\n${filterStack(Error())}]]\n`);
|
||||
Cu.reportError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkLoadURL(url, options = {}) {
|
||||
let ssm = Services.scriptSecurityManager;
|
||||
|
||||
let flags = ssm.STANDARD;
|
||||
if (!options.allowScript) {
|
||||
flags |= ssm.DISALLOW_SCRIPT;
|
||||
}
|
||||
if (!options.allowInheritsPrincipal) {
|
||||
flags |= ssm.DISALLOW_INHERIT_PRINCIPAL;
|
||||
}
|
||||
if (options.dontReportErrors) {
|
||||
flags |= ssm.DONT_REPORT_ERRORS;
|
||||
// As an optimization, f the URL starts with the extension's base URL,
|
||||
// don't do any further checks. It's always allowed to load it.
|
||||
if (url.startsWith(this.extension.baseURL)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
ssm.checkLoadURIWithPrincipal(this.principal,
|
||||
Services.io.newURI(url),
|
||||
flags);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return ExtensionUtils.checkLoadURL(url, this.principal, options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -430,9 +436,9 @@ class BaseContext {
|
|||
* belonging to the target scope. Otherwise, undefined.
|
||||
*/
|
||||
wrapPromise(promise, callback = null) {
|
||||
let runSafe = this.runSafe.bind(this);
|
||||
if (promise instanceof this.cloneScope.Promise) {
|
||||
runSafe = this.runSafeWithoutClone.bind(this);
|
||||
let applySafe = this.applySafe.bind(this);
|
||||
if (Cu.getGlobalForObject(promise) === this.cloneScope) {
|
||||
applySafe = this.applySafeWithoutClone.bind(this);
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
|
@ -443,11 +449,11 @@ class BaseContext {
|
|||
} else if (!this.active) {
|
||||
dump(`Promise resolved while context is inactive\n`);
|
||||
} else if (args instanceof NoCloneSpreadArgs) {
|
||||
this.runSafeWithoutClone(callback, ...args.unwrappedValues);
|
||||
this.applySafeWithoutClone(callback, args.unwrappedValues);
|
||||
} else if (args instanceof SpreadArgs) {
|
||||
runSafe(callback, ...args);
|
||||
applySafe(callback, args);
|
||||
} else {
|
||||
runSafe(callback, args);
|
||||
applySafe(callback, [args]);
|
||||
}
|
||||
},
|
||||
error => {
|
||||
|
@ -457,7 +463,7 @@ class BaseContext {
|
|||
} else if (!this.active) {
|
||||
dump(`Promise rejected while context is inactive\n`);
|
||||
} else {
|
||||
this.runSafeWithoutClone(callback);
|
||||
this.applySafeWithoutClone(callback, []);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -471,11 +477,11 @@ class BaseContext {
|
|||
dump(`Promise resolved while context is inactive\n`);
|
||||
} else if (value instanceof NoCloneSpreadArgs) {
|
||||
let values = value.unwrappedValues;
|
||||
this.runSafeWithoutClone(resolve, values.length == 1 ? values[0] : values);
|
||||
this.applySafeWithoutClone(resolve, values.length == 1 ? [values[0]] : [values]);
|
||||
} else if (value instanceof SpreadArgs) {
|
||||
runSafe(resolve, value.length == 1 ? value[0] : value);
|
||||
applySafe(resolve, value.length == 1 ? value : [value]);
|
||||
} else {
|
||||
runSafe(resolve, value);
|
||||
applySafe(resolve, [value]);
|
||||
}
|
||||
},
|
||||
value => {
|
||||
|
@ -484,7 +490,7 @@ class BaseContext {
|
|||
} else if (!this.active) {
|
||||
dump(`Promise rejected while context is inactive: ${value && value.message}\n`);
|
||||
} else {
|
||||
this.runSafeWithoutClone(reject, this.normalizeError(value));
|
||||
this.applySafeWithoutClone(reject, [this.normalizeError(value)]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -45,6 +45,7 @@ var {
|
|||
} = ExtensionCommon;
|
||||
|
||||
var {
|
||||
DefaultMap,
|
||||
DefaultWeakMap,
|
||||
ExtensionError,
|
||||
MessageManagerProxy,
|
||||
|
@ -413,6 +414,12 @@ class ProxyContextParent extends BaseContext {
|
|||
return this.sandbox;
|
||||
}
|
||||
|
||||
runSafe(...args) {
|
||||
// There's no need to clone when calling listeners for a proxied
|
||||
// context.
|
||||
return this.runSafeWithoutClone(...args);
|
||||
}
|
||||
|
||||
get xulBrowser() {
|
||||
return this.messageManagerProxy.eventTarget;
|
||||
}
|
||||
|
@ -725,7 +732,7 @@ ParentAPIManager = {
|
|||
};
|
||||
|
||||
try {
|
||||
let args = data.noClone ? data.args : Cu.cloneInto(data.args, context.sandbox);
|
||||
let args = data.args;
|
||||
let pendingBrowser = context.pendingEventBrowser;
|
||||
let fun = await context.apiCan.asyncFindAPIPath(data.path);
|
||||
let result = context.withPendingBrowser(pendingBrowser,
|
||||
|
@ -781,7 +788,7 @@ ParentAPIManager = {
|
|||
|
||||
context.listenerProxies.set(data.listenerId, listener);
|
||||
|
||||
let args = Cu.cloneInto(data.args, context.sandbox);
|
||||
let args = data.args;
|
||||
let promise = context.apiCan.asyncFindAPIPath(data.path);
|
||||
|
||||
// Store pending listener additions so we can be sure they're all
|
||||
|
@ -1214,13 +1221,11 @@ function extensionNameFromURI(uri) {
|
|||
return GlobalManager.getExtension(id).name;
|
||||
}
|
||||
|
||||
const INTEGER = /^[1-9]\d*$/;
|
||||
|
||||
// Manages icon details for toolbar buttons in the |pageAction| and
|
||||
// |browserAction| APIs.
|
||||
let IconDetails = {
|
||||
// WeakMap<Extension -> Map<url-string -> object>>
|
||||
iconCache: new DefaultWeakMap(() => new Map()),
|
||||
iconCache: new DefaultWeakMap(() => new DefaultMap(() => new Map())),
|
||||
|
||||
// Normalizes the various acceptable input formats into an object
|
||||
// with icon size as key and icon URL as value.
|
||||
|
@ -1232,16 +1237,21 @@ let IconDetails = {
|
|||
// If no context is specified, instead of throwing an error, this
|
||||
// function simply logs a warning message.
|
||||
normalize(details, extension, context = null) {
|
||||
if (!details.imageData && typeof details.path === "string") {
|
||||
let icons = this.iconCache.get(extension);
|
||||
if (!details.imageData && details.path) {
|
||||
// Pick a cache key for the icon paths. If the path is a string,
|
||||
// use it directly. Otherwise, stringify the path object.
|
||||
let key = details.path;
|
||||
if (typeof key !== "string") {
|
||||
key = uneval(key);
|
||||
}
|
||||
|
||||
let baseURI = context ? context.uri : extension.baseURI;
|
||||
let url = baseURI.resolve(details.path);
|
||||
let icons = this.iconCache.get(extension)
|
||||
.get(context && context.uri.spec);
|
||||
|
||||
let icon = icons.get(url);
|
||||
let icon = icons.get(key);
|
||||
if (!icon) {
|
||||
icon = this._normalize(details, extension, context);
|
||||
icons.set(url, icon);
|
||||
icons.set(key, icon);
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
@ -1261,9 +1271,6 @@ let IconDetails = {
|
|||
}
|
||||
|
||||
for (let size of Object.keys(imageData)) {
|
||||
if (!INTEGER.test(size)) {
|
||||
throw new ExtensionError(`Invalid icon size ${size}, must be an integer`);
|
||||
}
|
||||
result[size] = imageData[size];
|
||||
}
|
||||
}
|
||||
|
@ -1276,17 +1283,13 @@ let IconDetails = {
|
|||
}
|
||||
|
||||
for (let size of Object.keys(path)) {
|
||||
if (!INTEGER.test(size)) {
|
||||
throw new ExtensionError(`Invalid icon size ${size}, must be an integer`);
|
||||
}
|
||||
|
||||
let url = baseURI.resolve(path[size]);
|
||||
|
||||
// The Chrome documentation specifies these parameters as
|
||||
// relative paths. We currently accept absolute URLs as well,
|
||||
// which means we need to check that the extension is allowed
|
||||
// to load them. This will throw an error if it's not allowed.
|
||||
this._checkURL(url, extension.principal);
|
||||
this._checkURL(url, extension);
|
||||
|
||||
result[size] = url;
|
||||
}
|
||||
|
@ -1297,8 +1300,8 @@ let IconDetails = {
|
|||
let lightURL = baseURI.resolve(light);
|
||||
let darkURL = baseURI.resolve(dark);
|
||||
|
||||
this._checkURL(lightURL, extension.principal);
|
||||
this._checkURL(darkURL, extension.principal);
|
||||
this._checkURL(lightURL, extension);
|
||||
this._checkURL(darkURL, extension);
|
||||
|
||||
let defaultURL = result[size];
|
||||
result[size] = {
|
||||
|
@ -1324,12 +1327,8 @@ let IconDetails = {
|
|||
|
||||
// Checks if the extension is allowed to load the given URL with the specified principal.
|
||||
// This will throw an error if the URL is not allowed.
|
||||
_checkURL(url, principal) {
|
||||
try {
|
||||
Services.scriptSecurityManager.checkLoadURIWithPrincipal(
|
||||
principal, Services.io.newURI(url),
|
||||
Services.scriptSecurityManager.DISALLOW_SCRIPT);
|
||||
} catch (e) {
|
||||
_checkURL(url, extension) {
|
||||
if (!extension.checkLoadURL(url, {allowInheritsPrincipal: true})) {
|
||||
throw new ExtensionError(`Illegal URL ${url}`);
|
||||
}
|
||||
},
|
||||
|
@ -1531,6 +1530,13 @@ class CacheStore {
|
|||
return result;
|
||||
}
|
||||
|
||||
async set(path, value) {
|
||||
let [store, key] = await this.getStore(path);
|
||||
|
||||
store.set(key, value);
|
||||
StartupCache.save();
|
||||
}
|
||||
|
||||
async getAll() {
|
||||
let [store] = await this.getStore();
|
||||
|
||||
|
|
|
@ -652,7 +652,32 @@ class MessageManagerProxy {
|
|||
}
|
||||
}
|
||||
|
||||
function checkLoadURL(url, principal, options) {
|
||||
let ssm = Services.scriptSecurityManager;
|
||||
|
||||
let flags = ssm.STANDARD;
|
||||
if (!options.allowScript) {
|
||||
flags |= ssm.DISALLOW_SCRIPT;
|
||||
}
|
||||
if (!options.allowInheritsPrincipal) {
|
||||
flags |= ssm.DISALLOW_INHERIT_PRINCIPAL;
|
||||
}
|
||||
if (options.dontReportErrors) {
|
||||
flags |= ssm.DONT_REPORT_ERRORS;
|
||||
}
|
||||
|
||||
try {
|
||||
ssm.checkLoadURIWithPrincipal(principal,
|
||||
Services.io.newURI(url),
|
||||
flags);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
this.ExtensionUtils = {
|
||||
checkLoadURL,
|
||||
defineLazyGetter,
|
||||
flushJarCache,
|
||||
getConsole,
|
||||
|
|
|
@ -522,6 +522,7 @@ this.MessageChannel = {
|
|||
|
||||
let channelId = ExtensionUtils.getUniqueId();
|
||||
let message = {messageName, channelId, sender, recipient, data, responseType};
|
||||
data = null;
|
||||
|
||||
if (responseType == this.RESPONSE_NONE) {
|
||||
try {
|
||||
|
@ -561,6 +562,7 @@ this.MessageChannel = {
|
|||
} catch (e) {
|
||||
deferred.reject(e);
|
||||
}
|
||||
message = null;
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
|
@ -595,6 +597,7 @@ this.MessageChannel = {
|
|||
return Promise.reject(e);
|
||||
}
|
||||
});
|
||||
data = null;
|
||||
responses = responses.filter(response => response !== undefined);
|
||||
|
||||
switch (responseType) {
|
||||
|
@ -634,6 +637,7 @@ this.MessageChannel = {
|
|||
Cu.reportError(e.stack ? `${e}\n${e.stack}` : e.message || e);
|
||||
});
|
||||
});
|
||||
data = null;
|
||||
// Note: Unhandled messages are silently dropped.
|
||||
return;
|
||||
}
|
||||
|
@ -649,11 +653,12 @@ this.MessageChannel = {
|
|||
deferred.reject = reject;
|
||||
|
||||
this._callHandlers(handlers, data).then(resolve, reject);
|
||||
data = null;
|
||||
}).then(
|
||||
value => {
|
||||
let response = {
|
||||
result: this.RESULT_SUCCESS,
|
||||
messageName: data.channelId,
|
||||
messageName: deferred.channelId,
|
||||
recipient: {},
|
||||
value,
|
||||
};
|
||||
|
@ -673,7 +678,7 @@ this.MessageChannel = {
|
|||
|
||||
let response = {
|
||||
result: this.RESULT_ERROR,
|
||||
messageName: data.channelId,
|
||||
messageName: deferred.channelId,
|
||||
recipient: {},
|
||||
error: {},
|
||||
};
|
||||
|
|
|
@ -94,7 +94,7 @@ this.storage = class extends ExtensionAPI {
|
|||
try {
|
||||
let result = await context.childManager.callParentAsyncFunction("storage.local.get", [
|
||||
serialize(keys),
|
||||
], null, {noClone: true}).then(deserialize);
|
||||
]).then(deserialize);
|
||||
TelemetryStopwatch.finish(storageGetHistogram, stopwatchKey);
|
||||
return result;
|
||||
} catch (e) {
|
||||
|
@ -108,7 +108,7 @@ this.storage = class extends ExtensionAPI {
|
|||
try {
|
||||
let result = await context.childManager.callParentAsyncFunction("storage.local.set", [
|
||||
serialize(items),
|
||||
], null, {noClone: true});
|
||||
]);
|
||||
TelemetryStopwatch.finish(storageSetHistogram, stopwatchKey);
|
||||
return result;
|
||||
} catch (e) {
|
||||
|
|
|
@ -41,6 +41,12 @@ import multiprocessing
|
|||
|
||||
from optparse import OptionParser
|
||||
|
||||
from mozbuild.util import memoize
|
||||
from mozbuild.generated_sources import (
|
||||
get_filename_with_digest,
|
||||
get_generated_sources,
|
||||
get_s3_region_and_bucket,
|
||||
)
|
||||
from mozpack.copier import FileRegistry
|
||||
from mozpack.manifests import (
|
||||
InstallManifest,
|
||||
|
@ -331,6 +337,15 @@ def make_file_mapping(install_manifests):
|
|||
file_mapping[abs_dest] = src.path
|
||||
return file_mapping
|
||||
|
||||
@memoize
|
||||
def get_generated_file_s3_path(filename, rel_path, bucket):
|
||||
"""Given a filename, return a path formatted similarly to
|
||||
GetVCSFilename but representing a file available in an s3 bucket."""
|
||||
with open(filename, 'rb') as f:
|
||||
path = get_filename_with_digest(rel_path, f.read())
|
||||
return 's3:{bucket}:{path}:'.format(bucket=bucket, path=path)
|
||||
|
||||
|
||||
def GetPlatformSpecificDumper(**kwargs):
|
||||
"""This function simply returns a instance of a subclass of Dumper
|
||||
that is appropriate for the current platform."""
|
||||
|
@ -376,6 +391,8 @@ class Dumper:
|
|||
copy_debug=False,
|
||||
vcsinfo=False,
|
||||
srcsrv=False,
|
||||
generated_files=None,
|
||||
s3_bucket=None,
|
||||
file_mapping=None):
|
||||
# popen likes absolute paths, at least on windows
|
||||
self.dump_syms = os.path.abspath(dump_syms)
|
||||
|
@ -389,6 +406,8 @@ class Dumper:
|
|||
self.copy_debug = copy_debug
|
||||
self.vcsinfo = vcsinfo
|
||||
self.srcsrv = srcsrv
|
||||
self.generated_files = generated_files or {}
|
||||
self.s3_bucket = s3_bucket
|
||||
self.file_mapping = file_mapping or {}
|
||||
# Add a static mapping for Rust sources.
|
||||
target_os = buildconfig.substs['OS_ARCH']
|
||||
|
@ -496,7 +515,12 @@ class Dumper:
|
|||
if filename in self.file_mapping:
|
||||
filename = self.file_mapping[filename]
|
||||
if self.vcsinfo:
|
||||
(filename, rootname) = GetVCSFilename(filename, self.srcdirs)
|
||||
gen_path = self.generated_files.get(filename)
|
||||
if gen_path and self.s3_bucket:
|
||||
filename = get_generated_file_s3_path(filename, gen_path, self.s3_bucket)
|
||||
rootname = ''
|
||||
else:
|
||||
(filename, rootname) = GetVCSFilename(filename, self.srcdirs)
|
||||
# sets vcs_root in case the loop through files were to end on an empty rootname
|
||||
if vcs_root is None:
|
||||
if rootname:
|
||||
|
@ -667,6 +691,7 @@ class Dumper_Win32(Dumper):
|
|||
os.remove(stream_output_path)
|
||||
return result
|
||||
|
||||
|
||||
class Dumper_Linux(Dumper):
|
||||
objcopy = os.environ['OBJCOPY'] if 'OBJCOPY' in os.environ else 'objcopy'
|
||||
def ShouldProcess(self, file):
|
||||
|
@ -840,6 +865,9 @@ to canonical locations in the source repository. Specify
|
|||
parser.error(str(e))
|
||||
exit(1)
|
||||
file_mapping = make_file_mapping(manifests)
|
||||
generated_files = {os.path.join(buildconfig.topobjdir, f): f
|
||||
for (f, _) in get_generated_sources()}
|
||||
_, bucket = get_s3_region_and_bucket()
|
||||
dumper = GetPlatformSpecificDumper(dump_syms=args[0],
|
||||
symbol_path=args[1],
|
||||
copy_debug=options.copy_debug,
|
||||
|
@ -847,6 +875,8 @@ to canonical locations in the source repository. Specify
|
|||
srcdirs=options.srcdir,
|
||||
vcsinfo=options.vcsinfo,
|
||||
srcsrv=options.srcsrv,
|
||||
generated_files=generated_files,
|
||||
s3_bucket=bucket,
|
||||
file_mapping=file_mapping)
|
||||
|
||||
dumper.Process(args[2])
|
||||
|
|
|
@ -214,6 +214,28 @@ class TestGetVCSFilename(HelperMixin, unittest.TestCase):
|
|||
symbolstore.GetVCSFilename(filename, [self.test_dir])[0])
|
||||
|
||||
|
||||
# SHA-512 of a zero-byte file
|
||||
EMPTY_SHA512 = 'cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e'
|
||||
|
||||
|
||||
class TestGeneratedFilePath(HelperMixin, unittest.TestCase):
|
||||
def setUp(self):
|
||||
HelperMixin.setUp(self)
|
||||
|
||||
def tearDown(self):
|
||||
HelperMixin.tearDown(self)
|
||||
|
||||
def test_generated_file_path(self):
|
||||
# Make an empty generated file
|
||||
g = os.path.join(self.test_dir, 'generated')
|
||||
rel_path = 'a/b/generated'
|
||||
with open(g, 'wb') as f:
|
||||
pass
|
||||
expected = 's3:bucket:{}/{}:'.format(EMPTY_SHA512,
|
||||
rel_path)
|
||||
self.assertEqual(expected, symbolstore.get_generated_file_s3_path(g, rel_path, 'bucket'))
|
||||
|
||||
|
||||
if target_platform() == 'WINNT':
|
||||
class TestFixFilenameCase(HelperMixin, unittest.TestCase):
|
||||
def test_fix_filename_case(self):
|
||||
|
|
|
@ -80,6 +80,9 @@ PKG_SRCPACK_PATH =
|
|||
SYMBOL_FULL_ARCHIVE_BASENAME = $(PKG_BASENAME).crashreporter-symbols-full
|
||||
SYMBOL_ARCHIVE_BASENAME = $(PKG_BASENAME).crashreporter-symbols
|
||||
|
||||
# Generated file package naming
|
||||
GENERATED_SOURCE_FILE_PACKAGE = $(PKG_BASENAME).generated-files.tar.gz
|
||||
|
||||
# Code coverage package naming
|
||||
CODE_COVERAGE_ARCHIVE_BASENAME = $(PKG_BASENAME).code-coverage-gcno
|
||||
|
||||
|
|
|
@ -397,6 +397,7 @@ UPLOAD_FILES= \
|
|||
$(call QUOTED_WILDCARD,$(DIST)/$(PKG_PATH)$(WP_TEST_PACKAGE)) \
|
||||
$(call QUOTED_WILDCARD,$(DIST)/$(PKG_PATH)$(GTEST_PACKAGE)) \
|
||||
$(call QUOTED_WILDCARD,$(DIST)/$(PKG_PATH)$(SYMBOL_ARCHIVE_BASENAME).zip) \
|
||||
$(call QUOTED_WILDCARD,$(DIST)/$(PKG_PATH)$(GENERATED_SOURCE_FILE_PACKAGE)) \
|
||||
$(call QUOTED_WILDCARD,$(MOZ_SOURCESTAMP_FILE)) \
|
||||
$(call QUOTED_WILDCARD,$(MOZ_BUILDINFO_FILE)) \
|
||||
$(call QUOTED_WILDCARD,$(MOZ_BUILDID_INFO_TXT_FILE)) \
|
||||
|
|
|
@ -34,4 +34,7 @@ SOURCES += [
|
|||
'FuzzerUtilLinux.cpp',
|
||||
'FuzzerUtilPosix.cpp',
|
||||
'FuzzerUtilWindows.cpp'
|
||||
]
|
||||
]
|
||||
|
||||
if CONFIG['CLANG_CXX']:
|
||||
CXXFLAGS += ['-Wno-unreachable-code-return']
|
||||
|
|
|
@ -57,7 +57,7 @@ endif
|
|||
'$(DIST)/$(COMPLETE_MAR)' \
|
||||
'$(PACKAGE_DIR)'
|
||||
ifdef MOZ_SIGN_CMD
|
||||
$(MOZ_SIGN_CMD) -f mar '$(DIST)/$(COMPLETE_MAR)'
|
||||
$(MOZ_SIGN_CMD) -f mar_sha384 '$(DIST)/$(COMPLETE_MAR)'
|
||||
endif
|
||||
|
||||
partial-patch:: $(dir-stage)
|
||||
|
@ -69,5 +69,5 @@ partial-patch:: $(dir-stage)
|
|||
'$(SRC_BUILD)' \
|
||||
'$(DST_BUILD)'
|
||||
ifdef MOZ_SIGN_CMD
|
||||
$(MOZ_SIGN_CMD) -f mar '$(STAGE_DIR)/$(PKG_UPDATE_BASENAME).partial.$(SRC_BUILD_ID)-$(DST_BUILD_ID).mar'
|
||||
$(MOZ_SIGN_CMD) -f mar_sha384 '$(STAGE_DIR)/$(PKG_UPDATE_BASENAME).partial.$(SRC_BUILD_ID)-$(DST_BUILD_ID).mar'
|
||||
endif
|
||||
|
|
|
@ -275,6 +275,7 @@ class PrioritizedEventQueue<InnerQueueT>::EnablePrioritizationRunnable final
|
|||
public:
|
||||
explicit EnablePrioritizationRunnable(PrioritizedEventQueue<InnerQueueT>* aQueue)
|
||||
: Runnable("EnablePrioritizationRunnable")
|
||||
, mQueue(aQueue)
|
||||
{}
|
||||
|
||||
NS_IMETHOD Run() override
|
||||
|
@ -289,6 +290,8 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
// This is a weak pointer. It's guaranteed to stay alive until this runnable
|
||||
// runs since it functions as the event loop in which the runnable is posted.
|
||||
PrioritizedEventQueue<InnerQueueT>* mQueue;
|
||||
};
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче