зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central to mozilla-inbound
This commit is contained in:
Коммит
17253c6f93
|
@ -16,6 +16,8 @@ js/src/builtin/intl/TimeZoneDataGenerated.h
|
|||
js/src/jsapi-tests/.*
|
||||
# See bug 1395584
|
||||
js/src/vm/Opcodes.h
|
||||
# Ignored because of bug 1506117
|
||||
layout/style/nsCSSKeywordList.h
|
||||
# Ignored because of bug 1342657
|
||||
layout/style/nsCSSPropAliasList.h
|
||||
# Ignored because of bug 1342657
|
||||
|
|
|
@ -14,9 +14,7 @@ obj*/**
|
|||
# We ignore all these directories by default, until we get them enabled.
|
||||
# If you are enabling a directory, please add directory specific exclusions
|
||||
# below.
|
||||
docshell/resources/**
|
||||
docshell/test/browser/**
|
||||
docshell/test/chrome/**
|
||||
docshell/test/iframesandbox/**
|
||||
docshell/test/mochitest/**
|
||||
extensions/cookie/**
|
||||
|
|
|
@ -104,23 +104,15 @@ var gDragSpaceObserver = {
|
|||
pref: "browser.tabs.extraDragSpace",
|
||||
|
||||
init() {
|
||||
this._update();
|
||||
Services.prefs.addObserver(this.pref, this);
|
||||
this.observe();
|
||||
},
|
||||
|
||||
uninit() {
|
||||
Services.prefs.removeObserver(this.pref, this);
|
||||
},
|
||||
|
||||
observe(aSubject, aTopic, aPrefName) {
|
||||
if (aTopic != "nsPref:changed" || aPrefName != this.pref) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._update();
|
||||
},
|
||||
|
||||
_update() {
|
||||
observe() {
|
||||
if (Services.prefs.getBoolPref(this.pref)) {
|
||||
document.documentElement.setAttribute("extradragspace", "true");
|
||||
} else {
|
||||
|
|
|
@ -35,6 +35,7 @@ Preferences.addAll([
|
|||
{ id: "network.proxy.backup.socks_port", type: "int" },
|
||||
{ id: "network.trr.mode", type: "int" },
|
||||
{ id: "network.trr.uri", type: "string" },
|
||||
{ id: "network.trr.custom_uri", "type": "string" },
|
||||
]);
|
||||
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
|
@ -55,6 +56,10 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
|
||||
var gConnectionsDialog = {
|
||||
beforeAccept() {
|
||||
if (document.getElementById("customDnsOverHttpsUrlRadio").selected) {
|
||||
Services.prefs.setStringPref("network.trr.uri", document.getElementById("customDnsOverHttpsInput").value);
|
||||
}
|
||||
|
||||
var proxyTypePref = Preferences.get("network.proxy.type");
|
||||
if (proxyTypePref.value == 2) {
|
||||
this.doAutoconfigURLFixup();
|
||||
|
@ -304,16 +309,25 @@ var gConnectionsDialog = {
|
|||
return trrModeCheckbox.checked ? 2 : 0;
|
||||
},
|
||||
|
||||
writeDnsOverHttpsUri() {
|
||||
// called to update pref with user input
|
||||
let input = document.getElementById("networkDnsOverHttpsUrl");
|
||||
let uriString = input.value.trim();
|
||||
// turn an empty string into `undefined` to clear the pref back to the default
|
||||
return uriString.length ? uriString : undefined;
|
||||
updateDnsOverHttpsUI() {
|
||||
// Disable the custom url input box if the parent checkbox and custom radio button attached to it is not selected.
|
||||
// Disable the custom radio button if the parent checkbox is not selected.
|
||||
let parentCheckbox = document.getElementById("networkDnsOverHttps");
|
||||
let customDnsOverHttpsUrlRadio = document.getElementById("customDnsOverHttpsUrlRadio");
|
||||
let customDnsOverHttpsInput = document.getElementById("customDnsOverHttpsInput");
|
||||
customDnsOverHttpsInput.disabled = !parentCheckbox.checked || !customDnsOverHttpsUrlRadio.selected;
|
||||
customDnsOverHttpsUrlRadio.disabled = !parentCheckbox.checked;
|
||||
},
|
||||
|
||||
initDnsOverHttpsUI() {
|
||||
let input = document.getElementById("networkDnsOverHttpsUrl");
|
||||
input.placeholder = Preferences.get("network.trr.uri").defaultValue;
|
||||
let defaultDnsOverHttpsUrlRadio = document.getElementById("defaultDnsOverHttpsUrlRadio");
|
||||
let defaultPrefUrl = Preferences.get("network.trr.uri").defaultValue;
|
||||
document.l10n.setAttributes(defaultDnsOverHttpsUrlRadio, "connection-dns-over-https-url-default", {
|
||||
url: defaultPrefUrl,
|
||||
});
|
||||
defaultDnsOverHttpsUrlRadio.value = defaultPrefUrl;
|
||||
let radioGroup = document.getElementById("DnsOverHttpsUrlRadioGroup");
|
||||
radioGroup.selectedIndex = Preferences.get("network.trr.uri").hasUserValue ? 1 : 0;
|
||||
this.updateDnsOverHttpsUI();
|
||||
},
|
||||
};
|
||||
|
|
|
@ -152,11 +152,20 @@
|
|||
data-l10n-id="connection-dns-over-https"
|
||||
preference="network.trr.mode"
|
||||
onsyncfrompreference="return gConnectionsDialog.readDnsOverHttpsMode();"
|
||||
onsynctopreference="return gConnectionsDialog.writeDnsOverHttpsMode()" />
|
||||
<hbox class="indent" flex="1" align="center">
|
||||
<label control="networkDnsOverHttpsUrl" data-l10n-id="connection-dns-over-https-url"
|
||||
data-l10n-attrs="tooltiptext"/>
|
||||
<textbox id="networkDnsOverHttpsUrl" flex="1" preference="network.trr.uri"
|
||||
onsynctopreference="return gConnectionsDialog.writeDnsOverHttpsUri()" />
|
||||
onsynctopreference="return gConnectionsDialog.writeDnsOverHttpsMode();"
|
||||
oncommand="gConnectionsDialog.updateDnsOverHttpsUI();"/>
|
||||
<vbox class="indent" flex="1">
|
||||
<radiogroup id="DnsOverHttpsUrlRadioGroup" orient="vertical">
|
||||
<radio id="defaultDnsOverHttpsUrlRadio"
|
||||
data-l10n-id="connection-dns-over-https-url-default"
|
||||
preference="network.trr.uri"
|
||||
oncommand="gConnectionsDialog.updateDnsOverHttpsUI();"/>
|
||||
<hbox>
|
||||
<radio id="customDnsOverHttpsUrlRadio"
|
||||
data-l10n-id="connection-dns-over-https-url-custom"
|
||||
oncommand="gConnectionsDialog.updateDnsOverHttpsUI();"/>
|
||||
<textbox id="customDnsOverHttpsInput" flex="1" preference="network.trr.custom_uri"/>
|
||||
</hbox>
|
||||
</radiogroup>
|
||||
</vbox>
|
||||
</dialog>
|
||||
|
|
|
@ -709,7 +709,8 @@
|
|||
connection-proxy-autologin.label,
|
||||
connection-proxy-socks-remote-dns.label,
|
||||
connection-dns-over-https,
|
||||
connection-dns-over-https-url
|
||||
connection-dns-over-https-url-custom,
|
||||
connection-dns-over-https-url-default,
|
||||
" />
|
||||
</hbox>
|
||||
</hbox>
|
||||
|
|
|
@ -3,17 +3,20 @@ ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|||
const SUBDIALOG_URL = "chrome://browser/content/preferences/connection.xul";
|
||||
const TRR_MODE_PREF = "network.trr.mode";
|
||||
const TRR_URI_PREF = "network.trr.uri";
|
||||
const TRR_CUSTOM_URI_PREF = "network.trr.custom_uri";
|
||||
|
||||
const modeCheckboxSelector = "#networkDnsOverHttps";
|
||||
const uriTextboxSelector = "#networkDnsOverHttpsUrl";
|
||||
const uriTextboxSelector = "#customDnsOverHttpsInput";
|
||||
const defaultPrefValues = Object.freeze({
|
||||
[TRR_MODE_PREF]: 0,
|
||||
[TRR_URI_PREF]: "https://mozilla.cloudflare-dns.com/dns-query",
|
||||
[TRR_CUSTOM_URI_PREF]: "",
|
||||
});
|
||||
|
||||
function resetPrefs() {
|
||||
Services.prefs.clearUserPref(TRR_MODE_PREF);
|
||||
Services.prefs.clearUserPref(TRR_URI_PREF);
|
||||
Services.prefs.clearUserPref(TRR_CUSTOM_URI_PREF);
|
||||
}
|
||||
|
||||
let preferencesOpen = new Promise(res => open_preferences(res));
|
||||
|
@ -53,6 +56,9 @@ async function testWithProperties(props, startTime) {
|
|||
if (props.hasOwnProperty(TRR_MODE_PREF)) {
|
||||
Services.prefs.setIntPref(TRR_MODE_PREF, props[TRR_MODE_PREF]);
|
||||
}
|
||||
if (props.hasOwnProperty(TRR_CUSTOM_URI_PREF)) {
|
||||
Services.prefs.setStringPref(TRR_CUSTOM_URI_PREF, props[TRR_CUSTOM_URI_PREF]);
|
||||
}
|
||||
if (props.hasOwnProperty(TRR_URI_PREF)) {
|
||||
Services.prefs.setStringPref(TRR_URI_PREF, props[TRR_URI_PREF]);
|
||||
}
|
||||
|
@ -84,7 +90,7 @@ async function testWithProperties(props, startTime) {
|
|||
}
|
||||
if (props.hasOwnProperty("inputUriKeys")) {
|
||||
info((Date.now() - startTime) + ": testWithProperties: inputUriKeys, waiting for the pref observer");
|
||||
uriPrefChangedPromise = waitForPrefObserver(TRR_URI_PREF);
|
||||
uriPrefChangedPromise = waitForPrefObserver(TRR_CUSTOM_URI_PREF);
|
||||
info((Date.now() - startTime) + ": testWithProperties: inputUriKeys, pref changed, now enter the new value");
|
||||
uriTextbox.focus();
|
||||
uriTextbox.value = props.inputUriKeys;
|
||||
|
@ -117,17 +123,20 @@ async function testWithProperties(props, startTime) {
|
|||
}
|
||||
|
||||
add_task(async function default_values() {
|
||||
let customUriPref = Services.prefs.getStringPref(TRR_CUSTOM_URI_PREF);
|
||||
let uriPref = Services.prefs.getStringPref(TRR_URI_PREF);
|
||||
let modePref = Services.prefs.getIntPref(TRR_MODE_PREF);
|
||||
is(modePref, defaultPrefValues[TRR_MODE_PREF],
|
||||
`Actual value of ${TRR_MODE_PREF} matches expected default value`);
|
||||
is(uriPref, defaultPrefValues[TRR_URI_PREF],
|
||||
`Actual value of ${TRR_MODE_PREF} matches expected default value`);
|
||||
`Actual value of ${TRR_URI_PREF} matches expected default value`);
|
||||
is(customUriPref, defaultPrefValues[TRR_CUSTOM_URI_PREF],
|
||||
`Actual value of ${TRR_CUSTOM_URI_PREF} matches expected default value`);
|
||||
});
|
||||
|
||||
let testVariations = [
|
||||
// verify state with defaults
|
||||
{ expectedModePref: 0, expectedUriValue: "https://mozilla.cloudflare-dns.com/dns-query" },
|
||||
{ expectedModePref: 0, expectedUriValue: "" },
|
||||
|
||||
// verify each of the modes maps to the correct checked state
|
||||
{ [TRR_MODE_PREF]: 0, expectedModeChecked: false },
|
||||
|
@ -140,31 +149,27 @@ let testVariations = [
|
|||
{ [TRR_MODE_PREF]: 77, expectedModeChecked: false },
|
||||
|
||||
// verify toggling the checkbox gives the right outcomes
|
||||
{ clickMode: true, expectedModeValue: 2, expectedUriValue: "https://mozilla.cloudflare-dns.com/dns-query" },
|
||||
{ clickMode: true, expectedModeValue: 2, expectedUriValue: "" },
|
||||
{
|
||||
[TRR_MODE_PREF]: 4,
|
||||
expectedModeChecked: true, clickMode: true, expectedModePref: 0,
|
||||
},
|
||||
// test that setting TRR_CUSTOM_URI_PREF subsequently changes TRR_URI_PREF
|
||||
{
|
||||
[TRR_MODE_PREF]: 2, [TRR_URI_PREF]: "https://example.com",
|
||||
[TRR_MODE_PREF]: 2, [TRR_CUSTOM_URI_PREF]: "https://example.com",
|
||||
expectedModeValue: true, expectedUriValue: "https://example.com",
|
||||
},
|
||||
{
|
||||
[TRR_URI_PREF]: "",
|
||||
clickMode: true, inputUriKeys: "https://example.com",
|
||||
expectedModePref: 2, expectedFinalUriPref: "https://example.com",
|
||||
},
|
||||
|
||||
// verify the uri can be cleared
|
||||
{
|
||||
[TRR_MODE_PREF]: 2, [TRR_URI_PREF]: "https://example.com",
|
||||
[TRR_MODE_PREF]: 2, [TRR_URI_PREF]: "https://example.com", [TRR_CUSTOM_URI_PREF]: "https://example.com",
|
||||
expectedUriValue: "https://example.com", inputUriKeys: "", expectedFinalUriPref: "",
|
||||
},
|
||||
|
||||
// verify uri gets sanitized
|
||||
{
|
||||
clickMode: true, inputUriKeys: " https://example.com ",
|
||||
expectedModePref: 2, expectedFinalUriPref: "https://example.com",
|
||||
},
|
||||
];
|
||||
|
||||
for (let props of testVariations) {
|
||||
|
|
|
@ -86,6 +86,14 @@ connection-dns-over-https =
|
|||
.label = Enable DNS over HTTPS
|
||||
.accesskey = b
|
||||
|
||||
connection-dns-over-https-url = URL
|
||||
# Variables:
|
||||
# $url (String) - URL for the DNS over HTTPS provider
|
||||
connection-dns-over-https-url-default =
|
||||
.label = Use default ({ $url })
|
||||
.accesskey = U
|
||||
.tooltiptext = URL for resolving DNS over HTTPS
|
||||
.tooltiptext = Use the default URL for resolving DNS over HTTPS
|
||||
|
||||
connection-dns-over-https-url-custom =
|
||||
.label = Custom
|
||||
.accesskey = C
|
||||
.tooltiptext = Enter your preferred URL for resolving DNS over HTTPS
|
||||
|
|
|
@ -632,12 +632,19 @@ notification[value="translation"] menulist > .menulist-dropmarker {
|
|||
}
|
||||
}
|
||||
|
||||
:root[tabsintitlebar] > #navigator-toolbox > #titlebar:not(:-moz-lwtheme) {
|
||||
:root[tabsintitlebar] > #navigator-toolbox > #titlebar {
|
||||
-moz-appearance: -moz-window-titlebar-maximized;
|
||||
}
|
||||
:root[tabsintitlebar][sizemode="normal"] > #navigator-toolbox > #titlebar:not(:-moz-lwtheme) {
|
||||
:root[tabsintitlebar][sizemode="normal"] > #navigator-toolbox > #titlebar {
|
||||
-moz-appearance: -moz-window-titlebar;
|
||||
}
|
||||
:root[tabsintitlebar] > #navigator-toolbox > #titlebar:-moz-lwtheme {
|
||||
visibility: hidden;
|
||||
}
|
||||
:root[tabsintitlebar] #toolbar-menubar:-moz-lwtheme,
|
||||
:root[tabsintitlebar] #TabsToolbar:-moz-lwtheme {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
/* Add extra space to titlebar for dragging */
|
||||
:root[sizemode="normal"][chromehidden~="menubar"] #TabsToolbar,
|
||||
|
|
|
@ -93,7 +93,6 @@ toolbar[brighttext] {
|
|||
|
||||
.findbar-button {
|
||||
-moz-appearance: none;
|
||||
padding: 0;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
|
@ -113,7 +112,7 @@ toolbar .toolbarbutton-1 > menupopup {
|
|||
margin-top: -3px;
|
||||
}
|
||||
|
||||
.findbar-button > .toolbarbutton-text,
|
||||
.findbar-button,
|
||||
toolbarbutton.bookmark-item:not(.subviewbutton),
|
||||
toolbar .toolbarbutton-1 > .toolbarbutton-icon,
|
||||
toolbar .toolbarbutton-1 > .toolbarbutton-text,
|
||||
|
@ -173,7 +172,7 @@ toolbar[brighttext] .toolbaritem-combined-buttons > separator {
|
|||
#PersonalToolbar .toolbarbutton-1:not([disabled=true]):not([checked]):not([open]):not(:active):hover,
|
||||
.tabbrowser-arrowscrollbox > .scrollbutton-up:not([disabled=true]):hover,
|
||||
.tabbrowser-arrowscrollbox > .scrollbutton-down:not([disabled=true]):hover,
|
||||
.findbar-button:not(:-moz-any([checked="true"],[disabled="true"])):hover > .toolbarbutton-text,
|
||||
.findbar-button:not(:-moz-any([checked="true"],[disabled="true"])):hover,
|
||||
toolbarbutton.bookmark-item:not(.subviewbutton):hover:not([disabled="true"]):not([open]),
|
||||
toolbar .toolbarbutton-1:not([disabled=true]):not([checked]):not([open]):not(:active):hover > .toolbarbutton-icon,
|
||||
toolbar .toolbarbutton-1:not([disabled=true]):not([checked]):not([open]):not(:active):hover > .toolbarbutton-text,
|
||||
|
@ -183,7 +182,7 @@ toolbar .toolbarbutton-1:not([disabled=true]):not([checked]):not([open]):not(:ac
|
|||
}
|
||||
|
||||
#PersonalToolbar .toolbarbutton-1:not([disabled=true]):-moz-any([open],[checked],:hover:active),
|
||||
.findbar-button:not([disabled=true]):-moz-any([checked="true"],:hover:active) > .toolbarbutton-text,
|
||||
.findbar-button:not([disabled=true]):-moz-any([checked="true"],:hover:active),
|
||||
toolbarbutton.bookmark-item:not(.subviewbutton):hover:active:not([disabled="true"]),
|
||||
toolbarbutton.bookmark-item[open="true"],
|
||||
toolbar .toolbarbutton-1:not([disabled=true]):-moz-any([open],[checked],:hover:active) > .toolbarbutton-icon,
|
||||
|
|
|
@ -72,7 +72,7 @@ def rust_compiler(rustc_info, cargo_info):
|
|||
You can install rust by running './mach bootstrap'
|
||||
or by directly running the installer from https://rustup.rs/
|
||||
'''))
|
||||
rustc_min_version = Version('1.29.0')
|
||||
rustc_min_version = Version('1.30.0')
|
||||
cargo_min_version = rustc_min_version
|
||||
|
||||
version = rustc_info.version
|
||||
|
|
|
@ -166,11 +166,7 @@ function requestWorkers() {
|
|||
const client = getCurrentClient(getState().runtimes);
|
||||
|
||||
try {
|
||||
const {
|
||||
other: otherWorkers,
|
||||
service: serviceWorkers,
|
||||
shared: sharedWorkers,
|
||||
} = await client.mainRoot.listAllWorkers();
|
||||
const { otherWorkers, serviceWorkers, sharedWorkers } = await client.listWorkers();
|
||||
|
||||
dispatch({
|
||||
type: REQUEST_WORKERS_SUCCESS,
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const { ADB } = require("devtools/shared/adb/adb");
|
||||
const { DebuggerClient } = require("devtools/shared/client/debugger-client");
|
||||
const { DebuggerServer } = require("devtools/server/main");
|
||||
|
||||
const Actions = require("./index");
|
||||
|
@ -16,6 +14,8 @@ const {
|
|||
} = require("../modules/runtimes-state-helper");
|
||||
const { isSupportedDebugTarget } = require("../modules/debug-target-support");
|
||||
|
||||
const { createClientForRuntime } = require("../modules/runtime-client-factory");
|
||||
|
||||
const {
|
||||
CONNECT_RUNTIME_FAILURE,
|
||||
CONNECT_RUNTIME_START,
|
||||
|
@ -38,47 +38,9 @@ const {
|
|||
WATCH_RUNTIME_SUCCESS,
|
||||
} = require("../constants");
|
||||
|
||||
async function createLocalClient() {
|
||||
DebuggerServer.init();
|
||||
DebuggerServer.registerAllActors();
|
||||
const client = new DebuggerClient(DebuggerServer.connectPipe());
|
||||
await client.connect();
|
||||
return { client };
|
||||
}
|
||||
|
||||
async function createNetworkClient(host, port) {
|
||||
const transportDetails = { host, port };
|
||||
const transport = await DebuggerClient.socketConnect(transportDetails);
|
||||
const client = new DebuggerClient(transport);
|
||||
await client.connect();
|
||||
return { client, transportDetails };
|
||||
}
|
||||
|
||||
async function createUSBClient(socketPath) {
|
||||
const port = await ADB.prepareTCPConnection(socketPath);
|
||||
return createNetworkClient("localhost", port);
|
||||
}
|
||||
|
||||
async function createClientForRuntime(runtime) {
|
||||
const { extra, type } = runtime;
|
||||
|
||||
if (type === RUNTIMES.THIS_FIREFOX) {
|
||||
return createLocalClient();
|
||||
} else if (type === RUNTIMES.NETWORK) {
|
||||
const { host, port } = extra.connectionParameters;
|
||||
return createNetworkClient(host, port);
|
||||
} else if (type === RUNTIMES.USB) {
|
||||
const { socketPath } = extra.connectionParameters;
|
||||
return createUSBClient(socketPath);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async function getRuntimeInfo(runtime, client) {
|
||||
const { extra, type } = runtime;
|
||||
const deviceFront = await client.mainRoot.getFront("device");
|
||||
const { brandName: name, channel, version } = await deviceFront.getDescription();
|
||||
const { name, channel, version } = await client.getDeviceDescription();
|
||||
const icon =
|
||||
(channel === "release" || channel === "beta" || channel === "aurora")
|
||||
? `chrome://devtools/skin/images/aboutdebugging-firefox-${ channel }.svg`
|
||||
|
@ -107,9 +69,9 @@ function connectRuntime(id) {
|
|||
const runtime = findRuntimeById(id, getState().runtimes);
|
||||
const { client, transportDetails } = await createClientForRuntime(runtime);
|
||||
const info = await getRuntimeInfo(runtime, client);
|
||||
const preferenceFront = await client.mainRoot.getFront("preference");
|
||||
const connectionPromptEnabled =
|
||||
await preferenceFront.getBoolPref(RUNTIME_PREFERENCE.CONNECTION_PROMPT);
|
||||
|
||||
const promptPrefName = RUNTIME_PREFERENCE.CONNECTION_PROMPT;
|
||||
const connectionPromptEnabled = await client.getPreference(promptPrefName);
|
||||
const runtimeDetails = { connectionPromptEnabled, client, info, transportDetails };
|
||||
|
||||
if (runtime.type === RUNTIMES.USB) {
|
||||
|
@ -168,12 +130,10 @@ function updateConnectionPromptSetting(connectionPromptEnabled) {
|
|||
try {
|
||||
const runtime = getCurrentRuntime(getState().runtimes);
|
||||
const client = runtime.runtimeDetails.client;
|
||||
const preferenceFront = await client.mainRoot.getFront("preference");
|
||||
await preferenceFront.setBoolPref(RUNTIME_PREFERENCE.CONNECTION_PROMPT,
|
||||
connectionPromptEnabled);
|
||||
const promptPrefName = RUNTIME_PREFERENCE.CONNECTION_PROMPT;
|
||||
await client.setPreference(promptPrefName, connectionPromptEnabled);
|
||||
// Re-get actual value from the runtime.
|
||||
connectionPromptEnabled =
|
||||
await preferenceFront.getBoolPref(RUNTIME_PREFERENCE.CONNECTION_PROMPT);
|
||||
connectionPromptEnabled = await client.getPreference(promptPrefName);
|
||||
|
||||
dispatch({ type: UPDATE_CONNECTION_PROMPT_SETTING_SUCCESS,
|
||||
runtime, connectionPromptEnabled });
|
||||
|
|
|
@ -40,7 +40,7 @@ class SidebarRuntimeItem extends PureComponent {
|
|||
},
|
||||
dom.button(
|
||||
{
|
||||
className: "default-button default-button--micro",
|
||||
className: "default-button default-button--micro js-connect-button",
|
||||
onClick: () => {
|
||||
const { dispatch, runtimeId } = this.props;
|
||||
dispatch(Actions.connectRuntime(runtimeId));
|
||||
|
|
|
@ -60,7 +60,7 @@ function debugTargetListenerMiddleware(store) {
|
|||
const { client } = runtime.runtimeDetails;
|
||||
|
||||
if (isSupportedDebugTarget(runtime.type, DEBUG_TARGETS.TAB)) {
|
||||
client.mainRoot.on("tabListChanged", onTabsUpdated);
|
||||
client.addListener("tabListChanged", onTabsUpdated);
|
||||
}
|
||||
|
||||
if (isSupportedDebugTarget(runtime.type, DEBUG_TARGETS.EXTENSION)) {
|
||||
|
@ -68,9 +68,9 @@ function debugTargetListenerMiddleware(store) {
|
|||
}
|
||||
|
||||
if (isSupportedDebugTarget(runtime.type, DEBUG_TARGETS.WORKER)) {
|
||||
client.mainRoot.on("workerListChanged", onWorkersUpdated);
|
||||
client.mainRoot.on("serviceWorkerRegistrationListChanged", onWorkersUpdated);
|
||||
client.mainRoot.on("processListChanged", onWorkersUpdated);
|
||||
client.addListener("workerListChanged", onWorkersUpdated);
|
||||
client.addListener("serviceWorkerRegistrationListChanged", onWorkersUpdated);
|
||||
client.addListener("processListChanged", onWorkersUpdated);
|
||||
client.addListener("registration-changed", onWorkersUpdated);
|
||||
client.addListener("push-subscription-modified", onWorkersUpdated);
|
||||
}
|
||||
|
@ -81,7 +81,7 @@ function debugTargetListenerMiddleware(store) {
|
|||
const { client } = runtime.runtimeDetails;
|
||||
|
||||
if (isSupportedDebugTarget(runtime.type, DEBUG_TARGETS.TAB)) {
|
||||
client.mainRoot.off("tabListChanged", onTabsUpdated);
|
||||
client.removeListener("tabListChanged", onTabsUpdated);
|
||||
}
|
||||
|
||||
if (isSupportedDebugTarget(runtime.type, DEBUG_TARGETS.EXTENSION)) {
|
||||
|
@ -89,9 +89,9 @@ function debugTargetListenerMiddleware(store) {
|
|||
}
|
||||
|
||||
if (isSupportedDebugTarget(runtime.type, DEBUG_TARGETS.WORKER)) {
|
||||
client.mainRoot.off("workerListChanged", onWorkersUpdated);
|
||||
client.mainRoot.off("serviceWorkerRegistrationListChanged", onWorkersUpdated);
|
||||
client.mainRoot.off("processListChanged", onWorkersUpdated);
|
||||
client.removeListener("workerListChanged", onWorkersUpdated);
|
||||
client.removeListener("serviceWorkerRegistrationListChanged", onWorkersUpdated);
|
||||
client.removeListener("processListChanged", onWorkersUpdated);
|
||||
client.removeListener("registration-changed", onWorkersUpdated);
|
||||
client.removeListener("push-subscription-modified", onWorkersUpdated);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { RUNTIME_PREFERENCE } = require("../constants");
|
||||
|
||||
const PREF_TYPES = {
|
||||
BOOL: "BOOL",
|
||||
};
|
||||
|
||||
// Map of preference to preference type.
|
||||
const PREF_TO_TYPE = {
|
||||
[RUNTIME_PREFERENCE.CONNECTION_PROMPT]: PREF_TYPES.BOOL,
|
||||
};
|
||||
|
||||
// Some events are fired by mainRoot rather than client.
|
||||
const MAIN_ROOT_EVENTS = [
|
||||
"processListChanged",
|
||||
"serviceWorkerRegistrationListChanged",
|
||||
"tabListChanged",
|
||||
"workerListChanged",
|
||||
];
|
||||
|
||||
/**
|
||||
* The ClientWrapper class is used to isolate aboutdebugging from the DevTools client API
|
||||
* The modules of about:debugging should never call DevTools client APIs directly.
|
||||
*/
|
||||
class ClientWrapper {
|
||||
constructor(client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
addOneTimeListener(evt, listener) {
|
||||
if (MAIN_ROOT_EVENTS.includes(evt)) {
|
||||
this.client.mainRoot.once(evt, listener);
|
||||
} else {
|
||||
this.client.addOneTimeListener(evt, listener);
|
||||
}
|
||||
}
|
||||
|
||||
addListener(evt, listener) {
|
||||
if (MAIN_ROOT_EVENTS.includes(evt)) {
|
||||
this.client.mainRoot.on(evt, listener);
|
||||
} else {
|
||||
this.client.addListener(evt, listener);
|
||||
}
|
||||
}
|
||||
|
||||
removeListener(evt, listener) {
|
||||
if (MAIN_ROOT_EVENTS.includes(evt)) {
|
||||
this.client.mainRoot.off(evt, listener);
|
||||
} else {
|
||||
this.client.removeListener(evt, listener);
|
||||
}
|
||||
}
|
||||
|
||||
async getDeviceDescription() {
|
||||
const deviceFront = await this.client.mainRoot.getFront("device");
|
||||
const { brandName, channel, version } = await deviceFront.getDescription();
|
||||
return { name: brandName, channel, version };
|
||||
}
|
||||
|
||||
async setPreference(prefName, value) {
|
||||
const prefType = PREF_TO_TYPE[prefName];
|
||||
const preferenceFront = await this.client.mainRoot.getFront("preference");
|
||||
switch (prefType) {
|
||||
case PREF_TYPES.BOOL:
|
||||
return preferenceFront.setBoolPref(prefName, value);
|
||||
default:
|
||||
throw new Error("Unsupported preference" + prefName);
|
||||
}
|
||||
}
|
||||
|
||||
async getPreference(prefName) {
|
||||
const prefType = PREF_TO_TYPE[prefName];
|
||||
const preferenceFront = await this.client.mainRoot.getFront("preference");
|
||||
switch (prefType) {
|
||||
case PREF_TYPES.BOOL:
|
||||
return preferenceFront.getBoolPref(prefName);
|
||||
default:
|
||||
throw new Error("Unsupported preference:" + prefName);
|
||||
}
|
||||
}
|
||||
|
||||
async listTabs(options) {
|
||||
return this.client.listTabs(options);
|
||||
}
|
||||
|
||||
async listAddons() {
|
||||
return this.client.listAddons();
|
||||
}
|
||||
|
||||
async listWorkers() {
|
||||
const { other, service, shared } = await this.client.mainRoot.listAllWorkers();
|
||||
|
||||
return {
|
||||
otherWorkers: other,
|
||||
serviceWorkers: service,
|
||||
sharedWorkers: shared,
|
||||
};
|
||||
}
|
||||
|
||||
async request(options) {
|
||||
return this.client.request(options);
|
||||
}
|
||||
|
||||
async close() {
|
||||
return this.client.close();
|
||||
}
|
||||
}
|
||||
|
||||
exports.ClientWrapper = ClientWrapper;
|
|
@ -3,11 +3,14 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DevToolsModules(
|
||||
'client-wrapper.js',
|
||||
'debug-target-collapsibilities.js',
|
||||
'debug-target-support.js',
|
||||
'extensions-helper.js',
|
||||
'l10n.js',
|
||||
'network-locations.js',
|
||||
'runtime-client-factory.js',
|
||||
'runtimes-state-helper.js',
|
||||
'test-helper.js',
|
||||
'usb-runtimes.js',
|
||||
)
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { ADB } = require("devtools/shared/adb/adb");
|
||||
const { DebuggerClient } = require("devtools/shared/client/debugger-client");
|
||||
const { DebuggerServer } = require("devtools/server/main");
|
||||
const { ClientWrapper } = require("./client-wrapper");
|
||||
|
||||
const { RUNTIMES } = require("../constants");
|
||||
|
||||
async function createLocalClient() {
|
||||
DebuggerServer.init();
|
||||
DebuggerServer.registerAllActors();
|
||||
const client = new DebuggerClient(DebuggerServer.connectPipe());
|
||||
await client.connect();
|
||||
return { client: new ClientWrapper(client) };
|
||||
}
|
||||
|
||||
async function createNetworkClient(host, port) {
|
||||
const transportDetails = { host, port };
|
||||
const transport = await DebuggerClient.socketConnect(transportDetails);
|
||||
const client = new DebuggerClient(transport);
|
||||
await client.connect();
|
||||
return { client: new ClientWrapper(client), transportDetails };
|
||||
}
|
||||
|
||||
async function createUSBClient(socketPath) {
|
||||
const port = await ADB.prepareTCPConnection(socketPath);
|
||||
return createNetworkClient("localhost", port);
|
||||
}
|
||||
|
||||
async function createClientForRuntime(runtime) {
|
||||
const { extra, type } = runtime;
|
||||
|
||||
if (type === RUNTIMES.THIS_FIREFOX) {
|
||||
return createLocalClient();
|
||||
} else if (type === RUNTIMES.NETWORK) {
|
||||
const { host, port } = extra.connectionParameters;
|
||||
return createNetworkClient(host, port);
|
||||
} else if (type === RUNTIMES.USB) {
|
||||
const { socketPath } = extra.connectionParameters;
|
||||
return createUSBClient(socketPath);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
exports.createClientForRuntime = createClientForRuntime;
|
||||
|
||||
require("./test-helper").enableMocks(module, "modules/runtime-client-factory");
|
|
@ -0,0 +1,84 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const flags = require("devtools/shared/flags");
|
||||
|
||||
/**
|
||||
* The methods from this module are going to be called from both aboutdebugging
|
||||
* BrowserLoader modules (enableMocks) and from tests (setMockedModules,
|
||||
* removeMockedModule). We use the main devtools loader to communicate between the
|
||||
* different sandboxes involved.
|
||||
*/
|
||||
const { Cu } = require("chrome");
|
||||
const { loader } = Cu.import("resource://devtools/shared/Loader.jsm", {});
|
||||
|
||||
const ROOT_PATH = "resource://devtools/client/aboutdebugging-new/src/";
|
||||
|
||||
/**
|
||||
* Allows to load a mock instead of an about:debugging module.
|
||||
* To use it, the module to mock needs to call
|
||||
* require("devtools/client/aboutdebugging-new/src/modules/test-helper").
|
||||
* enableMocks(module, "path/to/module.js");
|
||||
*
|
||||
* From the tests, the devtools loader should be provided with the mocked module using
|
||||
* setMockedModule().
|
||||
*
|
||||
* At the end of the test, the mocks should be discarded with removeMockedModule().
|
||||
*
|
||||
* @param {Object} moduleToMock
|
||||
* The `module` global from the module we want to mock, in order to override its
|
||||
* `module.exports`.
|
||||
* @param {String} modulePath
|
||||
* Path to the module, relative to `devtools/client/aboutdebugging-new/src/`
|
||||
*/
|
||||
function enableMocks(moduleToMock, modulePath) {
|
||||
if (flags.testing) {
|
||||
const path = ROOT_PATH + modulePath + ".js";
|
||||
if (loader.mocks && loader.mocks[path]) {
|
||||
moduleToMock.exports = loader.mocks[path];
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.enableMocks = enableMocks;
|
||||
|
||||
/**
|
||||
* Assign a mock object to the provided module path, relative to
|
||||
* `devtools/client/aboutdebugging-new/src/`.
|
||||
*/
|
||||
function setMockedModule(mock, modulePath) {
|
||||
loader.mocks = loader.mocks || {};
|
||||
const mockedModule = {};
|
||||
Object.keys(mock).forEach(key => {
|
||||
if (typeof mock[key] === "function") {
|
||||
// All the mock methods are wrapped again here so that if the test replaces the
|
||||
// methods dynamically and call sites used
|
||||
// `const { someMethod } = require("./runtime-client-factory")`, someMethod will
|
||||
// still internally call the current method defined on the mock, and not the one
|
||||
// that was defined at the moment of the import.
|
||||
mockedModule[key] = function() {
|
||||
return mock[key].apply(mock, arguments);
|
||||
};
|
||||
} else {
|
||||
mockedModule[key] = mock[key];
|
||||
}
|
||||
});
|
||||
|
||||
const path = ROOT_PATH + modulePath + ".js";
|
||||
loader.mocks[path] = mockedModule;
|
||||
}
|
||||
exports.setMockedModule = setMockedModule;
|
||||
|
||||
/**
|
||||
* Remove any mock object defined for the provided module path, relative to
|
||||
* `devtools/client/aboutdebugging-new/src/`.
|
||||
*/
|
||||
function removeMockedModule(modulePath) {
|
||||
const path = ROOT_PATH + modulePath + ".js";
|
||||
if (loader.mocks && loader.mocks[path]) {
|
||||
delete loader.mocks[path];
|
||||
}
|
||||
}
|
||||
exports.removeMockedModule = removeMockedModule;
|
|
@ -42,3 +42,5 @@ function refreshUSBRuntimes() {
|
|||
return adbScanner.scan();
|
||||
}
|
||||
exports.refreshUSBRuntimes = refreshUSBRuntimes;
|
||||
|
||||
require("./test-helper").enableMocks(module, "modules/usb-runtimes");
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
"use strict";
|
||||
|
||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
const { ClientWrapper } = require("./modules/client-wrapper");
|
||||
|
||||
const runtimeInfo = {
|
||||
// device name which is running the runtime,
|
||||
|
@ -30,8 +31,8 @@ const runtimeTransportDetails = {
|
|||
};
|
||||
|
||||
const runtimeDetails = {
|
||||
// debugger client instance
|
||||
client: PropTypes.object.isRequired,
|
||||
// ClientWrapper built using a DebuggerClient for the runtime
|
||||
client: PropTypes.instanceOf(ClientWrapper).isRequired,
|
||||
|
||||
// reflect devtools.debugger.prompt-connection preference of this runtime
|
||||
connectionPromptEnabled: PropTypes.bool.isRequired,
|
||||
|
|
|
@ -5,6 +5,7 @@ support-files =
|
|||
debug-target-pane_collapsibilities_head.js
|
||||
head-addons-script.js
|
||||
head.js
|
||||
mocks/*
|
||||
resources/test-adb-extension/*
|
||||
resources/test-temporary-extension/*
|
||||
!/devtools/client/shared/test/shared-head.js
|
||||
|
@ -20,6 +21,8 @@ skip-if = (os == 'linux' && bits == 32) # ADB start() fails on linux 32, see Bug
|
|||
[browser_aboutdebugging_debug-target-pane_empty.js]
|
||||
[browser_aboutdebugging_navigate.js]
|
||||
[browser_aboutdebugging_sidebar_network_runtimes.js]
|
||||
[browser_aboutdebugging_sidebar_usb_runtime.js]
|
||||
[browser_aboutdebugging_sidebar_usb_runtime_connect.js]
|
||||
[browser_aboutdebugging_sidebar_usb_status.js]
|
||||
skip-if = (os == 'linux' && bits == 32) # ADB start() fails on linux 32, see Bug 1499638
|
||||
[browser_aboutdebugging_thisfirefox.js]
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/* import-globals-from mocks/head-usb-runtimes-mock.js */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Load USB Runtimes mock module
|
||||
Services.scriptloader.loadSubScript(
|
||||
CHROME_URL_ROOT + "mocks/head-usb-runtimes-mock.js", this);
|
||||
|
||||
// Test that USB runtimes appear and disappear from the sidebar.
|
||||
add_task(async function() {
|
||||
const usbRuntimesMock = createUsbRuntimesMock();
|
||||
const observerMock = addObserverMock(usbRuntimesMock);
|
||||
enableUsbRuntimesMock(usbRuntimesMock);
|
||||
|
||||
// Disable our mock when the test ends.
|
||||
registerCleanupFunction(() => {
|
||||
disableUsbRuntimesMock();
|
||||
});
|
||||
|
||||
const { document, tab } = await openAboutDebugging();
|
||||
|
||||
usbRuntimesMock.getUSBRuntimes = function() {
|
||||
return [{
|
||||
id: "test_device_id",
|
||||
_socketPath: "test/path",
|
||||
deviceName: "test device name",
|
||||
shortName: "testshort",
|
||||
}];
|
||||
};
|
||||
observerMock.emit("runtime-list-updated");
|
||||
|
||||
info("Wait until the USB sidebar item appears");
|
||||
await waitUntil(() => findSidebarItemByText("test device name", document));
|
||||
const usbRuntimeSidebarItem = findSidebarItemByText("test device name", document);
|
||||
ok(usbRuntimeSidebarItem.textContent.includes("testshort"),
|
||||
"The short name of the usb runtime is visible");
|
||||
|
||||
usbRuntimesMock.getUSBRuntimes = function() {
|
||||
return [];
|
||||
};
|
||||
observerMock.emit("runtime-list-updated");
|
||||
|
||||
info("Wait until the USB sidebar item disappears");
|
||||
await waitUntil(() => !findSidebarItemByText("test device name", document));
|
||||
|
||||
await removeTab(tab);
|
||||
});
|
|
@ -0,0 +1,88 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const MOCKS_ROOT = CHROME_URL_ROOT + "mocks/";
|
||||
|
||||
/* import-globals-from mocks/head-client-wrapper-mock.js */
|
||||
Services.scriptloader.loadSubScript(MOCKS_ROOT + "head-client-wrapper-mock.js", this);
|
||||
/* 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 mocks/head-usb-runtimes-mock.js */
|
||||
Services.scriptloader.loadSubScript(MOCKS_ROOT + "head-usb-runtimes-mock.js", this);
|
||||
|
||||
const RUNTIME_ID = "test-runtime-id";
|
||||
|
||||
const { RUNTIMES } = require("devtools/client/aboutdebugging-new/src/constants");
|
||||
|
||||
// Test that USB runtimes appear and disappear from the sidebar.
|
||||
add_task(async function() {
|
||||
const usbRuntimesMock = createUsbRuntimesMock();
|
||||
const observerMock = addObserverMock(usbRuntimesMock);
|
||||
enableUsbRuntimesMock(usbRuntimesMock);
|
||||
|
||||
// Create a basic mocked ClientWrapper that only defines a custom description.
|
||||
const mockUsbClient = createClientMock();
|
||||
mockUsbClient.getDeviceDescription = () => {
|
||||
return {
|
||||
name: "TestBrand",
|
||||
channel: "release",
|
||||
version: "1.0",
|
||||
};
|
||||
};
|
||||
|
||||
// Mock the runtime-helper module to return mocked ClientWrappers for our test runtime
|
||||
// ids.
|
||||
const RuntimeClientFactoryMock = createRuntimeClientFactoryMock();
|
||||
enableRuntimeClientFactoryMock(RuntimeClientFactoryMock);
|
||||
RuntimeClientFactoryMock.createClientForRuntime = runtime => {
|
||||
let client = null;
|
||||
if (runtime.id === RUNTIMES.THIS_FIREFOX) {
|
||||
client = createThisFirefoxClientMock();
|
||||
} else if (runtime.id === RUNTIME_ID) {
|
||||
client = mockUsbClient;
|
||||
}
|
||||
return { client };
|
||||
};
|
||||
|
||||
// Disable mocks when the test ends.
|
||||
registerCleanupFunction(() => {
|
||||
disableRuntimeClientFactoryMock();
|
||||
disableUsbRuntimesMock();
|
||||
});
|
||||
|
||||
const { document, tab } = await openAboutDebugging();
|
||||
|
||||
usbRuntimesMock.getUSBRuntimes = function() {
|
||||
return [{
|
||||
id: RUNTIME_ID,
|
||||
_socketPath: "test/path",
|
||||
deviceName: "test device name",
|
||||
shortName: "testshort",
|
||||
}];
|
||||
};
|
||||
observerMock.emit("runtime-list-updated");
|
||||
|
||||
info("Wait until the USB sidebar item appears");
|
||||
await waitUntil(() => findSidebarItemByText("test device name", document));
|
||||
const usbRuntimeSidebarItem = findSidebarItemByText("test device name", document);
|
||||
const connectButton = usbRuntimeSidebarItem.querySelector(".js-connect-button");
|
||||
ok(connectButton, "Connect button is displayed for the USB runtime");
|
||||
|
||||
info("Click on the connect button and wait until it disappears");
|
||||
connectButton.click();
|
||||
await waitUntil(() => usbRuntimeSidebarItem.querySelector(".js-connect-button"));
|
||||
|
||||
info("Remove all USB runtimes");
|
||||
usbRuntimesMock.getUSBRuntimes = function() {
|
||||
return [];
|
||||
};
|
||||
observerMock.emit("runtime-list-updated");
|
||||
|
||||
info("Wait until the USB sidebar item disappears");
|
||||
await waitUntil(() => !findSidebarItemByText("test device name", document));
|
||||
|
||||
await removeTab(tab);
|
||||
});
|
|
@ -0,0 +1,74 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
/* import-globals-from ../../../../shared/test/shared-head.js */
|
||||
|
||||
"use strict";
|
||||
|
||||
// This head file contains helpers to create mock versions of the ClientWrapper class
|
||||
// defined at devtools/client/aboutdebugging-new/src/modules/client-wrapper.js .
|
||||
|
||||
const { RUNTIME_PREFERENCE } =
|
||||
require("devtools/client/aboutdebugging-new/src/constants");
|
||||
|
||||
// Sensible default values for runtime preferences that should be usable in most
|
||||
// situations
|
||||
const DEFAULT_PREFERENCES = {
|
||||
[RUNTIME_PREFERENCE.CONNECTION_PROMPT]: true,
|
||||
};
|
||||
|
||||
// Creates a simple mock ClientWrapper.
|
||||
function createClientMock() {
|
||||
return {
|
||||
// no-op
|
||||
addListener: () => {},
|
||||
// no-op
|
||||
close: () => {},
|
||||
// no-op
|
||||
connect: () => {},
|
||||
// no-op
|
||||
getDeviceDescription: () => {},
|
||||
// Return default preference value or null if no match.
|
||||
getPreference: (prefName) => {
|
||||
if (prefName in DEFAULT_PREFERENCES) {
|
||||
return DEFAULT_PREFERENCES[prefName];
|
||||
}
|
||||
return null;
|
||||
},
|
||||
// Empty array of addons
|
||||
listAddons: () => ({ addons: [] }),
|
||||
// Empty array of tabs
|
||||
listTabs: () => ({ tabs: []}),
|
||||
// Empty arrays of workers
|
||||
listWorkers: () => ({
|
||||
otherWorkers: [],
|
||||
serviceWorkers: [],
|
||||
sharedWorkers: [],
|
||||
}),
|
||||
// no-op
|
||||
removeListener: () => {},
|
||||
// no-op
|
||||
setPreference: () => {},
|
||||
};
|
||||
}
|
||||
|
||||
// Create a ClientWrapper mock that can be used to replace the this-firefox runtime.
|
||||
function createThisFirefoxClientMock() {
|
||||
const mockThisFirefoxDescription = {
|
||||
name: "Firefox",
|
||||
channel: "nightly",
|
||||
version: "63.0",
|
||||
};
|
||||
|
||||
// Create a fake about:debugging tab because our test helper openAboutDebugging
|
||||
// waits until about:debugging is displayed in the list of tabs.
|
||||
const mockAboutDebuggingTab = {
|
||||
outerWindowID: 0,
|
||||
url: "about:debugging",
|
||||
};
|
||||
|
||||
const mockThisFirefoxClient = createClientMock();
|
||||
mockThisFirefoxClient.listTabs = () => ({ tabs: [mockAboutDebuggingTab] });
|
||||
mockThisFirefoxClient.getDeviceDescription = () => mockThisFirefoxDescription;
|
||||
|
||||
return mockThisFirefoxClient;
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
/* import-globals-from ../../../../shared/test/shared-head.js */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Setup the loader to return the provided mock object instead of the regular
|
||||
* runtime-client-factory module.
|
||||
*
|
||||
* @param {Object}
|
||||
* mock should implement the following methods:
|
||||
* - createClientForRuntime(runtime)
|
||||
*/
|
||||
function enableRuntimeClientFactoryMock(mock) {
|
||||
const { setMockedModule } = require("devtools/client/aboutdebugging-new/src/modules/test-helper");
|
||||
setMockedModule(mock, "modules/runtime-client-factory");
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the loader to clear the mock entry for the runtime-client-factory module.
|
||||
*/
|
||||
function disableRuntimeClientFactoryMock() {
|
||||
const { removeMockedModule } = require("devtools/client/aboutdebugging-new/src/modules/test-helper");
|
||||
removeMockedModule("modules/runtime-client-factory");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a simple mock version for runtime-client-factory, implementing all the expected
|
||||
* methods with empty placeholders.
|
||||
*/
|
||||
function createRuntimeClientFactoryMock() {
|
||||
const RuntimeClientFactoryMock = {};
|
||||
RuntimeClientFactoryMock.createClientForRuntime = function(runtime) {
|
||||
console.log("MOCKED METHOD createClientForRuntime");
|
||||
};
|
||||
|
||||
return RuntimeClientFactoryMock;
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
/* import-globals-from ../../../../shared/test/shared-head.js */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Setup the loader to return the provided mock object instead of the regular
|
||||
* usb-runtimes module.
|
||||
*
|
||||
* @param {Object}
|
||||
* mock should implement the following methods:
|
||||
* - addUSBRuntimesObserver(listener)
|
||||
* - disableUSBRuntimes()
|
||||
* - enableUSBRuntimes()
|
||||
* - getUSBRuntimes()
|
||||
* - removeUSBRuntimesObserver(listener)
|
||||
*/
|
||||
function enableUsbRuntimesMock(mock) {
|
||||
const { setMockedModule } = require("devtools/client/aboutdebugging-new/src/modules/test-helper");
|
||||
setMockedModule(mock, "modules/usb-runtimes");
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the loader to clear the mock entry for the usb-runtimes module.
|
||||
*/
|
||||
function disableUsbRuntimesMock() {
|
||||
const { removeMockedModule } = require("devtools/client/aboutdebugging-new/src/modules/test-helper");
|
||||
removeMockedModule("modules/usb-runtimes");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a simple mock version for usb-runtimes, implementing all the expected methods
|
||||
* with empty placeholders.
|
||||
*/
|
||||
function createUsbRuntimesMock() {
|
||||
const usbRuntimesMock = {};
|
||||
usbRuntimesMock.addUSBRuntimesObserver = function(listener) {
|
||||
console.log("MOCKED METHOD addUSBRuntimesObserver");
|
||||
};
|
||||
|
||||
usbRuntimesMock.disableUSBRuntimes = function() {
|
||||
console.log("MOCKED METHOD disableUSBRuntimes");
|
||||
};
|
||||
|
||||
usbRuntimesMock.enableUSBRuntimes = function() {
|
||||
console.log("MOCKED METHOD enableUSBRuntimes");
|
||||
};
|
||||
|
||||
usbRuntimesMock.getUSBRuntimes = function() {
|
||||
console.log("MOCKED METHOD getUSBRuntimes");
|
||||
};
|
||||
|
||||
usbRuntimesMock.removeUSBRuntimesObserver = function(listener) {
|
||||
console.log("MOCKED METHOD removeUSBRuntimesObserver");
|
||||
};
|
||||
|
||||
return usbRuntimesMock;
|
||||
}
|
||||
|
||||
/**
|
||||
* The usb-runtimes module allows to observer runtime updates. To simulate this behaviour
|
||||
* the easiest is to use an EventEmitter-decorated object that can accept listeners and
|
||||
* can emit events from the test.
|
||||
*
|
||||
* This method will update the addUSBRuntimesObserver method of the provided
|
||||
* usbRuntimesMock in order to add listeners to a mockObserver, and returns said observer
|
||||
* so that the test can emit "runtime-list-updated" when needed.
|
||||
*/
|
||||
function addObserverMock(usbRuntimesMock) {
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
|
||||
const observerMock = {};
|
||||
EventEmitter.decorate(observerMock);
|
||||
usbRuntimesMock.addUSBRuntimesObserver = function(listener) {
|
||||
console.log("MOCKED METHOD addUSBRuntimesObserver with mock scanner");
|
||||
observerMock.on("runtime-list-updated", listener);
|
||||
};
|
||||
|
||||
// NOTE FOR REVIEW: Instead of emitting "runtime-list-updated" events in the test,
|
||||
// this mock could have a emitObservedEvent method, that would just emit the correct
|
||||
// event. This way if the event name changes, everything remains contained in this
|
||||
// method.
|
||||
|
||||
return observerMock;
|
||||
}
|
|
@ -388,6 +388,16 @@ Target.prototype = {
|
|||
return this._inspector;
|
||||
},
|
||||
|
||||
// Run callback on every front of this type that currently exists, and on every
|
||||
// instantiation of front type in the future.
|
||||
onFront(typeName, callback) {
|
||||
const front = this.fronts.get(typeName);
|
||||
if (front) {
|
||||
return callback(front);
|
||||
}
|
||||
return this.on(typeName, callback);
|
||||
},
|
||||
|
||||
// Get a Front for a target-scoped actor.
|
||||
// i.e. an actor served by RootActor.listTabs or RootActorActor.getTab requests
|
||||
getFront(typeName) {
|
||||
|
@ -397,6 +407,7 @@ Target.prototype = {
|
|||
return front;
|
||||
}
|
||||
front = getFront(this.client, typeName, this.form);
|
||||
this.emit(typeName, front);
|
||||
this.fronts.set(typeName, front);
|
||||
return front;
|
||||
},
|
||||
|
|
|
@ -77,6 +77,7 @@ skip-if = os == 'win' || debug # Bug 1282269, 1448084
|
|||
[browser_source_map-late-script.js]
|
||||
[browser_target_from_url.js]
|
||||
[browser_target_events.js]
|
||||
[browser_target_listeners.js]
|
||||
[browser_target_remote.js]
|
||||
[browser_target_support.js]
|
||||
[browser_toolbox_custom_host.js]
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
add_task(async function() {
|
||||
gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
|
||||
await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
|
||||
|
||||
const target = await TargetFactory.forTab(gBrowser.selectedTab);
|
||||
await target.attach();
|
||||
|
||||
info("Test applying onFront to a front that will be created");
|
||||
const promise = new Promise(resolve => {
|
||||
target.onFront("accessibility", resolve);
|
||||
});
|
||||
const getFrontFront = target.getFront("accessibility");
|
||||
const onFrontFront = await promise;
|
||||
is(getFrontFront, onFrontFront, "got the front instantiated in the future and it's the same");
|
||||
|
||||
info("Test applying onFront to an existing front");
|
||||
await new Promise(resolve => {
|
||||
target.onFront("accessibility", front => {
|
||||
is(front, getFrontFront, "got the already instantiated front and it's the same");
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -284,6 +284,25 @@ BrowsingContext::GetChildren(nsTArray<RefPtr<BrowsingContext>>& aChildren)
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
BrowsingContext::SetOpener(BrowsingContext* aOpener)
|
||||
{
|
||||
if (mOpener == aOpener) {
|
||||
return;
|
||||
}
|
||||
|
||||
mOpener = aOpener;
|
||||
|
||||
if (!XRE_IsContentProcess()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto cc = ContentChild::GetSingleton();
|
||||
MOZ_DIAGNOSTIC_ASSERT(cc);
|
||||
cc->SendSetOpenerBrowsingContext(BrowsingContextId(Id()),
|
||||
BrowsingContextId(aOpener ? aOpener->Id() : 0));
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
BrowsingContext::GetRootBrowsingContexts(nsTArray<RefPtr<BrowsingContext>>& aBrowsingContexts)
|
||||
{
|
||||
|
|
|
@ -111,6 +111,13 @@ public:
|
|||
|
||||
void GetChildren(nsTArray<RefPtr<BrowsingContext>>& aChildren);
|
||||
|
||||
BrowsingContext* GetOpener()
|
||||
{
|
||||
return mOpener;
|
||||
}
|
||||
|
||||
void SetOpener(BrowsingContext* aOpener);
|
||||
|
||||
static void GetRootBrowsingContexts(
|
||||
nsTArray<RefPtr<BrowsingContext>>& aBrowsingContexts);
|
||||
|
||||
|
@ -141,6 +148,7 @@ private:
|
|||
|
||||
WeakPtr<BrowsingContext> mParent;
|
||||
Children mChildren;
|
||||
WeakPtr<BrowsingContext> mOpener;
|
||||
nsCOMPtr<nsIDocShell> mDocShell;
|
||||
nsString mName;
|
||||
};
|
||||
|
|
|
@ -12,16 +12,14 @@
|
|||
// which is the URL displayed in the location bar, i.e.
|
||||
// the URI that the user attempted to load.
|
||||
|
||||
function getErrorCode()
|
||||
{
|
||||
function getErrorCode() {
|
||||
var url = document.documentURI;
|
||||
var error = url.search(/e\=/);
|
||||
var duffUrl = url.search(/\&u\=/);
|
||||
return decodeURIComponent(url.slice(error + 2, duffUrl));
|
||||
}
|
||||
|
||||
function getCSSClass()
|
||||
{
|
||||
function getCSSClass() {
|
||||
var url = document.documentURI;
|
||||
var matches = url.match(/s\=([^&]+)\&/);
|
||||
// s is optional, if no match just return nothing
|
||||
|
@ -32,8 +30,7 @@
|
|||
return decodeURIComponent(matches[1]);
|
||||
}
|
||||
|
||||
function getDescription()
|
||||
{
|
||||
function getDescription() {
|
||||
var url = document.documentURI;
|
||||
var desc = url.search(/d\=/);
|
||||
|
||||
|
@ -45,8 +42,7 @@
|
|||
return decodeURIComponent(url.slice(desc + 2));
|
||||
}
|
||||
|
||||
function retryThis(buttonEl)
|
||||
{
|
||||
function retryThis(buttonEl) {
|
||||
// Note: The application may wish to handle switching off "offline mode"
|
||||
// before this event handler runs, but using a capturing event handler.
|
||||
|
||||
|
@ -63,23 +59,20 @@
|
|||
buttonEl.disabled = true;
|
||||
}
|
||||
|
||||
function initPage()
|
||||
{
|
||||
function initPage() {
|
||||
var err = getErrorCode();
|
||||
|
||||
// if it's an unknown error or there's no title or description
|
||||
// defined, get the generic message
|
||||
var errTitle = document.getElementById("et_" + err);
|
||||
var errDesc = document.getElementById("ed_" + err);
|
||||
if (!errTitle || !errDesc)
|
||||
{
|
||||
if (!errTitle || !errDesc) {
|
||||
errTitle = document.getElementById("et_generic");
|
||||
errDesc = document.getElementById("ed_generic");
|
||||
}
|
||||
|
||||
var title = document.getElementById("errorTitleText");
|
||||
if (title)
|
||||
{
|
||||
if (title) {
|
||||
title.parentNode.replaceChild(errTitle, title);
|
||||
// change id to the replaced child's id so styling works
|
||||
errTitle.id = "errorTitleText";
|
||||
|
@ -90,8 +83,7 @@
|
|||
sd.textContent = getDescription();
|
||||
|
||||
var ld = document.getElementById("errorLongDesc");
|
||||
if (ld)
|
||||
{
|
||||
if (ld) {
|
||||
ld.parentNode.replaceChild(errDesc, ld);
|
||||
// change id to the replaced child's id so styling works
|
||||
errDesc.id = "errorLongDesc";
|
||||
|
@ -139,8 +131,7 @@
|
|||
document.getElementById("errorTryAgain").style.display = "none";
|
||||
document.getElementById("errorPageContainer").setAttribute("class", "certerror");
|
||||
addDomainErrorLink();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// Remove the override block for non-certificate errors. CSS-hiding
|
||||
// isn't good enough here, because of bug 39098
|
||||
var secOverride = document.getElementById("securityOverrideDiv");
|
||||
|
@ -165,8 +156,8 @@
|
|||
|
||||
function showSecuritySection() {
|
||||
// Swap link out, content in
|
||||
document.getElementById('securityOverrideContent').style.display = '';
|
||||
document.getElementById('securityOverrideLink').style.display = 'none';
|
||||
document.getElementById("securityOverrideContent").style.display = "";
|
||||
document.getElementById("securityOverrideLink").style.display = "none";
|
||||
}
|
||||
|
||||
/* In the case of SSL error pages about domain mismatch, see if
|
||||
|
@ -207,7 +198,7 @@
|
|||
sd.appendChild(document.createTextNode(desc.slice(desc.indexOf("</a>") + "</a>".length)));
|
||||
}
|
||||
|
||||
var link = document.getElementById('cert_domain_link');
|
||||
var link = document.getElementById("cert_domain_link");
|
||||
if (!link)
|
||||
return;
|
||||
|
||||
|
|
|
@ -7,12 +7,10 @@
|
|||
<body onpageshow="showpageshowcount()">
|
||||
<script>
|
||||
var pageshowcount = 0;
|
||||
function showpageshowcount()
|
||||
{
|
||||
function showpageshowcount() {
|
||||
pageshowcount++;
|
||||
var div1 = document.getElementById("div1");
|
||||
while (div1.firstChild)
|
||||
{
|
||||
while (div1.firstChild) {
|
||||
div1.firstChild.remove();
|
||||
}
|
||||
div1.appendChild(document.createTextNode(
|
||||
|
|
|
@ -7,6 +7,10 @@ for (var name of imports) {
|
|||
window[name] = window.opener.wrappedJSObject[name];
|
||||
}
|
||||
ChromeUtils.import("resource://testing-common/BrowserTestUtils.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
// Some functions assume chrome-harness.js has been loaded.
|
||||
/* import-globals-from ../../../testing/mochitest/chrome-harness.js */
|
||||
|
||||
/**
|
||||
* Define global constants and variables.
|
||||
|
@ -88,6 +92,7 @@ var gExtractedPath = null; //used to cache file path for extracting files fro
|
|||
* must contain an object for each pagehide and pageshow event which occurs as
|
||||
* a result of this navigation.
|
||||
*/
|
||||
// eslint-disable-next-line complexity
|
||||
function doPageNavigation(params) {
|
||||
// Parse the parameters.
|
||||
let back = params.back ? params.back : false;
|
||||
|
@ -149,7 +154,7 @@ function doPageNavigation(params) {
|
|||
|
||||
// If the test explicitly sets .eventsToListenFor to [], don't wait for any
|
||||
// events.
|
||||
gFinalEvent = eventsToListenFor.length == 0 ? true : false;
|
||||
gFinalEvent = eventsToListenFor.length == 0;
|
||||
|
||||
// Add an event listener for each type of event in the .eventsToListenFor
|
||||
// property of the input parameters.
|
||||
|
@ -163,30 +168,24 @@ function doPageNavigation(params) {
|
|||
if (back) {
|
||||
gNavType = NAV_BACK;
|
||||
TestWindow.getBrowser().goBack();
|
||||
}
|
||||
else if (forward) {
|
||||
} else if (forward) {
|
||||
gNavType = NAV_FORWARD;
|
||||
TestWindow.getBrowser().goForward();
|
||||
}
|
||||
else if (uri) {
|
||||
} else if (uri) {
|
||||
gNavType = NAV_URI;
|
||||
BrowserTestUtils.loadURI(TestWindow.getBrowser(), uri);
|
||||
}
|
||||
else if (reload) {
|
||||
} else if (reload) {
|
||||
gNavType = NAV_RELOAD;
|
||||
TestWindow.getBrowser().reload();
|
||||
}
|
||||
else if (waitOnly) {
|
||||
} else if (waitOnly) {
|
||||
gNavType = NAV_NONE;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
throw "No valid navigation type passed to doPageNavigation!";
|
||||
}
|
||||
|
||||
// If we're listening for events and there is an .onNavComplete callback,
|
||||
// wait for all events to occur, and then call doPageNavigation_complete().
|
||||
if (eventsToListenFor.length > 0 && params.onNavComplete)
|
||||
{
|
||||
if (eventsToListenFor.length > 0 && params.onNavComplete) {
|
||||
waitForTrue(
|
||||
function() { return gFinalEvent; },
|
||||
function() {
|
||||
|
@ -282,8 +281,7 @@ function pageEventListener(event) {
|
|||
// If no expected events were specified, mark the final event as having been
|
||||
// triggered when a pageshow event is fired; this will allow
|
||||
// doPageNavigation() to return.
|
||||
if ((typeof(gExpectedEvents) == "undefined") && event.type == "pageshow")
|
||||
{
|
||||
if ((typeof(gExpectedEvents) == "undefined") && event.type == "pageshow") {
|
||||
waitForNextPaint(function() { gFinalEvent = true; });
|
||||
return;
|
||||
}
|
||||
|
@ -347,9 +345,7 @@ function finish() {
|
|||
// If the test changed the value of max_total_viewers via a call to
|
||||
// enableBFCache(), then restore it now.
|
||||
if (typeof(gOrigMaxTotalViewers) != "undefined") {
|
||||
var prefs = Cc["@mozilla.org/preferences-service;1"]
|
||||
.getService(Ci.nsIPrefBranch);
|
||||
prefs.setIntPref("browser.sessionhistory.max_total_viewers",
|
||||
Services.prefs.setIntPref("browser.sessionhistory.max_total_viewers",
|
||||
gOrigMaxTotalViewers);
|
||||
}
|
||||
|
||||
|
@ -358,11 +354,9 @@ function finish() {
|
|||
let SimpleTest = opener.wrappedJSObject.SimpleTest;
|
||||
|
||||
// Wait for the window to be closed before finishing the test
|
||||
let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"]
|
||||
.getService(Ci.nsIWindowWatcher);
|
||||
ww.registerNotification(function(subject, topic, data) {
|
||||
Services.ww.registerNotification(function observer(subject, topic, data) {
|
||||
if (topic == "domwindowclosed") {
|
||||
ww.unregisterNotification(arguments.callee);
|
||||
Services.ww.unregisterNotification(observer);
|
||||
SimpleTest.waitForFocus(SimpleTest.finish, opener);
|
||||
}
|
||||
});
|
||||
|
@ -405,7 +399,7 @@ function waitForTrue(fn, onWaitComplete, timeout) {
|
|||
var timeoutHit = false;
|
||||
if (typeof(timeout) != "undefined") {
|
||||
timeoutHit = new Date().valueOf() - start >=
|
||||
timeout ? true : false;
|
||||
timeout;
|
||||
if (timeoutHit) {
|
||||
ok(false, "Timed out waiting for condition");
|
||||
}
|
||||
|
@ -431,25 +425,21 @@ function waitForNextPaint(cb) {
|
|||
* to 0 (disabled), if a number, set it to that specific number
|
||||
*/
|
||||
function enableBFCache(enable) {
|
||||
var prefs = Cc["@mozilla.org/preferences-service;1"]
|
||||
.getService(Ci.nsIPrefBranch);
|
||||
|
||||
// If this is the first time the test called enableBFCache(),
|
||||
// store the original value of max_total_viewers, so it can
|
||||
// be restored at the end of the test.
|
||||
if (typeof(gOrigMaxTotalViewers) == "undefined") {
|
||||
gOrigMaxTotalViewers =
|
||||
prefs.getIntPref("browser.sessionhistory.max_total_viewers");
|
||||
Services.prefs.getIntPref("browser.sessionhistory.max_total_viewers");
|
||||
}
|
||||
|
||||
if (typeof(enable) == "boolean") {
|
||||
if (enable)
|
||||
prefs.setIntPref("browser.sessionhistory.max_total_viewers", -1);
|
||||
Services.prefs.setIntPref("browser.sessionhistory.max_total_viewers", -1);
|
||||
else
|
||||
prefs.setIntPref("browser.sessionhistory.max_total_viewers", 0);
|
||||
}
|
||||
else if (typeof(enable) == "number") {
|
||||
prefs.setIntPref("browser.sessionhistory.max_total_viewers", enable);
|
||||
Services.prefs.setIntPref("browser.sessionhistory.max_total_viewers", 0);
|
||||
} else if (typeof(enable) == "number") {
|
||||
Services.prefs.setIntPref("browser.sessionhistory.max_total_viewers", enable);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -470,7 +460,7 @@ function getHttpRoot() {
|
|||
} else {
|
||||
return null;
|
||||
}
|
||||
return "file://" + gExtractedPath + '/';
|
||||
return "file://" + gExtractedPath + "/";
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -492,10 +482,10 @@ function getHttpUrl(filename) {
|
|||
var TestWindow = {};
|
||||
TestWindow.getWindow = function() {
|
||||
return document.getElementById("content").contentWindow;
|
||||
}
|
||||
};
|
||||
TestWindow.getBrowser = function() {
|
||||
return document.getElementById("content");
|
||||
}
|
||||
};
|
||||
TestWindow.getDocument = function() {
|
||||
return document.getElementById("content").contentDocument;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -55,7 +55,7 @@ function loadIframe(iframe) {
|
|||
}
|
||||
|
||||
var progressListener = {
|
||||
onStateChange: function (webProgress, req, flags, status) {
|
||||
onStateChange(webProgress, req, flags, status) {
|
||||
if (!(flags & Ci.nsIWebProgressListener.STATE_STOP))
|
||||
return;
|
||||
is(Components.isSuccessCode(status), false,
|
||||
|
@ -64,16 +64,7 @@ var progressListener = {
|
|||
runNextTest();
|
||||
},
|
||||
|
||||
QueryInterface: function (iid) {
|
||||
var iids = [
|
||||
Ci.nsIWebProgressListener,
|
||||
Ci.nsISupportsWeakReference,
|
||||
Ci.nsISupports,
|
||||
];
|
||||
if (iids.some(function (i) { return iid.equals(i); }))
|
||||
return this;
|
||||
throw Cr.NS_ERROR_NO_INTERFACE;
|
||||
},
|
||||
QueryInterface: ChromeUtils.generateQI(["nsIWebProgressListener", "nsISupportsWeakReference"]),
|
||||
};
|
||||
|
||||
</script>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
SimpleTest.waitForExplicitFinish();
|
||||
addLoadEvent(test);
|
||||
|
||||
ChromeUtils.import('resource://gre/modules/XPCOMUtils.jsm');
|
||||
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
// The default flags we will stick on the docShell - every request made by the
|
||||
// docShell should include those flags.
|
||||
|
@ -41,8 +41,8 @@ function test() {
|
|||
|
||||
// an nsIWebProgressListener that checks all requests made by the docShell
|
||||
// have the flags we expect.
|
||||
RequestWatcher = {
|
||||
init: function(docShell, callback) {
|
||||
var RequestWatcher = {
|
||||
init(docShell, callback) {
|
||||
this.callback = callback;
|
||||
this.docShell = docShell;
|
||||
docShell.
|
||||
|
@ -60,7 +60,7 @@ RequestWatcher = {
|
|||
"http://mochi.test:8888/tests/SimpleTest/test.css",
|
||||
"http://mochi.test:8888/tests/docshell/test/chrome/red.png",
|
||||
// the content of an iframe in the test html.
|
||||
"http://mochi.test:8888/chrome/docshell/test/chrome/generic.html"
|
||||
"http://mochi.test:8888/chrome/docshell/test/chrome/generic.html",
|
||||
]) {
|
||||
this.requestCounts[url] = 0;
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ RequestWatcher = {
|
|||
|
||||
// Finalize the test after we detect a completed load. We check we saw the
|
||||
// correct requests and make a callback to exit.
|
||||
finalize: function() {
|
||||
finalize() {
|
||||
ok(Object.keys(this.requestCounts).length, "we expected some requests");
|
||||
for (var url in this.requestCounts) {
|
||||
var count = this.requestCounts[url];
|
||||
|
@ -86,7 +86,7 @@ RequestWatcher = {
|
|||
this.callback();
|
||||
},
|
||||
|
||||
onStateChange: function (webProgress, req, flags, status) {
|
||||
onStateChange(webProgress, req, flags, status) {
|
||||
// We are checking requests - if there isn't one, ignore it.
|
||||
if (!req) {
|
||||
return;
|
||||
|
@ -112,8 +112,8 @@ RequestWatcher = {
|
|||
QueryInterface: ChromeUtils.generateQI([
|
||||
Ci.nsIWebProgressListener,
|
||||
Ci.nsISupportsWeakReference,
|
||||
])
|
||||
}
|
||||
]),
|
||||
};
|
||||
|
||||
</script>
|
||||
</head>
|
||||
|
|
|
@ -40,8 +40,8 @@ if (isNotLoaded()) {
|
|||
}
|
||||
|
||||
function onHiddenPrivateWindowReady() {
|
||||
var iframe = hidden.document.createElement('iframe');
|
||||
iframe.src = 'generic.html';
|
||||
var iframe = hidden.document.createElement("iframe");
|
||||
iframe.src = "generic.html";
|
||||
hidden.document.body.appendChild(iframe);
|
||||
|
||||
var win = mainWindow.OpenBrowserWindow({private: true});
|
||||
|
|
|
@ -2243,6 +2243,9 @@ nsGlobalWindowOuter::SetOpenerWindow(nsPIDOMWindowOuter* aOpener,
|
|||
{
|
||||
nsWeakPtr opener = do_GetWeakReference(aOpener);
|
||||
if (opener == mOpener) {
|
||||
MOZ_DIAGNOSTIC_ASSERT(
|
||||
!aOpener || (GetBrowsingContext() && GetBrowsingContext()->GetOpener() ==
|
||||
aOpener->GetBrowsingContext()));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -2255,6 +2258,13 @@ nsGlobalWindowOuter::SetOpenerWindow(nsPIDOMWindowOuter* aOpener,
|
|||
mOpener = opener.forget();
|
||||
NS_ASSERTION(mOpener || !aOpener, "Opener must support weak references!");
|
||||
|
||||
if (mDocShell && aOpener) {
|
||||
// TODO(farre): Here we really wish to only consider the case
|
||||
// where 'aOriginalOpener' is false, and we also really want to
|
||||
// move opener entirely to BrowsingContext. See bug 1502330.
|
||||
GetBrowsingContext()->SetOpener(aOpener->GetBrowsingContext());
|
||||
}
|
||||
|
||||
// Check that the js visible opener matches! We currently don't depend on this
|
||||
// being true outside of nightly, so we disable the assertion in optimized
|
||||
// release / beta builds.
|
||||
|
@ -7868,3 +7878,9 @@ nsAutoPopupStatePusherInternal::~nsAutoPopupStatePusherInternal()
|
|||
{
|
||||
nsContentUtils::PopPopupControlState(mOldState);
|
||||
}
|
||||
|
||||
mozilla::dom::BrowsingContext*
|
||||
nsPIDOMWindowOuter::GetBrowsingContext() const
|
||||
{
|
||||
return mDocShell ? nsDocShell::Cast(mDocShell)->GetBrowsingContext() : nullptr;
|
||||
}
|
||||
|
|
|
@ -48,6 +48,7 @@ namespace mozilla {
|
|||
class AutoplayPermissionManager;
|
||||
namespace dom {
|
||||
class AudioContext;
|
||||
class BrowsingContext;
|
||||
class ClientInfo;
|
||||
class ClientState;
|
||||
class ContentFrameMessageManager;
|
||||
|
@ -929,6 +930,8 @@ public:
|
|||
*/
|
||||
inline nsIDocShell *GetDocShell() const;
|
||||
|
||||
mozilla::dom::BrowsingContext* GetBrowsingContext() const;
|
||||
|
||||
/**
|
||||
* Set a new document in the window. Calling this method will in most cases
|
||||
* create a new inner window. This may be called with a pointer to the current
|
||||
|
|
|
@ -14,4 +14,6 @@ interface BrowsingContext {
|
|||
readonly attribute nsIDocShell? docShell;
|
||||
|
||||
readonly attribute unsigned long long id;
|
||||
|
||||
readonly attribute BrowsingContext? opener;
|
||||
};
|
||||
|
|
|
@ -6080,3 +6080,39 @@ ContentParent::RecvDetachBrowsingContext(const BrowsingContextId& aContextId,
|
|||
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
ContentParent::RecvSetOpenerBrowsingContext(
|
||||
const BrowsingContextId& aContextId,
|
||||
const BrowsingContextId& aOpenerContextId)
|
||||
{
|
||||
RefPtr<ChromeBrowsingContext> context = ChromeBrowsingContext::Get(aContextId);
|
||||
|
||||
if (!context) {
|
||||
MOZ_LOG(BrowsingContext::GetLog(),
|
||||
LogLevel::Debug,
|
||||
("ParentIPC: Trying to set opener already detached 0x%08" PRIx64,
|
||||
(uint64_t)aContextId));
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
if (!context->IsOwnedByProcess(ChildID())) {
|
||||
// Where trying to set opener on a child BrowsingContext in
|
||||
// another child process. This is illegal since the owner of the
|
||||
// BrowsingContext is the proccess with the in-process docshell,
|
||||
// which is tracked by OwnerProcessId.
|
||||
|
||||
// TODO(farre): To crash or not to crash. Same reasoning as in
|
||||
// above TODO. [Bug 1471598]
|
||||
MOZ_LOG(BrowsingContext::GetLog(),
|
||||
LogLevel::Warning,
|
||||
("ParentIPC: Trying to set opener on out of process context 0x%08" PRIx64,
|
||||
context->Id()));
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
RefPtr<BrowsingContext> opener = BrowsingContext::Get(aOpenerContextId);
|
||||
context->SetOpener(opener);
|
||||
|
||||
return IPC_OK();
|
||||
}
|
||||
|
|
|
@ -685,6 +685,10 @@ public:
|
|||
const BrowsingContextId& aContextId,
|
||||
const bool& aMoveToBFCache) override;
|
||||
|
||||
virtual mozilla::ipc::IPCResult RecvSetOpenerBrowsingContext(
|
||||
const BrowsingContextId& aContextId,
|
||||
const BrowsingContextId& aOpenerContextId) override;
|
||||
|
||||
protected:
|
||||
void OnChannelConnected(int32_t pid) override;
|
||||
|
||||
|
|
|
@ -1187,6 +1187,14 @@ parent:
|
|||
*/
|
||||
async DetachBrowsingContext(BrowsingContextId aContextId,
|
||||
bool aMoveToBFCache);
|
||||
|
||||
/**
|
||||
* Set the opener of browsing context with id 'aContextId' to the
|
||||
* browsing context with id 'aOpenerId'.
|
||||
*/
|
||||
async SetOpenerBrowsingContext(BrowsingContextId aContextId,
|
||||
BrowsingContextId aOpenerContextId);
|
||||
|
||||
both:
|
||||
async AsyncMessage(nsString aMessage, CpowEntry[] aCpows,
|
||||
Principal aPrincipal, ClonedMessageData aData);
|
||||
|
|
|
@ -60,6 +60,10 @@ skip-if = !e10s || verify # This is a test of e10s functionality.
|
|||
support-files =
|
||||
set-samesite-cookies-and-redirect.sjs
|
||||
mimeme.sjs
|
||||
[browser_persist_mixed_content_image.js]
|
||||
support-files =
|
||||
test_mixed_content_image.html
|
||||
dummy.png
|
||||
[browser_test_focus_after_modal_state.js]
|
||||
skip-if = verify
|
||||
support-files =
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_PATH = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "https://example.org");
|
||||
|
||||
var MockFilePicker = SpecialPowers.MockFilePicker;
|
||||
MockFilePicker.init(window);
|
||||
|
||||
registerCleanupFunction(async function() {
|
||||
info("Running the cleanup code");
|
||||
MockFilePicker.cleanup();
|
||||
if (gTestDir && gTestDir.exists()) {
|
||||
// On Windows, sometimes nsIFile.remove() throws, probably because we're
|
||||
// still writing to the directory we're trying to remove, despite
|
||||
// waiting for the download to complete. Just retry a bit later...
|
||||
let succeeded = false;
|
||||
while (!succeeded) {
|
||||
try {
|
||||
gTestDir.remove(true);
|
||||
succeeded = true;
|
||||
} catch (ex) {
|
||||
await new Promise(requestAnimationFrame);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let gTestDir = null;
|
||||
|
||||
function createTemporarySaveDirectory() {
|
||||
var saveDir = Services.dirsvc.get("TmpD", Ci.nsIFile);
|
||||
saveDir.append("testsavedir");
|
||||
if (!saveDir.exists()) {
|
||||
info("create testsavedir!");
|
||||
saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
|
||||
}
|
||||
info("return from createTempSaveDir: " + saveDir.path);
|
||||
return saveDir;
|
||||
}
|
||||
|
||||
|
||||
add_task(async function test_image_download() {
|
||||
await BrowserTestUtils.withNewTab(TEST_PATH + "test_mixed_content_image.html", async (browser) => {
|
||||
// Add the image, and wait for it to load.
|
||||
await ContentTask.spawn(browser, null, function() {
|
||||
let loc = content.document.location.href;
|
||||
let httpRoot = loc.replace("https", "http");
|
||||
let imgloc = new content.URL("dummy.png", httpRoot);
|
||||
let img = content.document.createElement("img");
|
||||
img.src = imgloc;
|
||||
return new Promise(resolve => {
|
||||
img.onload = resolve;
|
||||
content.document.body.appendChild(img);
|
||||
});
|
||||
});
|
||||
gTestDir = createTemporarySaveDirectory();
|
||||
|
||||
let destFile = gTestDir.clone();
|
||||
|
||||
MockFilePicker.displayDirectory = gTestDir;
|
||||
let fileName;
|
||||
MockFilePicker.showCallback = function(fp) {
|
||||
info("showCallback");
|
||||
fileName = fp.defaultString;
|
||||
info("fileName: " + fileName);
|
||||
destFile.append(fileName);
|
||||
info("path: " + destFile.path);
|
||||
MockFilePicker.setFiles([destFile]);
|
||||
MockFilePicker.filterIndex = 0; // just save the file
|
||||
info("done showCallback");
|
||||
};
|
||||
let downloadFinishedPromise = new Promise(async (resolve) => {
|
||||
let dls = await Downloads.getList(Downloads.PUBLIC);
|
||||
dls.addView({
|
||||
onDownloadChanged(download) {
|
||||
info("Download changed!");
|
||||
if (download.succeeded || download.error) {
|
||||
info("Download succeeded or errored");
|
||||
dls.removeView(this);
|
||||
dls.removeFinished();
|
||||
resolve(download);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
// open the context menu.
|
||||
let popup = document.getElementById("contentAreaContextMenu");
|
||||
let popupShown = BrowserTestUtils.waitForEvent(popup, "popupshown");
|
||||
BrowserTestUtils.synthesizeMouseAtCenter("img", {type: "contextmenu", button: 2}, browser);
|
||||
await popupShown;
|
||||
let popupHidden = BrowserTestUtils.waitForEvent(popup, "popuphidden");
|
||||
popup.querySelector("#context-saveimage").click();
|
||||
popup.hidePopup();
|
||||
await popupHidden;
|
||||
info("Context menu hidden, waiting for download to finish");
|
||||
let imageDownload = await downloadFinishedPromise;
|
||||
ok(imageDownload.succeeded, "Image should have downloaded successfully");
|
||||
});
|
||||
|
||||
});
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 703 B |
|
@ -0,0 +1 @@
|
|||
<body></body>
|
|
@ -1358,7 +1358,7 @@ nsresult nsWebBrowserPersist::SaveURIInternal(
|
|||
aURI,
|
||||
aTriggeringPrincipal,
|
||||
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
|
||||
nsIContentPolicy::TYPE_OTHER,
|
||||
nsIContentPolicy::TYPE_SAVEAS_DOWNLOAD,
|
||||
nullptr, // aPerformanceStorage
|
||||
nullptr, // aLoadGroup
|
||||
static_cast<nsIInterfaceRequestor*>(this),
|
||||
|
|
|
@ -431,7 +431,7 @@ pub struct PicturePrimitive {
|
|||
|
||||
/// The spatial node index of this picture when it is
|
||||
/// composited into the parent picture.
|
||||
spatial_node_index: SpatialNodeIndex,
|
||||
pub spatial_node_index: SpatialNodeIndex,
|
||||
|
||||
/// The local rect of this picture. It is built
|
||||
/// dynamically during the first picture traversal.
|
||||
|
@ -670,31 +670,29 @@ impl PicturePrimitive {
|
|||
pub fn add_split_plane(
|
||||
splitter: &mut PlaneSplitter,
|
||||
transforms: &TransformPalette,
|
||||
prim_instance: &PrimitiveInstance,
|
||||
original_local_rect: LayoutRect,
|
||||
local_rect: LayoutRect,
|
||||
spatial_node_index: SpatialNodeIndex,
|
||||
plane_split_anchor: usize,
|
||||
world_bounds: WorldRect,
|
||||
) -> bool {
|
||||
let transform = transforms
|
||||
.get_world_transform(prim_instance.spatial_node_index);
|
||||
let matrix = transform.cast();
|
||||
// If the picture isn't visible, then ensure it's not added
|
||||
// to the plane splitter, to avoid assertions during batching
|
||||
// about each split plane having a surface.
|
||||
if local_rect.size.width <= 0.0 ||
|
||||
local_rect.size.height <= 0.0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Apply the local clip rect here, before splitting. This is
|
||||
// because the local clip rect can't be applied in the vertex
|
||||
// shader for split composites, since we are drawing polygons
|
||||
// rather that rectangles. The interpolation still works correctly
|
||||
// since we determine the UVs by doing a bilerp with a factor
|
||||
// from the original local rect.
|
||||
let local_rect = match original_local_rect
|
||||
.intersection(&prim_instance.combined_local_clip_rect)
|
||||
{
|
||||
Some(rect) => rect.cast(),
|
||||
None => return false,
|
||||
};
|
||||
let transform = transforms
|
||||
.get_world_transform(spatial_node_index);
|
||||
let matrix = transform.cast();
|
||||
let local_rect = local_rect.cast();
|
||||
let world_bounds = world_bounds.cast();
|
||||
|
||||
match transform.transform_kind() {
|
||||
TransformedRectKind::AxisAligned => {
|
||||
let inv_transform = transforms
|
||||
.get_world_inv_transform(prim_instance.spatial_node_index);
|
||||
.get_world_inv_transform(spatial_node_index);
|
||||
let polygon = Polygon::from_transformed_rect_with_inverse(
|
||||
local_rect,
|
||||
&matrix,
|
||||
|
@ -711,7 +709,7 @@ impl PicturePrimitive {
|
|||
plane_split_anchor,
|
||||
),
|
||||
&matrix,
|
||||
prim_instance.clipped_world_rect.map(|r| r.to_f64()),
|
||||
Some(world_bounds),
|
||||
);
|
||||
if let Ok(results) = results {
|
||||
for poly in results {
|
||||
|
|
|
@ -2341,9 +2341,10 @@ impl PrimitiveStore {
|
|||
PicturePrimitive::add_split_plane(
|
||||
splitter,
|
||||
frame_state.transforms,
|
||||
prim_instance,
|
||||
prim_local_rect,
|
||||
pic.spatial_node_index,
|
||||
plane_split_anchor,
|
||||
frame_context.world_rect,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -9,8 +9,9 @@ use serde::de::Deserializer;
|
|||
#[cfg(feature = "serialize")]
|
||||
use serde::ser::{Serializer, SerializeSeq};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Write};
|
||||
use std::io::{Read, stdout, Write};
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::Range;
|
||||
use std::{io, mem, ptr, slice};
|
||||
use time::precise_time_ns;
|
||||
use {AlphaType, BorderDetails, BorderDisplayItem, BorderRadius, BoxShadowClipMode};
|
||||
|
@ -924,20 +925,31 @@ impl DisplayListBuilder {
|
|||
self.save_state.take().expect("No save to clear in DisplayListBuilder");
|
||||
}
|
||||
|
||||
/// Print the display items in the list to stderr. If the start parameter
|
||||
/// is specified, only display items starting at that index (inclusive) will
|
||||
/// be printed. If the end parameter is specified, only display items before
|
||||
/// that index (exclusive) will be printed. Calling this function with
|
||||
/// end <= start is allowed but is just a waste of CPU cycles.
|
||||
/// This function returns the total number of items in the display list, which
|
||||
/// allows the caller to subsequently invoke this function to only dump the
|
||||
/// newly-added items.
|
||||
pub fn print_display_list(
|
||||
/// Print the display items in the list to stdout.
|
||||
pub fn print_display_list(&mut self) {
|
||||
self.emit_display_list(0, Range { start: None, end: None }, stdout());
|
||||
}
|
||||
|
||||
/// Emits a debug representation of display items in the list, for debugging
|
||||
/// purposes. If the range's start parameter is specified, only display
|
||||
/// items starting at that index (inclusive) will be printed. If the range's
|
||||
/// end parameter is specified, only display items before that index
|
||||
/// (exclusive) will be printed. Calling this function with end <= start is
|
||||
/// allowed but is just a waste of CPU cycles. The function emits the
|
||||
/// debug representation of the selected display items, one per line, with
|
||||
/// the given indent, to the provided sink object. The return value is
|
||||
/// the total number of items in the display list, which allows the
|
||||
/// caller to subsequently invoke this function to only dump the newly-added
|
||||
/// items.
|
||||
pub fn emit_display_list<W>(
|
||||
&mut self,
|
||||
indent: usize,
|
||||
start: Option<usize>,
|
||||
end: Option<usize>,
|
||||
) -> usize {
|
||||
range: Range<Option<usize>>,
|
||||
mut sink: W,
|
||||
) -> usize
|
||||
where
|
||||
W: Write
|
||||
{
|
||||
let mut temp = BuiltDisplayList::default();
|
||||
mem::swap(&mut temp.data, &mut self.data);
|
||||
|
||||
|
@ -945,8 +957,8 @@ impl DisplayListBuilder {
|
|||
{
|
||||
let mut iter = BuiltDisplayListIter::new(&temp);
|
||||
while let Some(item) = iter.next_raw() {
|
||||
if index >= start.unwrap_or(0) && end.map_or(true, |e| index < e) {
|
||||
eprintln!("{}{:?}", " ".repeat(indent), item.display_item());
|
||||
if index >= range.start.unwrap_or(0) && range.end.map_or(true, |e| index < e) {
|
||||
writeln!(sink, "{}{:?}", " ".repeat(indent), item.display_item());
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
|
|
|
@ -39,4 +39,4 @@ derive_helper_methods = true
|
|||
[defines]
|
||||
"target_os = windows" = "XP_WIN"
|
||||
"target_os = macos" = "XP_MACOSX"
|
||||
|
||||
"target_os = android" = "ANDROID"
|
||||
|
|
|
@ -1 +1 @@
|
|||
b8829189cfc1769550c9ab4a4bb994e28621f009
|
||||
874d782a891db37707364be2071d91117c6e255a
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
use std::ffi::{CStr, CString};
|
||||
use std::io::Cursor;
|
||||
use std::{mem, slice, ptr, env};
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::ops::Range;
|
||||
use std::os::raw::{c_void, c_char, c_float};
|
||||
#[cfg(target_os = "android")]
|
||||
use std::os::raw::{c_int};
|
||||
use gleam::gl;
|
||||
|
||||
use webrender::api::*;
|
||||
|
@ -32,6 +36,11 @@ use core_foundation::string::CFString;
|
|||
#[cfg(target_os = "macos")]
|
||||
use core_graphics::font::CGFont;
|
||||
|
||||
extern "C" {
|
||||
#[cfg(target_os = "android")]
|
||||
fn __android_log_write(prio: c_int, tag: *const c_char, text: *const c_char) -> c_int;
|
||||
}
|
||||
|
||||
/// The unique id for WR resource identification.
|
||||
static NEXT_NAMESPACE_ID: AtomicUsize = AtomicUsize::new(1);
|
||||
|
||||
|
@ -2563,9 +2572,26 @@ pub extern "C" fn wr_dump_display_list(state: &mut WrState,
|
|||
end: *const usize) -> usize {
|
||||
let start = unsafe { start.as_ref().cloned() };
|
||||
let end = unsafe { end.as_ref().cloned() };
|
||||
state.frame_builder
|
||||
let range = Range { start, end };
|
||||
let mut sink = Cursor::new(Vec::new());
|
||||
let index = state.frame_builder
|
||||
.dl_builder
|
||||
.print_display_list(indent, start, end)
|
||||
.emit_display_list(indent, range, &mut sink);
|
||||
|
||||
// For Android, dump to logcat instead of stderr. This is the same as
|
||||
// what printf_stderr does on the C++ side.
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
unsafe {
|
||||
__android_log_write(4 /* info */,
|
||||
CString::new("Gecko").unwrap().as_ptr(),
|
||||
CString::new(sink.into_inner()).unwrap().as_ptr());
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
eprint!("{}", String::from_utf8(sink.into_inner()).unwrap());
|
||||
|
||||
index
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
|
|
|
@ -1108,6 +1108,12 @@ extern void DeleteBlobFont(WrFontInstanceKey aKey);
|
|||
|
||||
extern void DeleteFontData(WrFontKey aKey);
|
||||
|
||||
#if defined(ANDROID)
|
||||
extern int __android_log_write(int aPrio,
|
||||
const char *aTag,
|
||||
const char *aText);
|
||||
#endif
|
||||
|
||||
extern void apz_deregister_sampler(WrWindowId aWindowId);
|
||||
|
||||
extern void apz_deregister_updater(WrWindowId aWindowId);
|
||||
|
|
|
@ -664,6 +664,25 @@ class LifoAlloc
|
|||
return allocImpl(n);
|
||||
}
|
||||
|
||||
// Allocates |n| bytes if we can guarantee that we will have
|
||||
// |needed| unused bytes remaining. Returns nullptr if we can't.
|
||||
// This is useful for maintaining our ballast invariants while
|
||||
// attempting fallible allocations.
|
||||
MOZ_ALWAYS_INLINE
|
||||
void* allocEnsureUnused(size_t n, size_t needed) {
|
||||
JS_OOM_POSSIBLY_FAIL();
|
||||
MOZ_ASSERT(fallibleScope_);
|
||||
|
||||
detail::BumpChunk::Mark m = mark();
|
||||
void *result = allocImpl(n);
|
||||
if (!ensureUnusedApproximate(needed)) {
|
||||
release(m);
|
||||
return nullptr;
|
||||
}
|
||||
cancelMark(m);
|
||||
return result;
|
||||
}
|
||||
|
||||
template<typename T, typename... Args>
|
||||
MOZ_ALWAYS_INLINE T*
|
||||
allocInSize(size_t n, Args&&... args)
|
||||
|
@ -802,6 +821,12 @@ class LifoAlloc
|
|||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void cancelMark(Mark mark) {
|
||||
markCount--;
|
||||
}
|
||||
|
||||
public:
|
||||
void releaseAll() {
|
||||
MOZ_ASSERT(!markCount);
|
||||
for (detail::BumpChunk& bc : chunks_) {
|
||||
|
|
|
@ -2196,6 +2196,9 @@ BytecodeEmitter::allocateResumeIndex(ptrdiff_t offset, uint32_t* resumeIndex)
|
|||
|
||||
static_assert(MaxResumeIndex < uint32_t(GeneratorObject::RESUME_INDEX_CLOSING),
|
||||
"resumeIndex should not include magic GeneratorObject resumeIndex values");
|
||||
static_assert(MaxResumeIndex <= INT32_MAX / sizeof(uintptr_t),
|
||||
"resumeIndex * sizeof(uintptr_t) must fit in an int32. JIT code relies "
|
||||
"on this when loading resume entries from BaselineScript");
|
||||
|
||||
*resumeIndex = resumeOffsetList.length();
|
||||
if (*resumeIndex > MaxResumeIndex) {
|
||||
|
|
|
@ -3949,6 +3949,18 @@ BaselineCompiler::emit_JSOP_GOSUB()
|
|||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
LoadBaselineScriptResumeEntries(MacroAssembler& masm, JSScript* script, Register dest,
|
||||
Register scratch)
|
||||
{
|
||||
MOZ_ASSERT(dest != scratch);
|
||||
|
||||
masm.movePtr(ImmGCPtr(script), dest);
|
||||
masm.loadPtr(Address(dest, JSScript::offsetOfBaselineScript()), dest);
|
||||
masm.load32(Address(dest, BaselineScript::offsetOfResumeEntriesOffset()), scratch);
|
||||
masm.addPtr(scratch, dest);
|
||||
}
|
||||
|
||||
bool
|
||||
BaselineCompiler::emit_JSOP_RETSUB()
|
||||
{
|
||||
|
@ -3969,10 +3981,7 @@ BaselineCompiler::emit_JSOP_RETSUB()
|
|||
// R0 is |false|. R1 contains the resumeIndex to jump to.
|
||||
Register scratch1 = R2.scratchReg();
|
||||
Register scratch2 = R0.scratchReg();
|
||||
masm.movePtr(ImmGCPtr(script), scratch1);
|
||||
masm.loadPtr(Address(scratch1, JSScript::offsetOfBaselineScript()), scratch1);
|
||||
masm.load32(Address(scratch1, BaselineScript::offsetOfResumeEntriesOffset()), scratch2);
|
||||
masm.addPtr(scratch2, scratch1);
|
||||
LoadBaselineScriptResumeEntries(masm, script, scratch1, scratch2);
|
||||
masm.unboxInt32(R1, scratch2);
|
||||
masm.loadPtr(BaseIndex(scratch1, scratch2, ScaleFromElemWidth(sizeof(uintptr_t))), scratch1);
|
||||
masm.jump(scratch1);
|
||||
|
@ -4488,9 +4497,44 @@ BaselineCompiler::emit_JSOP_TABLESWITCH()
|
|||
{
|
||||
frame.popRegsAndSync(1);
|
||||
|
||||
// Call IC.
|
||||
ICTableSwitch::Compiler compiler(cx, script, pc);
|
||||
return emitOpIC(compiler.getStub(&stubSpace_));
|
||||
jsbytecode* defaultpc = pc + GET_JUMP_OFFSET(pc);
|
||||
Label* defaultLabel = labelOf(defaultpc);
|
||||
|
||||
int32_t low = GET_JUMP_OFFSET(pc + 1 * JUMP_OFFSET_LEN);
|
||||
int32_t high = GET_JUMP_OFFSET(pc + 2 * JUMP_OFFSET_LEN);
|
||||
uint32_t firstResumeIndex = GET_RESUMEINDEX(pc + 3 * JUMP_OFFSET_LEN);
|
||||
int32_t length = high - low + 1;
|
||||
|
||||
Register key = R0.scratchReg();
|
||||
Register scratch1 = R1.scratchReg();
|
||||
Register scratch2 = R2.scratchReg();
|
||||
|
||||
// Call a stub to convert R0 from double to int32 if needed.
|
||||
// Note: this stub may clobber scratch1.
|
||||
masm.call(cx->runtime()->jitRuntime()->getDoubleToInt32ValueStub());
|
||||
|
||||
// Jump to the 'default' pc if not int32 (tableswitch is only used when
|
||||
// all cases are int32).
|
||||
masm.branchTestInt32(Assembler::NotEqual, R0, defaultLabel);
|
||||
masm.unboxInt32(R0, key);
|
||||
|
||||
// Subtract 'low'. Bounds check.
|
||||
if (low != 0) {
|
||||
masm.sub32(Imm32(low), key);
|
||||
}
|
||||
masm.branch32(Assembler::AboveOrEqual, key, Imm32(length), defaultLabel);
|
||||
|
||||
// Jump to resumeEntries[firstResumeIndex + key].
|
||||
//
|
||||
// Note: BytecodeEmitter::allocateResumeIndex static_asserts
|
||||
// |firstResumeIndex * sizeof(uintptr_t)| fits in int32_t.
|
||||
|
||||
LoadBaselineScriptResumeEntries(masm, script, scratch1, scratch2);
|
||||
masm.loadPtr(BaseIndex(scratch1, key, ScaleFromElemWidth(sizeof(uintptr_t)),
|
||||
firstResumeIndex * sizeof(uintptr_t)), scratch1);
|
||||
masm.jump(scratch1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
|
|
|
@ -5142,91 +5142,6 @@ ICCall_ScriptedFunCall::Compiler::generateStubCode(MacroAssembler& masm)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ICTableSwitch::Compiler::generateStubCode(MacroAssembler& masm)
|
||||
{
|
||||
Label isInt32, notInt32, outOfRange;
|
||||
Register scratch = R1.scratchReg();
|
||||
|
||||
masm.branchTestInt32(Assembler::NotEqual, R0, ¬Int32);
|
||||
|
||||
Register key = masm.extractInt32(R0, ExtractTemp0);
|
||||
|
||||
masm.bind(&isInt32);
|
||||
|
||||
masm.load32(Address(ICStubReg, offsetof(ICTableSwitch, min_)), scratch);
|
||||
masm.sub32(scratch, key);
|
||||
masm.branch32(Assembler::BelowOrEqual,
|
||||
Address(ICStubReg, offsetof(ICTableSwitch, length_)), key, &outOfRange);
|
||||
|
||||
masm.loadPtr(Address(ICStubReg, offsetof(ICTableSwitch, table_)), scratch);
|
||||
masm.loadPtr(BaseIndex(scratch, key, ScalePointer), scratch);
|
||||
|
||||
EmitChangeICReturnAddress(masm, scratch);
|
||||
EmitReturnFromIC(masm);
|
||||
|
||||
masm.bind(¬Int32);
|
||||
|
||||
masm.branchTestDouble(Assembler::NotEqual, R0, &outOfRange);
|
||||
masm.unboxDouble(R0, FloatReg0);
|
||||
|
||||
// N.B. -0 === 0, so convert -0 to a 0 int32.
|
||||
masm.convertDoubleToInt32(FloatReg0, key, &outOfRange, /* negativeZeroCheck = */ false);
|
||||
masm.jump(&isInt32);
|
||||
|
||||
masm.bind(&outOfRange);
|
||||
|
||||
masm.loadPtr(Address(ICStubReg, offsetof(ICTableSwitch, defaultTarget_)), scratch);
|
||||
|
||||
EmitChangeICReturnAddress(masm, scratch);
|
||||
EmitReturnFromIC(masm);
|
||||
return true;
|
||||
}
|
||||
|
||||
ICStub*
|
||||
ICTableSwitch::Compiler::getStub(ICStubSpace* space)
|
||||
{
|
||||
JitCode* code = getStubCode();
|
||||
if (!code) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
jsbytecode* pc = pc_;
|
||||
pc += JUMP_OFFSET_LEN;
|
||||
int32_t low = GET_JUMP_OFFSET(pc);
|
||||
pc += JUMP_OFFSET_LEN;
|
||||
int32_t high = GET_JUMP_OFFSET(pc);
|
||||
int32_t length = high - low + 1;
|
||||
pc += JUMP_OFFSET_LEN;
|
||||
|
||||
void** table = (void**) space->alloc(sizeof(void*) * length);
|
||||
if (!table) {
|
||||
ReportOutOfMemory(cx);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
jsbytecode* defaultpc = pc_ + GET_JUMP_OFFSET(pc_);
|
||||
|
||||
for (int32_t i = 0; i < length; i++) {
|
||||
table[i] = script_->tableSwitchCasePC(pc_, i);
|
||||
}
|
||||
|
||||
return newStub<ICTableSwitch>(space, code, table, low, length, defaultpc);
|
||||
}
|
||||
|
||||
void
|
||||
ICTableSwitch::fixupJumpTable(JSScript* script, BaselineScript* baseline)
|
||||
{
|
||||
PCMappingSlotInfo slotInfo;
|
||||
defaultTarget_ = baseline->nativeCodeForPC(script, (jsbytecode*) defaultTarget_, &slotInfo);
|
||||
MOZ_ASSERT(slotInfo.isStackSynced());
|
||||
|
||||
for (int32_t i = 0; i < length_; i++) {
|
||||
table_[i] = baseline->nativeCodeForPC(script, (jsbytecode*) table_[i], &slotInfo);
|
||||
MOZ_ASSERT(slotInfo.isStackSynced());
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// GetIterator_Fallback
|
||||
//
|
||||
|
|
|
@ -2614,42 +2614,6 @@ class ICCall_IsSuspendedGenerator : public ICStub
|
|||
};
|
||||
};
|
||||
|
||||
// Stub for performing a TableSwitch, updating the IC's return address to jump
|
||||
// to whatever point the switch is branching to.
|
||||
class ICTableSwitch : public ICStub
|
||||
{
|
||||
friend class ICStubSpace;
|
||||
|
||||
protected: // Protected to silence Clang warning.
|
||||
void** table_;
|
||||
int32_t min_;
|
||||
int32_t length_;
|
||||
void* defaultTarget_;
|
||||
|
||||
ICTableSwitch(JitCode* stubCode, void** table,
|
||||
int32_t min, int32_t length, void* defaultTarget)
|
||||
: ICStub(TableSwitch, stubCode), table_(table),
|
||||
min_(min), length_(length), defaultTarget_(defaultTarget)
|
||||
{}
|
||||
|
||||
public:
|
||||
void fixupJumpTable(JSScript* script, BaselineScript* baseline);
|
||||
|
||||
class Compiler : public ICStubCompiler {
|
||||
MOZ_MUST_USE bool generateStubCode(MacroAssembler& masm) override;
|
||||
|
||||
JSScript* script_;
|
||||
jsbytecode* pc_;
|
||||
|
||||
public:
|
||||
Compiler(JSContext* cx, JSScript* script, jsbytecode* pc)
|
||||
: ICStubCompiler(cx, ICStub::TableSwitch), script_(script), pc_(pc)
|
||||
{}
|
||||
|
||||
ICStub* getStub(ICStubSpace* space) override;
|
||||
};
|
||||
};
|
||||
|
||||
// IC for constructing an iterator from an input value.
|
||||
class ICGetIterator_Fallback : public ICFallbackStub
|
||||
{
|
||||
|
|
|
@ -61,8 +61,6 @@ namespace jit {
|
|||
\
|
||||
_(SetProp_Fallback) \
|
||||
\
|
||||
_(TableSwitch) \
|
||||
\
|
||||
_(GetIterator_Fallback) \
|
||||
_(IteratorMore_Fallback) \
|
||||
_(IteratorMore_Native) \
|
||||
|
|
|
@ -820,11 +820,6 @@ BaselineScript::copyICEntries(JSScript* script, const ICEntry* entries)
|
|||
ICTypeMonitor_Fallback* stub = realEntry.firstStub()->toTypeMonitor_Fallback();
|
||||
stub->fixupICEntry(&realEntry);
|
||||
}
|
||||
|
||||
if (realEntry.firstStub()->isTableSwitch()) {
|
||||
ICTableSwitch* stub = realEntry.firstStub()->toTableSwitch();
|
||||
stub->fixupJumpTable(script, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1150,7 +1145,7 @@ BaselineScript::purgeOptimizedStubs(Zone* zone)
|
|||
} else if (lastStub->isTypeMonitor_Fallback()) {
|
||||
lastStub->toTypeMonitor_Fallback()->resetMonitorStubChain(zone);
|
||||
} else {
|
||||
MOZ_ASSERT(lastStub->isTableSwitch());
|
||||
MOZ_CRASH("Unknown fallback stub");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9008,6 +9008,23 @@ JitRuntime::generateInterpreterStub(MacroAssembler& masm)
|
|||
masm.ret();
|
||||
}
|
||||
|
||||
void
|
||||
JitRuntime::generateDoubleToInt32ValueStub(MacroAssembler& masm)
|
||||
{
|
||||
doubleToInt32ValueStubOffset_ = startTrampolineCode(masm);
|
||||
|
||||
Label done;
|
||||
masm.branchTestDouble(Assembler::NotEqual, R0, &done);
|
||||
|
||||
masm.unboxDouble(R0, FloatReg0);
|
||||
masm.convertDoubleToInt32(FloatReg0, R1.scratchReg(), &done,
|
||||
/* negativeZeroCheck = */ false);
|
||||
masm.tagValue(JSVAL_TYPE_INT32, R1.scratchReg(), R0);
|
||||
|
||||
masm.bind(&done);
|
||||
masm.abiret();
|
||||
}
|
||||
|
||||
bool
|
||||
JitRuntime::generateTLEventVM(MacroAssembler& masm, const VMFunction& f, bool enter)
|
||||
{
|
||||
|
|
|
@ -179,6 +179,7 @@ JitRuntime::JitRuntime()
|
|||
invalidatorOffset_(0),
|
||||
lazyLinkStubOffset_(0),
|
||||
interpreterStubOffset_(0),
|
||||
doubleToInt32ValueStubOffset_(0),
|
||||
debugTrapHandler_(nullptr),
|
||||
baselineDebugModeOSRHandler_(nullptr),
|
||||
trampolineCode_(nullptr),
|
||||
|
@ -302,6 +303,9 @@ JitRuntime::initialize(JSContext* cx)
|
|||
JitSpew(JitSpew_Codegen, "# Emitting interpreter stub");
|
||||
generateInterpreterStub(masm);
|
||||
|
||||
JitSpew(JitSpew_Codegen, "# Emitting double-to-int32-value stub");
|
||||
generateDoubleToInt32ValueStub(masm);
|
||||
|
||||
JitSpew(JitSpew_Codegen, "# Emitting VM function wrappers");
|
||||
for (VMFunction* fun = VMFunction::functions; fun; fun = fun->next) {
|
||||
if (functionWrappers_->has(fun)) {
|
||||
|
|
|
@ -48,20 +48,7 @@ class TempAllocator
|
|||
MOZ_MUST_USE void* allocate(size_t bytes)
|
||||
{
|
||||
LifoAlloc::AutoFallibleScope fallibleAllocator(lifoAlloc());
|
||||
void* p = lifoScope_.alloc().alloc(bytes);
|
||||
|
||||
// The above allocation will allocate memory out of the
|
||||
// lifo alloc's ballast, so we call ensureBallast to
|
||||
// replenish it. If we fail to replenish the ballast, then
|
||||
// future "infallible" allocations could fail. (Returning
|
||||
// nullptr is insufficient because of cases like
|
||||
// CompilerConstraintList::add, where we delay the OOM
|
||||
// failure until later.)
|
||||
AutoEnterOOMUnsafeRegion oomUnsafe;
|
||||
if (!ensureBallast()) {
|
||||
oomUnsafe.crash("Failed to replenish ballast in TempAllocator::allocate");
|
||||
}
|
||||
return p;
|
||||
return lifoScope_.alloc().allocEnsureUnused(bytes, BallastSize);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
|
@ -72,12 +59,7 @@ class TempAllocator
|
|||
if (MOZ_UNLIKELY(!CalculateAllocSize<T>(n, &bytes))) {
|
||||
return nullptr;
|
||||
}
|
||||
T* p = static_cast<T*>(lifoScope_.alloc().alloc(bytes));
|
||||
AutoEnterOOMUnsafeRegion oomUnsafe;
|
||||
if (!ensureBallast()) {
|
||||
oomUnsafe.crash("Failed to replenish ballast in TempAllocator::allocateArray");
|
||||
}
|
||||
return p;
|
||||
return static_cast<T*>(lifoScope_.alloc().allocEnsureUnused(bytes, BallastSize));
|
||||
}
|
||||
|
||||
// View this allocator as a fallible allocator.
|
||||
|
|
|
@ -127,6 +127,10 @@ class JitRuntime
|
|||
// Thunk to enter the interpreter from JIT code.
|
||||
WriteOnceData<uint32_t> interpreterStubOffset_;
|
||||
|
||||
// Thunk to convert the value in R0 to int32 if it's a double.
|
||||
// Note: this stub treats -0 as +0 and may clobber R1.scratchReg().
|
||||
WriteOnceData<uint32_t> doubleToInt32ValueStubOffset_;
|
||||
|
||||
// Thunk used by the debugger for breakpoint and step mode.
|
||||
WriteOnceData<JitCode*> debugTrapHandler_;
|
||||
|
||||
|
@ -169,6 +173,7 @@ class JitRuntime
|
|||
private:
|
||||
void generateLazyLinkStub(MacroAssembler& masm);
|
||||
void generateInterpreterStub(MacroAssembler& masm);
|
||||
void generateDoubleToInt32ValueStub(MacroAssembler& masm);
|
||||
void generateProfilerExitFrameTailStub(MacroAssembler& masm, Label* profilerExitTail);
|
||||
void generateExceptionTailStub(MacroAssembler& masm, void* handler, Label* profilerExitTail);
|
||||
void generateBailoutTailStub(MacroAssembler& masm, Label* bailoutTail);
|
||||
|
@ -290,6 +295,10 @@ class JitRuntime
|
|||
return trampolineCode(interpreterStubOffset_);
|
||||
}
|
||||
|
||||
TrampolinePtr getDoubleToInt32ValueStub() const {
|
||||
return trampolineCode(doubleToInt32ValueStubOffset_);
|
||||
}
|
||||
|
||||
bool hasJitcodeGlobalTable() const {
|
||||
return jitcodeGlobalTable_ != nullptr;
|
||||
}
|
||||
|
|
|
@ -73,12 +73,6 @@ EmitReturnFromIC(MacroAssembler& masm)
|
|||
masm.ma_mov(lr, pc);
|
||||
}
|
||||
|
||||
inline void
|
||||
EmitChangeICReturnAddress(MacroAssembler& masm, Register reg)
|
||||
{
|
||||
masm.ma_mov(reg, lr);
|
||||
}
|
||||
|
||||
inline void
|
||||
EmitBaselineLeaveStubFrame(MacroAssembler& masm, bool calledIntoIon = false)
|
||||
{
|
||||
|
|
|
@ -73,12 +73,6 @@ EmitReturnFromIC(MacroAssembler& masm)
|
|||
masm.abiret(); // Defaults to lr.
|
||||
}
|
||||
|
||||
inline void
|
||||
EmitChangeICReturnAddress(MacroAssembler& masm, Register reg)
|
||||
{
|
||||
masm.movePtr(reg, lr);
|
||||
}
|
||||
|
||||
inline void
|
||||
EmitBaselineLeaveStubFrame(MacroAssembler& masm, bool calledIntoIon = false)
|
||||
{
|
||||
|
|
|
@ -83,12 +83,6 @@ EmitReturnFromIC(MacroAssembler& masm)
|
|||
masm.branch(ra);
|
||||
}
|
||||
|
||||
inline void
|
||||
EmitChangeICReturnAddress(MacroAssembler& masm, Register reg)
|
||||
{
|
||||
masm.movePtr(reg, ra);
|
||||
}
|
||||
|
||||
inline void
|
||||
EmitBaselineLeaveStubFrame(MacroAssembler& masm, bool calledIntoIon = false)
|
||||
{
|
||||
|
|
|
@ -17,7 +17,6 @@ inline void EmitRepushTailCallReg(MacroAssembler&) { MOZ_CRASH(); }
|
|||
inline void EmitCallIC(MacroAssembler&, CodeOffset*, CodeOffset*) { MOZ_CRASH(); }
|
||||
inline void EmitEnterTypeMonitorIC(MacroAssembler&, size_t v = 0) { MOZ_CRASH(); }
|
||||
inline void EmitReturnFromIC(MacroAssembler&) { MOZ_CRASH(); }
|
||||
inline void EmitChangeICReturnAddress(MacroAssembler&, Register) { MOZ_CRASH(); }
|
||||
inline void EmitBaselineLeaveStubFrame(MacroAssembler&, bool v = false) { MOZ_CRASH(); }
|
||||
inline void EmitStubGuardFailure(MacroAssembler&) { MOZ_CRASH(); }
|
||||
|
||||
|
|
|
@ -64,12 +64,6 @@ EmitReturnFromIC(MacroAssembler& masm)
|
|||
masm.ret();
|
||||
}
|
||||
|
||||
inline void
|
||||
EmitChangeICReturnAddress(MacroAssembler& masm, Register reg)
|
||||
{
|
||||
masm.storePtr(reg, Address(StackPointer, 0));
|
||||
}
|
||||
|
||||
inline void
|
||||
EmitBaselineLeaveStubFrame(MacroAssembler& masm, bool calledIntoIon = false)
|
||||
{
|
||||
|
|
|
@ -65,12 +65,6 @@ EmitReturnFromIC(MacroAssembler& masm)
|
|||
masm.ret();
|
||||
}
|
||||
|
||||
inline void
|
||||
EmitChangeICReturnAddress(MacroAssembler& masm, Register reg)
|
||||
{
|
||||
masm.storePtr(reg, Address(StackPointer, 0));
|
||||
}
|
||||
|
||||
inline void
|
||||
EmitBaselineLeaveStubFrame(MacroAssembler& masm, bool calledIntoIon = false)
|
||||
{
|
||||
|
|
|
@ -340,30 +340,9 @@ js::XDRScript(XDRState<mode>* xdr, HandleScope scriptEnclosingScope,
|
|||
/* NB: Keep this in sync with CopyScript. */
|
||||
|
||||
enum ScriptBits {
|
||||
NoScriptRval,
|
||||
Strict,
|
||||
ContainsDynamicNameAccess,
|
||||
FunHasExtensibleScope,
|
||||
FunHasAnyAliasedFormal,
|
||||
ArgumentsHasVarBinding,
|
||||
NeedsArgsObj,
|
||||
HasMappedArgsObj,
|
||||
FunctionHasThisBinding,
|
||||
FunctionHasExtraBodyVarScope,
|
||||
IsGenerator,
|
||||
IsAsync,
|
||||
HasRest,
|
||||
OwnSource,
|
||||
ExplicitUseStrict,
|
||||
SelfHosted,
|
||||
HasSingleton,
|
||||
TreatAsRunOnce,
|
||||
HasLazyScript,
|
||||
HasNonSyntacticScope,
|
||||
HasInnerFunctions,
|
||||
NeedsHomeObject,
|
||||
IsDerivedClassConstructor,
|
||||
IsDefaultClassConstructor,
|
||||
};
|
||||
|
||||
uint32_t length, lineno, column, nfixed, nslots;
|
||||
|
@ -374,6 +353,7 @@ js::XDRScript(XDRState<mode>* xdr, HandleScope scriptEnclosingScope,
|
|||
uint32_t nTypeSets = 0;
|
||||
uint32_t scriptBits = 0;
|
||||
uint32_t bodyScopeIndex = 0;
|
||||
uint32_t immutableFlags = 0;
|
||||
|
||||
JSContext* cx = xdr->cx();
|
||||
RootedScript script(cx);
|
||||
|
@ -414,6 +394,8 @@ js::XDRScript(XDRState<mode>* xdr, HandleScope scriptEnclosingScope,
|
|||
bodyScopeIndex = script->bodyScopeIndex();
|
||||
natoms = script->natoms();
|
||||
|
||||
immutableFlags = script->immutableFlags_;
|
||||
|
||||
nsrcnotes = script->numNotes();
|
||||
|
||||
nscopes = script->scopes().size();
|
||||
|
@ -436,79 +418,16 @@ js::XDRScript(XDRState<mode>* xdr, HandleScope scriptEnclosingScope,
|
|||
nTypeSets = script->nTypeSets();
|
||||
funLength = script->funLength();
|
||||
|
||||
if (script->noScriptRval()) {
|
||||
scriptBits |= (1 << NoScriptRval);
|
||||
}
|
||||
if (script->strict()) {
|
||||
scriptBits |= (1 << Strict);
|
||||
}
|
||||
if (script->explicitUseStrict()) {
|
||||
scriptBits |= (1 << ExplicitUseStrict);
|
||||
}
|
||||
if (script->selfHosted()) {
|
||||
scriptBits |= (1 << SelfHosted);
|
||||
}
|
||||
if (script->bindingsAccessedDynamically()) {
|
||||
scriptBits |= (1 << ContainsDynamicNameAccess);
|
||||
}
|
||||
if (script->funHasExtensibleScope()) {
|
||||
scriptBits |= (1 << FunHasExtensibleScope);
|
||||
}
|
||||
if (script->funHasAnyAliasedFormal()) {
|
||||
scriptBits |= (1 << FunHasAnyAliasedFormal);
|
||||
}
|
||||
if (script->argumentsHasVarBinding()) {
|
||||
scriptBits |= (1 << ArgumentsHasVarBinding);
|
||||
}
|
||||
if (script->analyzedArgsUsage() && script->needsArgsObj()) {
|
||||
scriptBits |= (1 << NeedsArgsObj);
|
||||
}
|
||||
if (script->hasMappedArgsObj()) {
|
||||
scriptBits |= (1 << HasMappedArgsObj);
|
||||
}
|
||||
if (script->functionHasThisBinding()) {
|
||||
scriptBits |= (1 << FunctionHasThisBinding);
|
||||
}
|
||||
if (script->functionHasExtraBodyVarScope()) {
|
||||
scriptBits |= (1 << FunctionHasExtraBodyVarScope);
|
||||
}
|
||||
MOZ_ASSERT_IF(sourceObjectArg, sourceObjectArg->source() == script->scriptSource());
|
||||
if (!sourceObjectArg) {
|
||||
scriptBits |= (1 << OwnSource);
|
||||
}
|
||||
if (script->isGenerator()) {
|
||||
scriptBits |= (1 << IsGenerator);
|
||||
}
|
||||
if (script->isAsync()) {
|
||||
scriptBits |= (1 << IsAsync);
|
||||
}
|
||||
if (script->hasRest()) {
|
||||
scriptBits |= (1 << HasRest);
|
||||
}
|
||||
if (script->hasSingletons()) {
|
||||
scriptBits |= (1 << HasSingleton);
|
||||
}
|
||||
if (script->treatAsRunOnce()) {
|
||||
scriptBits |= (1 << TreatAsRunOnce);
|
||||
}
|
||||
if (script->isRelazifiable()) {
|
||||
scriptBits |= (1 << HasLazyScript);
|
||||
}
|
||||
if (script->hasNonSyntacticScope()) {
|
||||
scriptBits |= (1 << HasNonSyntacticScope);
|
||||
}
|
||||
if (script->hasInnerFunctions()) {
|
||||
scriptBits |= (1 << HasInnerFunctions);
|
||||
}
|
||||
if (script->needsHomeObject()) {
|
||||
scriptBits |= (1 << NeedsHomeObject);
|
||||
}
|
||||
if (script->isDerivedClassConstructor()) {
|
||||
scriptBits |= (1 << IsDerivedClassConstructor);
|
||||
}
|
||||
if (script->isDefaultClassConstructor()) {
|
||||
scriptBits |= (1 << IsDefaultClassConstructor);
|
||||
}
|
||||
}
|
||||
|
||||
MOZ_TRY(xdr->codeUint32(&prologueLength));
|
||||
|
@ -525,6 +444,7 @@ js::XDRScript(XDRState<mode>* xdr, HandleScope scriptEnclosingScope,
|
|||
MOZ_TRY(xdr->codeUint32(&nTypeSets));
|
||||
MOZ_TRY(xdr->codeUint32(&funLength));
|
||||
MOZ_TRY(xdr->codeUint32(&scriptBits));
|
||||
MOZ_TRY(xdr->codeUint32(&immutableFlags));
|
||||
|
||||
MOZ_ASSERT(!!(scriptBits & (1 << OwnSource)) == !sourceObjectArg);
|
||||
RootedScriptSourceObject sourceObject(cx, sourceObjectArg);
|
||||
|
@ -534,18 +454,20 @@ js::XDRScript(XDRState<mode>* xdr, HandleScope scriptEnclosingScope,
|
|||
// the document. If the noScriptRval or selfHostingMode flag doesn't
|
||||
// match, we should fail. This only applies to the top-level and not
|
||||
// its inner functions.
|
||||
bool noScriptRval = !!(immutableFlags & uint32_t(ImmutableFlags::NoScriptRval));
|
||||
bool selfHosted = !!(immutableFlags & uint32_t(ImmutableFlags::SelfHosted));
|
||||
mozilla::Maybe<CompileOptions> options;
|
||||
if (xdr->hasOptions() && (scriptBits & (1 << OwnSource))) {
|
||||
options.emplace(xdr->cx(), xdr->options());
|
||||
if (options->noScriptRval != !!(scriptBits & (1 << NoScriptRval)) ||
|
||||
options->selfHostingMode != !!(scriptBits & (1 << SelfHosted)))
|
||||
if (options->noScriptRval != noScriptRval ||
|
||||
options->selfHostingMode != selfHosted)
|
||||
{
|
||||
return xdr->fail(JS::TranscodeResult_Failure_WrongCompileOption);
|
||||
}
|
||||
} else {
|
||||
options.emplace(xdr->cx());
|
||||
(*options).setNoScriptRval(!!(scriptBits & (1 << NoScriptRval)))
|
||||
.setSelfHostingMode(!!(scriptBits & (1 << SelfHosted)));
|
||||
(*options).setNoScriptRval(noScriptRval)
|
||||
.setSelfHostingMode(selfHosted);
|
||||
}
|
||||
|
||||
if (scriptBits & (1 << OwnSource)) {
|
||||
|
@ -611,66 +533,16 @@ js::XDRScript(XDRState<mode>* xdr, HandleScope scriptEnclosingScope,
|
|||
|
||||
scriptp.set(script);
|
||||
|
||||
if (scriptBits & (1 << Strict)) {
|
||||
script->setFlag(ImmutableFlags::Strict);
|
||||
}
|
||||
if (scriptBits & (1 << ExplicitUseStrict)) {
|
||||
script->setFlag(ImmutableFlags::ExplicitUseStrict);
|
||||
}
|
||||
if (scriptBits & (1 << ContainsDynamicNameAccess)) {
|
||||
script->setFlag(ImmutableFlags::BindingsAccessedDynamically);
|
||||
}
|
||||
if (scriptBits & (1 << FunHasExtensibleScope)) {
|
||||
script->setFlag(ImmutableFlags::FunHasExtensibleScope);
|
||||
}
|
||||
if (scriptBits & (1 << FunHasAnyAliasedFormal)) {
|
||||
script->setFlag(ImmutableFlags::FunHasAnyAliasedFormal);
|
||||
}
|
||||
if (scriptBits & (1 << ArgumentsHasVarBinding)) {
|
||||
script->immutableFlags_ = immutableFlags;
|
||||
|
||||
if (script->hasFlag(ImmutableFlags::ArgsHasVarBinding)) {
|
||||
// Call setArgumentsHasVarBinding to initialize the
|
||||
// NeedsArgsAnalysis flag.
|
||||
script->setArgumentsHasVarBinding();
|
||||
}
|
||||
if (scriptBits & (1 << NeedsArgsObj)) {
|
||||
script->setNeedsArgsObj(true);
|
||||
}
|
||||
if (scriptBits & (1 << HasMappedArgsObj)) {
|
||||
script->setFlag(ImmutableFlags::HasMappedArgsObj);
|
||||
}
|
||||
if (scriptBits & (1 << FunctionHasThisBinding)) {
|
||||
script->setFlag(ImmutableFlags::FunctionHasThisBinding);
|
||||
}
|
||||
if (scriptBits & (1 << FunctionHasExtraBodyVarScope)) {
|
||||
script->setFlag(ImmutableFlags::FunctionHasExtraBodyVarScope);
|
||||
}
|
||||
if (scriptBits & (1 << HasSingleton)) {
|
||||
script->setFlag(ImmutableFlags::HasSingletons);
|
||||
}
|
||||
if (scriptBits & (1 << TreatAsRunOnce)) {
|
||||
script->setFlag(ImmutableFlags::TreatAsRunOnce);
|
||||
}
|
||||
if (scriptBits & (1 << HasNonSyntacticScope)) {
|
||||
script->setFlag(ImmutableFlags::HasNonSyntacticScope);
|
||||
}
|
||||
if (scriptBits & (1 << HasInnerFunctions)) {
|
||||
script->setFlag(ImmutableFlags::HasInnerFunctions);
|
||||
}
|
||||
if (scriptBits & (1 << NeedsHomeObject)) {
|
||||
script->setFlag(ImmutableFlags::NeedsHomeObject);
|
||||
}
|
||||
if (scriptBits & (1 << IsDerivedClassConstructor)) {
|
||||
script->setFlag(ImmutableFlags::IsDerivedClassConstructor);
|
||||
}
|
||||
if (scriptBits & (1 << IsDefaultClassConstructor)) {
|
||||
script->setFlag(ImmutableFlags::IsDefaultClassConstructor);
|
||||
}
|
||||
if (scriptBits & (1 << IsGenerator)) {
|
||||
script->setFlag(ImmutableFlags::IsGenerator);
|
||||
}
|
||||
if (scriptBits & (1 << IsAsync)) {
|
||||
script->setFlag(ImmutableFlags::IsAsync);
|
||||
}
|
||||
if (scriptBits & (1 << HasRest)) {
|
||||
script->setFlag(ImmutableFlags::HasRest);
|
||||
}
|
||||
}
|
||||
|
||||
JS_STATIC_ASSERT(sizeof(jsbytecode) == 1);
|
||||
|
@ -3298,7 +3170,7 @@ JSScript::Create(JSContext* cx, const ReadOnlyCompileOptions& options,
|
|||
script->setFlag(ImmutableFlags::NoScriptRval, options.noScriptRval);
|
||||
script->setFlag(ImmutableFlags::SelfHosted, options.selfHostingMode);
|
||||
script->setFlag(ImmutableFlags::TreatAsRunOnce, options.isRunOnce);
|
||||
script->setFlag(ImmutableFlags::HideScriptFromDebugger, options.hideScriptFromDebugger);
|
||||
script->setFlag(MutableFlags::HideScriptFromDebugger, options.hideScriptFromDebugger);
|
||||
|
||||
if (cx->runtime()->lcovOutput().isEnabled()) {
|
||||
if (!script->initScriptName(cx)) {
|
||||
|
@ -3479,13 +3351,6 @@ JSScript::initFromFunctionBox(HandleScript script, frontend::FunctionBox* funbox
|
|||
/* static */ void
|
||||
JSScript::initFromModuleContext(HandleScript script)
|
||||
{
|
||||
script->clearFlag(ImmutableFlags::FunHasExtensibleScope);
|
||||
script->clearFlag(ImmutableFlags::NeedsHomeObject);
|
||||
script->clearFlag(ImmutableFlags::IsDerivedClassConstructor);
|
||||
script->funLength_ = 0;
|
||||
|
||||
script->clearFlag(ImmutableFlags::IsGenerator);
|
||||
|
||||
// Since modules are only run once, mark the script so that initializers
|
||||
// created within it may be given more precise types.
|
||||
script->setTreatAsRunOnce();
|
||||
|
@ -4011,8 +3876,6 @@ bool
|
|||
js::detail::CopyScript(JSContext* cx, HandleScript src, HandleScript dst,
|
||||
MutableHandle<GCVector<Scope*>> scopes)
|
||||
{
|
||||
using ImmutableFlags = JSScript::ImmutableFlags;
|
||||
|
||||
if (src->treatAsRunOnce() && !src->functionNonDelazifying()) {
|
||||
JS_ReportErrorASCII(cx, "No cloning toplevel run-once scripts");
|
||||
return false;
|
||||
|
@ -4119,31 +3982,19 @@ js::detail::CopyScript(JSContext* cx, HandleScript src, HandleScript dst,
|
|||
dst->bodyScopeIndex_ = src->bodyScopeIndex_;
|
||||
dst->funLength_ = src->funLength();
|
||||
dst->nTypeSets_ = src->nTypeSets();
|
||||
|
||||
dst->immutableFlags_ = src->immutableFlags_;
|
||||
dst->setFlag(JSScript::ImmutableFlags::HasNonSyntacticScope,
|
||||
scopes[0]->hasOnChain(ScopeKind::NonSyntactic));
|
||||
|
||||
dst->setFlag(JSScript::MutableFlags::HideScriptFromDebugger, src->hideScriptFromDebugger());
|
||||
|
||||
if (src->argumentsHasVarBinding()) {
|
||||
dst->setArgumentsHasVarBinding();
|
||||
if (src->analyzedArgsUsage()) {
|
||||
dst->setNeedsArgsObj(src->needsArgsObj());
|
||||
}
|
||||
}
|
||||
dst->setFlag(ImmutableFlags::HasMappedArgsObj, src->hasMappedArgsObj());
|
||||
dst->setFlag(ImmutableFlags::FunctionHasThisBinding, src->functionHasThisBinding());
|
||||
dst->setFlag(ImmutableFlags::FunctionHasExtraBodyVarScope, src->functionHasExtraBodyVarScope());
|
||||
dst->setFlag(ImmutableFlags::Strict, src->strict());
|
||||
dst->setFlag(ImmutableFlags::ExplicitUseStrict, src->explicitUseStrict());
|
||||
dst->setFlag(ImmutableFlags::HasNonSyntacticScope, scopes[0]->hasOnChain(ScopeKind::NonSyntactic));
|
||||
dst->setFlag(ImmutableFlags::BindingsAccessedDynamically, src->bindingsAccessedDynamically());
|
||||
dst->setFlag(ImmutableFlags::FunHasExtensibleScope, src->funHasExtensibleScope());
|
||||
dst->setFlag(ImmutableFlags::FunHasAnyAliasedFormal, src->funHasAnyAliasedFormal());
|
||||
dst->setFlag(ImmutableFlags::HasSingletons, src->hasSingletons());
|
||||
dst->setFlag(ImmutableFlags::TreatAsRunOnce, src->treatAsRunOnce());
|
||||
dst->setFlag(ImmutableFlags::HasInnerFunctions, src->hasInnerFunctions());
|
||||
dst->setFlag(ImmutableFlags::IsGenerator, src->isGenerator());
|
||||
dst->setFlag(ImmutableFlags::IsDerivedClassConstructor, src->isDerivedClassConstructor());
|
||||
dst->setFlag(ImmutableFlags::NeedsHomeObject, src->needsHomeObject());
|
||||
dst->setFlag(ImmutableFlags::IsDefaultClassConstructor, src->isDefaultClassConstructor());
|
||||
dst->setFlag(ImmutableFlags::IsAsync, src->isAsync());
|
||||
dst->setFlag(ImmutableFlags::HasRest, src->hasRest());
|
||||
dst->setFlag(ImmutableFlags::HideScriptFromDebugger, src->hideScriptFromDebugger());
|
||||
|
||||
{
|
||||
auto array = dst->data_->scopes();
|
||||
|
|
|
@ -1689,20 +1689,17 @@ class JSScript : public js::gc::TenuredCell
|
|||
// 'this', 'arguments' and f.apply() are used. This is likely to be a wrapper.
|
||||
IsLikelyConstructorWrapper = 1 << 17,
|
||||
|
||||
// Set if the debugger's onNewScript hook has not yet been called.
|
||||
HideScriptFromDebugger = 1 << 18,
|
||||
|
||||
// Set if this function is a generator function or async generator.
|
||||
IsGenerator = 1 << 19,
|
||||
IsGenerator = 1 << 18,
|
||||
|
||||
// Set if this function is an async function or async generator.
|
||||
IsAsync = 1 << 20,
|
||||
IsAsync = 1 << 19,
|
||||
|
||||
// Set if this function has a rest parameter.
|
||||
HasRest = 1 << 21,
|
||||
HasRest = 1 << 20,
|
||||
|
||||
// See comments below.
|
||||
ArgsHasVarBinding = 1 << 22,
|
||||
ArgsHasVarBinding = 1 << 21,
|
||||
};
|
||||
uint32_t immutableFlags_ = 0;
|
||||
|
||||
|
@ -1768,6 +1765,9 @@ class JSScript : public js::gc::TenuredCell
|
|||
// See comments below.
|
||||
NeedsArgsAnalysis = 1 << 17,
|
||||
NeedsArgsObj = 1 << 18,
|
||||
|
||||
// Set if the debugger's onNewScript hook has not yet been called.
|
||||
HideScriptFromDebugger = 1 << 19,
|
||||
};
|
||||
uint32_t mutableFlags_ = 0;
|
||||
|
||||
|
@ -2171,10 +2171,10 @@ class JSScript : public js::gc::TenuredCell
|
|||
}
|
||||
|
||||
bool hideScriptFromDebugger() const {
|
||||
return hasFlag(ImmutableFlags::HideScriptFromDebugger);
|
||||
return hasFlag(MutableFlags::HideScriptFromDebugger);
|
||||
}
|
||||
void clearHideScriptFromDebugger() {
|
||||
clearFlag(ImmutableFlags::HideScriptFromDebugger);
|
||||
clearFlag(MutableFlags::HideScriptFromDebugger);
|
||||
}
|
||||
|
||||
bool needsHomeObject() const {
|
||||
|
|
|
@ -705,7 +705,7 @@
|
|||
* Stack: i =>
|
||||
* len: len
|
||||
*/ \
|
||||
macro(JSOP_TABLESWITCH, 70, "tableswitch", NULL, 16, 1, 0, JOF_TABLESWITCH|JOF_DETECTING|JOF_IC) \
|
||||
macro(JSOP_TABLESWITCH, 70, "tableswitch", NULL, 16, 1, 0, JOF_TABLESWITCH|JOF_DETECTING) \
|
||||
/*
|
||||
* Prologue emitted in scripts expected to run once, which deoptimizes code
|
||||
* if it executes multiple times.
|
||||
|
|
|
@ -28,6 +28,8 @@
|
|||
//
|
||||
// See the comment at the top of ServoBindingTypes.h for how to use these.
|
||||
|
||||
// clang-format off
|
||||
// Needs to be a on single line
|
||||
GECKO_BORROWED_TYPE(mozilla::dom::Element, RawGeckoElement)
|
||||
GECKO_BORROWED_TYPE(nsIDocument, RawGeckoDocument)
|
||||
GECKO_BORROWED_TYPE(nsINode, RawGeckoNode)
|
||||
|
@ -50,3 +52,5 @@ GECKO_BORROWED_TYPE_MUT(nsTArray<nsCSSPropertyID>, RawGeckoCSSPropertyIDList)
|
|||
GECKO_BORROWED_TYPE_MUT(nsTArray<nsFontFaceRuleContainer>, RawGeckoFontFaceRuleList)
|
||||
GECKO_BORROWED_TYPE_MUT(nsTArray<RefPtr<RawServoAnimationValue>>, RawGeckoServoAnimationValueList)
|
||||
GECKO_BORROWED_TYPE_MUT(nsTimingFunction, nsTimingFunction)
|
||||
// clang-format on
|
||||
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
// If you add an entry to this file, you should also add an impl_arc_ffi!()
|
||||
// call to servo/components/style/gecko/arc_types.rs.
|
||||
|
||||
// clang-format off
|
||||
// Needs to be a on single line
|
||||
SERVO_ARC_TYPE(CssRules, ServoCssRules)
|
||||
SERVO_ARC_TYPE(StyleSheetContents, RawServoStyleSheetContents)
|
||||
SERVO_ARC_TYPE(DeclarationBlock, RawServoDeclarationBlock)
|
||||
|
@ -40,3 +42,4 @@ SERVO_ARC_TYPE(FontFaceRule, RawServoFontFaceRule)
|
|||
SERVO_ARC_TYPE(CounterStyleRule, RawServoCounterStyleRule)
|
||||
SERVO_ARC_TYPE(CssUrlData, RawServoCssUrlData)
|
||||
SERVO_ARC_TYPE(Quotes, RawServoQuotes)
|
||||
// clang-format on
|
||||
|
|
|
@ -24,8 +24,12 @@
|
|||
//
|
||||
// TODO(heycam): Do some of this automatically.
|
||||
|
||||
// clang-format off
|
||||
// Needs to be a on single line
|
||||
SERVO_BOXED_TYPE(StyleSet, RawServoStyleSet)
|
||||
SERVO_BOXED_TYPE(AuthorStyles, RawServoAuthorStyles)
|
||||
SERVO_BOXED_TYPE(SelectorList, RawServoSelectorList)
|
||||
SERVO_BOXED_TYPE(SourceSizeList, RawServoSourceSizeList)
|
||||
SERVO_BOXED_TYPE(UseCounters, StyleUseCounters)
|
||||
// clang-format on
|
||||
|
||||
|
|
|
@ -0,0 +1,433 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
ChromeUtils.import("resource://gre/modules/GeckoViewChildModule.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
class GeckoViewMediaChild extends GeckoViewChildModule {
|
||||
onInit() {
|
||||
this._videoIndex = 0;
|
||||
XPCOMUtils.defineLazyGetter(this, "_videos", () =>
|
||||
new Map());
|
||||
this._mediaEvents = [
|
||||
"abort", "canplay", "canplaythrough", "durationchange", "emptied",
|
||||
"ended", "error", "loadeddata", "loadedmetadata", "pause", "play", "playing",
|
||||
"progress", "ratechange", "resize", "seeked", "seeking", "stalled", "suspend",
|
||||
"timeupdate", "volumechange", "waiting",
|
||||
];
|
||||
|
||||
this._mediaEventCallback = (event) => {
|
||||
this.handleMediaEvent(event);
|
||||
};
|
||||
this._fullscreenMedia = null;
|
||||
this._stateSymbol = Symbol();
|
||||
}
|
||||
|
||||
onEnable() {
|
||||
debug `onEnable`;
|
||||
addEventListener("UAWidgetBindToTree", this, false);
|
||||
addEventListener("MozDOMFullscreen:Entered", this, false);
|
||||
addEventListener("MozDOMFullscreen:Exited", this, false);
|
||||
addEventListener("pagehide", this, false);
|
||||
|
||||
this.messageManager.addMessageListener("GeckoView:MediaObserve", this);
|
||||
this.messageManager.addMessageListener("GeckoView:MediaUnobserve", this);
|
||||
this.messageManager.addMessageListener("GeckoView:MediaPlay", this);
|
||||
this.messageManager.addMessageListener("GeckoView:MediaPause", this);
|
||||
this.messageManager.addMessageListener("GeckoView:MediaSeek", this);
|
||||
this.messageManager.addMessageListener("GeckoView:MediaSetVolume", this);
|
||||
this.messageManager.addMessageListener("GeckoView:MediaSetMuted", this);
|
||||
this.messageManager.addMessageListener("GeckoView:MediaSetPlaybackRate", this);
|
||||
}
|
||||
|
||||
onDisable() {
|
||||
debug `onDisable`;
|
||||
|
||||
removeEventListener("UAWidgetBindToTree", this);
|
||||
removeEventListener("MozDOMFullscreen:Entered", this);
|
||||
removeEventListener("MozDOMFullscreen:Exited", this);
|
||||
removeEventListener("pagehide", this);
|
||||
|
||||
this.messageManager.removeMessageListener("GeckoView:MediaObserve", this);
|
||||
this.messageManager.removeMessageListener("GeckoView:MediaUnobserve", this);
|
||||
this.messageManager.removeMessageListener("GeckoView:MediaPlay", this);
|
||||
this.messageManager.removeMessageListener("GeckoView:MediaPause", this);
|
||||
this.messageManager.removeMessageListener("GeckoView:MediaSeek", this);
|
||||
this.messageManager.removeMessageListener("GeckoView:MediaSetVolume", this);
|
||||
this.messageManager.removeMessageListener("GeckoView:MediaSetMuted", this);
|
||||
this.messageManager.removeMessageListener("GeckoView:MediaSetPlaybackRate", this);
|
||||
}
|
||||
|
||||
receiveMessage(aMsg) {
|
||||
debug `receiveMessage: ${aMsg.name}`;
|
||||
|
||||
const data = aMsg.data;
|
||||
const element = this.findElement(data.id);
|
||||
if (!element) {
|
||||
warn `Didn't find HTMLMediaElement with id: ${data.id}`;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (aMsg.name) {
|
||||
case "GeckoView:MediaObserve":
|
||||
this.observeMedia(element);
|
||||
break;
|
||||
case "GeckoView:MediaUnobserve":
|
||||
this.unobserveMedia(element);
|
||||
break;
|
||||
case "GeckoView:MediaPlay":
|
||||
element.play();
|
||||
break;
|
||||
case "GeckoView:MediaPause":
|
||||
element.pause();
|
||||
break;
|
||||
case "GeckoView:MediaSeek":
|
||||
element.currentTime = data.time;
|
||||
break;
|
||||
case "GeckoView:MediaSetVolume":
|
||||
element.volume = data.volume;
|
||||
break;
|
||||
case "GeckoView:MediaSetMuted":
|
||||
element.muted = !!data.muted;
|
||||
break;
|
||||
case "GeckoView:MediaSetPlaybackRate":
|
||||
element.playbackRate = data.playbackRate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
handleEvent(aEvent) {
|
||||
debug `handleEvent: ${aEvent.type}`;
|
||||
|
||||
switch (aEvent.type) {
|
||||
case "UAWidgetBindToTree":
|
||||
this.handleNewMedia(aEvent.composedTarget);
|
||||
break;
|
||||
case "MozDOMFullscreen:Entered":
|
||||
const element = content && content.document.fullscreenElement;
|
||||
if (this.isMedia(element)) {
|
||||
this.handleFullscreenChange(element);
|
||||
} else {
|
||||
// document.fullscreenElement can be a div container instead of the HTMLMediaElement
|
||||
// in some pages (e.g Vimeo).
|
||||
const childrenMedias = element.getElementsByTagName("video");
|
||||
if (childrenMedias && childrenMedias.length > 0) {
|
||||
this.handleFullscreenChange(childrenMedias[0]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "MozDOMFullscreen:Exited":
|
||||
this.handleFullscreenChange(null);
|
||||
break;
|
||||
case "pagehide":
|
||||
if (aEvent.target === content.document) {
|
||||
this.handleWindowUnload();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
handleWindowUnload() {
|
||||
for (const weakElement of this._videos.values()) {
|
||||
const element = weakElement.get();
|
||||
if (element) {
|
||||
this.unobserveMedia(element);
|
||||
}
|
||||
}
|
||||
if (this._videos.size > 0) {
|
||||
this.notifyMediaRemoveAll();
|
||||
}
|
||||
this._videos.clear();
|
||||
this._fullscreenMedia = null;
|
||||
}
|
||||
|
||||
handleNewMedia(aElement) {
|
||||
if (this.getState(aElement) || !this.isMedia(aElement)) {
|
||||
return;
|
||||
}
|
||||
const state = {
|
||||
id: ++this._videoIndex,
|
||||
notified: false,
|
||||
observing: false,
|
||||
};
|
||||
aElement[this._stateSymbol] = state;
|
||||
this._videos.set(state.id, Cu.getWeakReference(aElement));
|
||||
|
||||
this.notifyNewMedia(aElement);
|
||||
}
|
||||
|
||||
notifyNewMedia(aElement) {
|
||||
this.getState(aElement).notified = true;
|
||||
const message = {
|
||||
type: "GeckoView:MediaAdd",
|
||||
id: this.getState(aElement).id,
|
||||
};
|
||||
|
||||
this.fillMetadata(aElement, message);
|
||||
this.eventDispatcher.sendRequest(message);
|
||||
}
|
||||
|
||||
observeMedia(aElement) {
|
||||
if (this.isObserving(aElement)) {
|
||||
return;
|
||||
}
|
||||
this.getState(aElement).observing = true;
|
||||
|
||||
for (const name of this._mediaEvents) {
|
||||
aElement.addEventListener(name, this._mediaEventCallback, true);
|
||||
}
|
||||
|
||||
// Notify current state
|
||||
this.notifyTimeChange(aElement);
|
||||
this.notifyVolumeChange(aElement);
|
||||
this.notifyRateChange(aElement);
|
||||
if (aElement.readyState >= 1) {
|
||||
this.notifyMetadataChange(aElement);
|
||||
}
|
||||
this.notifyReadyStateChange(aElement);
|
||||
if (!aElement.paused) {
|
||||
this.notifyPlaybackStateChange(aElement, "play");
|
||||
}
|
||||
if (aElement === this._fullscreenMedia) {
|
||||
this.notifyFullscreenChange(aElement, true);
|
||||
}
|
||||
if (this.hasError(aElement)) {
|
||||
this.notifyMediaError(aElement);
|
||||
}
|
||||
}
|
||||
|
||||
unobserveMedia(aElement) {
|
||||
if (!this.isObserving(aElement)) {
|
||||
return;
|
||||
}
|
||||
this.getState(aElement).observing = false;
|
||||
for (const name of this._mediaEvents) {
|
||||
aElement.removeEventListener(name, this._mediaEventCallback);
|
||||
}
|
||||
}
|
||||
|
||||
isMedia(aElement) {
|
||||
if (!aElement) {
|
||||
return false;
|
||||
}
|
||||
const elementType = ChromeUtils.getClassName(aElement);
|
||||
return (elementType === "HTMLVideoElement") || (elementType === "HTMLAudioElement");
|
||||
}
|
||||
|
||||
isObserving(aElement) {
|
||||
return aElement && this.getState(aElement) && this.getState(aElement).observing;
|
||||
}
|
||||
|
||||
findElement(aId) {
|
||||
for (const weakElement of this._videos.values()) {
|
||||
const element = weakElement.get();
|
||||
if (element && this.getState(element).id === aId) {
|
||||
return element;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getState(aElement) {
|
||||
return aElement[this._stateSymbol];
|
||||
}
|
||||
|
||||
hasError(aElement) {
|
||||
// We either have an explicit error, or networkState is set to NETWORK_NO_SOURCE
|
||||
// after selecting a source.
|
||||
return aElement.error != null ||
|
||||
(aElement.networkState === aElement.NETWORK_NO_SOURCE &&
|
||||
this.hasSources(aElement));
|
||||
}
|
||||
|
||||
hasSources(aElement) {
|
||||
if (aElement.hasAttribute("src") && aElement.getAttribute("src") !== "") {
|
||||
return true;
|
||||
}
|
||||
for (var child = aElement.firstChild; child !== null; child = child.nextElementSibling) {
|
||||
if (child instanceof content.window.HTMLSourceElement) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fillMetadata(aElement, aOut) {
|
||||
aOut.src = aElement.currentSrc || aElement.src;
|
||||
aOut.width = aElement.videoWidth || 0;
|
||||
aOut.height = aElement.videoHeight || 0;
|
||||
aOut.duration = aElement.duration;
|
||||
aOut.seekable = !!aElement.seekable;
|
||||
if (aElement.audioTracks) {
|
||||
aOut.audioTrackCount = aElement.audioTracks.length;
|
||||
} else {
|
||||
aOut.audioTrackCount = (aElement.mozHasAudio || aElement.webkitAudioDecodedByteCount ||
|
||||
ChromeUtils.getClassName(aElement) === "HTMLAudioElement") ? 1 : 0;
|
||||
}
|
||||
if (aElement.videoTracks) {
|
||||
aOut.videoTrackCount = aElement.videoTracks.length;
|
||||
} else {
|
||||
aOut.videoTrackCount = ChromeUtils.getClassName(aElement) === "HTMLVideoElement" ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
handleMediaEvent(aEvent) {
|
||||
const element = aEvent.target;
|
||||
if (!this.isObserving(element)) {
|
||||
return;
|
||||
}
|
||||
switch (aEvent.type) {
|
||||
case "timeupdate":
|
||||
this.notifyTimeChange(element);
|
||||
break;
|
||||
case "volumechange":
|
||||
this.notifyVolumeChange(element);
|
||||
break;
|
||||
case "loadedmetadata":
|
||||
this.notifyMetadataChange(element);
|
||||
this.notifyReadyStateChange(element);
|
||||
break;
|
||||
case "ratechange":
|
||||
this.notifyRateChange(element);
|
||||
break;
|
||||
case "error":
|
||||
this.notifyMediaError(element);
|
||||
break;
|
||||
case "progress":
|
||||
this.notifyLoadProgress(element, aEvent);
|
||||
break;
|
||||
case "durationchange": // Fallthrough
|
||||
case "resize":
|
||||
this.notifyMetadataChange(element);
|
||||
break;
|
||||
case "canplay": // Fallthrough
|
||||
case "canplaythrough": // Fallthrough
|
||||
case "loadeddata":
|
||||
this.notifyReadyStateChange(element);
|
||||
break;
|
||||
default:
|
||||
this.notifyPlaybackStateChange(element, aEvent.type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
handleFullscreenChange(aElement) {
|
||||
if (aElement === this._fullscreenMedia) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isObserving(this._fullscreenMedia)) {
|
||||
this.notifyFullscreenChange(this._fullscreenMedia, false);
|
||||
}
|
||||
this._fullscreenMedia = aElement;
|
||||
|
||||
if (this.isObserving(aElement)) {
|
||||
this.notifyFullscreenChange(aElement, true);
|
||||
}
|
||||
}
|
||||
|
||||
notifyPlaybackStateChange(aElement, aName) {
|
||||
this.eventDispatcher.sendRequest({
|
||||
type: "GeckoView:MediaPlaybackStateChanged",
|
||||
id: this.getState(aElement).id,
|
||||
playbackState: aName,
|
||||
});
|
||||
}
|
||||
|
||||
notifyReadyStateChange(aElement) {
|
||||
this.eventDispatcher.sendRequest({
|
||||
type: "GeckoView:MediaReadyStateChanged",
|
||||
id: this.getState(aElement).id,
|
||||
readyState: aElement.readyState,
|
||||
});
|
||||
}
|
||||
|
||||
notifyMetadataChange(aElement) {
|
||||
const message = {
|
||||
type: "GeckoView:MediaMetadataChanged",
|
||||
id: this.getState(aElement).id,
|
||||
};
|
||||
this.fillMetadata(aElement, message);
|
||||
this.eventDispatcher.sendRequest(message);
|
||||
}
|
||||
|
||||
notifyLoadProgress(aElement, aEvent) {
|
||||
const message = {
|
||||
type: "GeckoView:MediaProgress",
|
||||
id: this.getState(aElement).id,
|
||||
loadedBytes: aEvent.lengthComputable ? aEvent.loaded : -1,
|
||||
totalBytes: aEvent.lengthComputable ? aEvent.total : -1,
|
||||
};
|
||||
if (aElement.buffered && aElement.buffered.length > 0) {
|
||||
message.timeRangeStarts = [];
|
||||
message.timeRangeEnds = [];
|
||||
for (let i = 0; i < aElement.buffered.length; ++i) {
|
||||
message.timeRangeStarts.push(aElement.buffered.start(i));
|
||||
message.timeRangeEnds.push(aElement.buffered.end(i));
|
||||
}
|
||||
}
|
||||
this.eventDispatcher.sendRequest(message);
|
||||
}
|
||||
|
||||
notifyTimeChange(aElement) {
|
||||
this.eventDispatcher.sendRequest({
|
||||
type: "GeckoView:MediaTimeChanged",
|
||||
id: this.getState(aElement).id,
|
||||
time: aElement.currentTime,
|
||||
});
|
||||
}
|
||||
|
||||
notifyRateChange(aElement) {
|
||||
this.eventDispatcher.sendRequest({
|
||||
type: "GeckoView:MediaRateChanged",
|
||||
id: this.getState(aElement).id,
|
||||
rate: aElement.playbackRate,
|
||||
});
|
||||
}
|
||||
|
||||
notifyVolumeChange(aElement) {
|
||||
this.eventDispatcher.sendRequest({
|
||||
type: "GeckoView:MediaVolumeChanged",
|
||||
id: this.getState(aElement).id,
|
||||
volume: aElement.volume,
|
||||
muted: !!aElement.muted,
|
||||
});
|
||||
}
|
||||
|
||||
notifyFullscreenChange(aElement, aIsFullscreen) {
|
||||
this.eventDispatcher.sendRequest({
|
||||
type: "GeckoView:MediaFullscreenChanged",
|
||||
id: this.getState(aElement).id,
|
||||
fullscreen: aIsFullscreen,
|
||||
});
|
||||
}
|
||||
|
||||
notifyMediaError(aElement) {
|
||||
let code = aElement.error ? aElement.error.code : 0;
|
||||
this.eventDispatcher.sendRequest({
|
||||
type: "GeckoView:MediaError",
|
||||
id: this.getState(aElement).id,
|
||||
code: code,
|
||||
});
|
||||
}
|
||||
|
||||
notifyMediaRemove(aElement) {
|
||||
this.eventDispatcher.sendRequest({
|
||||
type: "GeckoView:MediaRemove",
|
||||
id: this.getState(aElement).id,
|
||||
});
|
||||
}
|
||||
|
||||
notifyMediaRemoveAll() {
|
||||
this.eventDispatcher.sendRequest({
|
||||
type: "GeckoView:MediaRemoveAll",
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const {debug, warn} = GeckoViewMediaChild.initLogging("GeckoViewMedia");
|
||||
const module = GeckoViewMediaChild.create(this);
|
|
@ -391,6 +391,12 @@ function startup() {
|
|||
resource: "resource://gre/modules/GeckoViewContent.jsm",
|
||||
frameScript: "chrome://geckoview/content/GeckoViewContentChild.js",
|
||||
},
|
||||
}, {
|
||||
name: "GeckoViewMedia",
|
||||
onEnable: {
|
||||
resource: "resource://gre/modules/GeckoViewMedia.jsm",
|
||||
frameScript: "chrome://geckoview/content/GeckoViewMediaChild.js",
|
||||
},
|
||||
}, {
|
||||
name: "GeckoViewNavigation",
|
||||
onInit: {
|
||||
|
|
|
@ -13,6 +13,7 @@ geckoview.jar:
|
|||
content/geckoview.xul
|
||||
content/geckoview.js
|
||||
content/GeckoViewContentChild.js
|
||||
content/GeckoViewMediaChild.js
|
||||
content/GeckoViewNavigationChild.js
|
||||
content/GeckoViewProgressChild.js
|
||||
content/GeckoViewPromptChild.js
|
||||
|
|
|
@ -10,6 +10,7 @@ import org.mozilla.geckoview.GeckoResponse
|
|||
import org.mozilla.geckoview.GeckoResult
|
||||
import org.mozilla.geckoview.GeckoSession
|
||||
import org.mozilla.geckoview.GeckoSession.NavigationDelegate.LoadRequest
|
||||
import org.mozilla.geckoview.MediaElement
|
||||
import org.mozilla.geckoview.WebRequestError
|
||||
|
||||
import android.view.inputmethod.CursorAnchorInfo
|
||||
|
@ -21,7 +22,7 @@ class Callbacks private constructor() {
|
|||
|
||||
interface All : ContentDelegate, NavigationDelegate, PermissionDelegate, ProgressDelegate,
|
||||
PromptDelegate, ScrollDelegate, SelectionActionDelegate, TextInputDelegate,
|
||||
TrackingProtectionDelegate
|
||||
TrackingProtectionDelegate, MediaDelegate
|
||||
|
||||
interface ContentDelegate : GeckoSession.ContentDelegate {
|
||||
override fun onTitleChange(session: GeckoSession, title: String) {
|
||||
|
@ -177,4 +178,12 @@ class Callbacks private constructor() {
|
|||
override fun notifyAutoFill(session: GeckoSession, notification: Int, virtualId: Int) {
|
||||
}
|
||||
}
|
||||
|
||||
interface MediaDelegate: GeckoSession.MediaDelegate {
|
||||
override fun onMediaAdd(session: GeckoSession, element: MediaElement) {
|
||||
}
|
||||
|
||||
override fun onMediaRemove(session: GeckoSession, element: MediaElement) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,6 +49,7 @@ import android.support.annotation.StringDef;
|
|||
import android.support.annotation.UiThread;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
import android.util.LongSparseArray;
|
||||
import android.view.Surface;
|
||||
import android.view.inputmethod.CursorAnchorInfo;
|
||||
import android.view.inputmethod.ExtractedText;
|
||||
|
@ -519,10 +520,85 @@ public class GeckoSession extends LayerSession
|
|||
}
|
||||
};
|
||||
|
||||
private LongSparseArray<MediaElement> mMediaElements = new LongSparseArray<>();
|
||||
/* package */ LongSparseArray<MediaElement> getMediaElements() {
|
||||
return mMediaElements;
|
||||
}
|
||||
private final GeckoSessionHandler<MediaDelegate> mMediaHandler =
|
||||
new GeckoSessionHandler<MediaDelegate>(
|
||||
"GeckoViewMedia", this,
|
||||
new String[]{
|
||||
"GeckoView:MediaAdd",
|
||||
"GeckoView:MediaRemove",
|
||||
"GeckoView:MediaRemoveAll",
|
||||
"GeckoView:MediaReadyStateChanged",
|
||||
"GeckoView:MediaTimeChanged",
|
||||
"GeckoView:MediaPlaybackStateChanged",
|
||||
"GeckoView:MediaMetadataChanged",
|
||||
"GeckoView:MediaProgress",
|
||||
"GeckoView:MediaVolumeChanged",
|
||||
"GeckoView:MediaRateChanged",
|
||||
"GeckoView:MediaFullscreenChanged",
|
||||
"GeckoView:MediaError",
|
||||
}
|
||||
) {
|
||||
@Override
|
||||
public void handleMessage(final MediaDelegate delegate,
|
||||
final String event,
|
||||
final GeckoBundle message,
|
||||
final EventCallback callback) {
|
||||
if ("GeckoView:MediaAdd".equals(event)) {
|
||||
final MediaElement element = new MediaElement(message.getLong("id"), GeckoSession.this);
|
||||
delegate.onMediaAdd(GeckoSession.this, element);
|
||||
return;
|
||||
} else if ("GeckoView:MediaRemoveAll".equals(event)) {
|
||||
for (int i = 0; i < mMediaElements.size(); i++) {
|
||||
final long key = mMediaElements.keyAt(i);
|
||||
delegate.onMediaRemove(GeckoSession.this, mMediaElements.get(key));
|
||||
}
|
||||
mMediaElements.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
final long id = message.getLong("id", 0);
|
||||
final MediaElement element = mMediaElements.get(id);
|
||||
if (element == null) {
|
||||
Log.w(LOGTAG, "MediaElement not found for '" + id + "'");
|
||||
return;
|
||||
}
|
||||
|
||||
if ("GeckoView:MediaTimeChanged".equals(event)) {
|
||||
element.notifyTimeChange(message.getDouble("time"));
|
||||
} else if ("GeckoView:MediaProgress".equals(event)) {
|
||||
element.notifyLoadProgress(message);
|
||||
} else if ("GeckoView:MediaMetadataChanged".equals(event)) {
|
||||
element.notifyMetadataChange(message);
|
||||
} else if ("GeckoView:MediaReadyStateChanged".equals(event)) {
|
||||
element.notifyReadyStateChange(message.getInt("readyState"));
|
||||
} else if ("GeckoView:MediaPlaybackStateChanged".equals(event)) {
|
||||
element.notifyPlaybackStateChange(message.getString("playbackState"));
|
||||
} else if ("GeckoView:MediaVolumeChanged".equals(event)) {
|
||||
element.notifyVolumeChange(message.getDouble("volume"), message.getBoolean("muted"));
|
||||
} else if ("GeckoView:MediaRateChanged".equals(event)) {
|
||||
element.notifyPlaybackRateChange(message.getDouble("rate"));
|
||||
} else if ("GeckoView:MediaFullscreenChanged".equals(event)) {
|
||||
element.notifyFullscreenChange(message.getBoolean("fullscreen"));
|
||||
} else if ("GeckoView:MediaRemove".equals(event)) {
|
||||
delegate.onMediaRemove(GeckoSession.this, element);
|
||||
mMediaElements.remove(element.getVideoId());
|
||||
} else if ("GeckoView:MediaError".equals(event)) {
|
||||
element.notifyError(message.getInt("code"));
|
||||
} else {
|
||||
throw new UnsupportedOperationException(event + " media message not implemented");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/* package */ int handlersCount;
|
||||
|
||||
private final GeckoSessionHandler<?>[] mSessionHandlers = new GeckoSessionHandler<?>[] {
|
||||
mContentHandler, mNavigationHandler, mProgressHandler, mScrollHandler,
|
||||
mContentHandler, mMediaHandler, mNavigationHandler, mProgressHandler, mScrollHandler,
|
||||
mTrackingProtectionHandler, mPermissionHandler, mSelectionActionDelegate
|
||||
};
|
||||
|
||||
|
@ -1595,6 +1671,24 @@ public class GeckoSession extends LayerSession
|
|||
mSelectionActionDelegate.setDelegate(delegate, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the media callback handler.
|
||||
* This will replace the current handler.
|
||||
* @param delegate An implementation of MediaDelegate.
|
||||
*/
|
||||
public void setMediaDelegate(final @Nullable MediaDelegate delegate) {
|
||||
mMediaHandler.setDelegate(delegate, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Media callback handler.
|
||||
* @return The current Media callback handler.
|
||||
*/
|
||||
public @Nullable MediaDelegate getMediaDelegate() {
|
||||
return mMediaHandler.getDelegate();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the current selection action delegate for this GeckoSession.
|
||||
*
|
||||
|
@ -3504,4 +3598,22 @@ public class GeckoSession extends LayerSession
|
|||
void notifyAutoFill(@NonNull GeckoSession session, @AutoFillNotification int notification,
|
||||
int virtualId);
|
||||
}
|
||||
|
||||
/**
|
||||
* GeckoSession applications implement this interface to handle media events.
|
||||
*/
|
||||
public interface MediaDelegate {
|
||||
/**
|
||||
* An HTMLMediaElement has been created.
|
||||
* @param session Session instance.
|
||||
* @param element The media element that was just created.
|
||||
*/
|
||||
void onMediaAdd(@NonNull GeckoSession session, @NonNull MediaElement element);
|
||||
/**
|
||||
* An HTMLMediaElement has been unloaded.
|
||||
* @param session Session instance.
|
||||
* @param element The media element that was unloaded.
|
||||
*/
|
||||
void onMediaRemove(@NonNull GeckoSession session, @NonNull MediaElement element);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,540 @@
|
|||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* vim: ts=4 sw=4 expandtab:
|
||||
* 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/. */
|
||||
|
||||
package org.mozilla.geckoview;
|
||||
|
||||
import android.support.annotation.IntDef;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import org.mozilla.gecko.util.GeckoBundle;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* GeckoSession applications can use this class to handle media events
|
||||
* and control the HTMLMediaElement externally.
|
||||
**/
|
||||
public class MediaElement {
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({MEDIA_STATE_PLAY, MEDIA_STATE_PLAYING, MEDIA_STATE_PAUSE,
|
||||
MEDIA_STATE_ENDED, MEDIA_STATE_SEEKING, MEDIA_STATE_SEEKED,
|
||||
MEDIA_STATE_STALLED, MEDIA_STATE_SUSPEND, MEDIA_STATE_WAITING,
|
||||
MEDIA_STATE_ABORT, MEDIA_STATE_EMPTIED})
|
||||
/* package */ @interface MediaStateFlags {}
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({MEDIA_READY_STATE_HAVE_NOTHING, MEDIA_READY_STATE_HAVE_METADATA,
|
||||
MEDIA_READY_STATE_HAVE_CURRENT_DATA, MEDIA_READY_STATE_HAVE_FUTURE_DATA,
|
||||
MEDIA_READY_STATE_HAVE_ENOUGH_DATA})
|
||||
|
||||
/* package */ @interface ReadyStateFlags {}
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({MEDIA_ERROR_NETWORK_NO_SOURCE, MEDIA_ERROR_ABORTED, MEDIA_ERROR_NETWORK,
|
||||
MEDIA_ERROR_DECODE, MEDIA_ERROR_SRC_NOT_SUPPORTED})
|
||||
/* package */ @interface MediaErrorFlags {}
|
||||
|
||||
/**
|
||||
* The media is no longer paused, as a result of the play method, or the autoplay attribute.
|
||||
*/
|
||||
public static final int MEDIA_STATE_PLAY = 0;
|
||||
/**
|
||||
* Sent when the media has enough data to start playing, after the play event,
|
||||
* but also when recovering from being stalled, when looping media restarts,
|
||||
* and after seeked, if it was playing before seeking.
|
||||
*/
|
||||
public static final int MEDIA_STATE_PLAYING = 1;
|
||||
/**
|
||||
* Sent when the playback state is changed to paused.
|
||||
*/
|
||||
public static final int MEDIA_STATE_PAUSE = 2;
|
||||
/**
|
||||
* Sent when playback completes.
|
||||
*/
|
||||
public static final int MEDIA_STATE_ENDED = 3;
|
||||
/**
|
||||
* Sent when a seek operation begins.
|
||||
*/
|
||||
public static final int MEDIA_STATE_SEEKING = 4;
|
||||
/**
|
||||
* Sent when a seek operation completes.
|
||||
*/
|
||||
public static final int MEDIA_STATE_SEEKED = 5;
|
||||
/**
|
||||
* Sent when the user agent is trying to fetch media data,
|
||||
* but data is unexpectedly not forthcoming.
|
||||
*/
|
||||
public static final int MEDIA_STATE_STALLED = 6;
|
||||
/**
|
||||
* Sent when loading of the media is suspended. This may happen either because
|
||||
* the download has completed or because it has been paused for any other reason.
|
||||
*/
|
||||
public static final int MEDIA_STATE_SUSPEND = 7;
|
||||
/**
|
||||
* Sent when the requested operation (such as playback) is delayed
|
||||
* pending the completion of another operation (such as a seek).
|
||||
*/
|
||||
public static final int MEDIA_STATE_WAITING = 8;
|
||||
/**
|
||||
* Sent when playback is aborted; for example, if the media is playing
|
||||
* and is restarted from the beginning, this event is sent.
|
||||
*/
|
||||
public static final int MEDIA_STATE_ABORT = 9;
|
||||
/**
|
||||
* The media has become empty. For example, this event is sent if the media
|
||||
* has already been loaded, and the load() method is called to reload it.
|
||||
*/
|
||||
public static final int MEDIA_STATE_EMPTIED = 10;
|
||||
|
||||
|
||||
/**
|
||||
* No information is available about the media resource.
|
||||
*/
|
||||
public static final int MEDIA_READY_STATE_HAVE_NOTHING = 0;
|
||||
/**
|
||||
* Enough of the media resource has been retrieved that the metadata
|
||||
* attributes are available.
|
||||
*/
|
||||
public static final int MEDIA_READY_STATE_HAVE_METADATA = 1;
|
||||
/**
|
||||
* Data is available for the current playback position,
|
||||
* but not enough to actually play more than one frame.
|
||||
*/
|
||||
public static final int MEDIA_READY_STATE_HAVE_CURRENT_DATA = 2;
|
||||
/**
|
||||
* Data for the current playback position as well as for at least a little
|
||||
* bit of time into the future is available.
|
||||
*/
|
||||
public static final int MEDIA_READY_STATE_HAVE_FUTURE_DATA = 3;
|
||||
/**
|
||||
* Enough data is available—and the download rate is high enough that the media
|
||||
* can be played through to the end without interruption.
|
||||
*/
|
||||
public static final int MEDIA_READY_STATE_HAVE_ENOUGH_DATA = 4;
|
||||
|
||||
|
||||
/**
|
||||
* Media source not found or unable to select any of the child elements
|
||||
* for playback during resource selection.
|
||||
*/
|
||||
public static final int MEDIA_ERROR_NETWORK_NO_SOURCE = 0;
|
||||
/**
|
||||
* The fetching of the associated resource was aborted by the user's request.
|
||||
*/
|
||||
public static final int MEDIA_ERROR_ABORTED = 1;
|
||||
/**
|
||||
* Some kind of network error occurred which prevented the media from being
|
||||
* successfully fetched, despite having previously been available.
|
||||
*/
|
||||
public static final int MEDIA_ERROR_NETWORK = 2;
|
||||
/**
|
||||
* Despite having previously been determined to be usable,
|
||||
* an error occurred while trying to decode the media resource, resulting in an error.
|
||||
*/
|
||||
public static final int MEDIA_ERROR_DECODE = 3;
|
||||
/**
|
||||
* The associated resource or media provider object has been found to be unsuitable.
|
||||
*/
|
||||
public static final int MEDIA_ERROR_SRC_NOT_SUPPORTED = 4;
|
||||
|
||||
/**
|
||||
* Data class with the Metadata associated to a Media Element.
|
||||
**/
|
||||
public static class Metadata {
|
||||
/**
|
||||
* Contains the current media source URI.
|
||||
*/
|
||||
public final String currentSource;
|
||||
|
||||
/**
|
||||
* Indicates the duration of the media in seconds.
|
||||
*/
|
||||
public final double duration;
|
||||
|
||||
/**
|
||||
* Indicates the width of the video in device pixels.
|
||||
*/
|
||||
public final long width;
|
||||
|
||||
/**
|
||||
* Indicates the height of the video in device pixels.
|
||||
*/
|
||||
public final long height;
|
||||
|
||||
/**
|
||||
* Indicates if seek operations are compatible with the media.
|
||||
*/
|
||||
public final boolean isSeekable;
|
||||
|
||||
/**
|
||||
* Indicates the number of audio tracks included in the media.
|
||||
*/
|
||||
public final int audioTrackCount;
|
||||
|
||||
/**
|
||||
* Indicates the number of video tracks included in the media.
|
||||
*/
|
||||
public final int videoTrackCount;
|
||||
|
||||
/* package */ Metadata(final GeckoBundle bundle) {
|
||||
currentSource = bundle.getString("src", "");
|
||||
duration = bundle.getDouble("duration", 0);
|
||||
width = bundle.getLong("width", 0);
|
||||
height = bundle.getLong("height", 0);
|
||||
isSeekable = bundle.getBoolean("seekable", false);
|
||||
audioTrackCount = bundle.getInt("audioTrackCount", 0);
|
||||
videoTrackCount = bundle.getInt("videoTrackCount", 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data class that indicates infomation about a media load progress event.
|
||||
**/
|
||||
public static class LoadProgressInfo {
|
||||
/*
|
||||
* Class used to represent a set of time ranges.
|
||||
*/
|
||||
public class TimeRange {
|
||||
/* package */ TimeRange(double start, double end) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
/*
|
||||
* The start time of the range in seconds.
|
||||
*/
|
||||
public final double start;
|
||||
/*
|
||||
* The end time of the range in seconds.
|
||||
*/
|
||||
public final double end;
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of bytes transferred since the beginning of the operation
|
||||
* or -1 if the data is not computable.
|
||||
*/
|
||||
public final long loadedBytes;
|
||||
|
||||
/**
|
||||
* The total number of bytes of content that will be transferred during the operation
|
||||
* or -1 if the data is not computable.
|
||||
*/
|
||||
public final long totalBytes;
|
||||
|
||||
/**
|
||||
* The ranges of the media source that the browser has currently buffered.
|
||||
* Null if the browser has not buffered any time range or the data is not computable.
|
||||
*/
|
||||
public final @Nullable TimeRange[] buffered;
|
||||
|
||||
/* package */ LoadProgressInfo(final GeckoBundle bundle) {
|
||||
loadedBytes = bundle.getLong("loadedBytes", -1);
|
||||
totalBytes = bundle.getLong("loadedBytes", -1);
|
||||
double[] starts = bundle.getDoubleArray("timeRangeStarts");
|
||||
double[] ends = bundle.getDoubleArray("timeRangeEnds");
|
||||
if (starts == null || ends == null) {
|
||||
buffered = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (starts.length != ends.length) {
|
||||
throw new AssertionError("timeRangeStarts and timeRangeEnds length do not match");
|
||||
}
|
||||
|
||||
buffered = new TimeRange[starts.length];
|
||||
for (int i = 0; i < starts.length; ++i) {
|
||||
buffered[i] = new TimeRange(starts[i], ends[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This interface allows apps to handle media events.
|
||||
**/
|
||||
public interface Delegate {
|
||||
/**
|
||||
* The media playback state has changed.
|
||||
*
|
||||
* @param mediaElement A reference to the MediaElement that dispatched the event.
|
||||
* @param mediaState The playback state of the media.
|
||||
* One of the {@link #MEDIA_STATE_PLAY MEDIA_STATE_*} flags.
|
||||
*/
|
||||
default void onPlaybackStateChange(MediaElement mediaElement, @MediaStateFlags int mediaState) {}
|
||||
|
||||
/**
|
||||
* The readiness state of the media has changed.
|
||||
*
|
||||
* @param mediaElement A reference to the MediaElement that dispatched the event.
|
||||
* @param readyState The readiness state of the media.
|
||||
* One of the {@link #MEDIA_READY_STATE_HAVE_NOTHING MEDIA_READY_STATE_*} flags.
|
||||
*/
|
||||
default void onReadyStateChange(MediaElement mediaElement, @ReadyStateFlags int readyState) {}
|
||||
|
||||
/**
|
||||
* The media metadata has loaded or changed.
|
||||
*
|
||||
* @param mediaElement A reference to the MediaElement that dispatched the event.
|
||||
* @param metaData The MetaData values of the media.
|
||||
*/
|
||||
default void onMetadataChange(MediaElement mediaElement, Metadata metaData) {}
|
||||
|
||||
/**
|
||||
* Indicates that a loading operation is in progress for the media.
|
||||
*
|
||||
* @param mediaElement A reference to the MediaElement that dispatched the event.
|
||||
* @param progressInfo Information about the load progress and buffered ranges.
|
||||
*/
|
||||
default void onLoadProgress(MediaElement mediaElement, LoadProgressInfo progressInfo) {}
|
||||
|
||||
/**
|
||||
* The media audio volume has changed.
|
||||
*
|
||||
* @param mediaElement A reference to the MediaElement that dispatched the event.
|
||||
* @param volume The volume of the media.
|
||||
* @param muted True if the media is muted.
|
||||
*/
|
||||
default void onVolumeChange(MediaElement mediaElement, double volume, boolean muted) {}
|
||||
|
||||
/**
|
||||
* The current playback time has changed. This event is usually dispatched every 250ms.
|
||||
*
|
||||
* @param mediaElement A reference to the MediaElement that dispatched the event.
|
||||
* @param time The current playback time in seconds.
|
||||
*/
|
||||
default void onTimeChange(MediaElement mediaElement, double time) {}
|
||||
|
||||
/**
|
||||
* The media playback speed has changed.
|
||||
*
|
||||
* @param mediaElement A reference to the MediaElement that dispatched the event.
|
||||
* @param rate The current playback rate. A value of 1.0 indicates normal speed.
|
||||
*/
|
||||
default void onPlaybackRateChange(MediaElement mediaElement, double rate) {}
|
||||
|
||||
/**
|
||||
* A media element has entered or exited fullscreen mode.
|
||||
*
|
||||
* @param mediaElement A reference to the MediaElement that dispatched the event.
|
||||
* @param fullscreen True if the media has entered full screen mode.
|
||||
*/
|
||||
default void onFullscreenChange(MediaElement mediaElement, boolean fullscreen) {}
|
||||
|
||||
/**
|
||||
* An error has occurred.
|
||||
*
|
||||
* @param mediaElement A reference to the MediaElement that dispatched the event.
|
||||
* @param errorCode The error code.
|
||||
* One of the {@link #MEDIA_ERROR_NETWORK_NO_SOURCE MEDIA_ERROR_*} flags.
|
||||
*/
|
||||
default void onError(MediaElement mediaElement, @MediaErrorFlags int errorCode) {}
|
||||
}
|
||||
|
||||
/* package */ long getVideoId() {
|
||||
return mVideoId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current the media callback handler.
|
||||
*
|
||||
* @return the current media callback handler.
|
||||
*/
|
||||
public @Nullable MediaElement.Delegate getDelegate() {
|
||||
return mDelegate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the media callback handler.
|
||||
* This will replace the current handler.
|
||||
*
|
||||
* @param delegate An implementation of MediaDelegate.
|
||||
*/
|
||||
public void setDelegate(final @Nullable MediaElement.Delegate delegate) {
|
||||
if (mDelegate == delegate) {
|
||||
return;
|
||||
}
|
||||
MediaElement.Delegate oldDelegate = mDelegate;
|
||||
mDelegate = delegate;
|
||||
if (oldDelegate != null && mDelegate == null) {
|
||||
mSession.getEventDispatcher().dispatch("GeckoView:MediaUnobserve", createMessage());
|
||||
mSession.getMediaElements().remove(mVideoId);
|
||||
} else if (oldDelegate == null) {
|
||||
mSession.getMediaElements().put(mVideoId, this);
|
||||
mSession.getEventDispatcher().dispatch("GeckoView:MediaObserve", createMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pauses the media.
|
||||
*/
|
||||
public void pause() {
|
||||
mSession.getEventDispatcher().dispatch("GeckoView:MediaPause", createMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Plays the media.
|
||||
*/
|
||||
public void play() {
|
||||
mSession.getEventDispatcher().dispatch("GeckoView:MediaPlay", createMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Seek the media to a given time.
|
||||
*
|
||||
* @param time Seek time in seconds.
|
||||
*/
|
||||
public void seek(final double time) {
|
||||
final GeckoBundle message = createMessage();
|
||||
message.putDouble("time", time);
|
||||
mSession.getEventDispatcher().dispatch("GeckoView:MediaSeek", message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the volume at which the media will be played.
|
||||
*
|
||||
* @param volume A Volume value. It must fall between 0 and 1, where 0 is effectively muted
|
||||
* and 1 is the loudest possible value.
|
||||
*/
|
||||
public void setVolume(final double volume) {
|
||||
final GeckoBundle message = createMessage();
|
||||
message.putDouble("volume", volume);
|
||||
mSession.getEventDispatcher().dispatch("GeckoView:MediaSetVolume", message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutes the media.
|
||||
*
|
||||
* @param muted True in order to mute the audio.
|
||||
*/
|
||||
public void setMuted(final boolean muted) {
|
||||
final GeckoBundle message = createMessage();
|
||||
message.putBoolean("muted", muted);
|
||||
mSession.getEventDispatcher().dispatch("GeckoView:MediaSetMuted", message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the playback rate at which the media will be played.
|
||||
*
|
||||
* @param playbackRate The rate at which the media will be played.
|
||||
* A value of 1.0 indicates normal speed.
|
||||
*/
|
||||
public void setPlaybackRate(final double playbackRate) {
|
||||
final GeckoBundle message = createMessage();
|
||||
message.putDouble("playbackRate", playbackRate);
|
||||
mSession.getEventDispatcher().dispatch("GeckoView:MediaSetPlaybackRate", message);
|
||||
}
|
||||
|
||||
// Helper methods used for event observers to update the current video state
|
||||
|
||||
/* package */ void notifyPlaybackStateChange(final String event) {
|
||||
@MediaStateFlags int state;
|
||||
switch (event.toLowerCase()) {
|
||||
case "play":
|
||||
state = MEDIA_STATE_PLAY;
|
||||
break;
|
||||
case "playing":
|
||||
state = MEDIA_STATE_PLAYING;
|
||||
break;
|
||||
case "pause":
|
||||
state = MEDIA_STATE_PAUSE;
|
||||
break;
|
||||
case "ended":
|
||||
state = MEDIA_STATE_ENDED;
|
||||
break;
|
||||
case "seeking":
|
||||
state = MEDIA_STATE_SEEKING;
|
||||
break;
|
||||
case "seeked":
|
||||
state = MEDIA_STATE_SEEKED;
|
||||
break;
|
||||
case "stalled":
|
||||
state = MEDIA_STATE_STALLED;
|
||||
break;
|
||||
case "suspend":
|
||||
state = MEDIA_STATE_SUSPEND;
|
||||
break;
|
||||
case "waiting":
|
||||
state = MEDIA_STATE_WAITING;
|
||||
break;
|
||||
case "abort":
|
||||
state = MEDIA_STATE_ABORT;
|
||||
break;
|
||||
case "emptied":
|
||||
state = MEDIA_STATE_EMPTIED;
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException(event + " HTMLMediaElement event not implemented");
|
||||
}
|
||||
|
||||
if (mDelegate != null) {
|
||||
mDelegate.onPlaybackStateChange(this, state);
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ void notifyReadyStateChange(final int readyState) {
|
||||
if (mDelegate != null) {
|
||||
mDelegate.onReadyStateChange(this, readyState);
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ void notifyLoadProgress(final GeckoBundle message) {
|
||||
if (mDelegate != null) {
|
||||
mDelegate.onLoadProgress(this, new LoadProgressInfo(message));
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ void notifyTimeChange(final double currentTime) {
|
||||
if (mDelegate != null) {
|
||||
mDelegate.onTimeChange(this, currentTime);
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ void notifyVolumeChange(final double volume, final boolean muted) {
|
||||
if (mDelegate != null) {
|
||||
mDelegate.onVolumeChange(this, volume, muted);
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ void notifyPlaybackRateChange(final double rate) {
|
||||
if (mDelegate != null) {
|
||||
mDelegate.onPlaybackRateChange(this, rate);
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ void notifyMetadataChange(final GeckoBundle message) {
|
||||
if (mDelegate != null) {
|
||||
mDelegate.onMetadataChange(this, new Metadata(message));
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ void notifyFullscreenChange(final boolean fullscreen) {
|
||||
if (mDelegate != null) {
|
||||
mDelegate.onFullscreenChange(this, fullscreen);
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ void notifyError(final int aCode) {
|
||||
if (mDelegate != null) {
|
||||
mDelegate.onError(this, aCode);
|
||||
}
|
||||
}
|
||||
|
||||
private GeckoBundle createMessage() {
|
||||
final GeckoBundle bundle = new GeckoBundle();
|
||||
bundle.putLong("id", mVideoId);
|
||||
return bundle;
|
||||
}
|
||||
|
||||
/* package */ MediaElement(final long videoId, final GeckoSession session) {
|
||||
mVideoId = videoId;
|
||||
mSession = session;
|
||||
}
|
||||
|
||||
final protected GeckoSession mSession;
|
||||
final protected long mVideoId;
|
||||
protected MediaElement.Delegate mDelegate;
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
var EXPORTED_SYMBOLS = ["GeckoViewMedia"];
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/GeckoViewModule.jsm");
|
||||
|
||||
class GeckoViewMedia extends GeckoViewModule {
|
||||
onEnable() {
|
||||
this.registerListener([
|
||||
"GeckoView:MediaObserve",
|
||||
"GeckoView:MediaUnobserve",
|
||||
"GeckoView:MediaPlay",
|
||||
"GeckoView:MediaPause",
|
||||
"GeckoView:MediaSeek",
|
||||
"GeckoView:MediaSetVolume",
|
||||
"GeckoView:MediaSetMuted",
|
||||
"GeckoView:MediaSetPlaybackRate",
|
||||
]);
|
||||
}
|
||||
|
||||
onDisable() {
|
||||
this.unregisterListener();
|
||||
}
|
||||
|
||||
onEvent(aEvent, aData, aCallback) {
|
||||
debug `onEvent: event=${aEvent}, data=${aData}`;
|
||||
this.messageManager.sendAsyncMessage(aEvent, aData);
|
||||
}
|
||||
}
|
|
@ -12,6 +12,7 @@ EXTRA_JS_MODULES += [
|
|||
'GeckoViewChildModule.jsm',
|
||||
'GeckoViewConsole.jsm',
|
||||
'GeckoViewContent.jsm',
|
||||
'GeckoViewMedia.jsm',
|
||||
'GeckoViewModule.jsm',
|
||||
'GeckoViewNavigation.jsm',
|
||||
'GeckoViewProgress.jsm',
|
||||
|
|
|
@ -5457,6 +5457,7 @@ pref("network.trr.mode", 0);
|
|||
pref("network.trr.uri", "https://mozilla.cloudflare-dns.com/dns-query");
|
||||
// credentials to pass to DOH end-point
|
||||
pref("network.trr.credentials", "");
|
||||
pref("network.trr.custom_uri", "");
|
||||
// Wait for captive portal confirmation before enabling TRR
|
||||
#if defined(ANDROID)
|
||||
// On Android, the captive portal is handled by the OS itself
|
||||
|
|
|
@ -1247,18 +1247,34 @@ class Artifacts(object):
|
|||
return self._install_from_hg_pushheads(hg_pushheads, distdir)
|
||||
|
||||
def install_from_revset(self, revset, distdir):
|
||||
revision = None
|
||||
try:
|
||||
if self._hg:
|
||||
revision = subprocess.check_output([self._hg, 'log', '--template', '{node}\n',
|
||||
'-r', revset], cwd=self._topsrcdir).strip()
|
||||
if len(revision.split('\n')) != 1:
|
||||
raise ValueError('hg revision specification must resolve to exactly one commit')
|
||||
elif self._git:
|
||||
revset = subprocess.check_output([
|
||||
self._git, 'rev-parse', '%s^{commit}' % revset],
|
||||
stderr=open(os.devnull, 'w'), cwd=self._topsrcdir).strip()
|
||||
else:
|
||||
revision = subprocess.check_output([self._git, 'rev-parse', revset], cwd=self._topsrcdir).strip()
|
||||
revision = subprocess.check_output([self._git, 'cinnabar', 'git2hg', revision], cwd=self._topsrcdir).strip()
|
||||
# Fallback to the exception handling case from both hg and git
|
||||
raise subprocess.CalledProcessError()
|
||||
except subprocess.CalledProcessError:
|
||||
# If the mercurial of git commands above failed, it means the given
|
||||
# revset is not known locally to the VCS. But if the revset looks
|
||||
# like a complete sha1, assume it is a mercurial sha1 that hasn't
|
||||
# been pulled, and use that.
|
||||
if re.match(r'^[A-Fa-f0-9]{40}$', revset):
|
||||
revision = revset
|
||||
|
||||
if revision is None and self._git:
|
||||
revision = subprocess.check_output(
|
||||
[self._git, 'cinnabar', 'git2hg', revset], cwd=self._topsrcdir).strip()
|
||||
|
||||
if revision == "0" * 40 or revision is None:
|
||||
raise ValueError('revision specification must resolve to a commit known to hg')
|
||||
if len(revision.split('\n')) != 1:
|
||||
raise ValueError('hg revision specification must resolve to exactly one commit')
|
||||
if revision == "0" * 40:
|
||||
raise ValueError('git revision specification must resolve to a commit known to hg')
|
||||
raise ValueError('revision specification must resolve to exactly one commit')
|
||||
|
||||
self.log(logging.INFO, 'artifact',
|
||||
{'revset': revset,
|
||||
|
|
|
@ -267,7 +267,7 @@ linux64-base-toolchains/opt:
|
|||
toolchains:
|
||||
- linux64-clang-3.9
|
||||
- linux64-gcc-6
|
||||
- linux64-rust-1.29
|
||||
- linux64-rust-1.30
|
||||
- linux64-cbindgen
|
||||
- linux64-sccache
|
||||
- linux64-node
|
||||
|
@ -301,7 +301,7 @@ linux64-base-toolchains/debug:
|
|||
toolchains:
|
||||
- linux64-clang-3.9
|
||||
- linux64-gcc-6
|
||||
- linux64-rust-1.29
|
||||
- linux64-rust-1.30
|
||||
- linux64-cbindgen
|
||||
- linux64-sccache
|
||||
- linux64-node
|
||||
|
|
|
@ -88,11 +88,7 @@ web-platform-tests-reftests:
|
|||
description: "Web platform reftest run"
|
||||
suite: web-platform-tests-reftests
|
||||
treeherder-symbol: W(Wr)
|
||||
chunks:
|
||||
by-test-platform:
|
||||
macosx.*: 1
|
||||
default: 6
|
||||
|
||||
chunks: 6
|
||||
e10s:
|
||||
by-test-platform:
|
||||
linux32/debug: both
|
||||
|
@ -116,10 +112,7 @@ web-platform-tests-reftests-headless:
|
|||
description: "Web platform reftest headless run"
|
||||
suite: web-platform-tests-reftests
|
||||
treeherder-symbol: W(WrH)
|
||||
chunks:
|
||||
by-test-platform:
|
||||
macosx.*: 1
|
||||
default: 6
|
||||
chunks: 6
|
||||
e10s:
|
||||
by-test-platform:
|
||||
linux32/debug: both
|
||||
|
|
|
@ -488,29 +488,6 @@ linux64-rust-1.28:
|
|||
]
|
||||
toolchain-artifact: public/build/rustc.tar.xz
|
||||
|
||||
linux64-rust-1.29:
|
||||
description: "rust repack"
|
||||
treeherder:
|
||||
kind: build
|
||||
platform: toolchains/opt
|
||||
symbol: TL(rust-1.29)
|
||||
tier: 1
|
||||
worker-type: aws-provisioner-v1/gecko-{level}-b-linux
|
||||
worker:
|
||||
max-run-time: 7200
|
||||
env:
|
||||
UPLOAD_DIR: artifacts
|
||||
run:
|
||||
using: toolchain-script
|
||||
script: repack_rust.py
|
||||
arguments: [
|
||||
'--channel', '1.29.2',
|
||||
'--host', 'x86_64-unknown-linux-gnu',
|
||||
'--target', 'x86_64-unknown-linux-gnu',
|
||||
'--target', 'i686-unknown-linux-gnu',
|
||||
]
|
||||
toolchain-artifact: public/build/rustc.tar.xz
|
||||
|
||||
linux64-rust-1.30:
|
||||
description: "rust repack"
|
||||
treeherder:
|
||||
|
|
|
@ -230,9 +230,6 @@ class WebPlatformTest(TestingMixin, MercurialScript, CodeCoverageMixin, AndroidM
|
|||
cmd += ["--device-serial=%s" % self.device_serial]
|
||||
cmd += ["--package-name=%s" % self.query_package_name()]
|
||||
|
||||
if sys.platform == "darwin":
|
||||
cmd += ["--exclude=css"]
|
||||
|
||||
if mozinfo.info["os"] == "win" and mozinfo.info["os_version"] == "6.1":
|
||||
# On Windows 7 --install-fonts fails, so fall back to a Firefox-specific codepath
|
||||
self._install_fonts()
|
||||
|
|
|
@ -186,12 +186,14 @@ def get_raptor_test_list(args, oskey):
|
|||
# also allow the cmd line opt to override pagecycles auto set when gecko profiling is on
|
||||
if args.page_cycles is not None:
|
||||
LOG.info("setting page-cycles to %d as specified on the command line" % args.page_cycles)
|
||||
for next_test in tests_to_run:
|
||||
next_test['page_cycles'] = args.page_cycles
|
||||
|
||||
# if --page-timeout command line arg was provided, override the page_timeout value
|
||||
# that was in the manifest/test INI with the command line arg value instead
|
||||
if args.page_timeout is not None:
|
||||
LOG.info("setting page-timeout to %d as specified on the command line" % args.page_timeout)
|
||||
for next_test in tests_to_run:
|
||||
next_test['page_timeout'] = args.page_timeout
|
||||
|
||||
# write out .json test setting files for the control server to read and send to web ext
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
|
||||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/Timer.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "aomStartup",
|
||||
"@mozilla.org/addons/addon-manager-startup;1",
|
||||
|
@ -93,9 +94,25 @@ this.pageloader = class extends ExtensionAPI {
|
|||
]);
|
||||
|
||||
if (env.exists("MOZ_USE_PAGELOADER")) {
|
||||
// This is async but we're delibeately not await-ing or return-ing
|
||||
// TalosPowers is a separate WebExtension that may or may not already have
|
||||
// finished loading. tryLoad is used to wait for TalosPowers to be around
|
||||
// before continuing.
|
||||
async function tryLoad() {
|
||||
try {
|
||||
ChromeUtils.import("resource://talos-powers/TalosParentProfiler.jsm");
|
||||
} catch (err) {
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
return tryLoad();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// talosStart is async but we're deliberately not await-ing or return-ing
|
||||
// it here since it doesn't block extension startup.
|
||||
tryLoad().then(() => {
|
||||
talosStart();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,10 +8,8 @@
|
|||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/E10SUtils.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyScriptGetter(this, "TalosParentProfiler",
|
||||
"resource://talos-powers/TalosParentProfiler.js");
|
||||
ChromeUtils.defineModuleGetter(this, "TalosParentProfiler",
|
||||
"resource://talos-powers/TalosParentProfiler.jsm");
|
||||
|
||||
var NUM_CYCLES = 5;
|
||||
var numPageCycles = 1;
|
||||
|
|
|
@ -9,15 +9,14 @@
|
|||
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
|
||||
Services: "resource://gre/modules/Services.jsm",
|
||||
SessionStartup: "resource:///modules/sessionstore/SessionStartup.jsm",
|
||||
setTimeout: "resource://gre/modules/Timer.jsm",
|
||||
StartupPerformance: "resource:///modules/sessionstore/StartupPerformance.jsm",
|
||||
TalosParentProfiler: "resource://talos-powers/TalosParentProfiler.jsm",
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyScriptGetter(this, "TalosParentProfiler",
|
||||
"resource://talos-powers/TalosParentProfiler.js");
|
||||
|
||||
/* globals ExtensionAPI */
|
||||
|
||||
this.sessionrestore = class extends ExtensionAPI {
|
||||
|
@ -47,6 +46,21 @@ this.sessionrestore = class extends ExtensionAPI {
|
|||
async function observe() {
|
||||
Services.obs.removeObserver(observe, StartupPerformance.RESTORED_TOPIC);
|
||||
|
||||
let win = BrowserWindowTracker.getTopWindow();
|
||||
let args = win.arguments[0];
|
||||
if (args && args instanceof Ci.nsIArray) {
|
||||
// For start-up tests Gecko Profiler arguments are passed to the first URL in
|
||||
// the query string, with the presumption that some tab that is loaded at start-up
|
||||
// will want to use them in the TalosContentProfiler.js script.
|
||||
//
|
||||
// Because we're running this part of the test in the parent process, we
|
||||
// pull those arguments from the top window directly. This is mainly so
|
||||
// that TalosParentProfiler knows where to save the profile.
|
||||
Cu.importGlobalProperties(["URL"]);
|
||||
let url = new URL(args.queryElementAt(0, Ci.nsISupportsString).data);
|
||||
TalosParentProfiler.initFromURLQueryParams(url.search);
|
||||
}
|
||||
|
||||
await TalosParentProfiler.pause("This test measures the time between sessionRestoreInit and sessionRestored, ignore everything around that");
|
||||
await TalosParentProfiler.finishStartupProfiling();
|
||||
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
<html>
|
||||
<head>
|
||||
|
||||
<meta charset="UTF-8"/>
|
||||
<title>Session Restore Regression Test</title>
|
||||
|
||||
<script type="text/javascript" src="chrome://pageloader/content/MozillaFileLogger.js"></script>
|
||||
<script type="text/javascript" src="resource://talos-powers/TalosContentProfiler.js"></script>
|
||||
<script type="text/javascript" src="resource://talos-powers/TalosPowersContent.js"></script>
|
||||
<script type="text/javascript" src="chrome://session-restore-test/content/main.js">
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<strong>Time between sessionRestoreInit and sessionRestored</strong>
|
||||
<span id="sessionRestoreInit-to-sessionRestored">
|
||||
(in progress)
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1,40 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
var Services = ChromeUtils.import("resource://gre/modules/Services.jsm", {}).Services;
|
||||
|
||||
// Process Message Manager topics.
|
||||
const MSG_REQUEST = "session-restore-test?duration";
|
||||
const MSG_PROVIDE = "session-restore-test:duration";
|
||||
|
||||
addEventListener("load", function() {
|
||||
Services.cpmm.addMessageListener(MSG_PROVIDE,
|
||||
/**
|
||||
* Display the result, send it to the harness and quit.
|
||||
*/
|
||||
async function finish(msg) {
|
||||
console.log(`main.js: received data on ${MSG_PROVIDE}`, msg);
|
||||
Services.cpmm.removeMessageListener(MSG_PROVIDE, finish);
|
||||
var duration = msg.data.duration;
|
||||
TalosContentProfiler.initFromURLQueryParams(location.search);
|
||||
await TalosContentProfiler.pause("This test measures the time between sessionRestoreInit and sessionRestored, ignore everything around that");
|
||||
await TalosContentProfiler.finishStartupProfiling();
|
||||
|
||||
// Show result on screen. Nice but not really necessary.
|
||||
document.getElementById("sessionRestoreInit-to-sessionRestored").textContent = duration + "ms";
|
||||
|
||||
// Report data to Talos, if possible
|
||||
dumpLog("__start_report" +
|
||||
duration +
|
||||
"__end_report\n\n");
|
||||
|
||||
// Next one is required by the test harness but not used
|
||||
dumpLog("__startTimestamp" +
|
||||
Date.now() + // eslint-disable-line mozilla/avoid-Date-timing
|
||||
"__endTimestamp\n\n");
|
||||
TalosPowersContent.goQuitApplication();
|
||||
});
|
||||
|
||||
// In case the add-on has broadcasted the message before we were loaded,
|
||||
// request a second broadcast.
|
||||
Services.cpmm.sendAsyncMessage(MSG_REQUEST, {});
|
||||
});
|
|
@ -1,213 +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/. */
|
||||
|
||||
/**
|
||||
* This utility script is for instrumenting your Talos test for
|
||||
* performance profiles while running within the parent process.
|
||||
* Almost all of the functions that this script exposes to the
|
||||
* Gecko Profiler are synchronous, except for finishTest, since that
|
||||
* involves requesting the profiles from any content processes and
|
||||
* then writing to disk.
|
||||
*
|
||||
* If your test is running in the content process, you should use
|
||||
* TalosContentProfiler.js instead.
|
||||
*/
|
||||
|
||||
var TalosParentProfiler;
|
||||
|
||||
(function() {
|
||||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
// Whether or not this TalosContentProfiler object has had initFromObject
|
||||
// or initFromURLQueryParams called on it. Any functions that change the
|
||||
// state of the Gecko Profiler should only be called after calling either
|
||||
// initFromObject or initFromURLQueryParams.
|
||||
let initted = Services.profiler.IsActive();
|
||||
|
||||
// The subtest name that beginTest() was called with.
|
||||
let currentTest = "unknown";
|
||||
|
||||
// Profiler settings.
|
||||
let interval, entries, threadsArray, profileDir;
|
||||
|
||||
// Use a bit of XPCOM hackery to get at the Talos Powers service
|
||||
// implementation...
|
||||
let TalosPowers =
|
||||
Cc["@mozilla.org/talos/talos-powers-service;1"]
|
||||
.getService(Ci.nsISupports)
|
||||
.wrappedJSObject;
|
||||
|
||||
/**
|
||||
* Parses an url query string into a JS object.
|
||||
*
|
||||
* @param locationSearch (string)
|
||||
* The location string to parse.
|
||||
* @returns Object
|
||||
* The GET params from the location string as
|
||||
* key-value pairs in the Object.
|
||||
*/
|
||||
function searchToObject(locationSearch) {
|
||||
let pairs = locationSearch.substring(1).split("&");
|
||||
let result = {};
|
||||
|
||||
for (let i in pairs) {
|
||||
if (pairs[i] !== "") {
|
||||
let pair = pairs[i].split("=");
|
||||
result[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1] || "");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
TalosParentProfiler = {
|
||||
/**
|
||||
* Initialize the profiler using profiler settings supplied in a JS object.
|
||||
*
|
||||
* @param obj (object)
|
||||
* The following properties on the object are respected:
|
||||
* gecko_profile_interval (int)
|
||||
* gecko_profile_entries (int)
|
||||
* gecko_profile_threads (string, comma separated list of threads to filter with)
|
||||
* gecko_profile_dir (string)
|
||||
*/
|
||||
initFromObject(obj = {}) {
|
||||
if (!initted) {
|
||||
if (("gecko_profile_dir" in obj) && typeof obj.gecko_profile_dir == "string" &&
|
||||
("gecko_profile_interval" in obj) && Number.isFinite(obj.gecko_profile_interval * 1) &&
|
||||
("gecko_profile_entries" in obj) && Number.isFinite(obj.gecko_profile_entries * 1) &&
|
||||
("gecko_profile_threads" in obj) && typeof obj.gecko_profile_threads == "string") {
|
||||
interval = obj.gecko_profile_interval;
|
||||
entries = obj.gecko_profile_entries;
|
||||
threadsArray = obj.gecko_profile_threads.split(",");
|
||||
profileDir = obj.gecko_profile_dir;
|
||||
initted = true;
|
||||
} else {
|
||||
console.error("Profiler could not init with object: " + JSON.stringify(obj));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize the profiler using a string from a location string.
|
||||
*
|
||||
* @param locationSearch (string)
|
||||
* The location string to initialize with.
|
||||
*/
|
||||
initFromURLQueryParams(locationSearch) {
|
||||
this.initFromObject(searchToObject(locationSearch));
|
||||
},
|
||||
|
||||
/**
|
||||
* A Talos test is about to start. Note that the Gecko Profiler will be
|
||||
* paused immediately after starting and that resume() should be called
|
||||
* in order to collect samples.
|
||||
*
|
||||
* @param testName (string)
|
||||
* The name of the test to use in Profiler markers.
|
||||
*/
|
||||
beginTest(testName) {
|
||||
if (initted) {
|
||||
currentTest = testName;
|
||||
TalosPowers.profilerBegin({ entries, interval, threadsArray });
|
||||
} else {
|
||||
let msg = "You should not call beginTest without having first " +
|
||||
"initted the Profiler";
|
||||
console.error(msg);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* A Talos test has finished. This will stop the Gecko Profiler from
|
||||
* sampling, and return a Promise that resolves once the Profiler has
|
||||
* finished dumping the multi-process profile to disk.
|
||||
*
|
||||
* @returns Promise
|
||||
* Resolves once the profile has been dumped to disk. The test should
|
||||
* not try to quit the browser until this has resolved.
|
||||
*/
|
||||
finishTest() {
|
||||
if (initted) {
|
||||
let profileFile = profileDir + "/" + currentTest + ".profile";
|
||||
return TalosPowers.profilerFinish(profileFile);
|
||||
}
|
||||
let msg = "You should not call finishTest without having first " +
|
||||
"initted the Profiler";
|
||||
console.error(msg);
|
||||
return Promise.reject(msg);
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* A start-up test has finished. Callers don't need to run beginTest or
|
||||
* finishTest, but should pause the sampler as soon as possible, and call
|
||||
* this function to dump the profile.
|
||||
*
|
||||
* @returns Promise
|
||||
* Resolves once the profile has been dumped to disk. The test should
|
||||
* not try to quit the browser until this has resolved.
|
||||
*/
|
||||
finishStartupProfiling() {
|
||||
if (initted) {
|
||||
let profileFile = profileDir + "/startup.profile";
|
||||
return TalosPowers.profilerFinish(profileFile);
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
/**
|
||||
* Resumes the Gecko Profiler sampler. Can also simultaneously set a marker.
|
||||
*
|
||||
* @returns Promise
|
||||
* Resolves once the Gecko Profiler has resumed.
|
||||
*/
|
||||
resume(marker = "") {
|
||||
if (initted) {
|
||||
TalosPowers.profilerResume(marker);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Pauses the Gecko Profiler sampler. Can also simultaneously set a marker.
|
||||
*
|
||||
* @returns Promise
|
||||
* Resolves once the Gecko Profiler has paused.
|
||||
*/
|
||||
pause(marker = "") {
|
||||
if (initted) {
|
||||
TalosPowers.profilerPause(marker);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a marker to the profile.
|
||||
*
|
||||
* @returns Promise
|
||||
* Resolves once the marker has been set.
|
||||
*/
|
||||
mark(marker) {
|
||||
if (initted) {
|
||||
// If marker is omitted, just use the test name
|
||||
if (!marker) {
|
||||
marker = currentTest;
|
||||
}
|
||||
|
||||
TalosPowers.profilerMarker(marker);
|
||||
}
|
||||
},
|
||||
|
||||
afterProfileGathered() {
|
||||
if (!initted) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return new Promise(resolve => {
|
||||
Services.obs.addObserver(function onGathered() {
|
||||
Services.obs.removeObserver(onGathered, "talos-profile-gathered");
|
||||
resolve();
|
||||
}, "talos-profile-gathered");
|
||||
});
|
||||
},
|
||||
};
|
||||
})();
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче