Merge autoland to mozilla-central a=merge

This commit is contained in:
Coroiu Cristina 2018-12-01 04:29:26 +02:00
commit 8801452ad1
805 changed files with 6879 additions and 4143 deletions

10
Cargo.lock generated
View File

@ -799,21 +799,21 @@ name = "encoding_c"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"encoding_rs 0.8.12 (registry+https://github.com/rust-lang/crates.io-index)",
"encoding_rs 0.8.13 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "encoding_glue"
version = "0.1.0"
dependencies = [
"encoding_rs 0.8.12 (registry+https://github.com/rust-lang/crates.io-index)",
"encoding_rs 0.8.13 (registry+https://github.com/rust-lang/crates.io-index)",
"nserror 0.1.0",
"nsstring 0.1.0",
]
[[package]]
name = "encoding_rs"
version = "0.8.12"
version = "0.8.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1704,7 +1704,7 @@ name = "nsstring"
version = "0.1.0"
dependencies = [
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"encoding_rs 0.8.12 (registry+https://github.com/rust-lang/crates.io-index)",
"encoding_rs 0.8.13 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -3198,7 +3198,7 @@ dependencies = [
"checksum either 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18785c1ba806c258137c937e44ada9ee7e69a37e3c72077542cd2f069d78562a"
"checksum ena 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "88dc8393b3c7352f94092497f6b52019643e493b6b890eb417cdb7c46117e621"
"checksum encoding_c 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "769ecb8b33323998e482b218c0d13cd64c267609023b4b7ec3ee740714c318ee"
"checksum encoding_rs 0.8.12 (registry+https://github.com/rust-lang/crates.io-index)" = "ca20350a7cb5aab5b9034731123d6d412caf3e92d4985e739e411ba0955fd0eb"
"checksum encoding_rs 0.8.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1a8fa54e6689eb2549c4efed8d00d7f3b2b994a064555b0e8df4ae3764bcc4be"
"checksum env_logger 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0561146661ae44c579e993456bc76d11ce1e0c7d745e57b2fa7146b6e49fa2ad"
"checksum error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff511d5dc435d703f4971bc399647c9bc38e20cb41452e3b9feb4765419ed3f3"
"checksum euclid 0.19.3 (registry+https://github.com/rust-lang/crates.io-index)" = "600657e7e5c03bfbccdc68721bc3b5abcb761553973387124eae9c9e4f02c210"

View File

@ -1028,7 +1028,7 @@ pref("security.sandbox.gpu.level", 0);
pref("security.sandbox.gmp.win32k-disable", false);
#endif
#if defined(NIGHTLY_BUILD) && defined(XP_MACOSX) && defined(MOZ_SANDBOX)
#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
// Start the Mac sandbox early during child process startup instead
// of when messaged by the parent after the message loop is running.
pref("security.sandbox.content.mac.earlyinit", true);

View File

@ -318,7 +318,7 @@ var ContentBlocking = {
XPCOMUtils.defineLazyPreferenceGetter(this, "reportBreakageEnabled",
this.PREF_REPORT_BREAKAGE_ENABLED, false);
this.appMenuLabel.setAttribute("label", this.strings.appMenuTitle);
this.appMenuLabel.setAttribute("value", this.strings.appMenuTitle);
this.appMenuLabel.setAttribute("tooltiptext", this.strings.appMenuTooltip);
this.activeTooltipText =
@ -347,6 +347,7 @@ var ContentBlocking = {
return;
}
let button = document.getElementById("tracking-protection-preferences-button");
let appMenuCategoryLabel = document.getElementById("appMenu-tp-category");
let label;
let category = Services.prefs.getStringPref(this.PREF_CB_CATEGORY);
switch (category) {
@ -360,6 +361,7 @@ var ContentBlocking = {
label = gNavigatorBundle.getString("contentBlocking.category.custom");
break;
}
appMenuCategoryLabel.value = label;
button.label = label;
},

View File

@ -152,10 +152,6 @@ let gWhitelist = [{
file: "pocket.properties",
key: "tos",
type: "double-quote",
}, {
file: "aboutNetworking.dtd",
key: "aboutNetworking.logTutorial",
type: "single-quote",
}, {
file: "browser.dtd",
key: "addonPostInstallMessage.label",

View File

@ -4,6 +4,8 @@ const TP_PB_PREF = "privacy.trackingprotection.pbmode.enabled";
const TPC_PREF = "network.cookie.cookieBehavior";
const TT_PREF = "urlclassifier.trackingTable";
ChromeUtils.import("resource://testing-common/CustomizableUITestUtils.jsm", this);
registerCleanupFunction(function() {
Services.prefs.clearUserPref(TP_PREF);
Services.prefs.clearUserPref(TP_PB_PREF);
@ -40,3 +42,31 @@ add_task(async function testCategoryLabelsInControlPanel() {
"The preferencesButton label has been changed to custom");
});
});
add_task(async function testCategoryLabelsInAppMenu() {
await BrowserTestUtils.withNewTab("http://www.example.com", async function() {
let cuiTestUtils = new CustomizableUITestUtils(window);
await cuiTestUtils.openMainMenu();
let appMenuCategoryLabel = document.getElementById("appMenu-tp-category");
ok(appMenuCategoryLabel.value, "The appMenuCategory label exists");
Services.prefs.setStringPref(CAT_PREF, "strict");
await TestUtils.waitForCondition(() => appMenuCategoryLabel.value ==
gNavigatorBundle.getString("contentBlocking.category.strict"));
is(appMenuCategoryLabel.value, gNavigatorBundle.getString("contentBlocking.category.strict"),
"The appMenuCategory label has been changed to strict");
Services.prefs.setStringPref(CAT_PREF, "standard");
await TestUtils.waitForCondition(() => appMenuCategoryLabel.value ==
gNavigatorBundle.getString("contentBlocking.category.standard"));
is(appMenuCategoryLabel.value, gNavigatorBundle.getString("contentBlocking.category.standard"),
"The appMenuCategory label has been changed to standard");
Services.prefs.setStringPref(CAT_PREF, "custom");
await TestUtils.waitForCondition(() => appMenuCategoryLabel.value ==
gNavigatorBundle.getString("contentBlocking.category.custom"));
is(appMenuCategoryLabel.value, gNavigatorBundle.getString("contentBlocking.category.custom"),
"The appMenuCategory label has been changed to custom");
});
});

View File

@ -225,9 +225,13 @@
</toolbaritem>
<toolbarseparator class="sync-ui-item"/>
<toolbaritem>
<toolbarbutton id="appMenu-tp-label"
class="subviewbutton subviewbutton-iconic"
oncommand="ContentBlocking.openPreferences('appMenu-trackingprotection');"/>
<toolbarbutton id="appMenu-tp-button"
class="subviewbutton subviewbutton-iconic"
oncommand="ContentBlocking.openPreferences('appMenu-trackingprotection');">
<image id="appMenu-tp-icon" class="toolbarbutton-icon"/>
<label id="appMenu-tp-label" class="toolbarbutton-text"/>
<label id="appMenu-tp-category"/>
</toolbarbutton>
</toolbaritem>
<toolbarseparator id="appMenu-tp-separator"/>
<toolbarbutton id="appMenu-new-window-button"

View File

@ -15,9 +15,11 @@ const TELEMETRY_RESULT_ENUM = {
};
window.onload = function() {
let defaultEngine = document.getElementById("defaultEngine");
let defaultEngineParagraph = document.getElementById("defaultEngineParagraph");
let originalDefault = Services.search.originalDefaultEngine;
defaultEngine.textContent = originalDefault.name;
document.l10n.setAttributes(defaultEngineParagraph, "page-info-new-search-engine",
{ searchEngine: originalDefault.name });
let defaultEngine = document.getElementById("defaultEngine");
defaultEngine.style.backgroundImage =
'url("' + originalDefault.iconURI.spec + '")';

View File

@ -4,21 +4,12 @@
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!DOCTYPE html [
<!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
%htmlDTD;
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
%globalDTD;
<!ENTITY % searchresetDTD SYSTEM "chrome://browser/locale/aboutSearchReset.dtd">
%searchresetDTD;
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
%brandDTD;
]>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<head>
<title>&searchreset.tabtitle;</title>
<title data-l10n-id="tab-title"/>
<link rel="stylesheet" type="text/css" media="all"
href="chrome://global/skin/in-content/info-pages.css"/>
<link rel="stylesheet" type="text/css" media="all"
@ -28,31 +19,35 @@
<script type="application/javascript"
src="chrome://browser/content/search/searchReset.js"/>
<link rel="localization" href="browser/aboutSearchReset.ftl"/>
<link rel="localization" href="branding/brand.ftl"/>
</head>
<body dir="&locale.dir;">
<body>
<div class="container">
<div class="title">
<h1 class="title-text">&searchreset.pageTitle;</h1>
<h1 class="title-text" data-l10n-id="page-title"/>
</div>
<div class="description">
<p>&searchreset.pageInfo1;</p>
<p>&searchreset.selector.label;<span id="defaultEngine"/></p>
<p data-l10n-id="page-info-outofdate"/>
<p id="defaultEngineParagraph">
<span id="defaultEngine" data-l10n-name="default-engine"/>
</p>
<p>&searchreset.beforelink.pageInfo2;<a id="linkSettingsPage" href="about:preferences#search">&searchreset.link.pageInfo2;</a>&searchreset.afterlink.pageInfo2;</p>
<p data-l10n-id="page-info-how-to-change">
<a id="linkSettingsPage" href="about:preferences#search" data-l10n-name="link"></a>
</p>
</div>
<div class="button-container">
<xul:button id="searchResetKeepCurrent"
label="&searchreset.noChangeButton;"
accesskey="&searchreset.noChangeButton.access;"
data-l10n-id="no-change-button"
oncommand="keepCurrentEngine();"/>
<xul:button class="primary"
id="searchResetChangeEngine"
label="&searchreset.changeEngineButton;"
accesskey="&searchreset.changeEngineButton.access;"
data-l10n-id="change-engine-button"
oncommand="changeSearchEngine();"/>
</div>
</div>

View File

@ -6,19 +6,27 @@
function synthesizeMouseOver(element) {
window.windowUtils.disableNonTestMouseEvents(true);
let promise = BrowserTestUtils.waitForEvent(gURLBar.inputField, "mouseover");
EventUtils.synthesizeMouse(element, 1, 1, {type: "mouseover"});
EventUtils.synthesizeMouse(element, 2, 2, {type: "mousemove"});
EventUtils.synthesizeMouse(element, 3, 3, {type: "mousemove"});
EventUtils.synthesizeMouse(element, 4, 4, {type: "mousemove"});
return promise;
}
function synthesizeMouseOut(element) {
let promise = BrowserTestUtils.waitForEvent(gURLBar.inputField, "mouseout");
EventUtils.synthesizeMouse(element, 0, 0, {type: "mouseout"});
EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"});
EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"});
EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"});
window.windowUtils.disableNonTestMouseEvents(false);
return promise;
}
async function expectTooltip(text) {
@ -28,24 +36,24 @@ async function expectTooltip(text) {
let element = gURLBar.inputField;
let popupShownPromise = BrowserTestUtils.waitForEvent(tooltip, "popupshown");
synthesizeMouseOver(element);
await synthesizeMouseOver(element);
await popupShownPromise;
is(element.getAttribute("title"), text, "title attribute has expected text");
is(tooltip.textContent, text, "tooltip shows expected text");
synthesizeMouseOut(element);
await synthesizeMouseOut(element);
}
async function expectNoTooltip() {
ok(!gURLBar.hasAttribute("textoverflow"), "Urlbar isn't overflowing");
let element = gURLBar.inputField;
synthesizeMouseOver(element);
await synthesizeMouseOver(element);
is(element.getAttribute("title"), null, "title attribute shouldn't be set");
synthesizeMouseOut(element);
await synthesizeMouseOut(element);
}
add_task(async function() {

View File

@ -0,0 +1,17 @@
# 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/.
tab-title = Restore Search Settings
page-title = Restore your search settings?
page-info-outofdate = Your search settings might be out-of-date. { -brand-short-name } can help you restore the default search settings.
# Variables:
# $searchEngine (String) - Name of the default search engine e.g. Google
page-info-new-search-engine = This will set your default search engine to <span data-l10n-name="default-engine">{ $searchEngine }</span>
page-info-how-to-change = You can change these settings at any time from the <a data-l10n-name="link">Settings page</a>.
no-change-button =
.label = Dont Change
.accesskey = D
change-engine-button =
.label = Change Search Engine
.accesskey = C

View File

@ -1,30 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!ENTITY searchreset.tabtitle "Restore Search Settings">
<!ENTITY searchreset.pageTitle "Restore your search settings?">
<!ENTITY searchreset.pageInfo1 "Your search settings might be out-of-date. &brandShortName; can help you restore the default search settings.">
<!-- LOCALIZATION NOTE (searchreset.selector.label): this string is
followed by a dropdown of all the built-in search engines. -->
<!ENTITY searchreset.selector.label "This will set your default search engine to">
<!-- LOCALIZATION NOTE (searchreset.beforelink.pageInfo,
searchreset.afterlink.pageInfo): these two string are used respectively
before and after the "Settings page" link (searchreset.link.pageInfo2).
Localizers can use one of them, or both, to better adapt this sentence to
their language. -->
<!ENTITY searchreset.beforelink.pageInfo2 "You can change these settings at any time from the ">
<!ENTITY searchreset.afterlink.pageInfo2 ".">
<!ENTITY searchreset.link.pageInfo2 "Settings page">
<!ENTITY searchreset.noChangeButton "Dont Change">
<!ENTITY searchreset.noChangeButton.access "D">
<!ENTITY searchreset.changeEngineButton "Change Search Engine">
<!ENTITY searchreset.changeEngineButton.access "C">

View File

@ -1019,14 +1019,14 @@ you can use these alternative items. Otherwise, their values should be empty. -
<!-- LOCALIZATION NOTE (trackingProtection.unblock5.label, trackingProtection.unblock5.accesskey):
The associated button with this label and accesskey is only shown when opening the control
center while looking at a site with trackers in NON-private browsing mode. -->
<!ENTITY trackingProtection.unblock5.label "Turn off blocking for this site">
<!ENTITY trackingProtection.unblock5.label "Turn off Blocking for This Site">
<!ENTITY trackingProtection.unblock5.accesskey "T">
<!-- LOCALIZATION NOTE (trackingProtection.unblockPrivate6.label, trackingProtection.unblockPrivate6.accesskey):
The associated button with this label and accesskey is only shown when opening the control
center while looking at a site with trackers in PRIVATE browsing mode. -->
<!ENTITY trackingProtection.unblockPrivate5.label "Turn off blocking temporarily">
<!ENTITY trackingProtection.unblockPrivate5.label "Turn off Blocking Temporarily">
<!ENTITY trackingProtection.unblockPrivate5.accesskey "T">
<!ENTITY trackingProtection.block6.label "Turn on blocking for this site">
<!ENTITY trackingProtection.block6.label "Turn on Blocking for This Site">
<!ENTITY trackingProtection.block6.accesskey "T">
<!ENTITY trackingProtection.reload2.label "Reload Page">
<!ENTITY trackingProtection.reload2.accesskey "R">

View File

@ -17,7 +17,6 @@
locale/browser/aboutPrivateBrowsing.dtd (%chrome/browser/aboutPrivateBrowsing.dtd)
locale/browser/aboutRobots.dtd (%chrome/browser/aboutRobots.dtd)
locale/browser/accounts.properties (%chrome/browser/accounts.properties)
locale/browser/aboutSearchReset.dtd (%chrome/browser/aboutSearchReset.dtd)
locale/browser/aboutTabCrashed.dtd (%chrome/browser/aboutTabCrashed.dtd)
locale/browser/browser.dtd (%chrome/browser/browser.dtd)
locale/browser/baseMenuOverlay.dtd (%chrome/browser/baseMenuOverlay.dtd)

View File

@ -1051,6 +1051,13 @@ StorageAccessPermissionPrompt.prototype = {
let origins = new Set();
while (perms.length) {
let perm = perms.shift();
// Let's make sure that we're not looking at a permission for
// https://exampletracker.company when we mean to look for the
// permisison for https://exampletracker.com!
if (perm.type != prefix &&
!perm.type.startsWith(`${prefix}^`)) {
continue;
}
origins.add(perm.principal.origin);
}
return origins.size;

View File

@ -145,7 +145,7 @@
}
.identity-popup-preferences-button:-moz-locale-dir(rtl) {
background-position: center left;
background-position: center left 8px;
}
.identity-popup-preferences-button > .toolbarbutton-text {

View File

@ -596,8 +596,23 @@ toolbarbutton[constrain-size="true"][cui-areatype="menu-panel"] > .toolbarbutton
#appMenu-tp-label {
-moz-context-properties: fill;
fill: currentColor;
list-style-image: url(chrome://browser/skin/tracking-protection.svg);
-moz-box-flex: 1;
padding: 0;
padding-inline-start: 8px;
margin: 0;
}
#appMenu-tp-icon {
list-style-image: url(chrome://browser/skin/tracking-protection.svg);
}
#appMenu-tp-button {
-moz-box-flex: 1;
}
#appMenu-tp-category {
color: var(--panel-disabled-color);
margin-inline-end: 0;
}
.addon-banner-item > .toolbarbutton-text,

View File

@ -1581,17 +1581,19 @@ def security_hardening_cflags(hardening_flag, asan, optimize, c_compiler, target
js_flags.append("-U_FORTIFY_SOURCE")
js_flags.append("-D_FORTIFY_SOURCE=2")
# fstack-protector ------------------------------------
# Enable only if hardening is not disabled and ASAN is
# not on as ASAN will catch the crashes for us
if compiler_is_gccish and not asan:
# mingw-clang cross-compile toolchain has bugs with stack protector
if target.os != 'WINNT' or c_compiler == 'gcc':
flags.append("-fstack-protector-strong")
# If ASAN _is_ on, undefine FOTIFY_SOURCE just to be safe
if asan:
flags.append("-U_FORTIFY_SOURCE")
js_flags.append("-U_FORTIFY_SOURCE")
# fstack-protector ------------------------------------
# Enable only if --enable-hardening is passed and ASAN is
# not on as ASAN will catch the crashes for us
if hardening_flag and compiler_is_gccish and not asan:
flags.append("-fstack-protector-strong")
# fno-common -----------------------------------------
# Do not merge variables for ASAN; can detect some subtle bugs
if asan:

View File

@ -14,11 +14,15 @@ add_old_configure_assignment(
# GCC/Clang warnings:
# https://gcc.gnu.org/onlinedocs/gcc-4.7.2/gcc/Warning-Options.html
# https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html
# https://clang.llvm.org/docs/DiagnosticsReference.html
# lots of useful warnings
add_gcc_warning('-Wall')
# catch implicit truncation of enum values assigned to smaller bit fields
check_and_add_gcc_warning('-Wbitfield-enum-conversion')
# catches bugs, e.g. "if (c); foo();", few false positives
add_gcc_warning('-Wempty-body')

View File

@ -80,7 +80,10 @@ class App extends PureComponent {
}
const isRuntimeAvailable = id => {
const runtimes = this.props.usbRuntimes;
const runtimes = [
...this.props.networkRuntimes,
...this.props.usbRuntimes,
];
return !!runtimes.find(x => x.id === id);
};

View File

@ -19,6 +19,7 @@ const SUPPORTED_TARGET_BY_RUNTIME = {
DEBUG_TARGETS.TAB,
],
[RUNTIMES.NETWORK]: [
DEBUG_TARGETS.EXTENSION,
DEBUG_TARGETS.TAB,
],
};
@ -44,6 +45,7 @@ const SUPPORTED_TARGET_PANE_BY_RUNTIME = {
DEBUG_TARGET_PANE.TAB,
],
[RUNTIMES.NETWORK]: [
DEBUG_TARGET_PANE.INSTALLED_EXTENSION,
DEBUG_TARGET_PANE.TAB,
],
};

View File

@ -8,6 +8,7 @@ prefs =
support-files =
debug-target-pane_collapsibilities_head.js
head-addons-script.js
head-mocks.js
head.js
mocks/*
resources/test-adb-extension/*
@ -17,7 +18,7 @@ support-files =
!/devtools/client/shared/test/shared-redux-head.js
!/devtools/client/shared/test/telemetry-test-helpers.js
[browser_aboutdebugging_addons_usb_runtime.js]
[browser_aboutdebugging_addons_remote_runtime.js]
[browser_aboutdebugging_connect_networklocations.js]
[browser_aboutdebugging_connect_toggle_usb_devices.js]
skip-if = (os == 'linux' && bits == 32) # ADB start() fails on linux 32, see Bug 1499638
@ -30,6 +31,7 @@ skip-if = (os == 'linux' && bits == 32) # ADB start() fails on linux 32, see Bug
[browser_aboutdebugging_navigate.js]
[browser_aboutdebugging_persist_connection.js]
[browser_aboutdebugging_routes.js]
[browser_aboutdebugging_select_network_runtime.js]
[browser_aboutdebugging_sidebar_network_runtimes.js]
[browser_aboutdebugging_sidebar_usb_runtime.js]
[browser_aboutdebugging_sidebar_usb_runtime_connect.js]

View File

@ -0,0 +1,91 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const NETWORK_RUNTIME_HOST = "localhost:6080";
const NETWORK_RUNTIME_APP_NAME = "TestNetworkApp";
const USB_RUNTIME_ID = "test-runtime-id";
const USB_RUNTIME_DEVICE_NAME = "test device name";
const USB_RUNTIME_APP_NAME = "TestUsbApp";
/* import-globals-from head-mocks.js */
Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "head-mocks.js", this);
// Test that addons are displayed and updated for USB runtimes when expected.
add_task(async function() {
const mocks = new Mocks();
const { document, tab } = await openAboutDebugging();
info("Prepare USB client mock");
const usbClient = mocks.createUSBRuntime(USB_RUNTIME_ID, {
deviceName: USB_RUNTIME_DEVICE_NAME,
name: USB_RUNTIME_APP_NAME,
});
mocks.emitUSBUpdate();
info("Test addons in runtime page for USB client");
await connectToRuntime(USB_RUNTIME_DEVICE_NAME, document);
await selectRuntime(USB_RUNTIME_DEVICE_NAME, USB_RUNTIME_APP_NAME, document);
await testAddonsOnMockedRemoteClient(usbClient, mocks.thisFirefoxClient, document);
info("Prepare Network client mock");
const networkClient = mocks.createNetworkRuntime(NETWORK_RUNTIME_HOST, {
name: NETWORK_RUNTIME_APP_NAME,
});
info("Test addons in runtime page for Network client");
await connectToRuntime(NETWORK_RUNTIME_HOST, document);
await selectRuntime(NETWORK_RUNTIME_HOST, NETWORK_RUNTIME_APP_NAME, document);
await testAddonsOnMockedRemoteClient(networkClient, mocks.thisFirefoxClient, document);
await removeTab(tab);
});
/**
* Check that addons are visible in the runtime page for a remote client (USB or network).
*/
async function testAddonsOnMockedRemoteClient(remoteClient, firefoxClient, document) {
const extensionPane = getDebugTargetPane("Extensions", document);
info("Check an empty target pane message is displayed");
ok(extensionPane.querySelector(".js-debug-target-list-empty"),
"Extensions list is empty");
info("Add an extension to the remote client");
const addon = { name: "Test extension name", debuggable: true };
remoteClient.listAddons = () => [addon];
remoteClient._eventEmitter.emit("addonListChanged");
info("Wait until the extension appears");
await waitUntil(() => !extensionPane.querySelector(".js-debug-target-list-empty"));
const extensionTarget = findDebugTargetByText("Test extension name", document);
ok(extensionTarget, "Extension target appeared for the remote runtime");
// The goal here is to check that runtimes addons are only updated when the remote
// runtime is sending addonListChanged events. The reason for this test is because the
// previous implementation was updating the remote runtime extensions list when the
// _local_ AddonManager was updated.
info("Remove the extension from the remote client WITHOUT sending an event");
remoteClient.listAddons = () => [];
info("Simulate an addon update on the ThisFirefox client");
firefoxClient._eventEmitter.emit("addonListChanged");
// To avoid wait for a set period of time we trigger another async update, adding a new
// tab. We assume that if the addon update mechanism had started, it would also be done
// when the new tab was processed.
info("Wait until the tab target for 'http://some.random/url.com' appears");
const testTab = { outerWindowID: 0, url: "http://some.random/url.com" };
remoteClient.listTabs = () => ({ tabs: [testTab] });
remoteClient._eventEmitter.emit("tabListChanged");
await waitUntil(() => findDebugTargetByText("http://some.random/url.com", document));
ok(findDebugTargetByText("Test extension name", document),
"The test extension is still visible");
info("Emit `addonListChanged` on remoteClient and wait for the target list to update");
remoteClient._eventEmitter.emit("addonListChanged");
await waitUntil(() => !findDebugTargetByText("Test extension name", document));
}

View File

@ -1,73 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const RUNTIME_ID = "test-runtime-id";
const RUNTIME_DEVICE_NAME = "test device name";
const RUNTIME_APP_NAME = "TestApp";
/* import-globals-from mocks/head-usb-mocks.js */
Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "mocks/head-usb-mocks.js", this);
// Test that addons are displayed and updated for USB runtimes when expected.
add_task(async function() {
const usbMocks = new UsbMocks();
usbMocks.enableMocks();
registerCleanupFunction(() => usbMocks.disableMocks());
const { document, tab } = await openAboutDebugging();
const usbClient = usbMocks.createRuntime(RUNTIME_ID, {
deviceName: RUNTIME_DEVICE_NAME,
name: RUNTIME_APP_NAME,
});
usbMocks.emitUpdate();
await connectToRuntime(RUNTIME_DEVICE_NAME, document);
await selectRuntime(RUNTIME_DEVICE_NAME, RUNTIME_APP_NAME, document);
const extensionPane = getDebugTargetPane("Extensions", document);
info("Check an empty target pane message is displayed");
ok(extensionPane.querySelector(".js-debug-target-list-empty"),
"Extensions list is empty");
info("Add an extension to the remote client");
const addon = { name: "Test extension name", debuggable: true };
usbClient.listAddons = () => [addon];
usbClient._eventEmitter.emit("addonListChanged");
info("Wait until the extension appears");
await waitUntil(() => !extensionPane.querySelector(".js-debug-target-list-empty"));
const extensionTarget = findDebugTargetByText("Test extension name", document);
ok(extensionTarget, "Extension target appeared for the USB runtime");
// The goal here is to check that USB runtimes addons are only updated when the USB
// runtime is sending addonListChanged events. The reason for this test is because the
// previous implementation was updating the USB runtime extensions list when the _local_
// AddonManager was updated.
info("Remove the extension from the remote client WITHOUT sending an event");
usbClient.listAddons = () => [];
info("Simulate an addon update on the ThisFirefox client");
usbMocks.thisFirefoxClient._eventEmitter.emit("addonListChanged");
// To avoid wait for a set period of time we trigger another async update, adding a new
// tab. We assume that if the addon update mechanism had started, it would also be done
// when the new tab was processed.
info("Wait until the tab target for 'http://some.random/url.com' appears");
const testTab = { outerWindowID: 0, url: "http://some.random/url.com" };
usbClient.listTabs = () => ({ tabs: [testTab] });
usbClient._eventEmitter.emit("tabListChanged");
await waitUntil(() => findDebugTargetByText("http://some.random/url.com", document));
ok(findDebugTargetByText("Test extension name", document),
"The test extension is still visible");
info("Emit `addonListChanged` on usbClient and wait for the target list to update");
usbClient._eventEmitter.emit("addonListChanged");
await waitUntil(() => !findDebugTargetByText("Test extension name", document));
await removeTab(tab);
});

View File

@ -7,24 +7,20 @@ const RUNTIME_ID = "test-runtime-id";
const RUNTIME_DEVICE_NAME = "test device name";
const RUNTIME_APP_NAME = "TestApp";
/* import-globals-from mocks/head-usb-mocks.js */
Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "mocks/head-usb-mocks.js", this);
/* import-globals-from head-mocks.js */
Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "head-mocks.js", this);
// Test that the expected supported categories are displayed for USB runtimes.
add_task(async function() {
const usbMocks = new UsbMocks();
usbMocks.enableMocks();
registerCleanupFunction(() => {
usbMocks.disableMocks();
});
const mocks = new Mocks();
const { document, tab } = await openAboutDebugging();
usbMocks.createRuntime(RUNTIME_ID, {
mocks.createUSBRuntime(RUNTIME_ID, {
deviceName: RUNTIME_DEVICE_NAME,
name: RUNTIME_APP_NAME,
});
usbMocks.emitUpdate();
mocks.emitUSBUpdate();
await connectToRuntime(RUNTIME_DEVICE_NAME, document);
await selectRuntime(RUNTIME_DEVICE_NAME, RUNTIME_APP_NAME, document);
@ -44,8 +40,8 @@ add_task(async function() {
}
info("Remove USB runtime");
usbMocks.removeRuntime(RUNTIME_ID);
usbMocks.emitUpdate();
mocks.removeUSBRuntime(RUNTIME_ID);
mocks.emitUSBUpdate();
info("Wait until the USB sidebar item disappears");
await waitUntil(() => !findSidebarItemByText(RUNTIME_DEVICE_NAME, document));

View File

@ -7,29 +7,27 @@ const RUNTIME_ID = "test-runtime-id";
const RUNTIME_DEVICE_NAME = "test device name";
const RUNTIME_APP_NAME = "TestApp";
/* import-globals-from mocks/head-usb-mocks.js */
Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "mocks/head-usb-mocks.js", this);
/* import-globals-from head-mocks.js */
Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "head-mocks.js", this);
// Test that remote runtime connections are persisted across about:debugging reloads.
add_task(async function() {
const usbMocks = new UsbMocks();
usbMocks.enableMocks();
registerCleanupFunction(() => usbMocks.disableMocks());
const mocks = new Mocks();
let { document, tab } = await openAboutDebugging();
const usbClient = usbMocks.createRuntime(RUNTIME_ID, {
const usbClient = mocks.createUSBRuntime(RUNTIME_ID, {
name: RUNTIME_APP_NAME,
deviceName: RUNTIME_DEVICE_NAME,
});
usbMocks.emitUpdate();
mocks.emitUSBUpdate();
await connectToRuntime(RUNTIME_DEVICE_NAME, document);
await selectRuntime(RUNTIME_DEVICE_NAME, RUNTIME_APP_NAME, document);
info("Reload about:debugging");
document = await reloadAboutDebugging(tab);
usbMocks.emitUpdate();
mocks.emitUSBUpdate();
info("Wait until the remote runtime appears as connected");
await waitUntil(() => {
@ -40,7 +38,7 @@ add_task(async function() {
// Remove the runtime without emitting an update.
// This is what happens today when we simply close Firefox for Android.
info("Remove the runtime from the list of USB runtimes");
usbMocks.removeRuntime(RUNTIME_ID);
mocks.removeUSBRuntime(RUNTIME_ID);
info("Emit 'closed' on the client and wait for the sidebar item to disappear");
usbClient._eventEmitter.emit("closed");

View File

@ -3,8 +3,8 @@
"use strict";
/* import-globals-from mocks/head-usb-mocks.js */
Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "mocks/head-usb-mocks.js", this);
/* import-globals-from head-mocks.js */
Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "head-mocks.js", this);
/**
* Test that the initial route is /runtime/this-firefox
@ -22,9 +22,7 @@ add_task(async function() {
*/
add_task(async function() {
// enable USB devices mocks
const usbMocks = new UsbMocks();
usbMocks.enableMocks();
registerCleanupFunction(() => usbMocks.disableMocks());
const mocks = new Mocks();
const { document, tab } = await openAboutDebugging();
@ -43,11 +41,11 @@ add_task(async function() {
info("Check 'USB device runtime' page");
// connect to a mocked USB runtime
usbMocks.createRuntime("1337id", {
mocks.createUSBRuntime("1337id", {
deviceName: "Fancy Phone",
name: "Lorem ipsum",
});
usbMocks.emitUpdate();
mocks.emitUSBUpdate();
await connectToRuntime("Fancy Phone", document);
// navigate to it via URL
document.location.hash = "#/runtime/1337id";

View File

@ -0,0 +1,47 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const NETWORK_RUNTIME_HOST = "localhost:6080";
const NETWORK_RUNTIME_APP_NAME = "TestNetworkApp";
const NETWORK_RUNTIME_CHANNEL = "SomeChannel";
const NETWORK_RUNTIME_VERSION = "12.3";
/* import-globals-from head-mocks.js */
Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "head-mocks.js", this);
// Test that network runtimes can be selected.
add_task(async function() {
const mocks = new Mocks();
const { document, tab } = await openAboutDebugging();
info("Prepare Network client mock");
const networkClient = mocks.createNetworkRuntime(NETWORK_RUNTIME_HOST, {
name: NETWORK_RUNTIME_APP_NAME,
});
networkClient.getDeviceDescription = () => {
return {
name: NETWORK_RUNTIME_APP_NAME,
channel: NETWORK_RUNTIME_CHANNEL,
version: NETWORK_RUNTIME_VERSION,
};
};
info("Test addons in runtime page for Network client");
await connectToRuntime(NETWORK_RUNTIME_HOST, document);
await selectRuntime(NETWORK_RUNTIME_HOST, NETWORK_RUNTIME_APP_NAME, document);
info("Check that the network runtime mock is properly displayed");
const thisFirefoxRuntimeInfo = document.querySelector(".js-runtime-info");
ok(thisFirefoxRuntimeInfo, "Runtime info for this-firefox runtime is displayed");
const runtimeInfoText = thisFirefoxRuntimeInfo.textContent;
ok(runtimeInfoText.includes(NETWORK_RUNTIME_APP_NAME),
"network runtime info shows the correct runtime name: " + runtimeInfoText);
ok(runtimeInfoText.includes(NETWORK_RUNTIME_VERSION),
"network runtime info shows the correct version number: " + runtimeInfoText);
await removeTab(tab);
});

View File

@ -3,24 +3,20 @@
"use strict";
/* import-globals-from mocks/head-usb-mocks.js */
Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "mocks/head-usb-mocks.js", this);
/* import-globals-from head-mocks.js */
Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "head-mocks.js", this);
const RUNTIME_ID = "test-runtime-id";
const RUNTIME_DEVICE_NAME = "test device name";
// Test that USB runtimes appear and disappear from the sidebar.
add_task(async function() {
const usbMocks = new UsbMocks();
usbMocks.enableMocks();
registerCleanupFunction(() => {
usbMocks.disableMocks();
});
const mocks = new Mocks();
const { document, tab } = await openAboutDebugging();
usbMocks.createRuntime(RUNTIME_ID, { deviceName: RUNTIME_DEVICE_NAME });
usbMocks.emitUpdate();
mocks.createUSBRuntime(RUNTIME_ID, { deviceName: RUNTIME_DEVICE_NAME });
mocks.emitUSBUpdate();
info("Wait until the USB sidebar item appears");
await waitUntil(() => findSidebarItemByText(RUNTIME_DEVICE_NAME, document));
@ -33,8 +29,8 @@ add_task(async function() {
await waitUntil(() => !usbRuntimeSidebarItem.querySelector(".js-connect-button"));
info("Remove all USB runtimes");
usbMocks.removeRuntime(RUNTIME_ID);
usbMocks.emitUpdate();
mocks.removeUSBRuntime(RUNTIME_ID);
mocks.emitUSBUpdate();
info("Wait until the USB sidebar item disappears");
await waitUntil(() => !findSidebarItemByText(RUNTIME_DEVICE_NAME, document));

View File

@ -10,34 +10,32 @@ const RUNTIME_APP_NAME = "TestApp";
const OTHER_RUNTIME_ID = "other-runtime-id";
const OTHER_RUNTIME_APP_NAME = "OtherApp";
/* import-globals-from mocks/head-usb-mocks.js */
Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "mocks/head-usb-mocks.js", this);
/* import-globals-from head-mocks.js */
Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "head-mocks.js", this);
// Test that USB runtimes are ot disconnected on refresh.
add_task(async function() {
const usbMocks = new UsbMocks();
usbMocks.enableMocks();
registerCleanupFunction(() => usbMocks.disableMocks());
const mocks = new Mocks();
const { document, tab } = await openAboutDebugging();
info("Create a first runtime and connect to it");
usbMocks.createRuntime(RUNTIME_ID, {
mocks.createUSBRuntime(RUNTIME_ID, {
deviceName: RUNTIME_DEVICE_NAME,
name: RUNTIME_APP_NAME,
});
usbMocks.emitUpdate();
mocks.emitUSBUpdate();
await connectToRuntime(RUNTIME_DEVICE_NAME, document);
await selectRuntime(RUNTIME_DEVICE_NAME, RUNTIME_APP_NAME, document);
info("Create a second runtime and click on Refresh Devices");
usbMocks.createRuntime(OTHER_RUNTIME_ID, {
mocks.createUSBRuntime(OTHER_RUNTIME_ID, {
deviceName: OTHER_RUNTIME_APP_NAME,
});
// Mock the refreshUSBRuntimes to emit an update.
usbMocks.usbRuntimesMock.refreshUSBRuntimes = () => usbMocks.emitUpdate();
mocks.usbRuntimesMock.refreshUSBRuntimes = () => mocks.emitUSBUpdate();
document.querySelector(".js-refresh-devices-button").click();
info(`Wait until the sidebar item for ${OTHER_RUNTIME_APP_NAME} appears`);

View File

@ -7,31 +7,32 @@ const MOCKS_ROOT = CHROME_URL_ROOT + "mocks/";
const { RUNTIMES } = require("devtools/client/aboutdebugging-new/src/constants");
/* import-globals-from head-client-wrapper-mock.js */
/* import-globals-from mocks/head-client-wrapper-mock.js */
Services.scriptloader.loadSubScript(MOCKS_ROOT + "head-client-wrapper-mock.js", this);
/* import-globals-from head-runtime-client-factory-mock.js */
/* import-globals-from mocks/head-runtime-client-factory-mock.js */
Services.scriptloader.loadSubScript(MOCKS_ROOT + "head-runtime-client-factory-mock.js",
this);
/* import-globals-from head-usb-runtimes-mock.js */
/* import-globals-from mocks/head-usb-runtimes-mock.js */
Services.scriptloader.loadSubScript(MOCKS_ROOT + "head-usb-runtimes-mock.js", this);
/**
* This wrapper around the USB mocks used in about:debugging tests provides helpers to
* quickly setup mocks for typical USB runtime tests.
* This wrapper around the mocks used in about:debugging tests provides helpers to
* quickly setup mocks for runtime tests involving USB, network or wifi runtimes that can
* are difficult to setup in a test environment.
*/
class UsbMocks {
class Mocks {
constructor() {
// Setup the usb-runtimes mock to rely on the internal _runtimes array.
// Setup the usb-runtimes mock to rely on the internal _usbRuntimes array.
this.usbRuntimesMock = createUsbRuntimesMock();
this._runtimes = [];
this._usbRuntimes = [];
this.usbRuntimesMock.getUSBRuntimes = () => {
return this._runtimes;
return this._usbRuntimes;
};
// refreshUSBRuntimes normally starts scan, which should ultimately fire the
// "runtime-list-updated" event.
this.usbRuntimesMock.refreshUSBRuntimes = () => {
this.emitUpdate();
this.emitUSBUpdate();
};
// Prepare a fake observer to be able to emit events from this mock.
@ -39,15 +40,23 @@ class UsbMocks {
// Setup the runtime-client-factory mock to rely on the internal _clients map.
this.runtimeClientFactoryMock = createRuntimeClientFactoryMock();
this._clients = {};
this._clients = {
[RUNTIMES.NETWORK]: {},
[RUNTIMES.THIS_FIREFOX]: {},
[RUNTIMES.USB]: {},
};
this.runtimeClientFactoryMock.createClientForRuntime = runtime => {
return this._clients[runtime.id];
return this._clients[runtime.type][runtime.id];
};
// Add a client for THIS_FIREFOX, since about:debugging will start on the This Firefox
// page.
this._thisFirefoxClient = createThisFirefoxClientMock();
this._clients[RUNTIMES.THIS_FIREFOX] = this._thisFirefoxClient;
this._clients[RUNTIMES.THIS_FIREFOX][RUNTIMES.THIS_FIREFOX] = this._thisFirefoxClient;
// Enable mocks and remove them after the test.
this.enableMocks();
registerCleanupFunction(() => this.disableMocks());
}
get thisFirefoxClient() {
@ -62,9 +71,40 @@ class UsbMocks {
disableMocks() {
disableUsbRuntimesMock();
disableRuntimeClientFactoryMock();
for (const host of Object.keys(this._clients[RUNTIMES.NETWORK])) {
this.removeNetworkRuntime(host);
}
}
emitUpdate() {
createNetworkRuntime(host, runtimeInfo) {
const { addNetworkLocation } =
require("devtools/client/aboutdebugging-new/src/modules/network-locations");
addNetworkLocation(host);
// Add a valid client that can be returned for this particular runtime id.
const mockNetworkClient = createClientMock();
mockNetworkClient.getDeviceDescription = () => {
return {
name: runtimeInfo.name || "TestBrand",
channel: runtimeInfo.channel || "release",
version: runtimeInfo.version || "1.0",
};
};
this._clients[RUNTIMES.NETWORK][host] = mockNetworkClient;
return mockNetworkClient;
}
removeNetworkRuntime(host) {
const { removeNetworkLocation } =
require("devtools/client/aboutdebugging-new/src/modules/network-locations");
removeNetworkLocation(host);
delete this._clients[RUNTIMES.NETWORK][host];
}
emitUSBUpdate() {
this._observerMock.emit("runtime-list-updated");
}
@ -82,9 +122,9 @@ class UsbMocks {
* @return {Object} Returns the mock client created for this runtime so that methods
* can be overridden on it.
*/
createRuntime(id, runtimeInfo = {}) {
createUSBRuntime(id, runtimeInfo = {}) {
// Add a new runtime to the list of scanned runtimes.
this._runtimes.push({
this._usbRuntimes.push({
id: id,
_socketPath: runtimeInfo.socketPath || "test/path",
deviceName: runtimeInfo.deviceName || "test device name",
@ -100,13 +140,13 @@ class UsbMocks {
version: runtimeInfo.version || "1.0",
};
};
this._clients[id] = mockUsbClient;
this._clients[RUNTIMES.USB][id] = mockUsbClient;
return mockUsbClient;
}
removeRuntime(id) {
this._runtimes = this._runtimes.filter(runtime => runtime.id !== id);
delete this._clients[id];
removeUSBRuntime(id) {
this._usbRuntimes = this._usbRuntimes.filter(runtime => runtime.id !== id);
delete this._clients[RUNTIMES.USB][id];
}
}

View File

@ -183,6 +183,6 @@ async function selectRuntime(deviceName, name, document) {
await waitUntil(() => {
const runtimeInfo = document.querySelector(".js-runtime-info");
return runtimeInfo.textContent.includes(name);
return runtimeInfo && runtimeInfo.textContent.includes(name);
});
}

View File

@ -1,22 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* eslint-env browser */
/* exported startup, shutdown, install, uninstall */
"use strict";
ChromeUtils.import("resource://gre/modules/Services.jsm");
// This function is called from the webconsole test:
// browser_addons_debug_bootstrapped.js
function myBootstrapAddonFunction() { // eslint-disable-line no-unused-vars
Services.obs.notifyObservers(null, "addon-console-works");
}
function startup() {
Services.obs.notifyObservers(null, "test-devtools");
}
function shutdown() {}
function install() {}
function uninstall() {}

View File

@ -1,26 +0,0 @@
<?xml version="1.0"?>
<!--
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest"
em:id="test-devtools@mozilla.org"
em:name="test-devtools"
em:version="1.0"
em:type="2"
em:creator="Mozilla">
<em:bootstrap>true</em:bootstrap>
<em:targetApplication>
<Description>
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
<em:minVersion>44.0a1</em:minVersion>
<em:maxVersion>*</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>

View File

@ -0,0 +1,10 @@
{
"manifest_version": 2,
"name": "test-devtools",
"version": "1.0",
"applications": {
"gecko": {
"id": "test-devtools@mozilla.org"
}
}
}

View File

@ -3,8 +3,7 @@ tags = devtools
subsuite = devtools
support-files =
head.js
addons/unpacked/bootstrap.js
addons/unpacked/install.rdf
addons/unpacked/manifest.json
addons/bad/manifest.json
addons/bug1273184.xpi
addons/test-devtools-webextension/*
@ -22,9 +21,6 @@ support-files =
!/devtools/client/shared/test/shared-head.js
!/devtools/client/shared/test/telemetry-test-helpers.js
[browser_addons_debug_bootstrapped.js]
# To be removed in bug 1497264
skip-if = true
[browser_addons_debug_info.js]
[browser_addons_debug_webextension.js]
tags = webextensions
@ -36,16 +32,11 @@ tags = webextensions
skip-if = (verify && debug) || (debug && os == "linux" && bits == 64) # verify: crashes on shutdown, timeouts linux debug Bug 1299001
tags = webextensions
[browser_addons_debugging_initial_state.js]
# To be removed or updated in bug 1497264
skip-if = true
[browser_addons_install.js]
# To be updated in bug 1497264 (was "verify && debug")
skip-if = true
skip-if = verify && debug
[browser_addons_reload.js]
[browser_addons_remove.js]
[browser_addons_toggle_debug.js]
# To be removed or updated in bug 1497264
skip-if = true
[browser_page_not_found.js]
[browser_service_workers.js]
[browser_service_workers_fetch_flag.js]

View File

@ -1,88 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// There are shutdown issues for which multiple rejections are left uncaught.
// See bug 1018184 for resolving these issues.
const { PromiseTestUtils } = scopedCuImport("resource://testing-common/PromiseTestUtils.jsm");
PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
// Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
requestLongerTimeout(2);
const ADDON_ID = "test-devtools@mozilla.org";
const ADDON_NAME = "test-devtools";
const { BrowserToolboxProcess } = ChromeUtils.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
add_task(async function() {
await new Promise(resolve => {
const options = {"set": [
// Force enabling of addons debugging
["devtools.chrome.enabled", true],
["devtools.debugger.remote-enabled", true],
// Disable security prompt
["devtools.debugger.prompt-connection", false],
// Enable Browser toolbox test script execution via env variable
["devtools.browser-toolbox.allow-unsafe-script", true],
]};
SpecialPowers.pushPrefEnv(options, resolve);
});
const { tab, document } = await openAboutDebugging("addons");
await waitForInitialAddonList(document);
await installAddon({
document,
path: "addons/unpacked/install.rdf",
name: ADDON_NAME,
});
// Retrieve the DEBUG button for the addon
const names = getInstalledAddonNames(document);
const name = names.filter(element => element.textContent === ADDON_NAME)[0];
ok(name, "Found the addon in the list");
const targetElement = name.parentNode.parentNode;
const debugBtn = targetElement.querySelector(".debug-button");
ok(debugBtn, "Found its debug button");
// Wait for a notification sent by a script evaluated the test addon via
// the web console.
const onCustomMessage = new Promise(done => {
Services.obs.addObserver(function listener() {
Services.obs.removeObserver(listener, "addon-console-works");
done();
}, "addon-console-works");
});
// Be careful, this JS function is going to be executed in the addon toolbox,
// which lives in another process. So do not try to use any scope variable!
const env = Cc["@mozilla.org/process/environment;1"]
.getService(Ci.nsIEnvironment);
const testScript = function() {
/* eslint-disable no-undef */
toolbox.selectTool("webconsole")
.then(console => {
const { jsterm } = console.hud;
return jsterm.execute("myBootstrapAddonFunction()");
})
.then(() => toolbox.destroy());
/* eslint-enable no-undef */
};
env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + testScript);
registerCleanupFunction(() => {
env.set("MOZ_TOOLBOX_TEST_SCRIPT", "");
});
const onToolboxClose = BrowserToolboxProcess.once("close");
debugBtn.click();
await onCustomMessage;
ok(true, "Received the notification message from the bootstrap.js function");
await onToolboxClose;
ok(true, "Addon toolbox closed");
await uninstallAddon({document, id: ADDON_ID, name: ADDON_NAME});
await closeAboutDebugging(tab);
});

View File

@ -13,29 +13,6 @@ function testFilePath(container, expectedFilePath) {
is(filePath.previousElementSibling.textContent, "Location", "file path has label");
}
// Remove in Bug 1497264
/*
add_task(async function testLegacyAddon() {
const addonId = "test-devtools@mozilla.org";
const addonName = "test-devtools";
const { tab, document } = await openAboutDebugging("addons");
await waitForInitialAddonList(document);
await installAddon({
document,
path: "addons/unpacked/install.rdf",
name: addonName,
});
const container = document.querySelector(`[data-addon-id="${addonId}"]`);
testFilePath(container, "browser/devtools/client/aboutdebugging/test/addons/unpacked/");
await uninstallAddon({document, id: addonId, name: addonName});
await closeAboutDebugging(tab);
});
*/
add_task(async function testWebExtension() {
const addonId = "test-devtools-webextension-nobg@mozilla.org";
const addonName = "test-devtools-webextension-nobg";
@ -57,7 +34,6 @@ add_task(async function testWebExtension() {
document,
file: addonFile,
name: addonName,
isWebExtension: true,
});
const container = document.querySelector(`[data-addon-id="${addonId}"]`);
@ -95,7 +71,6 @@ add_task(async function testTemporaryWebExtension() {
document,
file: addonFile,
name: addonName,
isWebExtension: true,
});
const addons =
@ -138,7 +113,6 @@ add_task(async function testUnknownManifestProperty() {
document,
file: addonFile,
name: addonName,
isWebExtension: true,
});
info("Wait until the addon appears in about:debugging");

View File

@ -52,7 +52,7 @@ async function testCheckboxState(testData) {
info("Install a test addon.");
await installAddon({
document,
path: "addons/unpacked/install.rdf",
path: "addons/unpacked/manifest.json",
name: ADDON_NAME,
});

View File

@ -31,22 +31,23 @@ function promiseWriteWebManifestForExtension(manifest, dir) {
dir.path, manifest.applications.gecko.id, files, true);
}
add_task(async function testLegacyInstallSuccess() {
const ADDON_ID = "test-devtools@mozilla.org";
const ADDON_NAME = "test-devtools";
add_task(async function testWebextensionInstallSuccess() {
const { tab, document } = await openAboutDebugging("addons");
await waitForInitialAddonList(document);
// Install this add-on, and verify that it appears in the about:debugging UI
await installAddon({
document,
path: "addons/unpacked/install.rdf",
name: ADDON_NAME,
path: "addons/unpacked/manifest.json",
name: "test-devtools",
});
// Install the add-on, and verify that it disappears in the about:debugging UI
await uninstallAddon({document, id: ADDON_ID, name: ADDON_NAME});
await uninstallAddon({
document,
id: "test-devtools@mozilla.org",
name: "test-devtools",
});
await closeAboutDebugging(tab);
});

View File

@ -68,7 +68,7 @@ add_task(async function reloadButtonReloadsAddon() {
await waitForInitialAddonList(document);
await installAddon({
document,
path: "addons/unpacked/install.rdf",
path: "addons/unpacked/manifest.json",
name: ADDON_NAME,
});
@ -76,16 +76,13 @@ add_task(async function reloadButtonReloadsAddon() {
is(reloadButton.title, "", "Reload button should not have a tooltip");
const onInstalled = promiseAddonEvent("onInstalled");
const onBootstrapInstallCalled = new Promise(done => {
Services.obs.addObserver(function listener() {
Services.obs.removeObserver(listener, ADDON_NAME);
info("Add-on was re-installed: " + ADDON_NAME);
done();
}, ADDON_NAME);
});
const reloaded = once(AboutDebugging, "addon-reload");
const onListUpdated = once(AboutDebugging, "addons-updated");
// The list is updated twice:
// - AddonManager's onInstalled event
// - WebExtension's Management's startup event
const onListUpdated = waitForNEvents(AboutDebugging, "addons-updated", 2);
reloadButton.click();
await reloaded;
await onListUpdated;
@ -94,7 +91,6 @@ add_task(async function reloadButtonReloadsAddon() {
is(reloadedAddon.name, ADDON_NAME,
"Add-on was reloaded: " + reloadedAddon.name);
await onBootstrapInstallCalled;
await tearDownAddon(AboutDebugging, reloadedAddon);
await closeAboutDebugging(tab);
});
@ -119,8 +115,9 @@ add_task(async function reloadButtonRefreshesMetadata() {
const tempExt = new TempWebExt(ADDON_ID);
tempExt.writeManifest(manifestBase);
// The list is updated twice. On AddonManager's onInstalled event as well
// as WebExtension's Management's startup event.
// List updated twice:
// - AddonManager's onInstalled event
// - WebExtension's Management's startup event.
let onListUpdated = waitForNEvents(AboutDebugging, "addons-updated", 2);
const onInstalled = promiseAddonEvent("onInstalled");
await AddonManager.installTemporaryAddon(tempExt.sourceDir);
@ -135,8 +132,9 @@ add_task(async function reloadButtonRefreshesMetadata() {
const newName = "Temporary web extension (updated)";
tempExt.writeManifest(Object.assign({}, manifestBase, {name: newName}));
// The list is updated twice, once for uninstall of the old
// and another one for install of the new
// List updated twice:
// - AddonManager's onInstalled event
// - WebExtension's Management's startup event.
onListUpdated = waitForNEvents(AboutDebugging, "addons-updated", 2);
// Wait for the add-on list to be updated with the reloaded name.
const onReInstall = promiseAddonEvent("onInstalled");
@ -162,8 +160,9 @@ add_task(async function onlyTempInstalledAddonsCanBeReloaded() {
const { AboutDebugging } = window;
await waitForInitialAddonList(document);
// The list is updated twice. On AddonManager's onInstalled event as well
// as WebExtension's Management's startup event.
// List updated twice:
// - AddonManager's onInstalled event
// - WebExtension's Management's startup event.
const onListUpdated = waitForNEvents(AboutDebugging, "addons-updated", 2);
await installAddonWithManager(getSupportsFile("addons/bug1273184.xpi").file);
await onListUpdated;

View File

@ -12,34 +12,6 @@ function getRemoveButton(document, id) {
return document.querySelector(`[data-addon-id="${id}"] .uninstall-button`);
}
// Remove in Bug 1497264
/*
add_task(async function removeLegacyExtension() {
const addonID = "test-devtools@mozilla.org";
const addonName = "test-devtools";
const { tab, document } = await openAboutDebugging("addons");
await waitForInitialAddonList(document);
// Install this add-on, and verify that it appears in the about:debugging UI
await installAddon({
document,
path: "addons/unpacked/install.rdf",
name: addonName,
});
ok(getTargetEl(document, addonID), "add-on is shown");
info("Click on the remove button and wait until the addon container is removed");
getRemoveButton(document, addonID).click();
await waitUntil(() => !getTargetEl(document, addonID), 100);
info("add-on is not shown");
await closeAboutDebugging(tab);
});
*/
add_task(async function removeWebextension() {
const addonID = "test-devtools-webextension@mozilla.org";
const addonName = "test-devtools-webextension";
@ -62,7 +34,6 @@ add_task(async function removeWebextension() {
document,
file: addonFile,
name: addonName,
isWebExtension: true,
});
ok(getTargetEl(document, addonID), "add-on is shown");
@ -81,8 +52,9 @@ add_task(async function onlyTempInstalledAddonsCanBeRemoved() {
const { AboutDebugging } = window;
await waitForInitialAddonList(document);
// The list is updated twice. On AddonManager's onInstalled event as well
// as WebExtension's Management's startup event.
// List updated twice:
// - AddonManager's onInstalled event
// - WebExtension's Management's startup event.
const onListUpdated = waitForNEvents(AboutDebugging, "addons-updated", 2);
await installAddonWithManager(getSupportsFile("addons/bug1273184.xpi").file);
await onListUpdated;

View File

@ -25,7 +25,7 @@ add_task(async function() {
info("Install a test addon.");
await installAddon({
document,
path: "addons/unpacked/install.rdf",
path: "addons/unpacked/manifest.json",
name: ADDON_NAME,
});

View File

@ -166,7 +166,7 @@ function getTabList(document) {
document.querySelector("#tabs.targets");
}
async function installAddon({document, path, file, name, isWebExtension}) {
async function installAddon({document, path, file, name}) {
// Mock the file picker to select a test addon
const MockFilePicker = SpecialPowers.MockFilePicker;
MockFilePicker.init(window);
@ -177,32 +177,27 @@ async function installAddon({document, path, file, name, isWebExtension}) {
MockFilePicker.setFiles([file]);
}
let onAddonInstalled;
const onAddonInstalled = new Promise(done => {
Management.on("startup", function listener(event, extension) {
if (extension.name != name) {
return;
}
if (isWebExtension) {
onAddonInstalled = new Promise(done => {
Management.on("startup", function listener(event, extension) {
if (extension.name != name) {
return;
}
Management.off("startup", listener);
done();
});
Management.off("startup", listener);
done();
});
} else {
// Wait for a "test-devtools" message sent by the addon's bootstrap.js file
onAddonInstalled = new Promise(done => {
Services.obs.addObserver(function listener() {
Services.obs.removeObserver(listener, "test-devtools");
});
const AboutDebugging = document.defaultView.AboutDebugging;
// List updated twice:
// - AddonManager's onInstalled event
// - WebExtension's Management's startup event.
const onListUpdated = waitForNEvents(AboutDebugging, "addons-updated", 2);
done();
}, "test-devtools");
});
}
// Trigger the file picker by clicking on the button
document.getElementById("load-addon-from-file").click();
await onListUpdated;
await onAddonInstalled;
ok(true, "Addon installed and running its bootstrap.js file");
@ -367,7 +362,6 @@ async function setupTestAboutDebuggingWebExtension(name, file) {
document,
file,
name,
isWebExtension: true,
});
// Retrieve the DEBUG button for the addon

View File

@ -52,8 +52,11 @@ class AccessibilityStartup {
await this.target.actorHasMethod("accessibility", "enable");
if (this._supports.enableDisable) {
this._supports.relations =
await this.target.actorHasMethod("accessible", "getRelations");
([ this._supports.relations, this._supports.snapshot ] = await Promise.all([
this.target.actorHasMethod("accessible", "getRelations"),
this.target.actorHasMethod("accessible", "snapshot"),
]));
await this._accessibility.bootstrap();
}

View File

@ -3,7 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
/* global gToolbox, EVENTS */
/* global gTelemetry, gToolbox, EVENTS */
// React & Redux
const { Component, createFactory } = require("devtools/client/shared/vendor/react");
@ -16,12 +16,25 @@ const TreeRow = require("devtools/client/shared/components/tree/TreeRow");
// Utils
const {flashElementOn, flashElementOff} =
require("devtools/client/inspector/markup/utils");
const { openDocLink } = require("devtools/client/shared/link");
const { VALUE_FLASHING_DURATION, VALUE_HIGHLIGHT_DURATION } = require("../constants");
// Actions
const { updateDetails } = require("../actions/details");
const { unhighlight } = require("../actions/accessibles");
const { L10N } = require("../utils/l10n");
loader.lazyRequireGetter(this, "Menu", "devtools/client/framework/menu");
loader.lazyRequireGetter(this, "MenuItem", "devtools/client/framework/menu-item");
const JSON_URL_PREFIX = "data:application/json;charset=UTF-8,";
const TELEMETRY_ACCESSIBLE_CONTEXT_MENU_OPENED =
"devtools.accessibility.accessible_context_menu_opened";
const TELEMETRY_ACCESSIBLE_CONTEXT_MENU_ITEM_ACTIVATED =
"devtools.accessibility.accessible_context_menu_item_activated";
class HighlightableTreeRowClass extends TreeRow {
shouldComponentUpdate(nextProps) {
const props = ["name", "open", "value", "loading", "selected", "hasChildren"];
@ -138,6 +151,53 @@ class AccessibilityRow extends Component {
walker.unhighlight().catch(error => console.warn(error));
}
async printToJSON() {
const { member, supports } = this.props;
if (!supports.snapshot) {
// Debugger server does not support Accessible actor snapshots.
return;
}
if (gTelemetry) {
gTelemetry.keyedScalarAdd(TELEMETRY_ACCESSIBLE_CONTEXT_MENU_ITEM_ACTIVATED,
"print-to-json", 1);
}
const snapshot = await member.object.snapshot();
openDocLink(`${JSON_URL_PREFIX}${encodeURIComponent(JSON.stringify(snapshot))}`);
}
onContextMenu(e) {
e.stopPropagation();
e.preventDefault();
if (!gToolbox) {
return;
}
const menu = new Menu({ id: "accessibility-row-contextmenu" });
const { supports } = this.props;
if (supports.snapshot) {
menu.append(new MenuItem({
id: "menu-printtojson",
label: L10N.getStr("accessibility.tree.menu.printToJSON"),
click: () => this.printToJSON(),
}));
}
menu.popup(e.screenX, e.screenY, gToolbox);
if (gTelemetry) {
gTelemetry.scalarAdd(TELEMETRY_ACCESSIBLE_CONTEXT_MENU_OPENED, 1);
}
}
get hasContextMenu() {
const { supports } = this.props;
return supports.snapshot;
}
/**
* Render accessible row component.
* @returns acecssible-row React component.
@ -145,6 +205,7 @@ class AccessibilityRow extends Component {
render() {
const { object } = this.props.member;
const props = Object.assign({}, this.props, {
onContextMenu: this.hasContextMenu && (e => this.onContextMenu(e)),
onMouseOver: () => this.highlight(object),
onMouseOut: () => this.unhighlight(),
});

View File

@ -240,8 +240,11 @@ AccessibilityPanel.prototype = {
this.panelWin.off(EVENTS.ACCESSIBILITY_INSPECTOR_UPDATED,
this.onAccessibilityInspectorUpdated);
this.picker.release();
this.picker = null;
// Older versions of debugger server do not support picker functionality.
if (this.picker) {
this.picker.release();
this.picker = null;
}
if (this.front) {
this.front.off("init", this.updateA11YServiceDurationTimer);

View File

@ -5,3 +5,4 @@ support-files =
[test_accessible_learnMoreLink.html]
[test_accessible_openLink.html]
[test_accessible_relations.html]
[test_accessible_row_context_menu.html]

View File

@ -0,0 +1,164 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!DOCTYPE HTML>
<html>
<!--
Test that openLink function is called if accessible object property is rendered as a link.
-->
<head>
<meta charset="utf-8">
<title>AccessibilityRow context menu test</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<link rel="stylesheet" href="chrome://devtools/skin/light-theme.css" type="text/css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript"></script>
<script type="application/javascript">
"use strict";
window.onload = async function() {
try {
const { gDevTools } = require("devtools/client/framework/devtools");
const Services = browserRequire("Services");
const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
const { createFactory, createElement } =
browserRequire("devtools/client/shared/vendor/react");
const { Provider } = require("devtools/client/shared/vendor/react-redux");
const createStore = require("devtools/client/shared/redux/create-store")();
const { Simulate } =
browserRequire("devtools/client/shared/vendor/react-dom-test-utils");
const AccessibilityRow = createFactory(
browserRequire("devtools/client/accessibility/components/AccessibilityRow"));
async function withMockEnv(func) {
const { gToolbox: originalToolbox, gTelemetry: originalTelemetry } = window;
window.gToolbox = { doc: document };
window.gTelemetry = null;
await func();
window.gToolbox = originalToolbox;
window.gTelemetry = originalTelemetry;
}
function renderAccessibilityRow(newProps, newState) {
let container = document.getElementById("container");
if (container) {
container.remove();
}
const accRow = AccessibilityRow(newProps);
const mockStore = createStore((state, action) =>
action ? { ...state, ...action } : state, newState);
const provider = createElement(Provider, { store: mockStore }, accRow);
container = document.createElement("div");
container.id = "container";
document.body.appendChild(container);
return ReactDOM.render(provider, container);
}
const checker = Symbol();
let isCalled;
const ROW_ID = "test-row";
const JSON_URL_PREFIX = "data:application/json;charset=UTF-8,";
const SNAPSHOT = { "snapshot": true };
const defaultProps = {
id: ROW_ID,
member: {
object: {
name: "test",
value: "test",
loading: false,
selected: false,
hasChildren: false,
snapshot: async () => SNAPSHOT,
},
},
columns: [
{ "id": "default", "title": "role" },
{ "id": "value", "title": "name" },
],
provider: {
getValue: (object, id) => object[id],
},
};
const mockProps = {
...defaultProps,
onContextMenu: () => {
isCalled = true;
},
};
const defaultState = { ui: { supports: { snapshot: true }}};
const mockState = { ui: { supports: {}}};
info("Check contextmenu default behaviour.");
renderAccessibilityRow(defaultProps, defaultState);
let row = document.getElementById(ROW_ID);
await withMockEnv(async function() {
Simulate.contextMenu(row);
const menu = document.getElementById("accessibility-row-contextmenu");
const printtojsonMenuItem = document.getElementById("menu-printtojson");
ok(menu, "Accessibility row context menu is open");
ok(printtojsonMenuItem, "Print to JSON menu item is visible");
const browserWindow = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
const defaultOpenWebLinkIn = browserWindow.openWebLinkIn;
let openWebLinkInCalled;
const openWebLinkInPromise = new Promise(resolve => {
openWebLinkInCalled = resolve;
});
// Mock top chrome window's @openWebLinkIn method.
browserWindow.openWebLinkIn = (...args) => {
openWebLinkInCalled(args);
};
printtojsonMenuItem.click();
const [ url, where ] = await openWebLinkInPromise;
is(url, `${JSON_URL_PREFIX}${encodeURIComponent(JSON.stringify(SNAPSHOT))}`,
"Correct URL is opened");
is(where, "tab", "URL was opened correctly");
// Reset @openWebLinkIn to default.
browserWindow.openWebLinkIn = defaultOpenWebLinkIn;
});
info("Check accessibility row when context menu is not supported.");
renderAccessibilityRow(mockProps, mockState);
row = document.getElementById(ROW_ID);
info("Check contextmenu listener is not called when context menu is not supported.");
isCalled = checker;
Simulate.contextMenu(row);
ok(isCalled === checker, "contextmenu event handler was never called.");
info("Check accessibility row when context menu is supported.");
renderAccessibilityRow(mockProps, defaultState);
row = document.getElementById(ROW_ID);
info("Check contextmenu listener is called when context menu is supported.");
isCalled = checker;
Simulate.contextMenu(row);
ok(isCalled, "contextmenu event handler was called correctly.");
} catch (e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
};
</script>
</pre>
</body>
</html>

View File

@ -283,8 +283,8 @@ button.invoke-getter {
mask: url("resource://devtools/client/shared/components/reps/images/input.svg") no-repeat;
display: inline-block;
background-color: var(--comment-node-color);
height: 12px;
vertical-align:bottom;
height: 10px;
vertical-align: middle;
border:none;
}

View File

@ -101,3 +101,8 @@ accessibility.description.general.p2=Accessibility features may affect the perfo
# when accessibility service description is provided when a client is connected
# to an older version of accessibility actor.
accessibility.description.oldVersion=You are connected to a debugger server that is too old. To use Accessibility panel, please connect to the latest debugger server version.
# LOCALIZATION NOTE (accessibility.tree.menu.printToJSON): A title text used when a
# context menu item for printing an accessible tree to JSON is rendered after triggering a
# context menu for an accessible tree row.
accessibility.tree.menu.printToJSON=Print to JSON

View File

@ -283,8 +283,8 @@ button.invoke-getter {
mask: url("resource://devtools/client/shared/components/reps/images/input.svg") no-repeat;
display: inline-block;
background-color: var(--comment-node-color);
height: 12px;
vertical-align:bottom;
height: 10px;
vertical-align: middle;
border:none;
}
@ -436,6 +436,7 @@ html[dir="rtl"] .tree-node button.arrow {
}
.tree-node.focused button.jump-definition,
.tree-node.focused button.open-inspector {
.tree-node.focused button.open-inspector,
.tree-node.focused button.invoke-getter {
background-color: currentColor;
}

File diff suppressed because it is too large Load Diff

View File

@ -49,6 +49,7 @@ define(function(require, exports, module) {
id: PropTypes.string.isRequired,
provider: PropTypes.object.isRequired,
onClick: PropTypes.func.isRequired,
onContextMenu: PropTypes.func,
onMouseOver: PropTypes.func,
onMouseOut: PropTypes.func,
};
@ -120,12 +121,14 @@ define(function(require, exports, module) {
render() {
const member = this.props.member;
const decorator = this.props.decorator;
const props = {
id: this.props.id,
role: "treeitem",
"aria-level": member.level,
"aria-selected": !!member.selected,
onClick: this.props.onClick,
onContextMenu: this.props.onContextMenu,
onMouseOver: this.props.onMouseOver,
onMouseOut: this.props.onMouseOut,
};

View File

@ -723,6 +723,11 @@ function getChartsFromToolId(id) {
timerHist = `DEVTOOLS_${id}_TIME_ACTIVE_SECONDS`;
countScalar = `devtools.accessibility.picker_used_count`;
break;
case "CHANGESVIEW":
useTimedEvent = true;
timerHist = `DEVTOOLS_${id}_TIME_ACTIVE_SECONDS`;
countScalar = `devtools.${lowerCaseId}.opened_count`;
break;
case "ANIMATIONINSPECTOR":
case "COMPUTEDVIEW":
case "FONTINSPECTOR":

View File

@ -0,0 +1,10 @@
{
"manifest_version": 2,
"name": "test-addon-1",
"version": "1.0",
"applications": {
"gecko": {
"id": "test-addon-1@mozilla.org"
}
}
}

View File

@ -0,0 +1,10 @@
{
"manifest_version": 2,
"name": "test-addon-2",
"version": "1.0",
"applications": {
"gecko": {
"id": "test-addon-2@mozilla.org"
}
}
}

View File

@ -2,9 +2,7 @@
tags = devtools
subsuite = devtools
support-files =
addon1.xpi
addon2.xpi
addon4.xpi
addons/*
browser_devices.json
code_listworkers-worker1.js
code_listworkers-worker2.js
@ -76,10 +74,6 @@ support-files =
[browser_cubic-bezier-05.js]
[browser_cubic-bezier-06.js]
[browser_cubic-bezier-07.js]
[browser_dbg_addon-console.js]
# To be removed or updated in bug 1497264
# previously was: (e10s && debug || os == 'win' || verify)
skip-if = true
tags = addons
[browser_dbg_debugger-statement.js]
skip-if = e10s && debug

View File

@ -1,151 +0,0 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/* import-globals-from helper_addons.js */
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/devtools/client/shared/test/helper_addons.js",
this);
var { DebuggerServer } = require("devtools/server/main");
var { DebuggerClient } = require("devtools/shared/client/debugger-client");
var { Toolbox } = require("devtools/client/framework/toolbox");
var { Task } = require("devtools/shared/task");
// Test that the we can see console messages from the add-on
const ADDON_ID = "browser_dbg_addon4@tests.mozilla.org";
const ADDON_PATH = "addon4.xpi";
function getCachedMessages(webConsole) {
const deferred = getDeferredPromise().defer();
webConsole.getCachedMessages(["ConsoleAPI"], response => {
if (response.error) {
deferred.reject(response.error);
return;
}
deferred.resolve(response.messages);
});
return deferred.promise;
}
// Creates an add-on debugger for a given add-on. The returned AddonDebugger
// object must be destroyed before finishing the test
function initAddonDebugger(addonId) {
const addonDebugger = new AddonDebugger();
return addonDebugger.init(addonId).then(() => addonDebugger);
}
function AddonDebugger() {
this._onMessage = this._onMessage.bind(this);
this._onConsoleAPICall = this._onConsoleAPICall.bind(this);
EventEmitter.decorate(this);
}
AddonDebugger.prototype = {
init: Task.async(function* (addonId) {
info("Initializing an addon debugger panel.");
DebuggerServer.init();
DebuggerServer.registerAllActors();
DebuggerServer.allowChromeProcess = true;
this.frame = document.createElement("iframe");
this.frame.setAttribute("height", 400);
document.documentElement.appendChild(this.frame);
window.addEventListener("message", this._onMessage);
const transport = DebuggerServer.connectPipe();
this.client = new DebuggerClient(transport);
yield this.client.connect();
const addonTargetFront = yield this.client.mainRoot.getAddon({ id: addonId });
const targetOptions = {
activeTab: addonTargetFront,
client: this.client,
chrome: true,
};
const toolboxOptions = {
customIframe: this.frame,
};
this.target = yield TargetFactory.forRemoteTab(targetOptions);
const toolbox = yield gDevTools.showToolbox(
this.target, "jsdebugger", Toolbox.HostType.CUSTOM, toolboxOptions);
info("Addon debugger panel shown successfully.");
this.debuggerPanel = toolbox.getCurrentPanel();
yield this._attachConsole();
}),
destroy: Task.async(function* () {
yield this.client.close();
this.frame.remove();
window.removeEventListener("message", this._onMessage);
}),
_attachConsole: function() {
const deferred = getDeferredPromise().defer();
this.client.attachConsole(this.target.form.consoleActor, ["ConsoleAPI"])
.then(([aResponse, aWebConsoleClient]) => {
this.webConsole = aWebConsoleClient;
this.client.addListener("consoleAPICall", this._onConsoleAPICall);
deferred.resolve();
}, e => {
deferred.reject(e);
});
return deferred.promise;
},
_onConsoleAPICall: function(type, packet) {
if (packet.from != this.webConsole.actor) {
return;
}
this.emit("console", packet.message);
},
_onMessage: function(event) {
if (!event.data) {
return;
}
const msg = event.data;
switch (msg.name) {
case "toolbox-title":
this.title = msg.data.value;
break;
}
},
};
add_task(async function test() {
const addon = await addTemporaryAddon(ADDON_PATH);
const addonDebugger = await initAddonDebugger(ADDON_ID);
const webConsole = addonDebugger.webConsole;
const messages = await getCachedMessages(webConsole);
is(messages.length, 1, "Should be one cached message");
is(messages[0].arguments[0].type, "object", "Should have logged an object");
is(messages[0].arguments[0].preview.ownProperties.msg.value,
"Hello from the test add-on", "Should have got the right message");
const consolePromise = addonDebugger.once("console");
console.log("Bad message");
Services.obs.notifyObservers(null, "addon-test-ping");
const messageGrip = await consolePromise;
is(messageGrip.arguments[0].type, "object", "Should have logged an object");
is(messageGrip.arguments[0].preview.ownProperties.msg.value,
"Hello again", "Should have got the right message");
await addonDebugger.destroy();
await removeAddon(addon);
finish();
});

View File

@ -16,103 +16,46 @@ var { DebuggerClient } = require("devtools/shared/client/debugger-client");
/**
* Make sure the listAddons request works as specified.
*/
const ADDON1_ID = "jid1-oBAwBoE5rSecNg@jetpack";
const ADDON1_PATH = "addon1.xpi";
const ADDON2_ID = "jid1-qjtzNGV8xw5h2A@jetpack";
const ADDON2_PATH = "addon2.xpi";
const ADDON1_ID = "test-addon-1@mozilla.org";
const ADDON1_PATH = "addons/test-addon-1/";
const ADDON2_ID = "test-addon-2@mozilla.org";
const ADDON2_PATH = "addons/test-addon-2/";
var gAddon1, gAddon1Front, gAddon2, gClient;
function test() {
add_task(async function() {
DebuggerServer.init();
DebuggerServer.registerAllActors();
const transport = DebuggerServer.connectPipe();
gClient = new DebuggerClient(transport);
gClient.connect().then(([aType, aTraits]) => {
is(aType, "browser",
"Root actor should identify itself as a browser.");
const client = new DebuggerClient(transport);
promise.resolve(null)
.then(testFirstAddon)
.then(testSecondAddon)
.then(testRemoveFirstAddon)
.then(testRemoveSecondAddon)
.then(() => gClient.close())
.then(finish)
.catch(error => {
ok(false, "Got an error: " + error.message + "\n" + error.stack);
});
});
}
const [type] = await client.connect();
is(type, "browser", "Root actor should identify itself as a browser.");
function testFirstAddon() {
let addonListChanged = false;
gClient.mainRoot.once("addonListChanged").then(() => {
addonListChanged = true;
});
let addonListChangedEvents = 0;
client.mainRoot.on("addonListChanged", () => addonListChangedEvents++);
return addTemporaryAddon(ADDON1_PATH).then(addon => {
gAddon1 = addon;
const addon1 = await addTemporaryAddon(ADDON1_PATH);
const addonFront1 = await client.mainRoot.getAddon({ id: ADDON1_ID });
is(addonListChangedEvents, 0, "Should not receive addonListChanged yet.");
ok(addonFront1, "Should find an addon actor for addon1.");
return gClient.mainRoot.getAddon({ id: ADDON1_ID }).then(front => {
ok(!addonListChanged, "Should not yet be notified that list of addons changed.");
ok(front, "Should find an addon actor for addon1.");
gAddon1Front = front;
});
});
}
const addon2 = await addTemporaryAddon(ADDON2_PATH);
const front1AfterAddingAddon2 = await client.mainRoot.getAddon({ id: ADDON1_ID });
const addonFront2 = await client.mainRoot.getAddon({ id: ADDON2_ID });
function testSecondAddon() {
let addonListChanged = false;
gClient.mainRoot.once("addonListChanged").then(() => {
addonListChanged = true;
});
is(addonListChangedEvents, 1, "Should have received an addonListChanged event.");
ok(addonFront2, "Should find an addon actor for addon2.");
is(addonFront1, front1AfterAddingAddon2, "Front for addon1 should be the same");
return addTemporaryAddon(ADDON2_PATH).then(addon => {
gAddon2 = addon;
await removeAddon(addon1);
const front1AfterRemove = await client.mainRoot.getAddon({ id: ADDON1_ID });
is(addonListChangedEvents, 2, "Should have received an addonListChanged event.");
ok(!front1AfterRemove, "Should no longer get a front for addon1");
return gClient.mainRoot.getAddon({ id: ADDON1_ID }).then(front1 => {
return gClient.mainRoot.getAddon({ id: ADDON2_ID }).then(front2 => {
ok(addonListChanged, "Should be notified that list of addons changed.");
is(front1, gAddon1Front, "First addon's actor shouldn't have changed.");
ok(front2, "Should find a addon actor for the second addon.");
});
});
});
}
await removeAddon(addon2);
const front2AfterRemove = await client.mainRoot.getAddon({ id: ADDON2_ID });
is(addonListChangedEvents, 3, "Should have received an addonListChanged event.");
ok(!front2AfterRemove, "Should no longer get a front for addon1");
function testRemoveFirstAddon() {
let addonListChanged = false;
gClient.mainRoot.once("addonListChanged").then(() => {
addonListChanged = true;
});
return removeAddon(gAddon1).then(() => {
return gClient.mainRoot.getAddon({ id: ADDON1_ID }).then(front => {
ok(addonListChanged, "Should be notified that list of addons changed.");
ok(!front, "Shouldn't find a addon actor for the first addon anymore.");
});
});
}
function testRemoveSecondAddon() {
let addonListChanged = false;
gClient.mainRoot.once("addonListChanged").then(() => {
addonListChanged = true;
});
return removeAddon(gAddon2).then(() => {
return gClient.mainRoot.getAddon({ id: ADDON2_ID }).then(front => {
ok(addonListChanged, "Should be notified that list of addons changed.");
ok(!front, "Shouldn't find a addon actor for the second addon anymore.");
});
});
}
registerCleanupFunction(function() {
gAddon1 = null;
gAddon1Front = null;
gAddon2 = null;
gClient = null;
await client.close();
});

View File

@ -20,6 +20,17 @@ const DATA = [
value: null,
extra: {
oldpanel: "layoutview",
newpanel: "changesview",
},
},
{
timestamp: null,
category: "devtools.main",
method: "sidepanel_changed",
object: "inspector",
value: null,
extra: {
oldpanel: "changesview",
newpanel: "animationinspector",
},
},
@ -64,6 +75,17 @@ const DATA = [
value: null,
extra: {
oldpanel: "computedview",
newpanel: "changesview",
},
},
{
timestamp: null,
category: "devtools.main",
method: "sidepanel_changed",
object: "inspector",
value: null,
extra: {
oldpanel: "changesview",
newpanel: "animationinspector",
},
},
@ -106,6 +128,9 @@ add_task(async function() {
// Let's reset the counts.
Services.telemetry.clearEvents();
// Ensure the Changes panel is enabled before running the tests.
await pushPref("devtools.inspector.changes.enabled", true);
// Ensure no events have been logged
const snapshot = Services.telemetry.snapshotEvents(OPTOUT, true);
ok(!snapshot.parent, "No events have been logged for the main process");
@ -130,7 +155,7 @@ function testSidebar(toolbox) {
const inspector = toolbox.getCurrentPanel();
let sidebarTools = ["computedview", "layoutview", "fontinspector",
"animationinspector"];
"animationinspector", "changesview"];
// Concatenate the array with itself so that we can open each tool twice.
sidebarTools = [...sidebarTools, ...sidebarTools];
@ -159,9 +184,11 @@ function checkResults() {
checkTelemetry("DEVTOOLS_COMPUTEDVIEW_OPENED_COUNT", "", {0: 2, 1: 0}, "array");
checkTelemetry("DEVTOOLS_LAYOUTVIEW_OPENED_COUNT", "", {0: 3, 1: 0}, "array");
checkTelemetry("DEVTOOLS_FONTINSPECTOR_OPENED_COUNT", "", {0: 2, 1: 0}, "array");
checkTelemetry("devtools.changesview.opened_count", "", 2, "scalar");
checkTelemetry("DEVTOOLS_COMPUTEDVIEW_TIME_ACTIVE_SECONDS", "", null, "hasentries");
checkTelemetry("DEVTOOLS_LAYOUTVIEW_TIME_ACTIVE_SECONDS", "", null, "hasentries");
checkTelemetry("DEVTOOLS_FONTINSPECTOR_TIME_ACTIVE_SECONDS", "", null, "hasentries");
checkTelemetry("DEVTOOLS_CHANGESVIEW_TIME_ACTIVE_SECONDS", "", null, "hasentries");
}
function checkEventTelemetry() {

View File

@ -4,12 +4,13 @@
"use strict";
const { Ci } = require("chrome");
const { Ci, Cu } = require("chrome");
const { Actor, ActorClassWithSpec } = require("devtools/shared/protocol");
const { accessibleSpec } = require("devtools/shared/specs/accessibility");
loader.lazyRequireGetter(this, "getContrastRatioFor", "devtools/server/actors/utils/accessibility", true);
loader.lazyRequireGetter(this, "isDefunct", "devtools/server/actors/utils/accessibility", true);
loader.lazyRequireGetter(this, "findCssSelector", "devtools/shared/inspector/css-logic", true);
const nsIAccessibleRelation = Ci.nsIAccessibleRelation;
const RELATIONS_TO_IGNORE = new Set([
@ -20,6 +21,92 @@ const RELATIONS_TO_IGNORE = new Set([
nsIAccessibleRelation.RELATION_SUBWINDOW_OF,
]);
const STATE_DEFUNCT = Ci.nsIAccessibleStates.EXT_STATE_DEFUNCT;
const CSS_TEXT_SELECTOR = "#text";
/**
* Get node inforamtion such as nodeType and the unique CSS selector for the node.
* @param {DOMNode} node
* Node for which to get the information.
* @return {Object}
* Information about the type of the node and how to locate it.
*/
function getNodeDescription(node) {
if (!node || Cu.isDeadWrapper(node)) {
return { nodeType: undefined, nodeCssSelector: "" };
}
const { nodeType } = node;
return {
nodeType,
// If node is a text node, we find a unique CSS selector for its parent and add a
// CSS_TEXT_SELECTOR postfix to indicate that it's a text node.
nodeCssSelector: nodeType === Node.TEXT_NODE ?
`${findCssSelector(node.parentNode)}${CSS_TEXT_SELECTOR}` :
findCssSelector(node),
};
}
/**
* Get a snapshot of the nsIAccessible object including its subtree. None of the subtree
* queried here is cached via accessible walker's refMap.
* @param {nsIAccessible} acc
* Accessible object to take a snapshot of.
* @param {nsIAccessibilityService} a11yService
* Accessibility service instance in the current process, used to get localized
* string representation of various accessible properties.
* @return {JSON}
* JSON snapshot of the accessibility tree with root at current accessible.
*/
function getSnapshot(acc, a11yService) {
if (isDefunct(acc)) {
return {
states: [ a11yService.getStringStates(0, STATE_DEFUNCT) ],
};
}
const actions = [];
for (let i = 0; i < acc.actionCount; i++) {
actions.push(acc.getActionDescription(i));
}
const attributes = {};
if (acc.attributes) {
for (const { key, value } of acc.attributes.enumerate()) {
attributes[key] = value;
}
}
const state = {};
const extState = {};
acc.getState(state, extState);
const states = [
...a11yService.getStringStates(state.value, extState.value),
];
const children = [];
for (let child = acc.firstChild; child; child = child.nextSibling) {
children.push(getSnapshot(child, a11yService));
}
const { nodeType, nodeCssSelector } = getNodeDescription(acc.DOMNode);
return {
name: acc.name,
role: a11yService.getStringRole(acc.role),
actions,
value: acc.value,
nodeCssSelector,
nodeType,
description: acc.description,
keyboardShortcut: acc.accessKey || acc.keyboardShortcut,
childCount: acc.childCount,
indexInParent: acc.indexInParent,
states,
children,
attributes,
};
}
/**
* The AccessibleActor provides information about a given accessible object: its
* role, name, states, etc.
@ -316,6 +403,10 @@ const AccessibleActor = ActorClassWithSpec(accessibleSpec, {
contrastRatio: this._getContrastRatio(),
};
},
snapshot() {
return getSnapshot(this.rawAccessible, this.walker.a11yService);
},
});
exports.AccessibleActor = AccessibleActor;

View File

@ -66,6 +66,42 @@ add_task(async function() {
is(relations[1].targets[0], docAccessibleFront,
"Label's containing document is a root document");
info("Snapshot");
const snapshot = await controlAccessibleFront.snapshot();
Assert.deepEqual(snapshot, {
"name": "Label",
"role": "entry",
"actions": [ "Activate" ],
"value": "",
"nodeCssSelector": "#control",
"nodeType": 1,
"description": "",
"keyboardShortcut": "",
"childCount": 0,
"indexInParent": 1,
"states": [
"focusable",
"autocompletion",
"editable",
"opaque",
"single line",
"enabled",
"sensitive",
],
"children": [],
"attributes": {
"margin-left": "0px",
"text-align": "start",
"text-indent": "0px",
"id": "control",
"tag": "input",
"margin-top": "0px",
"margin-bottom": "0px",
"margin-right": "0px",
"display": "inline",
"explicit-name": "true",
}});
await accessibility.disable();
await waitForA11yShutdown();
await target.destroy();

View File

@ -92,6 +92,12 @@ const accessibleSpec = generateActorSpec({
relations: RetVal("array:accessibleRelation"),
},
},
snapshot: {
request: {},
response: {
snapshot: RetVal("json"),
},
},
},
});

View File

@ -12,7 +12,7 @@
/**
* Default values for the nsViewportInfo class.
*/
static const mozilla::LayoutDeviceToScreenScale kViewportMinScale(0.1f);
static const mozilla::LayoutDeviceToScreenScale kViewportMinScale(0.25f);
static const mozilla::LayoutDeviceToScreenScale kViewportMaxScale(10.0f);
static const mozilla::CSSIntSize kViewportMinSize(200, 40);
static const mozilla::CSSIntSize kViewportMaxSize(10000, 10000);

View File

@ -21,13 +21,13 @@
await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
let info = getViewportInfo(800, 480);
fuzzeq(info.defaultZoom, 0.1, "initial scale is unspecified");
fuzzeq(info.minZoom, 0.1, "minimum scale defaults to the absolute minimum");
is(info.maxZoom, 10, "maximum scale defaults to the absolute maximum");
is(info.width, 980, "width is the default width");
is(info.height, 588, "height is proportional to displayHeight");
is(info.autoSize, false, "autoSize is disabled by default");
is(info.allowZoom, true, "zooming is enabled by default");
fuzzeq(info.defaultZoom, 0.25, "initial scale is clamped to the default mimumim scale");
fuzzeq(info.minZoom, 0.25, "minimum scale defaults to the absolute minimum");
is(info.maxZoom, 10, "maximum scale defaults to the absolute maximum");
is(info.width, 980, "width is the default width");
is(info.height, 588, "height is proportional to displayHeight");
is(info.autoSize, false, "autoSize is disabled by default");
is(info.allowZoom, true, "zooming is enabled by default");
info = getViewportInfo(490, 600);
is(info.width, 980, "width is still the default width");

View File

@ -45,8 +45,8 @@ var ecmaGlobals =
{name: "ArrayBuffer", insecureContext: true},
{name: "Atomics", insecureContext: true, disabled: true},
{name: "Boolean", insecureContext: true},
{name: "ByteLengthQueuingStrategy", insecureContext: true, nightly: true},
{name: "CountQueuingStrategy", insecureContext: true, nightly: true},
{name: "ByteLengthQueuingStrategy", insecureContext: true},
{name: "CountQueuingStrategy", insecureContext: true},
{name: "DataView", insecureContext: true},
{name: "Date", insecureContext: true},
{name: "Error", insecureContext: true},
@ -69,7 +69,7 @@ var ecmaGlobals =
{name: "Promise", insecureContext: true},
{name: "Proxy", insecureContext: true},
{name: "RangeError", insecureContext: true},
{name: "ReadableStream", insecureContext: true, nightly: true},
{name: "ReadableStream", insecureContext: true},
{name: "ReferenceError", insecureContext: true},
{name: "Reflect", insecureContext: true},
{name: "RegExp", insecureContext: true},

View File

@ -33,8 +33,8 @@ var ecmaGlobals =
{name: "ArrayBuffer", insecureContext: true},
{name: "Atomics", insecureContext: true, disabled: true},
{name: "Boolean", insecureContext: true},
{name: "ByteLengthQueuingStrategy", insecureContext: true, nightly: true},
{name: "CountQueuingStrategy", insecureContext: true, nightly: true},
{name: "ByteLengthQueuingStrategy", insecureContext: true},
{name: "CountQueuingStrategy", insecureContext: true},
{name: "DataView", insecureContext: true},
{name: "Date", insecureContext: true},
{name: "Error", insecureContext: true},
@ -59,7 +59,7 @@ var ecmaGlobals =
{name: "Promise", insecureContext: true},
{name: "Proxy", insecureContext: true},
{name: "RangeError", insecureContext: true},
{name: "ReadableStream", insecureContext: true, nightly: true},
{name: "ReadableStream", insecureContext: true},
{name: "ReferenceError", insecureContext: true},
{name: "Reflect", insecureContext: true},
{name: "RegExp", insecureContext: true},

View File

@ -1 +1 @@
a2b4202242d937d328eda21c2d9fcfece609283e
d771bae9f824769c73419fdc3ccffa2bdc47c3e4

View File

@ -10,7 +10,7 @@ use internal_types::{FastHashMap, FastHashSet};
use print_tree::{PrintTree, PrintTreePrinter};
use scene::SceneProperties;
use smallvec::SmallVec;
use spatial_node::{ScrollFrameInfo, SpatialNode, SpatialNodeType, StickyFrameInfo};
use spatial_node::{ScrollFrameInfo, SpatialNode, SpatialNodeType, StickyFrameInfo, ScrollFrameKind};
use util::{LayoutToWorldFastTransform, ScaleOffset};
pub type ScrollStates = FastHashMap<ExternalScrollId, ScrollFrameInfo>;
@ -326,6 +326,7 @@ impl ClipScrollTree {
frame_rect: &LayoutRect,
content_size: &LayoutSize,
scroll_sensitivity: ScrollSensitivity,
frame_kind: ScrollFrameKind,
) -> SpatialNodeIndex {
let node = SpatialNode::new_scroll_frame(
pipeline_id,
@ -334,6 +335,7 @@ impl ClipScrollTree {
frame_rect,
content_size,
scroll_sensitivity,
frame_kind,
);
self.add_spatial_node(node)
}
@ -435,6 +437,53 @@ impl ClipScrollTree {
self.print_node(self.root_reference_frame_index(), pt);
}
}
/// Return true if this is a guaranteed identity transform. This
/// is conservative, it assumes not identity if a property
/// binding animation, or scroll frame is found, for example.
pub fn node_is_identity(
&self,
spatial_node_index: SpatialNodeIndex,
) -> bool {
let mut current = spatial_node_index;
while current != ROOT_SPATIAL_NODE_INDEX {
let node = &self.spatial_nodes[current.0];
match node.node_type {
SpatialNodeType::ReferenceFrame(ref info) => {
if !info.source_perspective.is_identity() {
return false;
}
match info.source_transform {
PropertyBinding::Value(transform) => {
if transform != LayoutTransform::identity() {
return false;
}
}
PropertyBinding::Binding(..) => {
// Assume not identity since it may change with animation.
return false;
}
}
}
SpatialNodeType::ScrollFrame(ref info) => {
// Assume not identity since it may change with scrolling.
if let ScrollFrameKind::Explicit = info.frame_kind {
return false;
}
}
SpatialNodeType::StickyFrame(..) => {
// Assume not identity since it may change with scrolling.
return false;
}
}
current = node.parent.unwrap();
}
true
}
}
#[cfg(test)]

View File

@ -30,7 +30,7 @@ use render_backend::{DocumentView};
use resource_cache::{FontInstanceMap, ImageRequest};
use scene::{Scene, ScenePipeline, StackingContextHelpers};
use scene_builder::DocumentResources;
use spatial_node::{StickyFrameInfo};
use spatial_node::{StickyFrameInfo, ScrollFrameKind};
use std::{f32, mem};
use std::collections::vec_deque::VecDeque;
use tiling::{CompositeOps};
@ -382,6 +382,7 @@ impl<'a> DisplayListFlattener<'a> {
&frame_rect,
&content_rect.size,
info.scroll_sensitivity,
ScrollFrameKind::Explicit,
);
}
@ -499,6 +500,7 @@ impl<'a> DisplayListFlattener<'a> {
&iframe_rect,
&pipeline.content_size,
ScrollSensitivity::ScriptAndInputEvents,
ScrollFrameKind::PipelineRoot,
);
self.flatten_root(
@ -1078,6 +1080,25 @@ impl<'a> DisplayListFlattener<'a> {
pub fn pop_stacking_context(&mut self) {
let stacking_context = self.sc_stack.pop().unwrap();
// If we encounter a stacking context that is effectively a no-op, then instead
// of creating a picture, just append the primitive list to the parent stacking
// context as a short cut. This serves two purposes:
// (a) It's an optimization to reduce picture count and allocations, as display lists
// often contain a lot of these stacking contexts that don't require pictures or
// off-screen surfaces.
// (b) It's useful for the initial version of picture caching in gecko, by enabling
// is to just look for interesting scroll roots on the root stacking context,
// without having to consider cuts at stacking context boundaries.
if let Some(parent_sc) = self.sc_stack.last_mut() {
if stacking_context.is_redundant(
parent_sc,
self.clip_scroll_tree,
) {
parent_sc.primitives.extend(stacking_context.primitives);
return;
}
}
// An arbitrary large clip rect. For now, we don't
// specify a clip specific to the stacking context.
// However, now that they are represented as Picture
@ -1344,6 +1365,7 @@ impl<'a> DisplayListFlattener<'a> {
&LayoutRect::new(LayoutPoint::zero(), *viewport_size),
content_size,
ScrollSensitivity::ScriptAndInputEvents,
ScrollFrameKind::PipelineRoot,
);
}
@ -1455,6 +1477,7 @@ impl<'a> DisplayListFlattener<'a> {
frame_rect: &LayoutRect,
content_size: &LayoutSize,
scroll_sensitivity: ScrollSensitivity,
frame_kind: ScrollFrameKind,
) -> SpatialNodeIndex {
let parent_node_index = self.id_to_index_mapper.get_spatial_node_index(parent_id);
let node_index = self.clip_scroll_tree.add_scroll_frame(
@ -1464,6 +1487,7 @@ impl<'a> DisplayListFlattener<'a> {
frame_rect,
content_size,
scroll_sensitivity,
frame_kind,
);
self.id_to_index_mapper.map_spatial_node(new_node_id, node_index);
self.id_to_index_mapper.map_to_parent_clip_chain(new_node_id, &parent_id);
@ -2185,6 +2209,51 @@ impl FlattenedStackingContext {
self.transform_style == TransformStyle::Preserve3D && self.composite_ops.is_empty()
}
/// Return true if the stacking context isn't needed.
pub fn is_redundant(
&self,
parent: &FlattenedStackingContext,
clip_scroll_tree: &ClipScrollTree,
) -> bool {
// Any 3d context is required
if let Picture3DContext::In { .. } = self.context_3d {
return false;
}
// If there are filters / mix-blend-mode
if !self.composite_ops.is_empty() {
return false;
}
// If backface visibility is different
if self.primitive_data_handle.uid() != parent.primitive_data_handle.uid() {
return false;
}
// If rasterization space is different
if self.requested_raster_space != parent.requested_raster_space {
return false;
}
// If different clipp chains
if self.clip_chain_id != parent.clip_chain_id {
return false;
}
// If need to isolate in surface due to clipping / mix-blend-mode
if self.should_isolate {
return false;
}
// If represents a transform, it may affect backface visibility of children
if !clip_scroll_tree.node_is_identity(self.spatial_node_index) {
return false;
}
// It is redundant!
true
}
/// For a Preserve3D context, cut the sequence of the immediate flat children
/// recorded so far and generate a picture from them.
pub fn cut_flat_item_sequence(

View File

@ -775,7 +775,7 @@ impl TileCache {
// exact content of this tile.
if let Some(handle) = retained_tiles.remove(&tile.descriptor) {
// Only use if not evicted from texture cache in the meantime.
if !resource_cache.texture_cache.request(&handle, gpu_cache) {
if resource_cache.texture_cache.is_allocated(&handle) {
// We found a matching tile from the previous scene, so use it!
tile.handle = handle;
tile.is_valid = true;
@ -847,12 +847,19 @@ impl TileCache {
.expect("bug: unable to map tile to world coords");
tile.is_visible = frame_context.screen_world_rect.intersects(&tile_world_rect);
// If we have an invalid tile, which is also visible, add it to the
// dirty rect we will need to draw.
if !tile.is_valid && tile.is_visible && tile.in_use {
dirty_rect = dirty_rect.union(&tile_rect);
tile_offset.x = tile_offset.x.min(x);
tile_offset.y = tile_offset.y.min(y);
if tile.is_visible && tile.in_use {
// Ensure we request the texture cache handle for this tile
// each frame it will be used so the texture cache doesn't
// decide to evict tiles that we currently want to use.
resource_cache.texture_cache.request(&tile.handle, gpu_cache);
// If we have an invalid tile, which is also visible, add it to the
// dirty rect we will need to draw.
if !tile.is_valid {
dirty_rect = dirty_rect.union(&tile_rect);
tile_offset.x = tile_offset.x.min(x);
tile_offset.y = tile_offset.y.min(y);
}
}
}
}

View File

@ -1063,6 +1063,10 @@ impl ResourceCache {
assert!(!descriptor.rect.is_empty());
if !self.blob_image_templates.contains_key(&request.key) {
panic!("already missing blob image key {:?} deleted: {:?}", request, self.deleted_blob_keys);
}
self.missing_blob_images.push(
BlobImageParams {
request,

View File

@ -96,6 +96,7 @@ impl SpatialNode {
frame_rect: &LayoutRect,
content_size: &LayoutSize,
scroll_sensitivity: ScrollSensitivity,
frame_kind: ScrollFrameKind,
) -> Self {
let node_type = SpatialNodeType::ScrollFrame(ScrollFrameInfo::new(
*frame_rect,
@ -105,6 +106,7 @@ impl SpatialNode {
(content_size.height - frame_rect.size.height).max(0.0)
),
external_id,
frame_kind,
)
);
@ -558,6 +560,14 @@ impl SpatialNode {
}
}
/// Defines whether we have an implicit scroll frame for a pipeline root,
/// or an explicitly defined scroll frame from the display list.
#[derive(Copy, Clone, Debug)]
pub enum ScrollFrameKind {
PipelineRoot,
Explicit,
}
#[derive(Copy, Clone, Debug)]
pub struct ScrollFrameInfo {
/// The rectangle of the viewport of this scroll frame. This is important for
@ -575,6 +585,14 @@ pub struct ScrollFrameInfo {
/// which may change between frames.
pub external_id: Option<ExternalScrollId>,
/// Stores whether this is a scroll frame added implicitly by WR when adding
/// a pipeline (either the root or an iframe). We need to exclude these
/// when searching for scroll roots we care about for picture caching.
/// TODO(gw): I think we can actually completely remove the implicit
/// scroll frame being added by WR, and rely on the embedder
/// to define scroll frames. However, that involves API changes
/// so we will use this as a temporary hack!
pub frame_kind: ScrollFrameKind,
}
/// Manages scrolling offset.
@ -584,6 +602,7 @@ impl ScrollFrameInfo {
scroll_sensitivity: ScrollSensitivity,
scrollable_size: LayoutSize,
external_id: Option<ExternalScrollId>,
frame_kind: ScrollFrameKind,
) -> ScrollFrameInfo {
ScrollFrameInfo {
viewport_rect,
@ -591,6 +610,7 @@ impl ScrollFrameInfo {
scroll_sensitivity,
scrollable_size,
external_id,
frame_kind,
}
}
@ -611,6 +631,7 @@ impl ScrollFrameInfo {
scroll_sensitivity: self.scroll_sensitivity,
scrollable_size: self.scrollable_size,
external_id: self.external_id,
frame_kind: self.frame_kind,
}
}
}

View File

@ -582,6 +582,18 @@ impl<Src, Dst> FastTransform<Src, Dst> {
}
}
/// Return true if this is an identity transform
pub fn is_identity(&self)-> bool {
match *self {
FastTransform::Offset(offset) => {
offset == TypedVector2D::zero()
}
FastTransform::Transform { ref transform, .. } => {
*transform == TypedTransform3D::identity()
}
}
}
#[inline(always)]
pub fn pre_mul<NewSrc>(
&self,

View File

@ -5,11 +5,11 @@ root:
bounds: [10, 10, 100, 100]
clip-rect: [10, 10, 100, 100]
type: rect
color: 0 256 0 1.0
color: 0 255 0 1.0
-
bounds: [110, 10, 100, 100]
clip-rect: [110, 10, 100, 100]
type: rect
color: 0 256 0 1.0
color: 0 255 0 1.0
id: [0, 1]
pipelines: []

View File

@ -22,7 +22,7 @@ root:
clip-rect: [0, 0, 100, 100]
clip-and-scroll: root-reference-frame
type: rect
color: 0 256 0 1.0
color: 0 255 0 1.0
# The same test as above, except this time the stacking context also starts its
# own reference frame.
-
@ -44,6 +44,6 @@ root:
clip-rect: [0, 0, 100, 100]
clip-and-scroll: 4
type: rect
color: 0 256 0 1.0
color: 0 255 0 1.0
id: [0, 1]
pipelines: []

View File

@ -259,7 +259,7 @@ void Zone::discardJitCode(FreeOp* fop, bool discardBaselineCode,
// because TypeScript contains the ICScript* and there's no need to
// purge stubs if we just destroyed the Typescript.
if (discardBaselineCode && script->hasICScript()) {
script->icScript()->purgeOptimizedStubs(script->zone());
script->icScript()->purgeOptimizedStubs(script);
}
}

View File

@ -0,0 +1,17 @@
Object.defineProperty(this, "fuzzutils", {
value: {
orig_evaluate: evaluate,
evaluate: function(c, o) {
if (!o) {
o = {};
}
o.catchTermination = true;
return fuzzutils.orig_evaluate(c, o);
},
}
});
gczeal(21, 10);
fuzzutils.evaluate(`
enableShellAllocationMetadataBuilder();
function test() {}
`);

View File

@ -345,7 +345,7 @@ class ICScript {
void noteHasDenseAdd(uint32_t pcOffset);
void trace(JSTracer* trc);
void purgeOptimizedStubs(Zone* zone);
void purgeOptimizedStubs(JSScript* script);
ICEntry* maybeICEntryFromPCOffset(uint32_t pcOffset);
ICEntry* maybeICEntryFromPCOffset(uint32_t pcOffset,

View File

@ -1014,7 +1014,19 @@ void BaselineScript::toggleProfilerInstrumentation(bool enable) {
}
}
void ICScript::purgeOptimizedStubs(Zone* zone) {
void ICScript::purgeOptimizedStubs(JSScript* script) {
MOZ_ASSERT(script->icScript() == this);
Zone* zone = script->zone();
if (zone->isGCSweeping() && IsAboutToBeFinalizedDuringSweep(*script)) {
// We're sweeping and the script is dead. Don't purge optimized stubs
// because (1) accessing CacheIRStubInfo pointers in ICStubs is invalid
// because we may have swept them already when we started (incremental)
// sweeping and (2) it's unnecessary because this script will be finalized
// soon anyway.
return;
}
JitSpew(JitSpew_BaselineIC, "Purging optimized stubs");
for (size_t i = 0; i < numICEntries(); i++) {

View File

@ -0,0 +1,34 @@
<!DOCTYPE html>
<meta name="viewport" content="width=device-width">
<style>
html, body {
margin: 0;
width: 100%;
height: 100%;
scrollbar-width: none;
}
#container {
/*
* This value should be greater than reftest viewport width 800px * (1 / 0.25).
* 0.25 is the initial scale value used in the reference.
*/
min-width: 4000px;
/*
* This value should be greater than reftest viewport height
* 1000x * (1 / 0.25) to make the initial scale auto-calculation happen.
*/
min-height: 8000px;
position: relative;
}
#inner {
position: absolute;
top: 0;
left: 0;
width: 100px;
height: 100px;
background: green;
}
</style>
<div id="container">
<div id="inner"></div>
</div>

View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<meta name="viewport" content="initial-scale=0.25,width=device-width">
<style>
html, body {
margin: 0;
width: 100%;
height: 100%;
}
#container {
position: relative;
}
#inner {
position: absolute;
top: 0;
left: 0;
width: 100px;
height: 100px;
background: green;
}
</style>
<div id="container">
<div id="inner"></div>
</div>

View File

@ -8,3 +8,4 @@ default-preferences pref(dom.meta-viewport.enabled,true) pref(apz.allow_zooming,
== viewport-width.html initial-scale-0_5-ref.html
== initial-scale-1.html no-zoom-ref.html
== minimum-scale.html no-zoom-ref.html
== clamped-by-default-minimum-scale.html initial-scale-0_25-ref.html

View File

@ -148,19 +148,6 @@ bool GetWindowList(rtc::FunctionView<bool(CFDictionaryRef)> on_window,
continue;
}
//Skip windows of zero area
CFDictionaryRef bounds_ref = reinterpret_cast<CFDictionaryRef>(
CFDictionaryGetValue(window, kCGWindowBounds));
CGRect bounds_rect;
if(!(bounds_ref) ||
!(CGRectMakeWithDictionaryRepresentation(bounds_ref, &bounds_rect))){
continue;
}
bounds_rect = CGRectStandardize(bounds_rect);
if((bounds_rect.size.width <= 0) || (bounds_rect.size.height <= 0)){
continue;
}
// Skip windows with layer=0 (menu, dock).
// TODO(zijiehe): The windows with layer != 0 are skipped, is this a bug in
// code (not likely) or a bug in comments? What's the meaning of window

View File

@ -82,13 +82,6 @@ BOOL CALLBACK WindowsEnumerationHandler(HWND hwnd, LPARAM param) {
if (window.title.empty())
return TRUE;
// Skip windows of zero visible area, except IconicWindows
RECT bounds;
if(GetClientRect(hwnd,&bounds) && !IsIconic(hwnd)
&& IsRectEmpty(&bounds)){
return TRUE;
}
list->push_back(window);
return TRUE;

View File

@ -209,15 +209,6 @@ bool GetWindowList(XAtomCache* cache,
::Window app_window =
GetApplicationWindow(cache, children[num_children - 1 - i]);
if (app_window && !IsDesktopElement(cache, app_window)) {
XWindowAttributes window_attr;
if(!XGetWindowAttributes(display, app_window, &window_attr)) {
RTC_LOG(LS_ERROR)<<"Bad request for attributes for window ID:" << app_window;
continue;
}
if((window_attr.width <= 0) || (window_attr.height <=0)){
continue;
}
if (!on_window(app_window)) {
return true;
}

View File

@ -505,7 +505,8 @@ class AccessibilityTest : BaseSessionTest() {
@Test fun testScroll() {
var nodeId = View.NO_ID
sessionRule.session.loadString(
"""<body style="margin: 0;">
"""<meta name="viewport" content="width=device-width initial-scale=1">
<body style="margin: 0;">
<div style="height: 100vh;"></div>
<button>Hello</button>
<p style="margin: 0;">Lorem ipsum dolor sit amet, consectetur adipiscing elit,

View File

@ -1575,11 +1575,7 @@ pref("javascript.options.spectre.jit_to_C++_calls", true);
#endif
// Streams API
#ifdef NIGHTLY_BUILD
pref("javascript.options.streams", true);
#else
pref("javascript.options.streams", false);
#endif
// BigInt API
pref("javascript.options.bigint", false);
@ -5142,7 +5138,7 @@ pref("extensions.webextensions.performanceCountersMaxAge", 1000);
// Report Site Issue button
pref("extensions.webcompat-reporter.newIssueEndpoint", "https://webcompat.com/issues/new");
#if defined(MOZ_DEV_EDITION) || defined(NIGHTLY_BUILD)
#if MOZ_UPDATE_CHANNEL != release && MOZ_UPDATE_CHANNEL != esr
pref("extensions.webcompat-reporter.enabled", true);
#else
pref("extensions.webcompat-reporter.enabled", false);

View File

@ -0,0 +1,99 @@
from __future__ import absolute_import
import fluent.syntax.ast as FTL
from fluent.migrate.helpers import transforms_from
from fluent.migrate import CONCAT
from fluent.migrate import REPLACE
from fluent.migrate import COPY
def migrate(ctx):
""" Bug 1504751 - Migrate about:Networking to Fluent, part {index}. """
ctx.add_transforms(
"toolkit/toolkit/about/aboutNetworking.ftl",
"toolkit/toolkit/about/aboutNetworking.ftl",
transforms_from(
"""
title = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.title")}
warning = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.warning")}
show-next-time-checkbox = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.showNextTime")}
ok = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.ok")}
http = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.HTTP")}
sockets = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.sockets")}
dns = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.dns")}
websockets = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.websockets")}
refresh = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.refresh")}
auto-refresh = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.autoRefresh")}
hostname = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.hostname")}
port = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.port")}
http2 = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.http2")}
ssl = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.ssl")}
active = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.active")}
idle = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.idle")}
host = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.host")}
tcp = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.tcp")}
sent = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.sent")}
received = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.received")}
family = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.family")}
trr = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.trr")}
addresses = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.addresses")}
expires = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.expires")}
messages-sent = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.messagesSent")}
messages-received = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.messagesReceived")}
bytes-sent = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.bytesSent")}
bytes-received = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.bytesReceived")}
logging = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.logging")}
current-log-file = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.currentLogFile")}
current-log-modules = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.currentLogModules")}
set-log-file = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.setLogFile")}
set-log-modules = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.setLogModules")}
start-logging = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.startLogging")}
stop-logging = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.stopLogging")}
dns-lookup = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.dnsLookup")}
dns-lookup-button = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.dnsLookupButton")}
dns-lookup-table-column = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.dnsLookupTableColumn")}
rcwn = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.rcwn")}
rcwn-status = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.rcwnStatus")}
rcwn-cache-won-count = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.rcwnCacheWonCount")}
rcwn-net-won-count = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.rcwnNetWonCount")}
total-network-requests = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.totalNetworkRequests")}
rcwn-operation = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.rcwnOperation")}
rcwn-perf-open = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.rcwnPerfOpen")}
rcwn-perf-read = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.rcwnPerfRead")}
rcwn-perf-write = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.rcwnPerfWrite")}
rcwn-perf-entry-open = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.rcwnPerfEntryOpen")}
rcwn-avg-short = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.rcwnAvgShort")}
rcwn-avg-long = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.rcwnAvgLong")}
rcwn-std-dev-long = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.rcwnStddevLong")}
rcwn-cache-slow = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.rcwnCacheSlow")}
rcwn-cache-not-slow = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.rcwnCacheNotSlow")}
"""
)
)
ctx.add_transforms(
"toolkit/toolkit/about/aboutNetworking.ftl",
"toolkit/toolkit/about/aboutNetworking.ftl",
[
FTL.Message(
id=FTL.Identifier("log-tutorial"),
value=REPLACE(
"toolkit/chrome/global/aboutNetworking.dtd",
"aboutNetworking.logTutorial",
{
"href='https://developer.mozilla.org/docs/Mozilla/Debugging/HTTP_logging'": FTL.TextElement('data-l10n-name="logging"')
}
)
),
FTL.Message(
id=FTL.Identifier("dns-domain"),
value=CONCAT(
COPY(
"toolkit/chrome/global/aboutNetworking.dtd",
"aboutNetworking.dnsDomain",
),
FTL.TextElement(":"),
)
)
]
)

View File

@ -0,0 +1,111 @@
# coding=utf8
# Any copyright is dedicated to the Public Domain.
# http://creativecommons.org/publicdomain/zero/1.0/
from __future__ import absolute_import
import fluent.syntax.ast as FTL
from fluent.migrate.helpers import transforms_from
from fluent.migrate.helpers import TERM_REFERENCE
from fluent.migrate import COPY
from fluent.migrate import REPLACE
from fluent.migrate import CONCAT
def migrate(ctx):
"""Bug 1505846 Migrate about:searchreset to Fluent, part {index} """
ctx.add_transforms(
"browser/browser/aboutSearchReset.ftl",
"browser/browser/aboutSearchReset.ftl",
transforms_from(
"""
tab-title = { COPY("browser/chrome/browser/aboutSearchReset.dtd", "searchreset.tabtitle") }
page-title = { COPY("browser/chrome/browser/aboutSearchReset.dtd", "searchreset.pageTitle") }
"""
)
)
ctx.add_transforms(
"browser/browser/aboutSearchReset.ftl",
"browser/browser/aboutSearchReset.ftl",
[
FTL.Message(
id=FTL.Identifier("page-info-outofdate"),
value=REPLACE(
"browser/chrome/browser/aboutSearchReset.dtd",
"searchreset.pageInfo1",
{
"&brandShortName;": TERM_REFERENCE("-brand-short-name"),
}
)
),
FTL.Message(
id=FTL.Identifier("page-info-how-to-change"),
value=CONCAT(
COPY(
"browser/chrome/browser/aboutSearchReset.dtd",
"searchreset.beforelink.pageInfo2",
),
FTL.TextElement('<a data-l10n-name="link">'),
COPY(
"browser/chrome/browser/aboutSearchReset.dtd",
"searchreset.link.pageInfo2",
),
FTL.TextElement("</a>"),
COPY(
"browser/chrome/browser/aboutSearchReset.dtd",
"searchreset.afterlink.pageInfo2",
)
)
),
FTL.Message(
id=FTL.Identifier("no-change-button"),
attributes=[
FTL.Attribute(
id=FTL.Identifier("label"),
value=COPY(
"browser/chrome/browser/aboutSearchReset.dtd",
"searchreset.noChangeButton",
)
),
FTL.Attribute(
id=FTL.Identifier("accesskey"),
value=COPY(
"browser/chrome/browser/aboutSearchReset.dtd",
"searchreset.noChangeButton.access",
)
),
]
),
FTL.Message(
id=FTL.Identifier("change-engine-button"),
attributes=[
FTL.Attribute(
id=FTL.Identifier("label"),
value=COPY(
"browser/chrome/browser/aboutSearchReset.dtd",
"searchreset.changeEngineButton",
)
),
FTL.Attribute(
id=FTL.Identifier("accesskey"),
value=COPY(
"browser/chrome/browser/aboutSearchReset.dtd",
"searchreset.changeEngineButton.access",
)
),
]
),
FTL.Message(
id=FTL.Identifier("page-info-new-search-engine"),
value=CONCAT(
COPY(
"browser/chrome/browser/aboutSearchReset.dtd",
"searchreset.selector.label",
),
FTL.TextElement(' <span data-l10n-name="default-engine">{ $searchEngine }</span>'),
)
)
]
)

View File

@ -53,8 +53,8 @@ def check_executable_version(exe, wrap_call_with_node=False):
May raise ``subprocess.CalledProcessError`` or ``ValueError`` on failure.
"""
out = None
# npm may be a script, so we must call it with node.
if wrap_call_with_node:
# npm may be a script (Except on Windows), so we must call it with node.
if wrap_call_with_node and platform.system() != "Windows":
binary, _ = find_node_executable()
if binary:
out = subprocess.check_output([binary, exe, "--version"]).lstrip('v').rstrip()

View File

@ -35,6 +35,10 @@
#ifdef MOZ_WIDGET_GTK
#include <glib.h>
#ifdef MOZ_WAYLAND
#include <gdk/gdk.h>
#include <gdk/gdkx.h>
#endif
#endif
#include <dirent.h>
@ -386,12 +390,20 @@ SandboxBrokerPolicyFactory::SandboxBrokerPolicyFactory() {
}
policy->AddPath(SandboxBroker::MAY_CONNECT, bumblebeeSocket);
#if defined(MOZ_WIDGET_GTK)
// Allow local X11 connections, for Primus and VirtualGL to contact
// the secondary X server.
// the secondary X server. No exception for Wayland.
#if defined(MOZ_WAYLAND)
if (GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
policy->AddPrefix(SandboxBroker::MAY_CONNECT, "/tmp/.X11-unix/X");
}
#else
policy->AddPrefix(SandboxBroker::MAY_CONNECT, "/tmp/.X11-unix/X");
#endif
if (const auto xauth = PR_GetEnv("XAUTHORITY")) {
policy->AddPath(rdonly, xauth);
}
#endif
mCommonContentPolicy.reset(policy);
#endif

View File

@ -32,5 +32,6 @@ LOCAL_INCLUDES += [
if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
CXXFLAGS += CONFIG['GLIB_CFLAGS']
CXXFLAGS += CONFIG['TK_CFLAGS']
FINAL_LIBRARY = 'xul'

View File

@ -31,6 +31,11 @@ The ``get()`` method returns the list of entries for a specific key. Each entry
// await InternalAPI.load(entry.id, entry.label, entry.weight);
});
.. note::
The data updates are managed internally, and ``.get()`` only returns the local data.
The data is pulled from the server only if this collection has no local data yet and no JSON dump
could be found (see :ref:`services/initial-data` below).
.. note::
The ``id`` and ``last_modified`` (timestamp) attributes are assigned by the server.
@ -89,18 +94,23 @@ When an entry has a file attached to it, it has an ``attachment`` attribute, whi
}
});
.. _services/initial-data:
Initial data
------------
For newly created user profiles, the list of entries returned by the ``.get()`` method will be empty until the first synchronization happens.
It is possible to package a dump of the server records that will be loaded into the local database when no synchronization has happened yet.
It is possible to package a dump of the server records that will be loaded into the local database when no synchronization has happened yet. It will thus serve as the default dataset and also reduce the amount of data to be downloaded on the first synchronization.
The JSON dump will serve as the default dataset for ``.get()``, instead of doing a round-trip to pull the latest data. It will also reduce the amount of data to be downloaded on the first synchronization.
#. Place the JSON dump of the server records in the ``services/settings/dumps/main/`` folder
#. Add the filename to the ``FINAL_TARGET_FILES`` list in ``services/settings/dumps/main/moz.build``
Now, when ``RemoteSettings("some-key").get()`` is called from an empty profile, the ``some-key.json`` file is going to be loaded before the results are returned.
.. note::
JSON dumps are not shipped on Android to minimize the installer size.
Targets and A/B testing
=======================
@ -170,14 +180,15 @@ The synchronization of every known remote settings clients can be triggered manu
await RemoteSettings.pollChanges()
The synchronization of a single client can be forced with ``maybeSync()``:
The synchronization of a single client can be forced with the ``.sync()`` method:
.. code-block:: js
const fakeTimestamp = Infinity;
const fakeServerTime = Date.now();
await RemoteSettings("a-key").sync();
await RemoteSettings("a-key").maybeSync(fakeTimestamp, fakeServerTime)
.. important::
The above methods are only relevant during development or debugging and should never be called in production code.
Manipulate local data

View File

@ -51,7 +51,7 @@ add_task(async function test_something() {
server.registerPathHandler(recordsPath, handleResponse);
// Test an empty db populates
await OneCRLBlocklistClient.maybeSync(2000, Date.now());
await OneCRLBlocklistClient.maybeSync(2000);
// Open the collection, verify it's been populated:
const list = await OneCRLBlocklistClient.get();
@ -63,9 +63,7 @@ add_task(async function test_something() {
Services.prefs.clearUserPref("services.settings.server");
Services.prefs.setIntPref("services.blocklist.onecrl.checked", 0);
// Use any last_modified older than highest shipped in JSON dump.
await OneCRLBlocklistClient.maybeSync(123456, Date.now());
// Last check value was updated.
Assert.notEqual(0, Services.prefs.getIntPref("services.blocklist.onecrl.checked"));
await OneCRLBlocklistClient.maybeSync(123456);
// Restore server pref.
Services.prefs.setCharPref("services.settings.server", dummyServerURL);
@ -78,7 +76,7 @@ add_task(async function test_something() {
// single record
await collection.db.saveLastModified(1000);
await OneCRLBlocklistClient.maybeSync(2000, Date.now());
await OneCRLBlocklistClient.maybeSync(2000);
// Open the collection, verify it's been updated:
// Our test data now has two records; both should be in the local collection
@ -86,7 +84,7 @@ add_task(async function test_something() {
Assert.equal(before.length, 1);
// Test the db is updated when we call again with a later lastModified value
await OneCRLBlocklistClient.maybeSync(4000, Date.now());
await OneCRLBlocklistClient.maybeSync(4000);
// Open the collection, verify it's been updated:
// Our test data now has two records; both should be in the local collection
@ -97,23 +95,16 @@ add_task(async function test_something() {
// should be attempted.
// Clear the kinto base pref so any connections will cause a test failure
Services.prefs.clearUserPref("services.settings.server");
await OneCRLBlocklistClient.maybeSync(4000, Date.now());
await OneCRLBlocklistClient.maybeSync(4000);
// Try again with a lastModified value at some point in the past
await OneCRLBlocklistClient.maybeSync(3000, Date.now());
// Check the OneCRL check time pref is modified, even if the collection
// hasn't changed
Services.prefs.setIntPref("services.blocklist.onecrl.checked", 0);
await OneCRLBlocklistClient.maybeSync(3000, Date.now());
let newValue = Services.prefs.getIntPref("services.blocklist.onecrl.checked");
Assert.notEqual(newValue, 0);
await OneCRLBlocklistClient.maybeSync(3000);
// Check that a sync completes even when there's bad data in the
// collection. This will throw on fail, so just calling maybeSync is an
// acceptible test.
Services.prefs.setCharPref("services.settings.server", dummyServerURL);
await OneCRLBlocklistClient.maybeSync(5000, Date.now());
await OneCRLBlocklistClient.maybeSync(5000);
});
function run_test() {

View File

@ -102,7 +102,7 @@ add_task(async function test_initial_dump_is_loaded_as_synced_when_collection_is
}
// Test an empty db populates, but don't reach server (specified timestamp <= dump).
await client.maybeSync(1, Date.now());
await client.maybeSync(1);
// Verify the loaded data has status to synced:
const collection = await client.openCollection();
@ -134,19 +134,6 @@ add_task(async function test_initial_dump_is_loaded_when_using_get_on_empty_coll
});
add_task(clear_state);
add_task(async function test_current_server_time_is_saved_in_pref() {
for (let {client} of gBlocklistClients) {
// The lastCheckTimePref was customized:
ok(/services\.blocklist\.(\w+)\.checked/.test(client.lastCheckTimePref), client.lastCheckTimePref);
const serverTime = Date.now();
await client.maybeSync(3000, serverTime);
const after = Services.prefs.getIntPref(client.lastCheckTimePref);
equal(after, Math.round(serverTime / 1000));
}
});
add_task(clear_state);
add_task(async function test_sync_event_data_is_filtered_for_target() {
// Here we will synchronize 4 times, the first two to initialize the local DB and
// the last two about event filtered data.
@ -154,25 +141,23 @@ add_task(async function test_sync_event_data_is_filtered_for_target() {
const timestamp2 = 3001;
const timestamp3 = 4001;
const timestamp4 = 5001;
// Fake a date value obtained from server (used to store a pref, useless here).
const fakeServerTime = Date.now();
for (let {client} of gBlocklistClients) {
// Initialize the collection with some data (local is empty, thus no ?_since)
await client.maybeSync(timestamp1, fakeServerTime - 30, {loadDump: false});
await client.maybeSync(timestamp1, {loadDump: false});
// This will pick the data with ?_since=3000.
await client.maybeSync(timestamp2, fakeServerTime - 20);
await client.maybeSync(timestamp2);
// In ?_since=4000 entries, no target matches. The sync event is not called.
let called = false;
client.on("sync", e => called = true);
await client.maybeSync(timestamp3, fakeServerTime - 10);
await client.maybeSync(timestamp3);
equal(called, false, `shouldn't have sync event for ${client.collectionName}`);
// In ?_since=5000 entries, only one entry matches.
let syncEventData;
client.on("sync", e => syncEventData = e.data);
await client.maybeSync(timestamp4, fakeServerTime);
await client.maybeSync(timestamp4);
const { current, created, updated, deleted } = syncEventData;
equal(created.length + updated.length + deleted.length, 1, `event filtered data for ${client.collectionName}`);

View File

@ -80,7 +80,7 @@ add_task(async function test_something() {
Services.io.newURI("https://five.example.com"), 0));
// Test an empty db populates
await PinningPreloadClient.maybeSync(2000, Date.now());
await PinningPreloadClient.maybeSync(2000);
// Open the collection, verify it's been populated:
// Our test data has a single record; it should be in the local collection
@ -92,7 +92,7 @@ add_task(async function test_something() {
Services.io.newURI("https://one.example.com"), 0));
// Test the db is updated when we call again with a later lastModified value
await PinningPreloadClient.maybeSync(4000, Date.now());
await PinningPreloadClient.maybeSync(4000);
// Open the collection, verify it's been updated:
// Our data now has four new records; all should be in the local collection
@ -114,17 +114,10 @@ add_task(async function test_something() {
// should be attempted.
// Clear the kinto base pref so any connections will cause a test failure
Services.prefs.clearUserPref("services.settings.server");
await PinningPreloadClient.maybeSync(4000, Date.now());
await PinningPreloadClient.maybeSync(4000);
// Try again with a lastModified value at some point in the past
await PinningPreloadClient.maybeSync(3000, Date.now());
// Check the pinning check time pref is modified, even if the collection
// hasn't changed
Services.prefs.setIntPref("services.blocklist.onecrl.checked", 0);
await PinningPreloadClient.maybeSync(3000, Date.now());
let newValue = Services.prefs.getIntPref("services.blocklist.pinning.checked");
Assert.notEqual(newValue, 0);
await PinningPreloadClient.maybeSync(3000);
// Check that the HSTS preload added to the collection works...
ok(sss.isSecureURI(sss.HEADER_HSTS,
@ -139,7 +132,7 @@ add_task(async function test_something() {
// acceptible test (the data below with last_modified of 300 is nonsense).
Services.prefs.setCharPref("services.settings.server",
`http://localhost:${server.identity.primaryPort}/v1`);
await PinningPreloadClient.maybeSync(5000, Date.now());
await PinningPreloadClient.maybeSync(5000);
// The STS entry for five.example.com now has includeSubdomains set;
// ensure that the new includeSubdomains value is honored.

Some files were not shown because too many files have changed in this diff Show More