MozReview-Commit-ID: LCCoXUsCtmv
This commit is contained in:
Phil Ringnalda 2017-08-19 15:29:10 -07:00
Родитель 17fd8b2be0 d48c15182b
Коммит 9359f5bf39
69 изменённых файлов: 1177 добавлений и 353 удалений

Просмотреть файл

@ -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;
};