Merge mozilla-central to inbound a=merge on a CLOSED TREE

This commit is contained in:
Coroiu Cristina 2018-09-28 00:42:47 +03:00
Родитель c30332cd26 71988c7b7e
Коммит 47c0bc6c06
227 изменённых файлов: 8558 добавлений и 10433 удалений

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

@ -267,7 +267,7 @@
accesskey="&sendPageToDevice.accesskey;"
hidden="true">
<menupopup id="context-sendpagetodevice-popup"
onpopupshowing="(() => { gSync.populateSendTabToDevicesMenu(event.target, gBrowser.selectedTab); })()"/>
onpopupshowing="(() => { gSync.populateSendTabToDevicesMenu(event.target, gBrowser.currentURI.spec, gBrowser.contentTitle, gBrowser.selectedTab.multiselected); })()"/>
</menu>
<menuseparator id="context-sep-viewbgimage"/>
<menuitem id="context-viewbgimage"
@ -316,7 +316,7 @@
accesskey="&sendLinkToDevice.accesskey;"
hidden="true">
<menupopup id="context-sendlinktodevice-popup"
onpopupshowing="gSync.populateSendTabToDevicesMenu(event.target, gBrowser.selectedTab);"/>
onpopupshowing="gSync.populateSendTabToDevicesMenu(event.target, gContextMenu.linkURL, gContextMenu.linkTextStr);"/>
</menu>
<menuseparator id="frame-sep"/>
<menu id="frame" label="&thisFrameMenu.label;" accesskey="&thisFrameMenu.accesskey;">

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

@ -1036,10 +1036,14 @@ BrowserPageActions.sendToDevice = {
onShowingSubview(panelViewNode) {
let bodyNode = panelViewNode.querySelector(".panel-subview-body");
let panelNode = panelViewNode.closest("panel");
let browser = gBrowser.selectedBrowser;
let url = browser.currentURI.spec;
let title = browser.contentTitle;
let multiselected = gBrowser.selectedTab.multiselected;
// This is on top because it also clears the device list between state
// changes.
gSync.populateSendTabToDevicesMenu(bodyNode, gBrowser.selectedTab, (clientId, name, clientType, lastModified) => {
gSync.populateSendTabToDevicesMenu(bodyNode, url, title, multiselected, (clientId, name, clientType, lastModified) => {
if (!name) {
return document.createXULElement("toolbarseparator");
}

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

@ -35,7 +35,7 @@
<command id="cmd_printPreview" oncommand="PrintUtils.printPreview(PrintPreviewListener);"/>
<command id="cmd_close" oncommand="BrowserCloseTabOrWindow(event);"/>
<command id="cmd_closeWindow" oncommand="BrowserTryToCloseWindow()"/>
<command id="cmd_toggleMute" oncommand="gBrowser.selectedTab.toggleMuteAudio()"/>
<command id="cmd_toggleMute" oncommand="gBrowser.toggleMuteAudioOnMultiSelectedTabs(gBrowser.selectedTab)"/>
<command id="cmd_CustomizeToolbars" oncommand="gCustomizeMode.enter()"/>
<command id="cmd_toggleOfflineStatus" oncommand="BrowserOffline.toggleOfflineStatus();"/>
<command id="cmd_quitApplication" oncommand="goQuitApplication()"/>

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

@ -361,7 +361,7 @@ var gSync = {
}
},
populateSendTabToDevicesMenu(devicesPopup, aTab, createDeviceNodeFn) {
populateSendTabToDevicesMenu(devicesPopup, url, title, multiselected, createDeviceNodeFn) {
if (!createDeviceNodeFn) {
createDeviceNodeFn = (clientId, name, clientType, lastModified) => {
let eltName = name ? "menuitem" : "menuseparator";
@ -386,7 +386,7 @@ var gSync = {
const state = UIState.get();
if (state.status == UIState.STATUS_SIGNED_IN && this.remoteClients.length > 0) {
this._appendSendTabDeviceList(fragment, createDeviceNodeFn, aTab);
this._appendSendTabDeviceList(fragment, createDeviceNodeFn, url, title, multiselected);
} else if (state.status == UIState.STATUS_SIGNED_IN) {
this._appendSendTabSingleDevice(fragment, createDeviceNodeFn);
} else if (state.status == UIState.STATUS_NOT_VERIFIED ||
@ -402,26 +402,25 @@ var gSync = {
// TODO: once our transition from the old-send tab world is complete,
// this list should be built using the FxA device list instead of the client
// collection.
_appendSendTabDeviceList(fragment, createDeviceNodeFn, tab) {
let tabsToSend = tab.multiselected ? gBrowser.selectedTabs : [tab];
function getTabUrl(t) {
return t.linkedBrowser.currentURI.spec;
}
function getTabTitle(t) {
return t.linkedBrowser.contentTitle;
}
_appendSendTabDeviceList(fragment, createDeviceNodeFn, url, title, multiselected) {
let tabsToSend = multiselected ?
gBrowser.selectedTabs.map(t => {
return {
url: t.linkedBrowser.currentURI.spec,
title: t.linkedBrowser.contentTitle,
};
}) : [{url, title}];
const onSendAllCommand = (event) => {
for (let t of tabsToSend) {
this.sendTabToDevice(getTabUrl(t), this.remoteClients, getTabTitle(t));
this.sendTabToDevice(t.url, this.remoteClients, t.title);
}
};
const onTargetDeviceCommand = (event) => {
const clientId = event.target.getAttribute("clientId");
const client = this.remoteClients.find(c => c.id == clientId);
for (let t of tabsToSend) {
this.sendTabToDevice(getTabUrl(t), [client], getTabTitle(t));
this.sendTabToDevice(t.url, [client], t.title);
}
};

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

@ -108,4 +108,4 @@ support-files =
[browser_iframe_navigation.js]
support-files =
iframe_navigation.html
[browser_tls_handshake_failure.js]
[browser_navigation_failures.js]

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

@ -0,0 +1,41 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// Tests that the site identity indicator is properly updated for navigations
// that fail for various reasons. In particular, we currently test TLS handshake
// failures and about: pages that don't actually exist.
// See bug 1492424 and bug 1493427.
const kSecureURI = getRootDirectory(gTestPath).replace("chrome://mochitests/content",
"https://example.com") + "dummy_page.html";
add_task(async function() {
await BrowserTestUtils.withNewTab(kSecureURI, async (browser) => {
let identityMode = window.document.getElementById("identity-box").className;
is(identityMode, "verifiedDomain", "identity should be secure before");
const TLS_HANDSHAKE_FAILURE_URI = "https://ssl3.example.com/";
// Try to connect to a server where the TLS handshake will fail.
BrowserTestUtils.loadURI(browser, TLS_HANDSHAKE_FAILURE_URI);
await BrowserTestUtils.browserLoaded(browser, false, TLS_HANDSHAKE_FAILURE_URI, true);
let newIdentityMode = window.document.getElementById("identity-box").className;
is(newIdentityMode, "unknownIdentity", "identity should be unknown (not secure) after");
});
});
add_task(async function() {
await BrowserTestUtils.withNewTab(kSecureURI, async (browser) => {
let identityMode = window.document.getElementById("identity-box").className;
is(identityMode, "verifiedDomain", "identity should be secure before");
const BAD_ABOUT_PAGE_URI = "about:somethingthatdoesnotexist";
// Try to load an about: page that doesn't exist
BrowserTestUtils.loadURI(browser, BAD_ABOUT_PAGE_URI);
await BrowserTestUtils.browserLoaded(browser, false, BAD_ABOUT_PAGE_URI, true);
let newIdentityMode = window.document.getElementById("identity-box").className;
is(newIdentityMode, "unknownIdentity", "identity should be unknown (not secure) after");
});
});

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

@ -1,25 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// Tests that the site identity indicator is properly updated for connections
// where the TLS handshake fails.
// See bug 1492424.
add_task(async function() {
let rootURI = getRootDirectory(gTestPath).replace("chrome://mochitests/content",
"https://example.com");
await BrowserTestUtils.withNewTab(rootURI + "dummy_page.html", async (browser) => {
let identityMode = window.document.getElementById("identity-box").className;
is(identityMode, "verifiedDomain", "identity should be secure before");
const TLS_HANDSHAKE_FAILURE_URI = "https://ssl3.example.com/";
// Try to connect to a server where the TLS handshake will fail.
BrowserTestUtils.loadURI(browser, TLS_HANDSHAKE_FAILURE_URI);
await BrowserTestUtils.browserLoaded(browser, false, TLS_HANDSHAKE_FAILURE_URI, true);
let newIdentityMode = window.document.getElementById("identity-box").className;
is(newIdentityMode, "unknownIdentity", "identity should be unknown (not secure) after");
});
});

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

@ -34,6 +34,10 @@ add_task(async function test_page_contextmenu() {
add_task(async function test_link_contextmenu() {
const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, remoteClients: remoteClientsFixture,
state: UIState.STATUS_SIGNED_IN, isSendableURI: true });
let expectation = sandbox.mock(gSync)
.expects("sendTabToDevice")
.once()
.withExactArgs("https://www.example.org/", [{id: 1, name: "Foo"}], "Click on me!!");
// Add a link to the page
await ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
@ -44,17 +48,13 @@ add_task(async function test_link_contextmenu() {
content.document.body.appendChild(a);
});
await openContentContextMenu("#testingLink", "context-sendlinktodevice");
is(document.getElementById("context-sendlinktodevice").hidden, false, "Send tab to device is shown");
is(document.getElementById("context-sendlinktodevice").disabled, false, "Send tab to device is enabled");
checkPopup([
{ label: "Foo" },
{ label: "Bar" },
"----",
{ label: "Send to All Devices" },
]);
await openContentContextMenu("#testingLink", "context-sendlinktodevice", "context-sendlinktodevice-popup");
is(document.getElementById("context-sendlinktodevice").hidden, false, "Send link to device is shown");
is(document.getElementById("context-sendlinktodevice").disabled, false, "Send link to device is enabled");
document.getElementById("context-sendlinktodevice-popup").querySelector("menuitem").click();
await hideContentContextMenu();
expectation.verify();
sandbox.restore();
});

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

@ -79,7 +79,7 @@ add_task(async function muteTabs_usingButton() {
ok(!muted(tab3) && !activeMediaBlocked(tab3), "Tab3 is not muted and not activemedia-blocked");
ok(!muted(tab4) && !activeMediaBlocked(tab4), "Tab4 is not muted and not activemedia-blocked");
// Mute tab1 which is mutliselected, thus other multiselected tabs should be affected too
// Mute tab1 which is multiselected, thus other multiselected tabs should be affected too
// in the following way:
// a) muted tabs (tab0) will remain muted.
// b) unmuted tabs (tab1, tab3) will become muted.
@ -95,7 +95,6 @@ add_task(async function muteTabs_usingButton() {
ok(muted(tab3), "Tab3 is now muted");
ok(!muted(tab4) && !activeMediaBlocked(tab4), "Tab4 is not muted and not activemedia-blocked");
for (let tab of tabs) {
BrowserTestUtils.removeTab(tab);
}
@ -122,7 +121,7 @@ add_task(async function unmuteTabs_usingButton() {
// Multiselecting tab0, tab1, tab2 and tab3
await triggerClickOn(tab3, { shiftKey: true });
// Check mutliselection
// Check multiselection
for (let i = 0; i <= 3; i++) {
ok(tabs[i].multiselected, "tab" + i + " is multiselected");
}
@ -136,7 +135,7 @@ add_task(async function unmuteTabs_usingButton() {
ok(muted(tab4), "Tab4 is muted");
is(gBrowser.selectedTab, tab0, "Tab0 is active");
// unmute tab0 which is mutliselected, thus other multiselected tabs should be affected too
// unmute tab0 which is multiselected, thus other multiselected tabs should be affected too
// in the following way:
// a) muted tabs (tab3) will become unmuted.
// b) unmuted tabs (tab0) will remain unmuted.
@ -157,6 +156,58 @@ add_task(async function unmuteTabs_usingButton() {
}
});
add_task(async function muteAndUnmuteTabs_usingKeyboard() {
let tab0 = await addMediaTab();
let tab1 = await addMediaTab();
let tab2 = await addMediaTab();
let tab3 = await addMediaTab();
let tab4 = await addMediaTab();
let tabs = [tab0, tab1, tab2, tab3, tab4];
await BrowserTestUtils.switchTab(gBrowser, tab0);
let mutedPromise = get_wait_for_mute_promise(tab0, true);
EventUtils.synthesizeKey("M", {ctrlKey: true});
await mutedPromise;
ok(muted(tab0), "Tab0 should be muted");
ok(!muted(tab1), "Tab1 should not be muted");
ok(!muted(tab2), "Tab2 should not be muted");
ok(!muted(tab3), "Tab3 should not be muted");
ok(!muted(tab4), "Tab4 should not be muted");
// Multiselecting tab0, tab1, tab2 and tab3
await triggerClickOn(tab3, { shiftKey: true });
// Check multiselection
for (let i = 0; i <= 3; i++) {
ok(tabs[i].multiselected, "tab" + i + " is multiselected");
}
ok(!tab4.multiselected, "tab4 is not multiselected");
mutedPromise = get_wait_for_mute_promise(tab0, false);
EventUtils.synthesizeKey("M", {ctrlKey: true});
await mutedPromise;
ok(!muted(tab0), "Tab0 should not be muted");
ok(!muted(tab1), "Tab1 should not be muted");
ok(!muted(tab2), "Tab2 should not be muted");
ok(!muted(tab3), "Tab3 should not be muted");
ok(!muted(tab4), "Tab4 should not be muted");
mutedPromise = get_wait_for_mute_promise(tab0, true);
EventUtils.synthesizeKey("M", {ctrlKey: true});
await mutedPromise;
ok(muted(tab0), "Tab0 should be muted");
ok(muted(tab1), "Tab1 should be muted");
ok(muted(tab2), "Tab2 should be muted");
ok(muted(tab3), "Tab3 should be muted");
ok(!muted(tab4), "Tab4 should not be muted");
for (let tab of tabs) {
BrowserTestUtils.removeTab(tab);
}
});
add_task(async function playTabs_usingButton() {
let tab0 = await addMediaTab();
let tab1 = await addMediaTab();
@ -178,7 +229,7 @@ add_task(async function playTabs_usingButton() {
tab0.toggleMuteAudio();
tab4.toggleMuteAudio();
// Check mutliselection
// Check multiselection
for (let i = 0; i <= 3; i++) {
ok(tabs[i].multiselected, "tab" + i + " is multiselected");
}
@ -192,7 +243,7 @@ add_task(async function playTabs_usingButton() {
ok(muted(tab4), "Tab4 is muted");
is(gBrowser.selectedTab, tab0, "Tab0 is active");
// play tab2 which is mutliselected, thus other multiselected tabs should be affected too
// play tab2 which is multiselected, thus other multiselected tabs should be affected too
// in the following way:
// a) muted tabs (tab0) will become unmuted.
// b) unmuted tabs (tab3) will remain unmuted.
@ -229,12 +280,12 @@ add_task(async function checkTabContextMenu() {
await play(tab1, false);
tab2.toggleMuteAudio();
// Mutliselect tab0, tab1, tab2.
// multiselect tab0, tab1, tab2.
await triggerClickOn(tab0, { ctrlKey: true });
await triggerClickOn(tab1, { ctrlKey: true });
await triggerClickOn(tab2, { ctrlKey: true });
// Check mutliselected tabs
// Check multiselected tabs
for (let i = 0; i <= 2; i++) {
ok(tabs[i].multiselected, "Tab" + i + " is multi-selected");
}

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

@ -128,7 +128,7 @@ var paymentDialogWrapper = {
let addressData = this.temporaryStore.addresses.get(guid) ||
await formAutofillStorage.addresses.get(guid);
if (!addressData) {
throw new Error(`Shipping address not found: ${guid}`);
throw new Error(`Address not found: ${guid}`);
}
let address = this.createPaymentAddress({

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

@ -11,6 +11,7 @@ import ObservedPropertiesMixin from "../mixins/ObservedPropertiesMixin.js";
export default class LabelledCheckbox extends ObservedPropertiesMixin(HTMLElement) {
static get observedAttributes() {
return [
"infoTooltip",
"form",
"label",
"value",
@ -21,6 +22,10 @@ export default class LabelledCheckbox extends ObservedPropertiesMixin(HTMLElemen
this._label = document.createElement("label");
this._labelSpan = document.createElement("span");
this._infoTooltip = document.createElement("span");
this._infoTooltip.className = "info-tooltip";
this._infoTooltip.setAttribute("tabindex", "0");
this._infoTooltip.setAttribute("role", "tooltip");
this._checkbox = document.createElement("input");
this._checkbox.type = "checkbox";
}
@ -29,11 +34,13 @@ export default class LabelledCheckbox extends ObservedPropertiesMixin(HTMLElemen
this.appendChild(this._label);
this._label.appendChild(this._checkbox);
this._label.appendChild(this._labelSpan);
this._label.appendChild(this._infoTooltip);
this.render();
}
render() {
this._labelSpan.textContent = this.label;
this._infoTooltip.setAttribute("aria-label", this.infoTooltip);
// We don't use the ObservedPropertiesMixin behaviour because we want to be able to mirror
// form="" but ObservedPropertiesMixin removes attributes when "".
if (this.hasAttribute("form")) {

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

@ -139,6 +139,7 @@ export default class AddressForm extends PaymentStateSubscriberMixin(PaymentRequ
this.dataset.addButtonLabel;
}
this.persistCheckbox.label = this.dataset.persistCheckboxLabel;
this.persistCheckbox.infoTooltip = this.dataset.persistCheckboxInfoTooltip;
this.backButton.hidden = page.onboardingWizard;
this.cancelButton.hidden = !page.onboardingWizard;
@ -312,6 +313,7 @@ export default class AddressForm extends PaymentStateSubscriberMixin(PaymentRequ
if (page.onboardingWizard && !Object.keys(savedBasicCards).length) {
successStateChange = {
"basic-card-page": {
selectedStateKey: "selectedPaymentCard",
// Preserve field values as the user may have already edited the card
// page and went back to the address page to make a correction.
preserveFieldValues: true,

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

@ -150,6 +150,7 @@ export default class BasicCardForm extends PaymentStateSubscriberMixin(PaymentRe
this.dataset.addButtonLabel;
}
this.persistCheckbox.label = this.dataset.persistCheckboxLabel;
this.persistCheckbox.infoTooltip = this.dataset.persistCheckboxInfoTooltip;
this.addressAddLink.textContent = this.dataset.addressAddLinkLabel;
this.addressEditLink.textContent = this.dataset.addressEditLinkLabel;
this.acceptedCardsList.label = this.dataset.acceptedCardsLabel;
@ -168,6 +169,10 @@ export default class BasicCardForm extends PaymentStateSubscriberMixin(PaymentRe
this.form.querySelector("#cc-number").disabled = editing;
// The CVV fields should be hidden and disabled when editing.
this.form.querySelector("#cc-csc-container").hidden = editing;
this.form.querySelector("#cc-csc").disabled = editing;
// If a card is selected we want to edit it.
if (editing) {
this.pageTitleHeading.textContent = this.dataset.editBasicCardTitle;
@ -283,6 +288,7 @@ export default class BasicCardForm extends PaymentStateSubscriberMixin(PaymentRe
preserveFieldValues: true,
guid: basicCardPage.guid,
persistCheckboxValue: this.persistCheckbox.checked,
selectedStateKey: basicCardPage.selectedStateKey,
},
};
let billingAddressGUID = this.form.querySelector("#billingAddressGUID");
@ -399,7 +405,7 @@ export default class BasicCardForm extends PaymentStateSubscriberMixin(PaymentRe
record.isTemporary = true;
}
for (let editableFieldName of ["cc-name", "cc-exp-month", "cc-exp-year"]) {
for (let editableFieldName of ["cc-name", "cc-exp-month", "cc-exp-year", "cc-type"]) {
record[editableFieldName] = record[editableFieldName] || "";
}
@ -409,14 +415,23 @@ export default class BasicCardForm extends PaymentStateSubscriberMixin(PaymentRe
record["cc-number"] = record["cc-number"] || "";
}
// Never save the CSC in storage. Storage will throw and not save the record
// if it is passed.
delete record["cc-csc"];
try {
let {guid} = await paymentRequest.updateAutofillRecord("creditCards", record,
basicCardPage.guid);
let {selectedStateKey} = currentState["basic-card-page"];
if (!selectedStateKey) {
throw new Error(`state["basic-card-page"].selectedStateKey is required`);
}
this.requestStore.setState({
page: {
id: "payment-summary",
},
selectedPaymentCard: guid,
[selectedStateKey]: guid,
[selectedStateKey + "SecurityCode"]: this.form.querySelector("#cc-csc").value,
});
} catch (ex) {
log.warn("saveRecord: error:", ex);

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

@ -259,6 +259,7 @@ export default class PaymentDialog extends PaymentStateSubscriberMixin(HTMLEleme
(this._isPayerRequested(state.request.paymentOptions) &&
(!this._payerAddressPicker.selectedOption ||
this._payerAddressPicker.classList.contains(INVALID_CLASS_NAME))) ||
!this._paymentMethodPicker.securityCodeInput.validity.valid ||
!this._paymentMethodPicker.selectedOption ||
this._paymentMethodPicker.classList.contains(INVALID_CLASS_NAME) ||
state.changesPrevented;

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

@ -25,6 +25,7 @@ export default class PaymentMethodPicker extends RichPicker {
this.securityCodeInput.pattern = "[0-9]{3,}";
this.securityCodeInput.classList.add("security-code");
this.securityCodeInput.addEventListener("change", this);
this.securityCodeInput.addEventListener("input", this);
}
connectedCallback() {
@ -74,6 +75,11 @@ export default class PaymentMethodPicker extends RichPicker {
`does not exist in the payment method picker`);
}
let securityCodeState = state[this.selectedStateKey + "SecurityCode"];
if (securityCodeState && securityCodeState != this.securityCodeInput.value) {
this.securityCodeInput.defaultValue = securityCodeState;
}
super.render(state);
}
@ -88,7 +94,7 @@ export default class PaymentMethodPicker extends RichPicker {
}
let acceptedNetworks = paymentRequest.getAcceptedNetworks(state.request);
let selectedCard = state.savedBasicCards[selectedOption.value];
let selectedCard = paymentRequest.getBasicCards(state)[selectedOption.value];
let isSupported = selectedCard["cc-type"] &&
acceptedNetworks.includes(selectedCard["cc-type"]);
return isSupported;
@ -100,8 +106,9 @@ export default class PaymentMethodPicker extends RichPicker {
handleEvent(event) {
switch (event.type) {
case "input":
case "change": {
this.onChange(event);
this.onInputOrChange(event);
break;
}
case "click": {
@ -111,7 +118,7 @@ export default class PaymentMethodPicker extends RichPicker {
}
}
onChange({target}) {
onInputOrChange({target}) {
let selectedKey = this.selectedStateKey;
let stateChange = {};
@ -141,7 +148,9 @@ export default class PaymentMethodPicker extends RichPicker {
page: {
id: "basic-card-page",
},
"basic-card-page": {},
"basic-card-page": {
selectedStateKey: this.selectedStateKey,
},
};
switch (target) {

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

@ -17,6 +17,7 @@ export let requestStore = new PaymentsStore({
"basic-card-page": {
guid: null,
// preserveFieldValues: true,
selectedStateKey: null,
},
"address-page": {
guid: null,

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

@ -172,6 +172,47 @@ payment-dialog[changes-prevented][complete-status="success"] #pay {
left: 0;
}
.persist-checkbox {
padding: 5px 0;
}
.persist-checkbox > label {
display: flex;
align-items: center;
}
.info-tooltip {
display: inline-block;
background-image: url(chrome://global/skin/icons/help.svg);
width: 16px;
height: 16px;
padding: 2px 4px;
background-repeat: no-repeat;
background-position: center;
position: relative;
}
.info-tooltip:focus::after,
.info-tooltip:hover::after {
content: attr(aria-label);
display: block;
position: absolute;
padding: 2px 5px;
background-color: #fff;
border: 1px solid #bebebf;
box-shadow: 1px 1px 3px #bebebf;
font-size: smaller;
min-width: 188px;
left: -86px;
bottom: 20px;
}
.info-tooltip:dir(rtl):focus::after,
.info-tooltip:dir(rtl):hover::after {
left: auto;
right: -86px;
}
.branding {
background-image: url(chrome://branding/content/icon32.png);
background-size: 16px;
@ -182,6 +223,6 @@ payment-dialog[changes-prevented][complete-status="success"] #pay {
margin-inline-end: auto;
}
body[dir="rtl"] .branding {
.branding:dir(rtl) {
background-position: right center;
}

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

@ -60,7 +60,8 @@
<!ENTITY basicCardPage.addButton.label "Add">
<!ENTITY basicCardPage.nextButton.label "Next">
<!ENTITY basicCardPage.updateButton.label "Update">
<!ENTITY basicCardPage.persistCheckbox.label "Save credit card to &brandShortName; (Security code will not be saved)">
<!ENTITY basicCardPage.persistCheckbox.label "Save credit card to &brandShortName; (CVV will not be saved)">
<!ENTITY basicCardPage.persistCheckbox.infoTooltip "&brandShortName; can securely store your credit card information to use in forms like this, so you dont have to enter it every time.">
<!ENTITY addressPage.error.genericSave "There was an error saving the address.">
<!ENTITY addressPage.cancelButton.label "Cancel">
<!ENTITY addressPage.backButton.label "Back">
@ -68,6 +69,7 @@
<!ENTITY addressPage.nextButton.label "Next">
<!ENTITY addressPage.updateButton.label "Update">
<!ENTITY addressPage.persistCheckbox.label "Save address to &brandShortName;">
<!ENTITY addressPage.persistCheckbox.infoTooltip "&brandShortName; can add your address to forms like this, so you dont have to type it every time.">
<!ENTITY failErrorPage.title "We couldnt complete your payment to **host-name**">
<!ENTITY failErrorPage.suggestionHeading "The most likely cause is a hiccup with your credit card.">
<!ENTITY failErrorPage.suggestion1 "Make sure the card youre using hasnt expired">
@ -199,6 +201,7 @@
data-update-button-label="&basicCardPage.updateButton.label;"
data-cancel-button-label="&cancelPaymentButton.label;"
data-persist-checkbox-label="&basicCardPage.persistCheckbox.label;"
data-persist-checkbox-info-tooltip="&basicCardPage.persistCheckbox.infoTooltip;"
data-accepted-cards-label="&acceptedCards.label;"
data-field-required-symbol="&fieldRequiredSymbol;"
hidden="hidden"></basic-card-form>
@ -211,6 +214,7 @@
data-next-button-label="&addressPage.nextButton.label;"
data-update-button-label="&addressPage.updateButton.label;"
data-persist-checkbox-label="&addressPage.persistCheckbox.label;"
data-persist-checkbox-info-tooltip="&addressPage.persistCheckbox.infoTooltip;"
data-field-required-symbol="&fieldRequiredSymbol;"
hidden="hidden"></address-form>

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

@ -252,7 +252,9 @@ var PaymentTestUtils = {
* @returns {undefined}
*/
completePayment: () => {
content.document.getElementById("pay").click();
let button = content.document.getElementById("pay");
ok(!button.disabled, "Pay button should not be disabled when clicking it");
button.click();
},
setSecurityCode: ({securityCode}) => {

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

@ -564,6 +564,10 @@ add_task(async function test_private_persist_addresses() {
tempAddress.name.includes(address["family-name"]), "Address.name was computed");
}, {address: addressToAdd, tempAddressGuid});
await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.setSecurityCode, {
securityCode: "123",
});
info("clicking pay");
spawnPaymentDialogTask(frame, PTU.DialogContentTasks.completePayment);

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

@ -61,7 +61,10 @@ async function add_link(aOptions = {}) {
if (aOptions.hasOwnProperty("setCardPersistCheckedValue")) {
cardOptions.setPersistCheckedValue = aOptions.setCardPersistCheckedValue;
}
await fillInCardForm(frame, PTU.BasicCards.JaneMasterCard, cardOptions);
await fillInCardForm(frame, {
["cc-csc"]: 123,
...PTU.BasicCards.JaneMasterCard,
}, cardOptions);
await verifyCardNetwork(frame, cardOptions);
await verifyPersistCheckbox(frame, cardOptions);
@ -650,7 +653,10 @@ add_task(async function test_private_card_adding() {
"Check card page state");
});
await fillInCardForm(frame, PTU.BasicCards.JohnDoe);
await fillInCardForm(frame, {
["cc-csc"]: "999",
...PTU.BasicCards.JohnDoe,
});
await spawnPaymentDialogTask(frame, async function() {
let {

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

@ -103,6 +103,10 @@ add_task(async function test_change_shipping() {
btn.click();
});
await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.setSecurityCode, {
securityCode: "123",
});
info("clicking pay");
spawnPaymentDialogTask(frame, PTU.DialogContentTasks.completePayment);
@ -263,6 +267,10 @@ add_task(async function test_no_shippingchange_without_shipping() {
}, PTU.ContentTasks.ensureNoPaymentRequestEvent);
info("added shipping change handler");
await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.setSecurityCode, {
securityCode: "456",
});
info("clicking pay");
spawnPaymentDialogTask(frame, PTU.DialogContentTasks.completePayment);

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

@ -28,6 +28,10 @@ add_task(async function test_complete_success() {
}
);
await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.setSecurityCode, {
securityCode: "123",
});
spawnPaymentDialogTask(frame, PTU.DialogContentTasks.completePayment);
// Add a handler to complete the payment above.
@ -56,6 +60,10 @@ add_task(async function test_complete_fail() {
}
);
await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.setSecurityCode, {
securityCode: "456",
});
info("clicking pay");
spawnPaymentDialogTask(frame, PTU.DialogContentTasks.completePayment);
@ -89,6 +97,10 @@ add_task(async function test_complete_timeout() {
}
);
await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.setSecurityCode, {
securityCode: "789",
});
info("clicking pay");
await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.completePayment);

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

@ -91,7 +91,10 @@ add_task(async function test_onboarding_wizard_without_saved_addresses_and_saved
}, "Shipping address is selected as the billing address");
});
await fillInCardForm(frame, PTU.BasicCards.JohnDoe);
await fillInCardForm(frame, {
["cc-csc"]: "123",
...PTU.BasicCards.JohnDoe,
});
await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.clickPrimaryButton);
@ -362,7 +365,10 @@ add_task(async function test_onboarding_wizard_with_requestShipping_turned_off()
}, "Billing Address is correctly shown");
});
await fillInCardForm(frame, PTU.BasicCards.JohnDoe);
await fillInCardForm(frame, {
["cc-csc"]: "123",
...PTU.BasicCards.JohnDoe,
});
await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.clickPrimaryButton);

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

@ -125,6 +125,11 @@ add_task(async function test_show_completePayment2() {
info("select the shipping address");
await selectPaymentDialogShippingAddressByCountry(frame, "US");
info("entering CSC");
await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.setSecurityCode, {
securityCode: "123",
});
info("clicking pay");
spawnPaymentDialogTask(frame, PTU.DialogContentTasks.completePayment);
@ -234,6 +239,11 @@ add_task(async function test_supportedNetworks() {
}
);
info("entering CSC");
await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.setSecurityCode, {
securityCode: "789",
});
await spawnPaymentDialogTask(frame, () => {
let acceptedCards = content.document.querySelector("accepted-cards");
ok(acceptedCards && !content.isHidden(acceptedCards),

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

@ -138,6 +138,7 @@ add_task(async function test_saveButton() {
let year = (new Date()).getFullYear().toString();
fillField(form.form.querySelector("#cc-exp-year"), year);
fillField(form.form.querySelector("#cc-type"), "visa");
fillField(form.form.querySelector("#cc-csc"), "123");
isnot(form.form.querySelector("#billingAddressGUID").value, address2.guid,
"Check initial billing address");
fillField(form.form.querySelector("#billingAddressGUID"), address2.guid);
@ -203,7 +204,7 @@ add_task(async function test_requiredAttributePropagated() {
await asyncElementRendered();
let requiredElements = [...form.form.elements].filter(e => e.required && !e.disabled);
is(requiredElements.length, 6, "Number of required elements");
is(requiredElements.length, 7, "Number of required elements");
for (let element of requiredElements) {
if (element.id == "billingAddressGUID") {
// The billing address has a different layout.
@ -445,6 +446,7 @@ add_task(async function test_field_validity_updates() {
let ccNumber = form.form.querySelector("#cc-number");
let nameInput = form.form.querySelector("#cc-name");
let typeInput = form.form.querySelector("#cc-type");
let cscInput = form.form.querySelector("#cc-csc");
let monthInput = form.form.querySelector("#cc-exp-month");
let yearInput = form.form.querySelector("#cc-exp-year");
@ -460,6 +462,7 @@ add_task(async function test_field_validity_updates() {
let year = (new Date()).getFullYear().toString();
fillField(yearInput, year);
fillField(typeInput, "visa");
fillField(cscInput, "456");
ok(ccNumber.checkValidity(), "cc-number field is valid with good input");
ok(nameInput.checkValidity(), "cc-name field is valid with a value");
ok(monthInput.checkValidity(), "cc-exp-month field is valid with a value");

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

@ -115,6 +115,11 @@ async function setup({shippingRequired, payerRequired}) {
state.selectedShippingAddress = null;
state.selectedShippingOption = null;
await el1.requestStore.setState(state);
// Fill the security code input so it doesn't interfere with checking the pay
// button state for dropdown changes.
el1._paymentMethodPicker.securityCodeInput.select();
sendString("123");
await asyncElementRendered();
}
@ -216,6 +221,34 @@ add_task(async function runTests() {
}
}
});
add_task(async function test_securityCodeRequired() {
await setup({
payerRequired: false,
shippingRequired: false,
});
let picker = el1._paymentMethodPicker;
let payButton = document.getElementById("pay");
let stateChangedPromise = promiseStateChange(el1.requestStore);
selectFirstItemOfPicker(picker);
await stateChangedPromise;
picker.securityCodeInput.select();
stateChangedPromise = promiseStateChange(el1.requestStore);
synthesizeKey("VK_DELETE");
await stateChangedPromise;
ok(payButton.disabled, "Button is disabled when CVV is empty");
picker.securityCodeInput.select();
stateChangedPromise = promiseStateChange(el1.requestStore);
sendString("123");
await stateChangedPromise;
ok(!payButton.disabled, "Button is enabled when CVV is filled");
});
</script>
</body>

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

@ -210,6 +210,65 @@ add_task(async function test_delete() {
ok(options[0].textContent.includes("J Smith"), "Check remaining card #1");
ok(options[1].textContent.includes("Jane Fields"), "Check remaining card #2");
});
add_task(async function test_supportedNetworks_tempCards() {
await picker1.requestStore.reset();
let request = Object.assign({}, picker1.requestStore.getState().request);
request.paymentMethods = [
{
supportedMethods: "basic-card",
data: {
supportedNetworks: [
"mastercard",
"visa",
],
},
},
];
await picker1.requestStore.setState({
request,
selectedPaymentCard: "68gjdh354j",
tempBasicCards: {
"68gjdh354j": {
"cc-exp": "2017-08",
"cc-exp-month": 8,
"cc-exp-year": 2017,
"cc-name": "J Smith",
"cc-number": "***********1234",
"cc-type": "discover",
"guid": "68gjdh354j",
},
},
});
await asyncElementRendered();
let options = picker1.dropdown.popupBox.children;
is(options.length, 1, "Check dropdown has one card");
ok(options[0].textContent.includes("J Smith"), "Check remaining card #1");
ok(picker1.classList.contains("invalid-selected-option"),
"Check discover is recognized as not supported");
info("change the card to be a visa");
await picker1.requestStore.setState({
tempBasicCards: {
"68gjdh354j": {
"cc-exp": "2017-08",
"cc-exp-month": 8,
"cc-exp-year": 2017,
"cc-name": "J Smith",
"cc-number": "***********1234",
"cc-type": "visa",
"guid": "68gjdh354j",
},
},
});
await asyncElementRendered();
ok(!picker1.classList.contains("invalid-selected-option"),
"Check visa is recognized as supported");
});
</script>
</body>

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

@ -14,4 +14,5 @@ EXTRA_JS_MODULES += [
'UrlbarView.jsm',
]
BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']
XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']

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

@ -0,0 +1,7 @@
"use strict";
module.exports = {
"extends": [
"plugin:mozilla/browser-test"
]
};

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

@ -0,0 +1,8 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
[DEFAULT]
[browser_UrlbarInput_unit.js]
support-files = empty.xul

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

@ -14,7 +14,14 @@ let generalListener;
let input;
let inputOptions;
ChromeUtils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
ChromeUtils.import("resource:///modules/UrlbarController.jsm", this);
/* global sinon */
Services.scriptloader.loadSubScript("resource://testing-common/sinon-2.3.2.js");
registerCleanupFunction(function() {
delete window.sinon;
});
/**
* Asserts that the query context has the expected values.
@ -32,15 +39,6 @@ function assertContextMatches(context, expectedValues) {
}
}
/**
* @returns {object} A fake element with minimal functions for simulating textbox etc.
*/
function createFakeElement() {
return {
addEventListener() {},
};
}
/**
* Checks the result of a handleQuery call on the controller.
*
@ -68,7 +66,7 @@ function checkHandleQueryCall(stub, expectedQueryContextProps) {
}
}
add_task(function setup() {
add_task(async function setup() {
sandbox = sinon.sandbox.create();
fakeController = new UrlbarController();
@ -76,17 +74,30 @@ add_task(function setup() {
sandbox.stub(fakeController, "handleQuery");
sandbox.stub(PrivateBrowsingUtils, "isWindowPrivate").returns(false);
let textbox = createFakeElement();
textbox.inputField = createFakeElement();
textbox.inputField.controllers = { insertControllerAt() {} };
// Open a new window, so we don't affect other tests by adding extra
// UrbarInput wrappers around the urlbar.
let gTestRoot = getRootDirectory(gTestPath);
let win = window.openDialog(gTestRoot + "empty.xul",
"", "chrome");
await BrowserTestUtils.waitForEvent(win, "load");
registerCleanupFunction(async () => {
await BrowserTestUtils.closeWindow(win);
sandbox.restore();
});
// Clone the elements into the new window, so we get exact copies without having
// to replicate the xul.
let doc = win.document;
let textbox = doc.importNode(document.getElementById("urlbar"), true);
doc.documentElement.appendChild(textbox);
let panel = doc.importNode(document.getElementById("urlbar-results"), true);
doc.documentElement.appendChild(panel);
inputOptions = {
textbox,
panel: {
ownerDocument: {},
querySelector() {
return createFakeElement();
},
},
panel,
controller: fakeController,
};

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

@ -0,0 +1,2 @@
<?xml version="1.0"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/>

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

@ -6,4 +6,3 @@ firefox-appdir = browser
[test_tokenizer.js]
[test_UrlbarController_unit.js]
[test_UrlbarController_integration.js]
[test_UrlbarInput_unit.js]

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

@ -60,6 +60,17 @@
</select>
<span data-localization="cardNetwork" class="label-text"/>
</label>
<label id="cc-csc-container" class="container" hidden="hidden">
<!-- Keep these attributes in-sync with securityCodeInput in payment-method-picker.js -->
<input id="cc-csc"
type="text"
autocomplete="off"
size="3"
required="required"
pattern="[0-9]{3,}"
disabled="disabled"/>
<span data-localization="cardCVV" class="label-text"/>
</label>
<div id="billingAddressGUID-container" class="billingAddressRow container rich-picker">
<select id="billingAddressGUID" required="required">
</select>

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

@ -185,6 +185,8 @@ cardExpiresMonth = Exp. Month
cardExpiresYear = Exp. Year
billingAddress = Billing Address
cardNetwork = Card Type
# LOCALIZATION NOTE (cardCVV): Credit card security code https://en.wikipedia.org/wiki/Card_security_code
cardCVV = CVV
# LOCALIZATION NOTE: (cardNetwork.*): These are brand names and should only be translated when a locale-specific name for that brand is in common use
cardNetwork.amex = American Express

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

@ -44,6 +44,10 @@
grid-area: cc-type;
}
#cc-csc-container {
grid-area: cc-csc;
}
#billingAddressGUID-container {
grid-area: billingAddressGUID;
}

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

@ -70,12 +70,18 @@ class AboutDebuggingApp extends Component {
componentDidMount() {
window.addEventListener("hashchange", this.onHashChange);
this.onHashChange();
this.props.telemetry.toolOpened("aboutdebugging");
// aboutdebugging is not connected with a toolbox so we pass -1 as the
// toolbox session id.
this.props.telemetry.toolOpened("aboutdebugging", -1, this);
}
componentWillUnmount() {
window.removeEventListener("hashchange", this.onHashChange);
this.props.telemetry.toolClosed("aboutdebugging");
// aboutdebugging is not connected with a toolbox so we pass -1 as the
// toolbox session id.
this.props.telemetry.toolClosed("aboutdebugging", -1, this);
}
onHashChange() {

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

@ -59,11 +59,6 @@ AccessibilityPanel.prototype = {
resolver = resolve;
});
// Local monitoring needs to make the target remote.
if (!this.target.isRemote) {
await this.target.attach();
}
this._telemetry = new Telemetry();
this.panelWin.gTelemetry = this._telemetry;
@ -147,9 +142,9 @@ AccessibilityPanel.prototype = {
updateA11YServiceDurationTimer() {
if (this.front.enabled) {
this._telemetry.start(A11Y_SERVICE_DURATION, this, true);
this._telemetry.start(A11Y_SERVICE_DURATION, this);
} else {
this._telemetry.finish(A11Y_SERVICE_DURATION, this, true);
this._telemetry.finish(A11Y_SERVICE_DURATION, this);
}
},

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

@ -141,7 +141,8 @@ class Picker {
await this.walker.cancelPick();
this._telemetry.toolClosed("accessibility_picker");
this._telemetry.toolClosed(
"accessibility_picker", this.toolbox.sessionId, this);
this.walker.off("picker-accessible-hovered", this.onPickerAccessibleHovered);
this.walker.off("picker-accessible-picked", this.onPickerAccessiblePicked);
@ -172,7 +173,8 @@ class Picker {
await this.walker.pick(doFocus);
this._telemetry.toolOpened("accessibility_picker");
this._telemetry.toolOpened(
"accessibility_picker", this.toolbox.sessionId, this);
this.emit("picker-started");
}

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

@ -23,10 +23,6 @@ class ApplicationPanel {
}
async open() {
if (!this.toolbox.target.isRemote) {
await this.toolbox.target.attach();
}
await this.panelWin.Application.bootstrap({
toolbox: this.toolbox,
panel: this,

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

@ -48,7 +48,6 @@ function navigate(target, url, waitForTargetEvent = "navigate") {
async function openNewTabAndApplicationPanel(url) {
const tab = await addTab(url);
const target = await TargetFactory.forTab(tab);
await target.attach();
const toolbox = await gDevTools.showToolbox(target, "application");
const panel = toolbox.getCurrentPanel();

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

@ -27,31 +27,16 @@ CanvasDebuggerPanel.prototype = {
* @return object
* A promise that is resolved when the Canvas Debugger completes opening.
*/
open: function() {
let targetPromise;
// Local debugging needs to make the target remote.
if (!this.target.isRemote) {
targetPromise = this.target.attach();
} else {
targetPromise = Promise.resolve(this.target);
}
return targetPromise
.then(() => {
open: async function() {
this.panelWin.gToolbox = this._toolbox;
this.panelWin.gTarget = this.target;
this.panelWin.gFront = new CanvasFront(this.target.client, this.target.form);
return this.panelWin.startupCanvasDebugger();
})
.then(() => {
await this.panelWin.startupCanvasDebugger();
this.isReady = true;
this.emit("ready");
return this;
})
.catch(function onError(aReason) {
DevToolsUtils.reportException("CanvasDebuggerPanel.prototype.open", aReason);
});
},
// DevToolPanel API

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

@ -149,7 +149,6 @@ function initCanvasDebuggerBackend(aUrl) {
return (async function() {
const tab = await addTab(aUrl);
const target = await TargetFactory.forTab(tab);
await target.attach();
const front = new CanvasFront(target.client, target.form);

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

@ -296,10 +296,6 @@ var DebuggerController = {
connectThread: function () {
const { newSource, fetchEventListeners } = bindActionCreators(actions, this.dispatch);
// TODO: bug 806775, update the globals list using aPacket.hostAnnotations
// from bug 801084.
// this.client.addListener("newGlobal", ...);
this.activeThread.addListener("newSource", (event, packet) => {
newSource(packet.source);
@ -344,7 +340,6 @@ var DebuggerController = {
return;
}
this.client.removeListener("newGlobal");
this.activeThread.removeListener("newSource");
this.activeThread.removeListener("blackboxchange");
@ -495,7 +490,7 @@ Workers.prototype = {
return;
}
this._tabClient.listWorkers((response) => {
this._tabClient.listWorkers().then((response) => {
let workerForms = Object.create(null);
for (let worker of response.workers) {
workerForms[worker.actor] = worker;

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

@ -46777,7 +46777,7 @@ function mapTopLevelAwait(expression) {
const ast = hasTopLevelAwait(expression);
if (ast) {
const func = wrapExpression(ast);
return (0, _generator2.default)(_template2.default.ast(`(${func})().then(console.log).catch(console.error);`)).code;
return (0, _generator2.default)(_template2.default.ast(`(${func})();`)).code;
}
return expression;

56
devtools/client/debugger/new/dist/vendors.js поставляемый
Просмотреть файл

@ -7510,12 +7510,10 @@ class Telemetry {
* Event telemetry is disabled by default. Use this method to enable it for
* a particular category.
*
* @param {String} category
* The telemetry event category e.g. "devtools.main"
* @param {Boolean} enabled
* Enabled: true or false.
*/
setEventRecordingEnabled(category, enabled) {
setEventRecordingEnabled(enabled) {
return enabled;
}
@ -7529,9 +7527,10 @@ class Telemetry {
* properties have been received. Once they have all been received we send the
* telemetry event.
*
* @param {String} category
* The telemetry event category (a group name for events and helps to
* avoid name conflicts) e.g. "devtools.main"
* @param {Object} obj
* The telemetry event or ping is associated with this object, meaning
* that multiple events or pings for the same histogram may be run
* concurrently, as long as they are associated with different objects.
* @param {String} method
* The telemetry event method (describes the type of event that
* occurred e.g. "open")
@ -7549,16 +7548,17 @@ class Telemetry {
* "width"
* ]
*/
preparePendingEvent(category, method, object, value, expected = []) {}
preparePendingEvent(obj, method, object, value, expected = []) {}
/**
* Adds an expected property for either a current or future pending event.
* This means that if preparePendingEvent() is called before or after sending
* the event properties they will automatically added to the event.
*
* @param {String} category
* The telemetry event category (a group name for events and helps to
* avoid name conflicts) e.g. "devtools.main"
* @param {Object} obj
* The telemetry event or ping is associated with this object, meaning
* that multiple events or pings for the same histogram may be run
* concurrently, as long as they are associated with different objects.
* @param {String} method
* The telemetry event method (describes the type of event that
* occurred e.g. "open")
@ -7573,16 +7573,17 @@ class Telemetry {
* @param {String} pendingPropValue
* The pending property value
*/
addEventProperty(category, method, object, value, pendingPropName, pendingPropValue) {}
addEventProperty(obj, method, object, value, pendingPropName, pendingPropValue) {}
/**
* Adds expected properties for either a current or future pending event.
* This means that if preparePendingEvent() is called before or after sending
* the event properties they will automatically added to the event.
*
* @param {String} category
* The telemetry event category (a group name for events and helps to
* avoid name conflicts) e.g. "devtools.main"
* @param {Object} obj
* The telemetry event or ping is associated with this object, meaning
* that multiple events or pings for the same histogram may be run
* concurrently, as long as they are associated with different objects.
* @param {String} method
* The telemetry event method (describes the type of event that
* occurred e.g. "open")
@ -7596,16 +7597,17 @@ class Telemetry {
* An object containing key, value pairs that should be added to the
* event as properties.
*/
addEventProperties(category, method, object, value, pendingObject) {}
addEventProperties(obj, method, object, value, pendingObject) {}
/**
* A private method that is not to be used externally. This method is used to
* prepare a pending telemetry event for sending and then send it via
* recordEvent().
*
* @param {String} category
* The telemetry event category (a group name for events and helps to
* avoid name conflicts) e.g. "devtools.main"
* @param {Object} obj
* The telemetry event or ping is associated with this object, meaning
* that multiple events or pings for the same histogram may be run
* concurrently, as long as they are associated with different objects.
* @param {String} method
* The telemetry event method (describes the type of event that
* occurred e.g. "open")
@ -7616,14 +7618,11 @@ class Telemetry {
* The telemetry event value (a user defined value, providing context
* for the event) e.g. "console"
*/
_sendPendingEvent(category, method, object, value) {}
_sendPendingEvent(obj, method, object, value) {}
/**
* Send a telemetry event.
*
* @param {String} category
* The telemetry event category (a group name for events and helps to
* avoid name conflicts) e.g. "devtools.main"
* @param {String} method
* The telemetry event method (describes the type of event that
* occurred e.g. "open")
@ -7641,15 +7640,22 @@ class Telemetry {
* width: "1024"
* }
*/
recordEvent(category, method, object, value, extra) {}
recordEvent(method, object, value, extra) {}
/**
* Sends telemetry pings to indicate that a tool has been opened.
*
* @param {String} id
* The ID of the tool opened.
* @param {String} sessionId
* Toolbox session id used when we need to ensure a tool really has a
* timer before calculating a delta.
* @param {Object} obj
* The telemetry event or ping is associated with this object, meaning
* that multiple events or pings for the same histogram may be run
* concurrently, as long as they are associated with different objects.
*/
toolOpened(id) {}
toolOpened(id, sessionId, obj) {}
/**
* Sends telemetry pings to indicate that a tool has been closed.
@ -7657,7 +7663,7 @@ class Telemetry {
* @param {String} id
* The ID of the tool opened.
*/
toolClosed(id) {}
toolClosed(id, sessionId, obj) {}
}
module.exports = Telemetry;

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

@ -21,10 +21,6 @@ function DebuggerPanel(iframeWindow, toolbox) {
DebuggerPanel.prototype = {
open: async function() {
if (!this.toolbox.target.isRemote) {
await this.toolbox.target.attach();
}
const {
actions,
store,

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

@ -42,18 +42,18 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
* //
* // NOTE: You CAN send properties before preparing the event.
* //
* telemetry.preparePendingEvent("devtools.main", "pause", "debugger", null, [
* telemetry.preparePendingEvent(this, "pause", "debugger", null, [
* "reason", "collapsed_callstacks"
* ]);
*
* // Elsewhere in another codepath send the reason property
* telemetry.addEventProperty(
* "devtools.main", "pause", "debugger", null, "reason", "debugger-statement"
* this, "pause", "debugger", null, "reason", "debugger-statement"
* );
*
* // Elsewhere in another codepath send the collapsed_callstacks property
* telemetry.addEventProperty(
* "devtools.main", "pause", "debugger", null, "collapsed_callstacks", 1
* this, "pause", "debugger", null, "collapsed_callstacks", 1
* );
*/
const telemetry = new _telemetry2.default();
@ -75,7 +75,7 @@ function recordEvent(eventName, fields = {}) {
/* eslint-disable camelcase */
telemetry.recordEvent("devtools.main", eventName, "debugger", null, {
telemetry.recordEvent(eventName, "debugger", null, {
session_id: sessionId,
...fields
});

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

@ -8,7 +8,6 @@
*/
var gClient, gThreadClient;
var gNewGlobal = promise.defer();
var gNewChromeSource = promise.defer();
var { DevToolsLoader } = ChromeUtils.import(
@ -35,12 +34,6 @@ async function attachThread(client, actor) {
return threadClient;
}
function onNewGlobal() {
ok(true, "Received a new chrome global.");
gClient.removeListener("newGlobal", onNewGlobal);
gNewGlobal.resolve();
}
function onNewSource(event, packet) {
if (packet.source.url.startsWith("chrome:")) {
ok(true, "Received a new chrome source: " + packet.source.url);
@ -58,7 +51,6 @@ function resumeAndCloseConnection() {
registerCleanupFunction(function() {
gClient = null;
gThreadClient = null;
gNewGlobal = null;
gNewChromeSource = null;
customLoader = null;
@ -78,8 +70,7 @@ add_task(async function() {
// listen for a new source and global
gThreadClient.addListener("newSource", onNewSource);
gClient.addListener("newGlobal", onNewGlobal);
await promise.all([gNewGlobal.promise, gNewChromeSource.promise]);
await gNewChromeSource.promise;
await resumeAndCloseConnection();
});

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

@ -35,20 +35,13 @@ DebuggerPanel.prototype = {
* A promise that is resolved when the Debugger completes opening.
*/
open: function () {
let targetPromise;
// Local debugging needs to make the target remote.
if (!this.target.isRemote) {
targetPromise = this.target.attach();
// Listen for tab switching events to manage focus when the content window
// is paused and events suppressed.
if (this.target.isLocalTab) {
this.target.tab.addEventListener("TabSelect", this);
} else {
targetPromise = promise.resolve(this.target);
}
return targetPromise
.then(() => this._controller.startupDebugger())
return Promise.resolve(this._controller.startupDebugger())
.then(() => this._controller.connect())
.then(() => {
this._toolbox.on("host-changed", this.handleHostChanged);

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

@ -11,7 +11,6 @@ const TAB_URL = EXAMPLE_URL + "doc_inline-debugger-statement.html";
var gClient, gThreadClient;
var gAttached = promise.defer();
var gNewGlobal = promise.defer();
var gNewChromeSource = promise.defer();
var { DevToolsLoader } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
@ -30,7 +29,7 @@ function test() {
is(aType, "browser",
"Root actor should identify itself as a browser.");
promise.all([gAttached.promise, gNewGlobal.promise, gNewChromeSource.promise])
promise.all([gAttached.promise, gNewChromeSource.promise])
.then(resumeAndCloseConnection)
.then(finish)
.catch(aError => {
@ -43,8 +42,6 @@ function test() {
function testParentProcessTargetActor() {
gClient.getProcess().then(aResponse => {
gClient.addListener("newGlobal", onNewGlobal);
let actor = aResponse.form.actor;
gClient.attachTarget(actor).then(([response, tabClient]) => {
tabClient.attachThread(null).then(([aResponse, aThreadClient]) => {
@ -66,13 +63,6 @@ function testParentProcessTargetActor() {
});
}
function onNewGlobal() {
ok(true, "Received a new chrome global.");
gClient.removeListener("newGlobal", onNewGlobal);
gNewGlobal.resolve();
}
function onNewSource(aEvent, aPacket) {
if (aPacket.source.url.startsWith("chrome:")) {
ok(true, "Received a new chrome source: " + aPacket.source.url);
@ -92,7 +82,6 @@ registerCleanupFunction(function () {
gClient = null;
gThreadClient = null;
gAttached = null;
gNewGlobal = null;
gNewChromeSource = null;
customLoader = null;

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

@ -1093,11 +1093,7 @@ function attachTarget(client, tab) {
function listWorkers(tabClient) {
info("Listing workers.");
return new Promise(function (resolve) {
tabClient.listWorkers(function (response) {
resolve(response);
});
});
return tabClient.listWorkers();
}
function findWorker(workers, url) {

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

@ -36,26 +36,13 @@ DomPanel.prototype = {
* @return object
* A promise that is resolved when the DOM panel completes opening.
*/
async open() {
if (this._opening) {
return this._opening;
}
const deferred = defer();
this._opening = deferred.promise;
// Local monitoring needs to make the target remote.
if (!this.target.isRemote) {
await this.target.attach();
}
open() {
this.initialize();
this.isReady = true;
this.emit("ready");
deferred.resolve(this);
return this._opening;
return this;
},
// Initialization

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

@ -302,7 +302,9 @@ BrowserToolboxProcess.prototype = {
}).then(proc => {
this._dbgProcess = proc;
this._telemetry.toolOpened("jsbrowserdebugger");
// jsbrowserdebugger is not connected with a toolbox so we pass -1 as the
// toolbox session id.
this._telemetry.toolOpened("jsbrowserdebugger", -1, this);
dumpn("Chrome toolbox is now running...");
this.emit("run", this);
@ -355,7 +357,9 @@ BrowserToolboxProcess.prototype = {
this._dbgProcess.stdout.close();
await this._dbgProcess.kill();
this._telemetry.toolClosed("jsbrowserdebugger");
// jsbrowserdebugger is not connected with a toolbox so we pass -1 as the
// toolbox session id.
this._telemetry.toolClosed("jsbrowserdebugger", -1, this);
if (this.debuggerServer) {
this.debuggerServer.off("connectionchange", this._onConnectionChange);

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

@ -82,7 +82,7 @@ function createToolMenuElements(toolDefinition, doc) {
try {
const window = event.target.ownerDocument.defaultView;
await gDevToolsBrowser.selectToolCommand(window.gBrowser, id, Cu.now());
sendEntryPointTelemetry();
sendEntryPointTelemetry(window);
} catch (e) {
console.error(`Exception while opening ${id}: ${e}\n${e.stack}`);
}
@ -110,17 +110,15 @@ function createToolMenuElements(toolDefinition, doc) {
* `devtools/startup/devtools-startup.js` but that codepath is only used the
* first time a toolbox is opened for a tab.
*/
function sendEntryPointTelemetry() {
function sendEntryPointTelemetry(window) {
if (!telemetry) {
telemetry = new Telemetry();
}
telemetry.addEventProperty(
"devtools.main", "open", "tools", null, "shortcut", ""
);
telemetry.addEventProperty(window, "open", "tools", null, "shortcut", "");
telemetry.addEventProperty(
"devtools.main", "open", "tools", null, "entrypoint", "SystemMenu"
window, "open", "tools", null, "entrypoint", "SystemMenu"
);
}

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

@ -41,7 +41,7 @@ function DevTools() {
EventEmitter.decorate(this);
this._telemetry = new Telemetry();
this._telemetry.setEventRecordingEnabled("devtools.main", true);
this._telemetry.setEventRecordingEnabled(true);
// Listen for changes to the theme pref.
this._onThemeChanged = this._onThemeChanged.bind(this);
@ -488,9 +488,7 @@ DevTools.prototype = {
// the "open" event.
const width = Math.ceil(toolbox.win.outerWidth / 50) * 50;
const panelName = this.makeToolIdHumanReadable(toolId || toolbox.defaultToolId);
this._telemetry.addEventProperty(
"devtools.main", "enter", panelName, null, "width", width
);
this._telemetry.addEventProperty(toolbox, "enter", panelName, null, "width", width);
return toolbox;
},
@ -517,8 +515,9 @@ DevTools.prototype = {
"DEVTOOLS_COLD_TOOLBOX_OPEN_DELAY_MS" : "DEVTOOLS_WARM_TOOLBOX_OPEN_DELAY_MS";
this._telemetry.getKeyedHistogramById(telemetryKey).add(toolId, delay);
const browserWin = toolbox.win.top;
this._telemetry.addEventProperty(
"devtools.main", "open", "tools", null, "first_panel", panelName
browserWin, "open", "tools", null, "first_panel", panelName
);
},

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

@ -100,6 +100,18 @@ ToolSidebar.prototype = {
TABPANEL_ID_PREFIX: "sidebar-panel-",
get toolboxSessionId() {
const frameElement = this._panelDoc.ownerGlobal.parent.frameElement;
if (frameElement) {
return frameElement.getAttribute("session_id");
}
// We are not running inside a toolbox... this is probably a scratchpad
// instance so return -1.
return -1;
},
/**
* Add a "…" button at the end of the tabstripe that toggles a dropdown menu
* containing the list of all tabs if any become hidden due to lack of room.
@ -450,13 +462,13 @@ ToolSidebar.prototype = {
this._currentTool = this.getCurrentTabID();
if (previousTool) {
if (this._telemetry) {
this._telemetry.toolClosed(previousTool);
this._telemetry.toolClosed(previousTool, this.toolboxSessionId, this);
}
this.emit(previousTool + "-unselected");
}
if (this._telemetry) {
this._telemetry.toolOpened(this._currentTool);
this._telemetry.toolOpened(this._currentTool, this.toolboxSessionId, this);
}
this.emit(this._currentTool + "-selected");
@ -508,7 +520,7 @@ ToolSidebar.prototype = {
this._currentTool = id;
if (this._telemetry) {
this._telemetry.toolOpened(this._currentTool);
this._telemetry.toolOpened(this._currentTool, this.toolboxSessionId, this);
}
this._selectTabSoon(id);
@ -578,7 +590,7 @@ ToolSidebar.prototype = {
}
if (this._currentTool && this._telemetry) {
this._telemetry.toolClosed(this._currentTool);
this._telemetry.toolClosed(this._currentTool, this.toolboxSessionId, this);
}
this._toolPanel.emit("sidebar-destroyed", this);

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

@ -15,7 +15,6 @@ const L10N = new LocalizationHelper("devtools/client/locales/toolbox.properties"
add_task(async function() {
const tab = await addTab("about:blank");
const target = await TargetFactory.forTab(tab);
await target.attach();
const toolIDs = gDevTools.getToolDefinitionArray()
.filter(

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

@ -35,7 +35,6 @@ function test() {
toggleAllTools(true);
const tab = await addTab("about:blank");
const target = await TargetFactory.forTab(tab);
await target.attach();
await performChecks(target);
gBrowser.removeCurrentTab();
toggleAllTools(false);

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

@ -20,14 +20,12 @@ function test() {
addTab(TEST_URL).then(async () => {
target = await TargetFactory.forTab(gBrowser.selectedTab);
target.attach().then(() => {
toolIDs = gDevTools.getToolDefinitionArray()
.filter(def => def.isTargetSupported(target))
.map(def => def.id);
gDevTools.showToolbox(target, toolIDs[0], Toolbox.HostType.BOTTOM)
.then(startReloadTest);
});
});
}
function startReloadTest(aToolbox) {

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

@ -82,11 +82,6 @@ OptionsPanel.prototype = {
},
async open() {
// For local debugging we need to make the target remote.
if (!this.target.isRemote) {
await this.target.attach();
}
this.setupToolsList();
this.setupToolbarButtonsList();
this.setupThemeList();
@ -266,10 +261,15 @@ OptionsPanel.prototype = {
return tool.visibilityswitch && !tool.hiddenInOptions;
});
const fragment = this.panelDoc.createDocumentFragment();
for (const tool of toggleableTools) {
defaultToolsBox.appendChild(createToolCheckbox(tool));
fragment.appendChild(createToolCheckbox(tool));
}
const toolsNotSupportedLabelNode =
this.panelDoc.getElementById("tools-not-supported-label");
defaultToolsBox.insertBefore(fragment, toolsNotSupportedLabelNode);
// Clean up any existent additional tools content.
for (const label of additionalToolsBox.querySelectorAll("label")) {
label.remove();
@ -396,7 +396,15 @@ OptionsPanel.prototype = {
for (const prefDefinition of prefDefinitions) {
const parent = this.panelDoc.getElementById(prefDefinition.parentId);
parent.appendChild(createPreferenceOption(prefDefinition));
// We want to insert the new definition after the last existing
// definition, but before any other element.
// For example in the "Advanced Settings" column there's indeed a <span>
// text at the end, and we want that it stays at the end.
// The reference element can be `null` if there's no label or if there's
// no element after the last label. But that's OK and it will do what we
// want.
const referenceElement = parent.querySelector("label:last-of-type + *");
parent.insertBefore(createPreferenceOption(prefDefinition), referenceElement);
parent.removeAttribute("hidden");
}
},
@ -459,6 +467,10 @@ OptionsPanel.prototype = {
} else {
// Hide the checkbox and label
this.disableJSNode.parentNode.style.display = "none";
const triggersPageRefreshLabel =
this.panelDoc.getElementById("triggers-page-refresh-label");
triggersPageRefreshLabel.style.display = "none";
}
},
@ -519,7 +531,7 @@ OptionsPanel.prototype = {
"javascriptEnabled": this._origJavascriptEnabled,
"performReload": false
};
this.target.activeTab.reconfigure(options, resolve);
this.target.activeTab.reconfigure(options).then(resolve);
} else {
resolve();
}

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

@ -18,6 +18,9 @@
<div id="tools-box" class="options-vertical-pane">
<fieldset id="default-tools-box" class="options-groupbox">
<legend>&options.selectDefaultTools.label2;</legend>
<span id="tools-not-supported-label"
class="options-citation-label theme-comment">
&options.toolNotSupported.label;</span>
</fieldset>
<fieldset id="additional-tools-box" class="options-groupbox">
@ -26,9 +29,6 @@
<fieldset id="enabled-toolbox-buttons-box" class="options-groupbox">
<legend>&options.selectEnabledToolboxButtons.label;</legend>
<span id="tools-not-supported-label"
class="options-citation-label theme-comment">
&options.toolNotSupported.label;</span>
</fieldset>
</div>
@ -189,6 +189,7 @@
<span>&options.enableRemote.label3;</span>
</label>
<span class="options-citation-label theme-comment"
id="triggers-page-refresh-label"
>&options.context.triggersPageRefresh;</span>
</fieldset>
</div>

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

@ -540,14 +540,15 @@ Toolbox.prototype = {
// Wait until the original tool is selected so that the split
// console input will receive focus.
const browserWin = this.win.top;
let splitConsolePromise = promise.resolve();
if (Services.prefs.getBoolPref(SPLITCONSOLE_ENABLED_PREF)) {
splitConsolePromise = this.openSplitConsole();
this.telemetry.addEventProperty(
"devtools.main", "open", "tools", null, "splitconsole", true);
browserWin, "open", "tools", null, "splitconsole", true);
} else {
this.telemetry.addEventProperty(
"devtools.main", "open", "tools", null, "splitconsole", false);
browserWin, "open", "tools", null, "splitconsole", false);
}
await promise.all([
@ -736,7 +737,7 @@ Toolbox.prototype = {
},
_pingTelemetry: function() {
this.telemetry.toolOpened("toolbox");
this.telemetry.toolOpened("toolbox", this.sessionId, this);
this.telemetry.getHistogramById(HOST_HISTOGRAM).add(this._getTelemetryHostId());
@ -745,12 +746,13 @@ Toolbox.prototype = {
const currentTheme = Services.prefs.getCharPref("devtools.theme");
this.telemetry.keyedScalarAdd(CURRENT_THEME_SCALAR, currentTheme, 1);
this.telemetry.preparePendingEvent("devtools.main", "open", "tools", null, [
const browserWin = this.win.top;
this.telemetry.preparePendingEvent(browserWin, "open", "tools", null, [
"entrypoint", "first_panel", "host", "shortcut",
"splitconsole", "width", "session_id"
]);
this.telemetry.addEventProperty(
"devtools.main", "open", "tools", null, "host", this._getTelemetryHostString()
browserWin, "open", "tools", null, "host", this._getTelemetryHostString()
);
},
@ -1394,9 +1396,9 @@ Toolbox.prototype = {
*/
togglePaintFlashing: function() {
if (this.isPaintFlashing) {
this.telemetry.toolOpened("paintflashing");
this.telemetry.toolOpened("paintflashing", this.sessionId, this);
} else {
this.telemetry.toolClosed("paintflashing");
this.telemetry.toolClosed("paintflashing", this.sessionId, this);
}
this.isPaintFlashing = !this.isPaintFlashing;
return this.target.activeTab.reconfigure({"paintFlashing": this.isPaintFlashing});
@ -1910,7 +1912,7 @@ Toolbox.prototype = {
id === "options" ||
this.additionalToolDefinitions.get(id)) {
if (this.currentToolId) {
this.telemetry.toolClosed(this.currentToolId);
this.telemetry.toolClosed(this.currentToolId, this.sessionId, this);
}
this._pingTelemetrySelectTool(id, reason);
@ -1949,7 +1951,7 @@ Toolbox.prototype = {
// On first load this.currentToolId === undefined so we need to skip sending
// a devtools.main.exit telemetry event.
if (this.currentToolId) {
this.telemetry.recordEvent("devtools.main", "exit", prevPanelName, null, {
this.telemetry.recordEvent("exit", prevPanelName, null, {
"host": this._hostType,
"width": width,
"panel_name": prevPanelName,
@ -1959,7 +1961,8 @@ Toolbox.prototype = {
});
}
this.telemetry.addEventProperties("devtools.main", "open", "tools", null, {
const browserWin = this.win.top;
this.telemetry.addEventProperties(browserWin, "open", "tools", null, {
"width": width,
"session_id": this.sessionId
});
@ -1968,9 +1971,9 @@ Toolbox.prototype = {
pending.push("message_count");
}
this.telemetry.preparePendingEvent("devtools.main", "enter", panelName, null, pending);
this.telemetry.preparePendingEvent(this, "enter", panelName, null, pending);
this.telemetry.addEventProperties("devtools.main", "enter", panelName, null, {
this.telemetry.addEventProperties(this, "enter", panelName, null, {
"host": this._hostType,
"start_state": reason,
"panel_name": panelName,
@ -1981,7 +1984,7 @@ Toolbox.prototype = {
if (reason !== "initial_panel") {
const width = Math.ceil(this.win.outerWidth / 50) * 50;
this.telemetry.addEventProperty(
"devtools.main", "enter", panelName, null, "width", width
this, "enter", panelName, null, "width", width
);
}
@ -1989,10 +1992,10 @@ Toolbox.prototype = {
// devtools/client/webconsole/webconsole-output-wrapper.js
if (!cold && id === "webconsole") {
this.telemetry.addEventProperty(
"devtools.main", "enter", "webconsole", null, "message_count", 0);
this, "enter", "webconsole", null, "message_count", 0);
}
this.telemetry.toolOpened(id);
this.telemetry.toolOpened(id, this.sessionId, this);
},
/**
@ -2060,7 +2063,7 @@ Toolbox.prototype = {
return this.loadTool("webconsole").then(() => {
this.component.setIsSplitConsoleActive(true);
this.telemetry.recordEvent("devtools.main", "activate", "split_console", null, {
this.telemetry.recordEvent("activate", "split_console", null, {
"host": this._getTelemetryHostString(),
"width": Math.ceil(this.win.outerWidth / 50) * 50,
"session_id": this.sessionId
@ -2082,7 +2085,7 @@ Toolbox.prototype = {
this._refreshConsoleDisplay();
this.component.setIsSplitConsoleActive(false);
this.telemetry.recordEvent("devtools.main", "deactivate", "split_console", null, {
this.telemetry.recordEvent("deactivate", "split_console", null, {
"host": this._getTelemetryHostString(),
"width": Math.ceil(this.win.outerWidth / 50) * 50,
"session_id": this.sessionId
@ -2814,7 +2817,7 @@ Toolbox.prototype = {
// We normally handle toolClosed from selectTool() but in the event of the
// toolbox closing we need to handle it here instead.
this.telemetry.toolClosed(this.currentToolId);
this.telemetry.toolClosed(this.currentToolId, this.sessionId, this);
this._lastFocusedElement = null;
@ -2906,8 +2909,8 @@ Toolbox.prototype = {
const width = Math.ceil(win.outerWidth / 50) * 50;
const prevPanelName = this.getTelemetryPanelNameOrOther(this.currentToolId);
this.telemetry.toolClosed("toolbox");
this.telemetry.recordEvent("devtools.main", "exit", prevPanelName, null, {
this.telemetry.toolClosed("toolbox", this.sessionId, this);
this.telemetry.recordEvent("exit", prevPanelName, null, {
"host": host,
"width": width,
"panel_name": this.getTelemetryPanelNameOrOther(this.currentToolId),
@ -2915,7 +2918,7 @@ Toolbox.prototype = {
"reason": "toolbox_close",
"session_id": this.sessionId
});
this.telemetry.recordEvent("devtools.main", "close", "tools", null, {
this.telemetry.recordEvent("close", "tools", null, {
"host": host,
"width": width,
"session_id": this.sessionId

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

@ -172,7 +172,6 @@ Inspector.prototype = {
localizeMarkup(this.panelDoc);
this._cssProperties = await initCssProperties(this.toolbox);
await this.target.attach();
await this._getPageStyle();
// This may throw if the document is still loading and we are

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

@ -1550,7 +1550,7 @@ MarkupView.prototype = {
}
const end = this.telemetry.msSystemNow();
this.telemetry.recordEvent("devtools.main", "edit_html", "inspector", null, {
this.telemetry.recordEvent("edit_html", "inspector", null, {
"made_changes": commit,
"time_open": end - start,
"session_id": this.toolbox.sessionId

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

@ -604,7 +604,7 @@ RuleEditor.prototype = {
// the field gets destroyed (see _newPropertyDestroy)
this.editor.input.blur();
this.telemetry.recordEvent("devtools.main", "edit_rule", "ruleview", null, {
this.telemetry.recordEvent("edit_rule", "ruleview", null, {
"session_id": this.toolbox.sessionId
});
},

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

@ -754,7 +754,7 @@ TextPropertyEditor.prototype = {
}
this.prop.setEnabled(!checked);
event.stopPropagation();
this.telemetry.recordEvent("devtools.main", "edit_rule", "ruleview", null, {
this.telemetry.recordEvent("edit_rule", "ruleview", null, {
"session_id": this.toolbox.sessionId
});
},
@ -827,7 +827,7 @@ TextPropertyEditor.prototype = {
return;
}
this.telemetry.recordEvent("devtools.main", "edit_rule", "ruleview", null, {
this.telemetry.recordEvent("edit_rule", "ruleview", null, {
"session_id": this.toolbox.sessionId
});
@ -922,7 +922,7 @@ TextPropertyEditor.prototype = {
return;
}
this.telemetry.recordEvent("devtools.main", "edit_rule", "ruleview", null, {
this.telemetry.recordEvent("edit_rule", "ruleview", null, {
"session_id": this.toolbox.sessionId
});

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

@ -27,7 +27,8 @@ async function testRuleView(ruleView, inspector) {
const tooltip = ruleView.tooltips.getTooltip("previewTooltip");
const tooltipContent = ruleView.styleDocument.createElementNS(XHTML_NS, "div");
await tooltip.setContent(tooltipContent, {width: 100, height: 30});
tooltip.panel.appendChild(tooltipContent);
tooltip.setContentSize({width: 100, height: 30});
// Stop listening for mouse movements because it's not needed for this test,
// and causes intermittent failures on Linux. When this test runs in the suite
@ -52,7 +53,8 @@ async function testComputedView(computedView, inspector) {
const tooltip = computedView.tooltips.getTooltip("previewTooltip");
const tooltipContent = computedView.styleDocument.createElementNS(XHTML_NS, "div");
await tooltip.setContent(tooltipContent, {width: 100, height: 30});
tooltip.panel.appendChild(tooltipContent);
await tooltip.setContentSize({width: 100, height: 30});
// Stop listening for mouse movements because it's not needed for this test,
// and causes intermittent failures on Linux. When this test runs in the suite

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

@ -68,7 +68,8 @@ class ThreePaneOnboardingTooltip {
this.closeButton.addEventListener("click", this.onCloseButtonClick);
this.learnMoreLink.addEventListener("click", this.onLearnMoreLinkClick);
this.tooltip.setContent(container, { width: CONTAINER_WIDTH });
this.tooltip.panel.appendChild(container);
this.tooltip.setContentSize({ width: CONTAINER_WIDTH });
this.tooltip.show(this.doc.querySelector("#inspector-sidebar .sidebar-toggle"), {
position: "top",
});

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

@ -332,15 +332,15 @@ ToolSidebar.prototype = {
return;
}
const sessionId = this._toolPanel._toolbox.sessionId;
currentToolId = this.getTelemetryPanelNameOrOther(currentToolId);
if (previousToolId) {
const sessionId = this._toolPanel._toolbox.sessionId;
previousToolId = this.getTelemetryPanelNameOrOther(previousToolId);
this._telemetry.toolClosed(previousToolId, sessionId);
this._telemetry.toolClosed(previousToolId, sessionId, this);
this._telemetry.recordEvent("devtools.main", "sidepanel_changed", "inspector", null,
this._telemetry.recordEvent("sidepanel_changed", "inspector", null,
{
"oldpanel": previousToolId,
"newpanel": currentToolId,
@ -349,7 +349,7 @@ ToolSidebar.prototype = {
}
);
}
this._telemetry.toolOpened(currentToolId);
this._telemetry.toolOpened(currentToolId, sessionId, this);
},
/**
@ -446,7 +446,8 @@ ToolSidebar.prototype = {
}
if (this._currentTool && this._telemetry) {
this._telemetry.toolClosed(this._currentTool);
const sessionId = this._toolPanel._toolbox.sessionId;
this._telemetry.toolClosed(this._currentTool, sessionId, this);
}
this._toolPanel.emit("sidebar-destroyed", this);

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

@ -11,10 +11,6 @@ function NetMonitorPanel(iframeWindow, toolbox) {
NetMonitorPanel.prototype = {
async open() {
if (!this.toolbox.target.isRemote) {
await this.toolbox.target.attach();
}
// Reuse an existing Network monitor API object if available.
// It could have been created for WE API before Net panel opens.
const api = await this.toolbox.getNetMonitorAPI();

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

@ -317,10 +317,8 @@ class FirefoxConnector {
};
// Reconfigures the tab, optionally triggering a reload.
const reconfigureTab = (options) => {
return new Promise((resolve) => {
this.tabTarget.activeTab.reconfigure(options, resolve);
});
const reconfigureTab = options => {
return this.tabTarget.activeTab.reconfigure(options);
};
// Reconfigures the tab and waits for the target to finish navigating.

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

@ -45,9 +45,7 @@ HarAutomation.prototype = {
this.toolbox = toolbox;
const target = toolbox.target;
target.attach().then(() => {
this.startMonitoring(target.client, target.form);
});
},
destroy: function() {

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

@ -114,7 +114,7 @@ function filterChange({action, state, oldState, telemetry, sessionId}) {
trigger = "text";
}
telemetry.recordEvent("devtools.main", "filters_changed", "netmonitor", null, {
telemetry.recordEvent("filters_changed", "netmonitor", null, {
"trigger": trigger,
"active": activeFilters.join(","),
"inactive": inactiveFilters.join(","),
@ -128,7 +128,7 @@ function filterChange({action, state, oldState, telemetry, sessionId}) {
* telemetry event.
*/
function sidePanelChange({state, oldState, telemetry, sessionId}) {
telemetry.recordEvent("devtools.main", "sidepanel_changed", "netmonitor", null, {
telemetry.recordEvent("sidepanel_changed", "netmonitor", null, {
"oldpanel": oldState.ui.detailsPanelSelectedTab,
"newpanel": state.ui.detailsPanelSelectedTab,
"session_id": sessionId,
@ -140,7 +140,7 @@ function sidePanelChange({state, oldState, telemetry, sessionId}) {
* It's responsible for recording "edit_resend" telemetry event.
*/
function sendCustomRequest({telemetry, sessionId}) {
telemetry.recordEvent("devtools.main", "edit_resend", "netmonitor", null, {
telemetry.recordEvent("edit_resend", "netmonitor", null, {
"session_id": sessionId,
});
}
@ -150,7 +150,7 @@ function sendCustomRequest({telemetry, sessionId}) {
* It's responsible for recording "throttle_changed" telemetry event.
*/
function throttlingChange({action, telemetry, sessionId}) {
telemetry.recordEvent("devtools.main", "throttle_changed", "netmonitor", null, {
telemetry.recordEvent("throttle_changed", "netmonitor", null, {
"mode": action.profile,
"session_id": sessionId,
});

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

@ -136,12 +136,6 @@ function waitForNavigation(target) {
});
}
function reconfigureTab(target, options) {
return new Promise((resolve) => {
target.activeTab.reconfigure(options, resolve);
});
}
function toggleCache(target, disabled) {
const options = { cacheDisabled: disabled, performReload: true };
const navigationFinished = waitForNavigation(target);
@ -149,7 +143,7 @@ function toggleCache(target, disabled) {
// Disable the cache for any toolbox that it is opened from this point on.
Services.prefs.setBoolPref("devtools.cache.disabled", disabled);
return reconfigureTab(target, options).then(() => navigationFinished);
return target.activeTab.reconfigure(options).then(() => navigationFinished);
}
/**
@ -291,9 +285,6 @@ function initNetMonitor(url, enableCache) {
const target = await TargetFactory.forTab(tab);
await target.attach();
info("Target remoted.");
const toolbox = await gDevTools.showToolbox(target, "netmonitor");
info("Network monitor pane shown successfully.");

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

@ -1,6 +1,7 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const Telemetry = require("devtools/client/shared/telemetry");
@ -35,7 +36,7 @@ function PerformanceTelemetry(emitter) {
PerformanceTelemetry.prototype.destroy = function() {
if (this._previousView) {
this._telemetry.finishKeyed(
SELECTED_VIEW_HISTOGRAM_NAME, this._previousView, this);
SELECTED_VIEW_HISTOGRAM_NAME, this._previousView, this, false);
}
for (const [event] of EVENT_MAP_FLAGS) {
@ -79,7 +80,7 @@ PerformanceTelemetry.prototype.onRecordingStateChange = function(status, model)
PerformanceTelemetry.prototype.onViewSelected = function(viewName) {
if (this._previousView) {
this._telemetry.finishKeyed(
SELECTED_VIEW_HISTOGRAM_NAME, this._previousView, this);
SELECTED_VIEW_HISTOGRAM_NAME, this._previousView, this, false);
}
this._previousView = viewName;
this._telemetry.startKeyed(SELECTED_VIEW_HISTOGRAM_NAME, viewName, this);

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

@ -24,7 +24,6 @@ exports.initPanelInTab = async function({ tool, tab }) {
dump(`Initializing a ${tool} panel.\n`);
const target = await TargetFactory.forTab(tab);
await target.attach();
// Open a toolbox and wait for the connection to the performance actors
// to be opened. This is necessary because of the WebConsole's

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

@ -36,7 +36,9 @@ const bootstrap = {
store: null,
async init() {
this.telemetry.toolOpened("responsive");
// responsive is not connected with a toolbox so we pass -1 as the
// toolbox session id.
this.telemetry.toolOpened("responsive", -1, this);
const store = this.store = Store();
const provider = createElement(Provider, { store }, App());
@ -46,7 +48,10 @@ const bootstrap = {
destroy() {
this.store = null;
this.telemetry.toolClosed("responsive");
// responsive is not connected with a toolbox so we pass -1 as the
// toolbox session id.
this.telemetry.toolClosed("responsive", -1, this);
this.telemetry = null;
},

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

@ -125,7 +125,7 @@ const ResponsiveUIManager = exports.ResponsiveUIManager = {
tel.scalarAdd("devtools.responsive.toolbox_opened_first", 1);
}
tel.recordEvent("devtools.main", "activate", "responsive_design", null, {
tel.recordEvent("activate", "responsive_design", null, {
"host": hostType,
"width": Math.ceil(window.outerWidth / 50) * 50,
"session_id": toolbox ? toolbox.sessionId : -1
@ -188,7 +188,7 @@ const ResponsiveUIManager = exports.ResponsiveUIManager = {
const hostType = toolbox ? toolbox.hostType : "none";
const t = this._telemetry;
t.recordEvent("devtools.main", "deactivate", "responsive_design", null, {
t.recordEvent("deactivate", "responsive_design", null, {
"host": hostType,
"width": Math.ceil(window.outerWidth / 50) * 50,
"session_id": toolbox ? toolbox.sessionId : -1

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

@ -42,7 +42,8 @@ class SettingOnboardingTooltip {
this.closeButton.addEventListener("click", this.onCloseButtonClick);
this.tooltip.setContent(container, { width: CONTAINER_WIDTH });
this.tooltip.panel.appendChild(container);
this.tooltip.setContentSize({ width: CONTAINER_WIDTH });
this.tooltip.show(this.doc.getElementById("settings-button"), {
position: "bottom",
});

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

@ -34,11 +34,6 @@ ShaderEditorPanel.prototype = {
* A promise that is resolved when the Shader Editor completes opening.
*/
async open() {
// Local debugging needs to make the target remote.
if (!this.target.isRemote) {
await this.target.attach();
}
this.front = new WebGLFront(this.target.client, this.target.form);
this.shadersListView = new ShadersListView();
this.eventsHandler = new EventsHandler();

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

@ -591,7 +591,7 @@ class ShadersEditorsView {
messageDiv.textContent = message;
div.appendChild(messageDiv);
}
tooltip.setContent(div);
tooltip.panel.appendChild(div);
tooltip.startTogglingOnHover(node, () => true, {
toggleDelay: GUTTER_ERROR_PANEL_DELAY

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

@ -165,8 +165,6 @@ function initShaderEditor(aUrl) {
const tab = await addTab(aUrl);
const target = await TargetFactory.forTab(tab);
await target.attach();
Services.prefs.setBoolPref("devtools.shadereditor.enabled", true);
const toolbox = await gDevTools.showToolbox(target, "shadereditor");
const panel = toolbox.getCurrentPanel();

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

@ -0,0 +1,107 @@
/* 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/. */
/**
* WeakMapMap is a weakmap collection dual-keyed using an object and a string.
* This is useful for keeping data compartmentalized e.g. grouped by tab.
*
* It's name comes from the internal structure which maps a WeakMap to a map,
* which contains the target data.
*
* Usage:
* const myWeakMapMap = new WeakMapMap();
* const key = { randomObject: true };
* myWeakMapMap.set(key, "text1", "Some value1");
* myWeakMapMap.set(key, "text2", "Some value2");
* myWeakMapMap.get(key, "text1"); // Returns "Some value1"
* myWeakMapMap.get(key, "text2"); // Returns "Some value2"
* myWeakMapMap.has(key, "text1"); // Returns true
* myWeakMapMap.has(key, "notakey"); // Returns false
*/
"use strict";
class WeakMapMap {
constructor() {
this.clear();
}
/**
* Returns the value associated to the key and nestedKey, or undefined if
* there is none.
*
* @param {Object} key
* The key associated with the desired value.
* @param {String} nestedKey
* The nested key associated with the desired value.
*/
get(key, nestedKey) {
if (!this.has(key, nestedKey)) {
return undefined;
}
return this.store.get(key).get(nestedKey);
}
/**
* Returns the value associated to the key and nestedKey, or undefined if
* there is none.
*
* @param {Object} key
* The key associated with the desired value.
* @param {String} nestedKey
* The nested key associated with the desired value.
*/
has(key, nestedKey) {
const hasKey = this.store.has(key);
return hasKey && this.store.get(key).has(nestedKey);
}
/**
*
* @param {Object} key
* The key associated with the value.
* @param {String} nestedKey
* The nested key associated with the value.
* @param {any} value
* The value to add.
*/
set(key, nestedKey, value) {
if (!this.store.has(key)) {
this.store.set(key, new Map());
}
const innerMap = this.store.get(key);
innerMap.set(nestedKey, value);
}
/**
* Removes the value associated to the key and nestedKey.
*
* @param {Object} key
* The key associated with the desired value.
* @param {String} nestedKey
* The nested key associated with the desired value.
*
* @returns True if an element in the store has been removed successfully.
* False if the key is not found in the store.
*/
delete(key, nestedKey) {
if (!this.store.has(key)) {
return false;
}
return this.store.get(key).delete(nestedKey);
}
/**
* Clear the store.
*/
clear() {
this.store = new WeakMap();
}
}
module.exports = WeakMapMap;

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

@ -72,7 +72,8 @@ function AutocompletePopup(toolboxDoc, options = {}) {
this._listPadding = Number(listPadding);
}
this._tooltip.setContent(this._list, { height: Infinity });
this._tooltip.panel.appendChild(this._list);
this._tooltip.setContentSize({ height: Infinity });
this.onClick = this.onClick.bind(this);
this._list.addEventListener("click", this.onClick);

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

@ -291,10 +291,6 @@ class MenuButton extends PureComponent {
}
render() {
// We bypass the call to HTMLTooltip. setContent and set the panel contents
// directly here.
//
// Bug 1472942: Do this for all users of HTMLTooltip.
const menu = ReactDOM.createPortal(
typeof this.props.children === "function"
? this.props.children()

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

@ -53,6 +53,7 @@ DevToolsModules(
'undo.js',
'unicode-url.js',
'view-source.js',
'WeakMapMap.js',
'webgl-utils.js',
'zoom-keys.js',
)

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

@ -14,10 +14,13 @@ const Services = require("Services");
const { TelemetryStopwatch } = require("devtools/client/shared/TelemetryStopwatch.jsm");
const { getNthPathExcluding } = require("devtools/shared/platform/stack");
const { TelemetryEnvironment } = require("resource://gre/modules/TelemetryEnvironment.jsm");
const WeakMapMap = require("devtools/client/shared/WeakMapMap");
const CATEGORY = "devtools.main";
// Object to be shared among all instances.
const PENDING_EVENTS = new Map();
const PENDING_EVENT_PROPERTIES = new Map();
const PENDING_EVENT_PROPERTIES = new WeakMapMap();
const PENDING_EVENTS = new WeakMapMap();
class Telemetry {
constructor() {
@ -33,6 +36,7 @@ class Telemetry {
this.setEventRecordingEnabled = this.setEventRecordingEnabled.bind(this);
this.preparePendingEvent = this.preparePendingEvent.bind(this);
this.addEventProperty = this.addEventProperty.bind(this);
this.addEventProperties = this.addEventProperties.bind(this);
this.toolOpened = this.toolOpened.bind(this);
this.toolClosed = this.toolClosed.bind(this);
}
@ -77,11 +81,9 @@ class Telemetry {
* @param {String} histogramId
* A string which must be a valid histogram name.
* @param {Object} obj
* Optional parameter. If specified, the timer is associated with this
* object, meaning that multiple timers for the same histogram may be
* run concurrently, as long as they are associated with different
* objects.
*
* The telemetry event or ping is associated with this object, meaning
* that multiple events or pings for the same histogram may be run
* concurrently, as long as they are associated with different objects.
* @returns {Boolean}
* True if the timer was successfully started, false otherwise. If a
* timer already exists, it can't be started again, and the existing
@ -103,10 +105,9 @@ class Telemetry {
* @param {String} key
* A string which must be a valid histgram key.
* @param {Object} obj
* Optional parameter. If specified, the timer is associated with this
* object, meaning that multiple timers for the same histogram may be
* run concurrently,as long as they are associated with different
* objects.
* The telemetry event or ping is associated with this object, meaning
* that multiple events or pings for the same histogram may be run
* concurrently, as long as they are associated with different objects.
*
* @returns {Boolean}
* True if the timer was successfully started, false otherwise. If a
@ -125,8 +126,9 @@ class Telemetry {
* @param {String} histogramId
* A string which must be a valid histogram name.
* @param {Object} obj
* Optional parameter which associates the histogram timer with the
* given object.
* The telemetry event or ping is associated with this object, meaning
* that multiple events or pings for the same histogram may be run
* concurrently, as long as they are associated with different objects.
* @param {Boolean} canceledOkay
* Optional parameter which will suppress any warnings that normally
* fire when a stopwatch is finished after being canceled.
@ -137,15 +139,7 @@ class Telemetry {
* to the histogram, False otherwise.
*/
finish(histogramId, obj, canceledOkay) {
// Avoid errors caused by the toolbox not being properly initialized.
this.ignoreStopwatchErrors(true);
const result = TelemetryStopwatch.finish(histogramId, obj, canceledOkay);
// Watch for errors again.
this.ignoreStopwatchErrors(false);
return result;
return TelemetryStopwatch.finish(histogramId, obj, canceledOkay);
}
/**
@ -158,8 +152,9 @@ class Telemetry {
* @param {String} key
* A string which must be a valid histogram key.
* @param {Object} obj
* Optional parameter which associates the histogram timer with the
* given object.
* The telemetry event or ping is associated with this object, meaning
* that multiple events or pings for the same histogram may be run
* concurrently, as long as they are associated with different objects.
* @param {Boolean} canceledOkay
* Optional parameter which will suppress any warnings that normally
* fire when a stopwatch is finished after being canceled.
@ -170,28 +165,7 @@ class Telemetry {
* to the histogram, False otherwise.
*/
finishKeyed(histogramId, key, obj, canceledOkay) {
// Avoid errors caused by the toolbox not being properly initialized.
this.ignoreStopwatchErrors(true);
const result = TelemetryStopwatch.finishKeyed(histogramId, key, obj, canceledOkay);
// Watch for errors again.
this.ignoreStopwatchErrors(false);
return result;
}
/**
* Set a flag to ignore TelemetryStopwatch errors.
*
* @param {Boolean} testing
* Flag to select whether to ignore TelemetryStopwatch errors.
*/
ignoreStopwatchErrors(testing) {
// FIXME: https://bugzil.la/1491879 - Fix telemetry support for multiple
// tabs / windows. This method should be removed as it is hiding
// a problem with using telemetry in multiple tabs / windows.
TelemetryStopwatch.setTestModeEnabled(testing);
return TelemetryStopwatch.finishKeyed(histogramId, key, obj, canceledOkay);
}
/**
@ -366,16 +340,14 @@ class Telemetry {
}
/**
* Event telemetry is disabled by default. Use this method to enable it for
* a particular category.
* Event telemetry is disabled by default. Use this method to enable or
* disable it.
*
* @param {String} category
* The telemetry event category e.g. "devtools.main"
* @param {Boolean} enabled
* Enabled: true or false.
*/
setEventRecordingEnabled(category, enabled) {
return Services.telemetry.setEventRecordingEnabled(category, enabled);
setEventRecordingEnabled(enabled) {
return Services.telemetry.setEventRecordingEnabled(CATEGORY, enabled);
}
/**
@ -388,9 +360,10 @@ class Telemetry {
* properties have been received. Once they have all been received we send the
* telemetry event.
*
* @param {String} category
* The telemetry event category (a group name for events and helps to
* avoid name conflicts) e.g. "devtools.main"
* @param {Object} obj
* The telemetry event or ping is associated with this object, meaning
* that multiple events or pings for the same histogram may be run
* concurrently, as long as they are associated with different objects.
* @param {String} method
* The telemetry event method (describes the type of event that
* occurred e.g. "open")
@ -408,8 +381,8 @@ class Telemetry {
* "width"
* ]
*/
preparePendingEvent(category, method, object, value, expected = []) {
const sig = `${category},${method},${object},${value}`;
preparePendingEvent(obj, method, object, value, expected = []) {
const sig = `${method},${object},${value}`;
if (expected.length === 0) {
throw new Error(`preparePendingEvent() was called without any expected ` +
@ -417,17 +390,19 @@ class Telemetry {
`CALLER: ${getCaller()}`);
}
PENDING_EVENTS.set(sig, {
const data = {
extra: {},
expected: new Set(expected)
});
};
const props = PENDING_EVENT_PROPERTIES.get(sig);
PENDING_EVENTS.set(obj, sig, data);
const props = PENDING_EVENT_PROPERTIES.get(obj, sig);
if (props) {
for (const [name, val] of Object.entries(props)) {
this.addEventProperty(category, method, object, value, name, val);
this.addEventProperty(obj, method, object, value, name, val);
}
PENDING_EVENT_PROPERTIES.delete(sig);
PENDING_EVENT_PROPERTIES.delete(obj, sig);
}
}
@ -436,9 +411,10 @@ class Telemetry {
* This means that if preparePendingEvent() is called before or after sending
* the event properties they will automatically added to the event.
*
* @param {String} category
* The telemetry event category (a group name for events and helps to
* avoid name conflicts) e.g. "devtools.main"
* @param {Object} obj
* The telemetry event or ping is associated with this object, meaning
* that multiple events or pings for the same histogram may be run
* concurrently, as long as they are associated with different objects.
* @param {String} method
* The telemetry event method (describes the type of event that
* occurred e.g. "open")
@ -453,31 +429,32 @@ class Telemetry {
* @param {String} pendingPropValue
* The pending property value
*/
addEventProperty(category, method, object, value, pendingPropName, pendingPropValue) {
const sig = `${category},${method},${object},${value}`;
addEventProperty(obj, method, object, value, pendingPropName, pendingPropValue) {
const sig = `${method},${object},${value}`;
const events = PENDING_EVENTS.get(obj, sig);
// If the pending event has not been created add the property to the pending
// list.
if (!PENDING_EVENTS.has(sig)) {
const props = PENDING_EVENT_PROPERTIES.get(sig);
if (!events) {
const props = PENDING_EVENT_PROPERTIES.get(obj, sig);
if (props) {
props[pendingPropName] = pendingPropValue;
} else {
PENDING_EVENT_PROPERTIES.set(sig, {
PENDING_EVENT_PROPERTIES.set(obj, sig, {
[pendingPropName]: pendingPropValue
});
}
return;
}
const { expected, extra } = PENDING_EVENTS.get(sig);
const { expected, extra } = events;
if (expected.has(pendingPropName)) {
extra[pendingPropName] = pendingPropValue;
if (expected.size === Object.keys(extra).length) {
this._sendPendingEvent(category, method, object, value);
this._sendPendingEvent(obj, method, object, value);
}
} else {
// The property was not expected, warn and bail.
@ -493,9 +470,10 @@ class Telemetry {
* This means that if preparePendingEvent() is called before or after sending
* the event properties they will automatically added to the event.
*
* @param {String} category
* The telemetry event category (a group name for events and helps to
* avoid name conflicts) e.g. "devtools.main"
* @param {Object} obj
* The telemetry event or ping is associated with this object, meaning
* that multiple events or pings for the same histogram may be run
* concurrently, as long as they are associated with different objects.
* @param {String} method
* The telemetry event method (describes the type of event that
* occurred e.g. "open")
@ -509,9 +487,9 @@ class Telemetry {
* An object containing key, value pairs that should be added to the
* event as properties.
*/
addEventProperties(category, method, object, value, pendingObject) {
addEventProperties(obj, method, object, value, pendingObject) {
for (const [key, val] of Object.entries(pendingObject)) {
this.addEventProperty(category, method, object, value, key, val);
this.addEventProperty(obj, method, object, value, key, val);
}
}
@ -520,9 +498,10 @@ class Telemetry {
* prepare a pending telemetry event for sending and then send it via
* recordEvent().
*
* @param {String} category
* The telemetry event category (a group name for events and helps to
* avoid name conflicts) e.g. "devtools.main"
* @param {Object} obj
* The telemetry event or ping is associated with this object, meaning
* that multiple events or pings for the same histogram may be run
* concurrently, as long as they are associated with different objects.
* @param {String} method
* The telemetry event method (describes the type of event that
* occurred e.g. "open")
@ -533,21 +512,18 @@ class Telemetry {
* The telemetry event value (a user defined value, providing context
* for the event) e.g. "console"
*/
_sendPendingEvent(category, method, object, value) {
const sig = `${category},${method},${object},${value}`;
const { extra } = PENDING_EVENTS.get(sig);
_sendPendingEvent(obj, method, object, value) {
const sig = `${method},${object},${value}`;
const { extra } = PENDING_EVENTS.get(obj, sig);
PENDING_EVENTS.delete(sig);
PENDING_EVENT_PROPERTIES.delete(sig);
this.recordEvent(category, method, object, value, extra);
PENDING_EVENTS.delete(obj, sig);
PENDING_EVENT_PROPERTIES.delete(obj, sig);
this.recordEvent(method, object, value, extra);
}
/**
* Send a telemetry event.
*
* @param {String} category
* The telemetry event category (a group name for events and helps to
* avoid name conflicts) e.g. "devtools.main"
* @param {String} method
* The telemetry event method (describes the type of event that
* occurred e.g. "open")
@ -565,7 +541,7 @@ class Telemetry {
* width: "1024"
* }
*/
recordEvent(category, method, object, value = null, extra = null) {
recordEvent(method, object, value = null, extra = null) {
// Only string values are allowed so cast all values to strings.
if (extra) {
for (let [name, val] of Object.entries(extra)) {
@ -573,7 +549,7 @@ class Telemetry {
extra[name] = val;
if (val.length > 80) {
const sig = `${category},${method},${object},${value}`;
const sig = `${method},${object},${value}`;
throw new Error(`The property "${name}" was added to a telemetry ` +
`event with the signature ${sig} but it's value ` +
@ -583,7 +559,7 @@ class Telemetry {
}
}
}
Services.telemetry.recordEvent(category, method, object, value, extra);
Services.telemetry.recordEvent(CATEGORY, method, object, value, extra);
}
/**
@ -591,12 +567,23 @@ class Telemetry {
*
* @param {String} id
* The ID of the tool opened.
* @param {String} sessionId
* Toolbox session id used when we need to ensure a tool really has a
* timer before calculating a delta.
* @param {Object} obj
* The telemetry event or ping is associated with this object, meaning
* that multiple events or pings for the same histogram may be run
* concurrently, as long as they are associated with different objects.
*
* NOTE: This method is designed for tools that send multiple probes on open,
* one of those probes being a counter and the other a timer. If you
* only have one probe you should be using another method.
*/
toolOpened(id) {
toolOpened(id, sessionId, obj) {
if (typeof sessionId === "undefined") {
throw new Error(`toolOpened called without a sessionId parameter.`);
}
const charts = getChartsFromToolId(id);
if (!charts) {
@ -604,16 +591,16 @@ class Telemetry {
}
if (charts.useTimedEvent) {
this.preparePendingEvent("devtools.main", "tool_timer", id, null, [
this.preparePendingEvent(obj, "tool_timer", id, null, [
"os",
"time_open",
"session_id"
]);
this.addEventProperty("devtools.main", "tool_timer", id, null,
this.addEventProperty(obj, "tool_timer", id, null,
"time_open", this.msSystemNow());
}
if (charts.timerHist) {
this.start(charts.timerHist, this);
this.start(charts.timerHist, obj);
}
if (charts.countHist) {
this.getHistogramById(charts.countHist).add(true);
@ -629,14 +616,21 @@ class Telemetry {
* @param {String} id
* The ID of the tool opened.
* @param {String} sessionId
* Optional toolbox session id used only when a tool's chart has a
* useTimedEvent property set to true.
* Toolbox session id.
* @param {Object} obj
* The telemetry event or ping is associated with this object, meaning
* that multiple events or pings for the same histogram may be run
* concurrently, as long as they are associated with different objects.
*
* NOTE: This method is designed for tools that send multiple probes on open,
* one of those probes being a counter and the other a timer. If you
* only have one probe you should be using another method.
*/
toolClosed(id, sessionId) {
toolClosed(id, sessionId, obj) {
if (typeof sessionId === "undefined") {
throw new Error(`toolClosed called without a sessionId parameter.`);
}
const charts = getChartsFromToolId(id);
if (!charts) {
@ -644,18 +638,11 @@ class Telemetry {
}
if (charts.useTimedEvent) {
const sig = `devtools.main,tool_timer,${id},null`;
const event = PENDING_EVENTS.get(sig);
if (!event) {
// FIXME: https://bugzil.la/1491879 - Fix telemetry support for multiple
// tabs / windows. For the moment we need to bail out.
return;
}
const sig = `tool_timer,${id},null`;
const event = PENDING_EVENTS.get(obj, sig);
const time = this.msSystemNow() - event.extra.time_open;
this.addEventProperties("devtools.main", "tool_timer", id, null, {
this.addEventProperties(obj, "tool_timer", id, null, {
"time_open": time,
"os": this.osNameAndVersion,
"session_id": sessionId
@ -663,7 +650,7 @@ class Telemetry {
}
if (charts.timerHist) {
this.finish(charts.timerHist, this);
this.finish(charts.timerHist, obj, false);
}
}
}

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

@ -41,7 +41,8 @@ async function runTests(doc) {
const tooltip = new HTMLTooltip(doc, {useXulWrapper});
info("Set tooltip content");
tooltip.setContent(getTooltipContent(doc), {width: 100, height: 50});
tooltip.panel.appendChild(getTooltipContent(doc));
tooltip.setContentSize({width: 100, height: 50});
is(tooltip.isVisible(), false, "Tooltip is not visible");

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

@ -41,7 +41,8 @@ async function testClickInTooltipContent(doc) {
info("Test a tooltip is not closed when clicking inside itself");
const tooltip = new HTMLTooltip(doc, {useXulWrapper});
tooltip.setContent(getTooltipContent(doc), {width: 100, height: 50});
tooltip.panel.appendChild(getTooltipContent(doc));
tooltip.setContentSize({width: 100, height: 50});
await showTooltip(tooltip, doc.getElementById("box1"));
const onTooltipContainerClick = once(tooltip.container, "click");
@ -57,7 +58,8 @@ async function testConsumeOutsideClicksFalse(doc) {
const box4 = doc.getElementById("box4");
const tooltip = new HTMLTooltip(doc, {consumeOutsideClicks: false, useXulWrapper});
tooltip.setContent(getTooltipContent(doc), {width: 100, height: 50});
tooltip.panel.appendChild(getTooltipContent(doc));
tooltip.setContentSize({width: 100, height: 50});
await showTooltip(tooltip, doc.getElementById("box1"));
const onBox4Clicked = once(box4, "click");
@ -80,7 +82,8 @@ async function testConsumeOutsideClicksTrue(doc) {
box4.addEventListener("click", () => box4clicks++);
const tooltip = new HTMLTooltip(doc, {consumeOutsideClicks: true, useXulWrapper});
tooltip.setContent(getTooltipContent(doc), {width: 100, height: 50});
tooltip.panel.appendChild(getTooltipContent(doc));
tooltip.setContentSize({width: 100, height: 50});
await showTooltip(tooltip, doc.getElementById("box1"));
const onHidden = once(tooltip, "hidden");
@ -98,7 +101,8 @@ async function testConsumeWithRightClick(doc) {
const box4 = doc.getElementById("box4");
const tooltip = new HTMLTooltip(doc, {consumeOutsideClicks: true, useXulWrapper});
tooltip.setContent(getTooltipContent(doc), {width: 100, height: 50});
tooltip.panel.appendChild(getTooltipContent(doc));
tooltip.setContentSize({width: 100, height: 50});
await showTooltip(tooltip, doc.getElementById("box1"));
// Only left-click events should be consumed, so we expect to catch a click when using
@ -120,7 +124,8 @@ async function testClickInOuterIframe(doc) {
const frame = doc.getElementById("frame");
const tooltip = new HTMLTooltip(doc, {useXulWrapper});
tooltip.setContent(getTooltipContent(doc), {width: 100, height: 50});
tooltip.panel.appendChild(getTooltipContent(doc));
tooltip.setContentSize({width: 100, height: 50});
await showTooltip(tooltip, doc.getElementById("box1"));
const onHidden = once(tooltip, "hidden");
@ -139,7 +144,8 @@ async function testClickInInnerIframe(doc) {
const iframe = doc.createElementNS(HTML_NS, "iframe");
iframe.style.width = "100px";
iframe.style.height = "50px";
tooltip.setContent(iframe, {width: 100, height: 50});
tooltip.panel.appendChild(iframe);
tooltip.setContentSize({width: 100, height: 50});
await showTooltip(tooltip, doc.getElementById("box1"));
const onTooltipContainerClick = once(tooltip.container, "click");

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

@ -85,6 +85,7 @@ function createTooltip(doc) {
input.setAttribute("type", "text");
div.appendChild(input);
tooltip.setContent(div, {width: 150, height: 50});
tooltip.panel.appendChild(div);
tooltip.setContentSize({width: 150, height: 50});
return tooltip;
}

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

@ -29,7 +29,8 @@ add_task(async function() {
const tooltip = new HTMLTooltip(doc, {useXulWrapper: false});
const div = doc.createElementNS(HTML_NS, "div");
div.style.height = "100%";
tooltip.setContent(div, {width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT});
tooltip.panel.appendChild(div);
tooltip.setContentSize({width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT});
const box1 = doc.getElementById("box1");
const box2 = doc.getElementById("box2");

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

@ -28,7 +28,8 @@ add_task(async function() {
const tooltip = new HTMLTooltip(doc, {useXulWrapper: false});
const div = doc.createElementNS(HTML_NS, "div");
div.style.height = "100%";
tooltip.setContent(div, {width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT});
tooltip.panel.appendChild(div);
tooltip.setContentSize({width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT});
const box1 = doc.getElementById("box1");
const box2 = doc.getElementById("box2");

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

@ -38,7 +38,8 @@ async function runTests(doc) {
const tooltip = new HTMLTooltip(doc, {type: "arrow", useXulWrapper});
const div = doc.createElementNS(HTML_NS, "div");
div.style.height = "35px";
tooltip.setContent(div, {width: 200, height: 35});
tooltip.panel.appendChild(div);
tooltip.setContentSize({width: 200, height: 35});
const {right: docRight} = doc.documentElement.getBoundingClientRect();

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

@ -37,7 +37,8 @@ async function runTests(doc) {
const tooltip = new HTMLTooltip(doc, {type: "arrow", useXulWrapper});
const div = doc.createElementNS(HTML_NS, "div");
div.style.height = "35px";
tooltip.setContent(div, {width: 200, height: 35});
tooltip.panel.appendChild(div);
tooltip.setContentSize({width: 200, height: 35});
const {right: docRight} = doc.documentElement.getBoundingClientRect();

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

@ -33,7 +33,8 @@ add_task(async function() {
const width = 100, height = 50;
const tooltip = new HTMLTooltip(doc, {useXulWrapper: false});
tooltip.setContent(getTooltipContent(doc), {width, height});
tooltip.panel.appendChild(getTooltipContent(doc));
tooltip.setContentSize({width, height});
info("Show the tooltip on each of the 4 hbox, without calling hide in between");

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

@ -40,7 +40,7 @@ async function runTests(doc) {
const div = doc.createElementNS(HTML_NS, "div");
div.style.width = "200px";
div.style.height = "35px";
tooltip.setContent(div);
tooltip.panel.appendChild(div);
const docBounds = doc.documentElement.getBoundingClientRect();

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

@ -40,7 +40,7 @@ async function runTests(doc) {
const div = doc.createElementNS(HTML_NS, "div");
div.style.width = "200px";
div.style.height = "35px";
tooltip.setContent(div);
tooltip.panel.appendChild(div);
const elements = [...doc.querySelectorAll(".anchor")];
for (const el of elements) {

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

@ -39,7 +39,7 @@ async function runTests(doc) {
"width: 300px; height: 150px; background: red;";
info("Set tooltip content using width:auto and height:auto");
tooltip.setContent(tooltipContent);
tooltip.panel.appendChild(tooltipContent);
info("Show the tooltip and check the tooltip panel dimensions.");
await showTooltip(tooltip, doc.getElementById("box1"));
@ -53,7 +53,7 @@ async function runTests(doc) {
info("Set tooltip content using fixed width and height:auto");
tooltipContent.style.cssText =
"width: auto; height: 200px; background: red;";
tooltip.setContent(tooltipContent, { width: 400 });
tooltip.setContentSize({ width: 400 });
info("Show the tooltip and check the tooltip panel height.");
await showTooltip(tooltip, doc.getElementById("box1"));

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше