зеркало из https://github.com/mozilla/gecko-dev.git
merge mozilla-central to mozilla-inbound. r=merge a=merge
This commit is contained in:
Коммит
d10e26c913
|
@ -64,7 +64,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=483573
|
|||
{
|
||||
ID: muteBtn,
|
||||
actionName: "press",
|
||||
events: CLICK_EVENTS,
|
||||
eventSeq: [
|
||||
// new focusChecker(muteBtn),
|
||||
new nameChecker(muteBtn, "Unmute"),
|
||||
|
@ -81,7 +80,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=483573
|
|||
{
|
||||
ID: playBtn,
|
||||
actionName: "press",
|
||||
events: CLICK_EVENTS,
|
||||
eventSeq: [
|
||||
// new focusChecker(playBtn),
|
||||
new nameChecker(playBtn, "Pause"),
|
||||
|
|
|
@ -185,12 +185,10 @@ var TabsInTitlebar = {
|
|||
|
||||
// On Windows 10, adjust the window controls to span the entire
|
||||
// tab strip height if we're not showing a menu bar.
|
||||
if (AppConstants.isPlatformAndVersionAtLeast("win", "10.0")) {
|
||||
if (!menuHeight) {
|
||||
// Add a pixel to slightly overlap the navbar border.
|
||||
titlebarContentHeight = fullTabsHeight + 1;
|
||||
$("titlebar-buttonbox").style.height = titlebarContentHeight + "px";
|
||||
}
|
||||
if (AppConstants.isPlatformAndVersionAtLeast("win", "10.0") &&
|
||||
!menuHeight) {
|
||||
titlebarContentHeight = fullTabsHeight;
|
||||
$("titlebar-buttonbox").style.height = titlebarContentHeight + "px";
|
||||
}
|
||||
|
||||
// If the menubar is around (menuHeight is non-zero), try to adjust
|
||||
|
|
|
@ -726,6 +726,14 @@ var gCookiesWindow = {
|
|||
aEvent.keyCode == KeyEvent.DOM_VK_BACK_SPACE)) {
|
||||
this.deleteCookie();
|
||||
aEvent.preventDefault();
|
||||
} else if (aEvent.getModifierState("Accel") &&
|
||||
document.getElementById("key_selectAll")
|
||||
.getAttribute("key")
|
||||
.toLocaleLowerCase()
|
||||
.charCodeAt(0) == aEvent.charCode) {
|
||||
let view = gCookiesWindow._view;
|
||||
view.selection.selectAll();
|
||||
aEvent.preventDefault();
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -8,7 +8,12 @@
|
|||
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css" type="text/css"?>
|
||||
|
||||
<!DOCTYPE dialog SYSTEM "chrome://browser/locale/preferences/cookies.dtd" >
|
||||
<!DOCTYPE dialog [
|
||||
<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
|
||||
%browserDTD;
|
||||
<!ENTITY % cookiesDTD SYSTEM "chrome://browser/locale/preferences/cookies.dtd">
|
||||
%cookiesDTD;
|
||||
]>
|
||||
|
||||
<window id="CookiesDialog" windowtype="Browser:Cookies"
|
||||
class="windowDialog" title="&window.title;"
|
||||
|
@ -27,6 +32,7 @@
|
|||
<keyset>
|
||||
<key key="&windowClose.key;" modifiers="accel" oncommand="window.close();"/>
|
||||
<key key="&focusSearch1.key;" modifiers="accel" oncommand="gCookiesWindow.focusFilterBox();"/>
|
||||
<key id="key_selectAll" key="&selectAllCmd.key;" modifiers="accel"/>
|
||||
</keyset>
|
||||
|
||||
<vbox flex="1" class="contentPane largeDialogContainer">
|
||||
|
|
|
@ -115,13 +115,7 @@ this.UITour = {
|
|||
// to automatically open the appMenu when annotating this target.
|
||||
widgetName: "appMenu-fxa-label",
|
||||
}],
|
||||
["addons", {
|
||||
query: (aDocument) => {
|
||||
// select toolbar icon if exist, fallback to appMenu item
|
||||
let node = aDocument.getElementById("add-ons-button");
|
||||
return node ? node : aDocument.getElementById("appMenu-addons-button");
|
||||
},
|
||||
}],
|
||||
["addons", {query: "#appMenu-addons-button"}],
|
||||
["appMenu", {
|
||||
addTargetListener: (aDocument, aCallback) => {
|
||||
let panelPopup = aDocument.defaultView.PanelUI.panel;
|
||||
|
@ -152,13 +146,7 @@ this.UITour = {
|
|||
}],
|
||||
["help", {query: "#appMenu-help-button"}],
|
||||
["home", {query: "#home-button"}],
|
||||
["library", {
|
||||
query: (aDocument) => {
|
||||
// select toolbar icon if exist, fallback to appMenu item
|
||||
let node = aDocument.getElementById("library-button");
|
||||
return node ? node : aDocument.getElementById("appMenu-library-button");
|
||||
},
|
||||
}],
|
||||
["library", {query: "#appMenu-library-button"}],
|
||||
["pocket", {
|
||||
allowAdd: true,
|
||||
query: (aDocument) => {
|
||||
|
|
|
@ -6,41 +6,7 @@ var gContentWindow;
|
|||
|
||||
add_task(setup_UITourTest);
|
||||
|
||||
add_UITour_task(async function test_highlight_library_icon_in_toolbar() {
|
||||
let highlight = document.getElementById("UITourHighlight");
|
||||
is_element_hidden(highlight, "Highlight should initially be hidden");
|
||||
|
||||
// Test highlighting the library button
|
||||
let highlightVisiblePromise = elementVisiblePromise(highlight, "Should show highlight");
|
||||
gContentAPI.showHighlight("library");
|
||||
await highlightVisiblePromise;
|
||||
UITour.getTarget(window, "library").then((target) => {
|
||||
is("library-button", target.node.id, "Should highlight the right target");
|
||||
});
|
||||
});
|
||||
|
||||
add_UITour_task(async function test_highlight_addons_icon_in_toolbar() {
|
||||
CustomizableUI.addWidgetToArea("add-ons-button", CustomizableUI.AREA_NAVBAR, 0);
|
||||
ok(!UITour.availableTargetsCache.has(window),
|
||||
"Targets should be evicted from cache after widget change");
|
||||
let highlight = document.getElementById("UITourHighlight");
|
||||
is_element_hidden(highlight, "Highlight should initially be hidden");
|
||||
|
||||
// Test highlighting the addons button on toolbar
|
||||
let highlightVisiblePromise = elementVisiblePromise(highlight, "Should show highlight");
|
||||
gContentAPI.showHighlight("addons");
|
||||
await highlightVisiblePromise;
|
||||
UITour.getTarget(window, "addons").then((target) => {
|
||||
is("add-ons-button", target.node.id, "Should highlight the right target");
|
||||
CustomizableUI.removeWidgetFromArea("add-ons-button");
|
||||
});
|
||||
});
|
||||
|
||||
add_UITour_task(async function test_highlight_library_and_show_library_subview() {
|
||||
CustomizableUI.removeWidgetFromArea("library-button");
|
||||
|
||||
ok(!UITour.availableTargetsCache.has(window),
|
||||
"Targets should be evicted from cache after widget change");
|
||||
let highlight = document.getElementById("UITourHighlight");
|
||||
is_element_hidden(highlight, "Highlight should initially be hidden");
|
||||
|
||||
|
@ -71,6 +37,4 @@ add_UITour_task(async function test_highlight_library_and_show_library_subview()
|
|||
gContentAPI.hideMenu("appMenu");
|
||||
await appMenuHiddenPromise;
|
||||
is(appMenu.state, "closed", "Should close the app menu");
|
||||
CustomizableUI.addWidgetToArea("library", CustomizableUI.AREA_NAVBAR, 0);
|
||||
});
|
||||
|
||||
|
|
|
@ -123,6 +123,30 @@ const CONTENT = {
|
|||
persistWhileVisible: true,
|
||||
popupIconURL: "chrome://formautofill/content/icon-credit-card.svg",
|
||||
hideClose: true,
|
||||
checkbox: {
|
||||
get checked() {
|
||||
return Services.prefs.getBoolPref("services.sync.engine.creditcards");
|
||||
},
|
||||
get label() {
|
||||
// Only set the label when the fallowing conditions existed:
|
||||
// - sync account is set
|
||||
// - credit card sync is disabled
|
||||
// - credit card sync is available
|
||||
// otherwise return null label to hide checkbox.
|
||||
return Services.prefs.prefHasUserValue("services.sync.username") &&
|
||||
!Services.prefs.getBoolPref("services.sync.engine.creditcards") &&
|
||||
Services.prefs.getBoolPref("services.sync.engine.creditcards.available") ?
|
||||
GetStringFromName("creditCardsSyncCheckbox") : null;
|
||||
},
|
||||
callback(event) {
|
||||
let {secondaryButton, menubutton} = event.target.parentNode.parentNode.parentNode;
|
||||
let checked = event.target.checked;
|
||||
Services.prefs.setBoolPref("services.sync.engine.creditcards", checked);
|
||||
secondaryButton.disabled = checked;
|
||||
menubutton.disabled = checked;
|
||||
log.debug("Set creditCard sync to", checked);
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -166,6 +190,11 @@ let FormAutofillDoorhanger = {
|
|||
|
||||
return [mainAction, secondaryActions];
|
||||
},
|
||||
_getNotificationElm(browser, id) {
|
||||
let notificationId = id + "-notification";
|
||||
let chromeDoc = browser.ownerDocument;
|
||||
return chromeDoc.getElementById(notificationId);
|
||||
},
|
||||
/**
|
||||
* Append the link label element to the popupnotificationcontent.
|
||||
* @param {XULElement} browser
|
||||
|
@ -225,26 +254,20 @@ let FormAutofillDoorhanger = {
|
|||
if (!options.checkbox) {
|
||||
return;
|
||||
}
|
||||
let id = notificationId + "-notification";
|
||||
let chromeDoc = browser.ownerDocument;
|
||||
let notification = chromeDoc.getElementById(id);
|
||||
let cb = notification.checkbox;
|
||||
let {checkbox} = this._getNotificationElm(browser, notificationId);
|
||||
|
||||
if (cb) {
|
||||
cb.addEventListener("command", options.checkbox.callback);
|
||||
if (checkbox && !checkbox.hidden) {
|
||||
checkbox.addEventListener("command", options.checkbox.callback);
|
||||
}
|
||||
},
|
||||
_removeCheckboxListener(browser, {notificationId, options}) {
|
||||
if (!options.checkbox) {
|
||||
return;
|
||||
}
|
||||
let id = notificationId + "-notification";
|
||||
let chromeDoc = browser.ownerDocument;
|
||||
let notification = chromeDoc.getElementById(id);
|
||||
let cb = notification.checkbox;
|
||||
let {checkbox} = this._getNotificationElm(browser, notificationId);
|
||||
|
||||
if (cb) {
|
||||
cb.removeEventListener("command", options.checkbox.callback);
|
||||
if (checkbox && !checkbox.hidden) {
|
||||
checkbox.removeEventListener("command", options.checkbox.callback);
|
||||
}
|
||||
},
|
||||
/**
|
||||
|
|
|
@ -539,11 +539,14 @@ this.FormAutofillHeuristics = {
|
|||
* all field details in the form.
|
||||
*/
|
||||
getFormInfo(form, allowDuplicates = false) {
|
||||
if (form.elements.length <= 0) {
|
||||
const eligibleFields = Array.from(form.elements)
|
||||
.filter(elem => FormAutofillUtils.isFieldEligibleForAutofill(elem));
|
||||
|
||||
if (eligibleFields.length <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let fieldScanner = new FieldScanner(form.elements);
|
||||
let fieldScanner = new FieldScanner(eligibleFields);
|
||||
while (!fieldScanner.parsingFinished) {
|
||||
let parsedPhoneFields = this._parsePhoneFields(fieldScanner);
|
||||
let parsedAddressFields = this._parseAddressFields(fieldScanner);
|
||||
|
@ -629,10 +632,6 @@ this.FormAutofillHeuristics = {
|
|||
},
|
||||
|
||||
getInfo(element) {
|
||||
if (!FormAutofillUtils.isFieldEligibleForAutofill(element)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let info = element.getAutocompleteInfo();
|
||||
// An input[autocomplete="on"] will not be early return here since it stll
|
||||
// needs to find the field name.
|
||||
|
|
|
@ -203,7 +203,12 @@ FormAutofillParent.prototype = {
|
|||
break;
|
||||
}
|
||||
case "FormAutofill:SaveCreditCard": {
|
||||
await this.profileStorage.creditCards.normalizeCCNumberFields(data.creditcard);
|
||||
// TODO: "MasterPassword.ensureLoggedIn" can be removed after the storage
|
||||
// APIs are refactored to be async functions (bug 1399367).
|
||||
if (!await MasterPassword.ensureLoggedIn()) {
|
||||
log.warn("User canceled master password entry");
|
||||
return;
|
||||
}
|
||||
this.profileStorage.creditCards.add(data.creditcard);
|
||||
break;
|
||||
}
|
||||
|
@ -454,15 +459,14 @@ FormAutofillParent.prototype = {
|
|||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.profileStorage.creditCards.normalizeCCNumberFields(creditCard.record);
|
||||
this.profileStorage.creditCards.add(creditCard.record);
|
||||
} catch (e) {
|
||||
if (e.result != Cr.NS_ERROR_ABORT) {
|
||||
throw e;
|
||||
}
|
||||
// TODO: "MasterPassword.ensureLoggedIn" can be removed after the storage
|
||||
// APIs are refactored to be async functions (bug 1399367).
|
||||
if (!await MasterPassword.ensureLoggedIn()) {
|
||||
log.warn("User canceled master password entry");
|
||||
return;
|
||||
}
|
||||
|
||||
this.profileStorage.creditCards.add(creditCard.record);
|
||||
},
|
||||
|
||||
_onFormSubmit(data, target) {
|
||||
|
|
|
@ -36,19 +36,42 @@ this.MasterPassword = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Display the master password login prompt no matter it's logged in or not.
|
||||
* If an existing MP prompt is already open, the result from it will be used instead.
|
||||
*
|
||||
* @returns {Promise<boolean>} True if it's logged in or no password is set and false
|
||||
* if it's still not logged in (prompt canceled or other error).
|
||||
* @returns {boolean} True if master password is logged in and false if not.
|
||||
*/
|
||||
async prompt() {
|
||||
get isLoggedIn() {
|
||||
return Services.logins.isLoggedIn;
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {boolean} True if there is another master password login dialog
|
||||
* existing and false otherwise.
|
||||
*/
|
||||
get isUIBusy() {
|
||||
return Services.logins.uiBusy;
|
||||
},
|
||||
|
||||
/**
|
||||
* Ensure the master password is logged in. It will display the master password
|
||||
* login prompt or do nothing if it's logged in already. If an existing MP
|
||||
* prompt is already prompted, the result from it will be used instead.
|
||||
*
|
||||
* @param {boolean} reauth Prompt the login dialog no matter it's logged in
|
||||
* or not if it's set to true.
|
||||
* @returns {Promise<boolean>} True if it's logged in or no password is set
|
||||
* and false if it's still not logged in (prompt
|
||||
* canceled or other error).
|
||||
*/
|
||||
async ensureLoggedIn(reauth = false) {
|
||||
if (!this.isEnabled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isLoggedIn && !reauth) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If a prompt is already showing then wait for and focus it.
|
||||
if (Services.logins.uiBusy) {
|
||||
if (this.isUIBusy) {
|
||||
return this.waitForExistingDialog();
|
||||
}
|
||||
|
||||
|
@ -81,17 +104,28 @@ this.MasterPassword = {
|
|||
* @returns {Promise<string>} resolves to the decrypted string, or rejects otherwise.
|
||||
*/
|
||||
async decrypt(cipherText, reauth = false) {
|
||||
let loggedIn = false;
|
||||
if (reauth) {
|
||||
loggedIn = await this.prompt();
|
||||
} else {
|
||||
loggedIn = await this.waitForExistingDialog();
|
||||
}
|
||||
|
||||
if (!loggedIn) {
|
||||
if (!await this.ensureLoggedIn(reauth)) {
|
||||
throw Components.Exception("User canceled master password entry", Cr.NS_ERROR_ABORT);
|
||||
}
|
||||
return cryptoSDR.decrypt(cipherText);
|
||||
},
|
||||
|
||||
/**
|
||||
* Decrypts cipherText synchronously. "ensureLoggedIn()" needs to be called
|
||||
* outside in case another dialog is showing.
|
||||
*
|
||||
* NOTE: This method will be removed soon once the ProfileStorage APIs are
|
||||
* refactored to be async functions (bug 1399367). Please use async
|
||||
* version instead.
|
||||
*
|
||||
* @deprecated
|
||||
* @param {string} cipherText Encrypted string including the algorithm details.
|
||||
* @returns {string} The decrypted string.
|
||||
*/
|
||||
decryptSync(cipherText) {
|
||||
if (this.isUIBusy) {
|
||||
throw Components.Exception("\"ensureLoggedIn()\" should be called first", Cr.NS_ERROR_UNEXPECTED);
|
||||
}
|
||||
return cryptoSDR.decrypt(cipherText);
|
||||
},
|
||||
|
||||
|
@ -102,13 +136,32 @@ this.MasterPassword = {
|
|||
* @returns {Promise<string>} resolves to the encrypted string (with algorithm), otherwise rejects.
|
||||
*/
|
||||
async encrypt(plainText) {
|
||||
if (Services.logins.uiBusy && !await this.waitForExistingDialog()) {
|
||||
if (!await this.ensureLoggedIn()) {
|
||||
throw Components.Exception("User canceled master password entry", Cr.NS_ERROR_ABORT);
|
||||
}
|
||||
|
||||
return cryptoSDR.encrypt(plainText);
|
||||
},
|
||||
|
||||
/**
|
||||
* Encrypts plainText synchronously. "ensureLoggedIn()" needs to be called
|
||||
* outside in case another dialog is showing.
|
||||
*
|
||||
* NOTE: This method will be removed soon once the ProfileStorage APIs are
|
||||
* refactored to be async functions (bug 1399367). Please use async
|
||||
* version instead.
|
||||
*
|
||||
* @deprecated
|
||||
* @param {string} plainText A plain string to be encrypted.
|
||||
* @returns {string} The encrypted cipher string.
|
||||
*/
|
||||
encryptSync(plainText) {
|
||||
if (this.isUIBusy) {
|
||||
throw Components.Exception("\"ensureLoggedIn()\" should be called first", Cr.NS_ERROR_UNEXPECTED);
|
||||
}
|
||||
return cryptoSDR.encrypt(plainText);
|
||||
},
|
||||
|
||||
/**
|
||||
* Resolve when master password dialogs are closed, immediately if none are open.
|
||||
*
|
||||
|
@ -118,10 +171,9 @@ this.MasterPassword = {
|
|||
* Resolves with whether the user is logged in to MP.
|
||||
*/
|
||||
async waitForExistingDialog() {
|
||||
if (!Services.logins.uiBusy) {
|
||||
log.debug("waitForExistingDialog: Dialog isn't showing. isLoggedIn:",
|
||||
Services.logins.isLoggedIn);
|
||||
return Services.logins.isLoggedIn;
|
||||
if (!this.isUIBusy) {
|
||||
log.debug("waitForExistingDialog: Dialog isn't showing. isLoggedIn:", this.isLoggedIn);
|
||||
return this.isLoggedIn;
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
|
|
|
@ -58,8 +58,8 @@
|
|||
*
|
||||
* // credit card fields
|
||||
* cc-name,
|
||||
* cc-number, // e.g. ************1234
|
||||
* cc-number-encrypted,
|
||||
* cc-number, // will be stored in masked format (************1234)
|
||||
* // (see details below)
|
||||
* cc-exp-month,
|
||||
* cc-exp-year, // 2-digit year will be converted to 4 digits
|
||||
* // upon saving
|
||||
|
@ -69,6 +69,8 @@
|
|||
* cc-given-name,
|
||||
* cc-additional-name,
|
||||
* cc-family-name,
|
||||
* cc-number-encrypted, // encrypted from the original unmasked "cc-number"
|
||||
* // (see details below)
|
||||
* cc-exp,
|
||||
*
|
||||
* // metadata
|
||||
|
@ -81,6 +83,22 @@
|
|||
* ]
|
||||
* }
|
||||
*
|
||||
*
|
||||
* Encrypt-related Credit Card Fields (cc-number & cc-number-encrypted):
|
||||
*
|
||||
* When saving or updating a credit-card record, the storage will encrypt the
|
||||
* value of "cc-number", store the encrypted number in "cc-number-encrypted"
|
||||
* field, and replace "cc-number" field with the masked number. These all happen
|
||||
* in "_computeFields". We do reverse actions in "_stripComputedFields", which
|
||||
* decrypts "cc-number-encrypted", restores it to "cc-number", and deletes
|
||||
* "cc-number-encrypted". Therefore, calling "_stripComputedFields" followed by
|
||||
* "_computeFields" can make sure the encrypt-related fields are up-to-date.
|
||||
*
|
||||
* In general, you have to decrypt the number by your own outside ProfileStorage
|
||||
* when necessary. However, you will get the decrypted records when querying
|
||||
* data with "rawData=true" to ensure they're ready to sync.
|
||||
*
|
||||
*
|
||||
* Sync Metadata:
|
||||
*
|
||||
* Records may also have a _sync field, which consists of:
|
||||
|
@ -182,7 +200,6 @@ const VALID_ADDRESS_COMPUTED_FIELDS = [
|
|||
const VALID_CREDIT_CARD_FIELDS = [
|
||||
"cc-name",
|
||||
"cc-number",
|
||||
"cc-number-encrypted",
|
||||
"cc-exp-month",
|
||||
"cc-exp-year",
|
||||
];
|
||||
|
@ -191,6 +208,7 @@ const VALID_CREDIT_CARD_COMPUTED_FIELDS = [
|
|||
"cc-given-name",
|
||||
"cc-additional-name",
|
||||
"cc-family-name",
|
||||
"cc-number-encrypted",
|
||||
"cc-exp",
|
||||
];
|
||||
|
||||
|
@ -247,7 +265,7 @@ class AutofillRecords {
|
|||
this._schemaVersion = schemaVersion;
|
||||
|
||||
let hasChanges = (result, record) => this._migrateRecord(record) || result;
|
||||
if (this._store.data[this._collectionName].reduce(hasChanges, false)) {
|
||||
if (this.data.reduce(hasChanges, false)) {
|
||||
this._store.saveSoon();
|
||||
}
|
||||
}
|
||||
|
@ -262,6 +280,16 @@ class AutofillRecords {
|
|||
return this._schemaVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the data of this collection.
|
||||
*
|
||||
* @returns {array}
|
||||
* The data object.
|
||||
*/
|
||||
get data() {
|
||||
return this._store.data[this._collectionName];
|
||||
}
|
||||
|
||||
// Ensures that we don't try to apply synced records with newer schema
|
||||
// versions. This is a temporary measure to ensure we don't accidentally
|
||||
// bump the schema version without a syncing strategy in place (bug 1377204).
|
||||
|
@ -292,9 +320,9 @@ class AutofillRecords {
|
|||
includeDeleted: true,
|
||||
});
|
||||
if (index > -1) {
|
||||
let existing = this._store.data[this._collectionName][index];
|
||||
let existing = this.data[index];
|
||||
if (existing.deleted) {
|
||||
this._store.data[this._collectionName].splice(index, 1);
|
||||
this.data.splice(index, 1);
|
||||
} else {
|
||||
throw new Error(`Record ${record.guid} already exists`);
|
||||
}
|
||||
|
@ -349,7 +377,7 @@ class AutofillRecords {
|
|||
sync.changeCounter = 0;
|
||||
}
|
||||
|
||||
this._store.data[this._collectionName].push(recordToSave);
|
||||
this.data.push(recordToSave);
|
||||
|
||||
this._store.saveSoon();
|
||||
|
||||
|
@ -379,13 +407,16 @@ class AutofillRecords {
|
|||
update(guid, record, preserveOldProperties = false) {
|
||||
this.log.debug("update:", guid, record);
|
||||
|
||||
let recordFound = this._findByGUID(guid);
|
||||
if (!recordFound) {
|
||||
let recordFoundIndex = this._findIndexByGUID(guid);
|
||||
if (recordFoundIndex == -1) {
|
||||
throw new Error("No matching record.");
|
||||
}
|
||||
|
||||
// Clone the record by Object assign API to preserve the property with empty string.
|
||||
let recordToUpdate = Object.assign({}, record);
|
||||
// Clone the record before modifying it to avoid exposing incomplete changes.
|
||||
let recordFound = this._clone(this.data[recordFoundIndex]);
|
||||
this._stripComputedFields(recordFound);
|
||||
|
||||
let recordToUpdate = this._clone(record);
|
||||
this._normalizeRecord(recordToUpdate);
|
||||
|
||||
for (let field of this.VALID_FIELDS) {
|
||||
|
@ -412,8 +443,8 @@ class AutofillRecords {
|
|||
syncMetadata.changeCounter += 1;
|
||||
}
|
||||
|
||||
this._stripComputedFields(recordFound);
|
||||
this._computeFields(recordFound);
|
||||
this.data[recordFoundIndex] = recordFound;
|
||||
|
||||
this._store.saveSoon();
|
||||
Services.obs.notifyObservers(null, "formautofill-storage-changed", "update");
|
||||
|
@ -461,7 +492,7 @@ class AutofillRecords {
|
|||
this.log.warn("attempting to remove non-existing entry", guid);
|
||||
return;
|
||||
}
|
||||
let existing = this._store.data[this._collectionName][index];
|
||||
let existing = this.data[index];
|
||||
if (existing.deleted) {
|
||||
return; // already a tombstone - don't touch it.
|
||||
}
|
||||
|
@ -469,7 +500,7 @@ class AutofillRecords {
|
|||
if (existingSync) {
|
||||
// existing sync metadata means it has been synced. This means we must
|
||||
// leave a tombstone behind.
|
||||
this._store.data[this._collectionName][index] = {
|
||||
this.data[index] = {
|
||||
guid,
|
||||
timeLastModified: Date.now(),
|
||||
deleted: true,
|
||||
|
@ -479,7 +510,7 @@ class AutofillRecords {
|
|||
} else {
|
||||
// If there's no sync meta-data, this record has never been synced, so
|
||||
// we can delete it.
|
||||
this._store.data[this._collectionName].splice(index, 1);
|
||||
this.data.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -507,7 +538,7 @@ class AutofillRecords {
|
|||
}
|
||||
|
||||
// The record is cloned to avoid accidental modifications from outside.
|
||||
let clonedRecord = this._clone(recordFound);
|
||||
let clonedRecord = this._cloneAndCleanUp(recordFound);
|
||||
if (rawData) {
|
||||
this._stripComputedFields(clonedRecord);
|
||||
} else {
|
||||
|
@ -529,9 +560,9 @@ class AutofillRecords {
|
|||
getAll({rawData = false, includeDeleted = false} = {}) {
|
||||
this.log.debug("getAll", rawData, includeDeleted);
|
||||
|
||||
let records = this._store.data[this._collectionName].filter(r => !r.deleted || includeDeleted);
|
||||
let records = this.data.filter(r => !r.deleted || includeDeleted);
|
||||
// Records are cloned to avoid accidental modifications from outside.
|
||||
let clonedRecords = records.map(r => this._clone(r));
|
||||
let clonedRecords = records.map(r => this._cloneAndCleanUp(r));
|
||||
clonedRecords.forEach(record => {
|
||||
if (rawData) {
|
||||
this._stripComputedFields(record);
|
||||
|
@ -595,16 +626,17 @@ class AutofillRecords {
|
|||
* remote record, and the shared parent that we synthesize from the last
|
||||
* synced fields - see _maybeStoreLastSyncedField.
|
||||
*
|
||||
* @param {Object} localRecord
|
||||
* The changed local record, currently in storage.
|
||||
* @param {Object} strippedLocalRecord
|
||||
* The changed local record, currently in storage. Computed fields
|
||||
* are stripped.
|
||||
* @param {Object} remoteRecord
|
||||
* The remote record.
|
||||
* @returns {Object|null}
|
||||
* The merged record, or `null` if there are conflicts and the
|
||||
* records can't be merged.
|
||||
*/
|
||||
_mergeSyncedRecords(localRecord, remoteRecord) {
|
||||
let sync = this._getSyncMetaData(localRecord, true);
|
||||
_mergeSyncedRecords(strippedLocalRecord, remoteRecord) {
|
||||
let sync = this._getSyncMetaData(strippedLocalRecord, true);
|
||||
|
||||
// Copy all internal fields from the remote record. We'll update their
|
||||
// values in `_replaceRecordAt`.
|
||||
|
@ -623,30 +655,30 @@ class AutofillRecords {
|
|||
// determine if the local and remote values are different. Hashing is
|
||||
// expensive, but we don't expect this to happen frequently.
|
||||
let lastSyncedValue = sync.lastSyncedFields[field];
|
||||
isLocalSame = lastSyncedValue == sha512(localRecord[field]);
|
||||
isLocalSame = lastSyncedValue == sha512(strippedLocalRecord[field]);
|
||||
isRemoteSame = lastSyncedValue == sha512(remoteRecord[field]);
|
||||
} else {
|
||||
// Otherwise, if the field hasn't changed since the last sync, we know
|
||||
// it's the same locally.
|
||||
isLocalSame = true;
|
||||
isRemoteSame = localRecord[field] == remoteRecord[field];
|
||||
isRemoteSame = strippedLocalRecord[field] == remoteRecord[field];
|
||||
}
|
||||
|
||||
let value;
|
||||
if (isLocalSame && isRemoteSame) {
|
||||
// Local and remote are the same; doesn't matter which one we pick.
|
||||
value = localRecord[field];
|
||||
value = strippedLocalRecord[field];
|
||||
} else if (isLocalSame && !isRemoteSame) {
|
||||
value = remoteRecord[field];
|
||||
} else if (!isLocalSame && isRemoteSame) {
|
||||
// We don't need to bump the change counter when taking the local
|
||||
// change, because the counter must already be > 0 if we're attempting
|
||||
// a three-way merge.
|
||||
value = localRecord[field];
|
||||
} else if (localRecord[field] == remoteRecord[field]) {
|
||||
value = strippedLocalRecord[field];
|
||||
} else if (strippedLocalRecord[field] == remoteRecord[field]) {
|
||||
// Shared parent doesn't match either local or remote, but the values
|
||||
// are identical, so there's no conflict.
|
||||
value = localRecord[field];
|
||||
value = strippedLocalRecord[field];
|
||||
} else {
|
||||
// Both local and remote changed to different values. We'll need to fork
|
||||
// the local record to resolve the conflict.
|
||||
|
@ -676,12 +708,12 @@ class AutofillRecords {
|
|||
* it's uploaded.
|
||||
*/
|
||||
_replaceRecordAt(index, remoteRecord, {keepSyncMetadata = false} = {}) {
|
||||
let localRecord = this._store.data[this._collectionName][index];
|
||||
let localRecord = this.data[index];
|
||||
let newRecord = this._clone(remoteRecord);
|
||||
|
||||
this._stripComputedFields(newRecord);
|
||||
|
||||
this._store.data[this._collectionName][index] = newRecord;
|
||||
this.data[index] = newRecord;
|
||||
|
||||
if (keepSyncMetadata) {
|
||||
// It's safe to move the Sync metadata from the old record to the new
|
||||
|
@ -720,18 +752,14 @@ class AutofillRecords {
|
|||
* Clones a local record, giving the clone a new GUID and Sync metadata. The
|
||||
* original record remains unchanged in storage.
|
||||
*
|
||||
* @param {Object} localRecord
|
||||
* The local record.
|
||||
* @param {Object} strippedLocalRecord
|
||||
* The local record. Computed fields are stripped.
|
||||
* @returns {string}
|
||||
* A clone of the local record with a new GUID.
|
||||
*/
|
||||
_forkLocalRecord(localRecord) {
|
||||
let forkedLocalRecord = this._clone(localRecord);
|
||||
|
||||
this._stripComputedFields(forkedLocalRecord);
|
||||
|
||||
_forkLocalRecord(strippedLocalRecord) {
|
||||
let forkedLocalRecord = this._cloneAndCleanUp(strippedLocalRecord);
|
||||
forkedLocalRecord.guid = this._generateGUID();
|
||||
this._store.data[this._collectionName].push(forkedLocalRecord);
|
||||
|
||||
// Give the record fresh Sync metadata and bump its change counter as a
|
||||
// side effect. This also excludes the forked record from de-duping on the
|
||||
|
@ -740,6 +768,7 @@ class AutofillRecords {
|
|||
this._getSyncMetaData(forkedLocalRecord, true);
|
||||
|
||||
this._computeFields(forkedLocalRecord);
|
||||
this.data.push(forkedLocalRecord);
|
||||
|
||||
return forkedLocalRecord;
|
||||
}
|
||||
|
@ -770,7 +799,7 @@ class AutofillRecords {
|
|||
throw new Error(`Record ${remoteRecord.guid} not found`);
|
||||
}
|
||||
|
||||
let localRecord = this._store.data[this._collectionName][localIndex];
|
||||
let localRecord = this.data[localIndex];
|
||||
let sync = this._getSyncMetaData(localRecord, true);
|
||||
|
||||
let forkedGUID = null;
|
||||
|
@ -781,7 +810,10 @@ class AutofillRecords {
|
|||
keepSyncMetadata: false,
|
||||
});
|
||||
} else {
|
||||
let mergedRecord = this._mergeSyncedRecords(localRecord, remoteRecord);
|
||||
let strippedLocalRecord = this._clone(localRecord);
|
||||
this._stripComputedFields(strippedLocalRecord);
|
||||
|
||||
let mergedRecord = this._mergeSyncedRecords(strippedLocalRecord, remoteRecord);
|
||||
if (mergedRecord) {
|
||||
// Local and remote modified, but we were able to merge. Replace the
|
||||
// local record with the merged record.
|
||||
|
@ -791,7 +823,7 @@ class AutofillRecords {
|
|||
} else {
|
||||
// Merge conflict. Fork the local record, then replace the original
|
||||
// with the merged record.
|
||||
let forkedLocalRecord = this._forkLocalRecord(localRecord);
|
||||
let forkedLocalRecord = this._forkLocalRecord(strippedLocalRecord);
|
||||
forkedGUID = forkedLocalRecord.guid;
|
||||
this._replaceRecordAt(localIndex, remoteRecord, {
|
||||
keepSyncMetadata: false,
|
||||
|
@ -821,11 +853,11 @@ class AutofillRecords {
|
|||
|
||||
let sync = this._getSyncMetaData(tombstone, true);
|
||||
sync.changeCounter = 0;
|
||||
this._store.data[this._collectionName].push(tombstone);
|
||||
this.data.push(tombstone);
|
||||
return;
|
||||
}
|
||||
|
||||
let existing = this._store.data[this._collectionName][index];
|
||||
let existing = this.data[index];
|
||||
let sync = this._getSyncMetaData(existing, true);
|
||||
if (sync.changeCounter > 0) {
|
||||
// Deleting a record with unsynced local changes. To avoid potential
|
||||
|
@ -842,7 +874,7 @@ class AutofillRecords {
|
|||
|
||||
// Removing a record that's not changed locally, and that's not already
|
||||
// deleted. Replace the record with a synced tombstone.
|
||||
this._store.data[this._collectionName][index] = {
|
||||
this.data[index] = {
|
||||
guid,
|
||||
timeLastModified: Date.now(),
|
||||
deleted: true,
|
||||
|
@ -864,7 +896,7 @@ class AutofillRecords {
|
|||
pullSyncChanges() {
|
||||
let changes = {};
|
||||
|
||||
let profiles = this._store.data[this._collectionName];
|
||||
let profiles = this.data;
|
||||
for (let profile of profiles) {
|
||||
let sync = this._getSyncMetaData(profile, true);
|
||||
if (sync.changeCounter < 1) {
|
||||
|
@ -921,7 +953,7 @@ class AutofillRecords {
|
|||
* metadata for all items is removed.
|
||||
*/
|
||||
resetSync() {
|
||||
for (let record of this._store.data[this._collectionName]) {
|
||||
for (let record of this.data) {
|
||||
delete record._sync;
|
||||
}
|
||||
// XXX - we should probably also delete all tombstones?
|
||||
|
@ -951,7 +983,7 @@ class AutofillRecords {
|
|||
}
|
||||
|
||||
let index = this._findIndexByGUID(oldID);
|
||||
let profile = this._store.data[this._collectionName][index];
|
||||
let profile = this.data[index];
|
||||
if (!profile) {
|
||||
throw new Error("changeGUID: no source record");
|
||||
}
|
||||
|
@ -985,49 +1017,50 @@ class AutofillRecords {
|
|||
* fields that match incoming remote records. This avoids creating
|
||||
* duplicate profiles with the same information.
|
||||
*
|
||||
* @param {Object} record
|
||||
* @param {Object} remoteRecord
|
||||
* The remote record.
|
||||
* @returns {string|null}
|
||||
* The GUID of the matching local record, or `null` if no records
|
||||
* match.
|
||||
*/
|
||||
findDuplicateGUID(record) {
|
||||
if (!record.guid) {
|
||||
findDuplicateGUID(remoteRecord) {
|
||||
if (!remoteRecord.guid) {
|
||||
throw new Error("Record missing GUID");
|
||||
}
|
||||
this._ensureMatchingVersion(record);
|
||||
if (record.deleted) {
|
||||
this._ensureMatchingVersion(remoteRecord);
|
||||
if (remoteRecord.deleted) {
|
||||
// Tombstones don't carry enough info to de-dupe, and we should have
|
||||
// handled them separately when applying the record.
|
||||
throw new Error("Tombstones can't have duplicates");
|
||||
}
|
||||
let records = this._store.data[this._collectionName];
|
||||
for (let profile of records) {
|
||||
if (profile.deleted) {
|
||||
let localRecords = this.data;
|
||||
for (let localRecord of localRecords) {
|
||||
if (localRecord.deleted) {
|
||||
continue;
|
||||
}
|
||||
if (profile.guid == record.guid) {
|
||||
throw new Error(`Record ${record.guid} already exists`);
|
||||
if (localRecord.guid == remoteRecord.guid) {
|
||||
throw new Error(`Record ${remoteRecord.guid} already exists`);
|
||||
}
|
||||
if (this._getSyncMetaData(profile)) {
|
||||
// This record has already been uploaded, so it can't be a dupe of
|
||||
if (this._getSyncMetaData(localRecord)) {
|
||||
// This local record has already been uploaded, so it can't be a dupe of
|
||||
// another incoming item.
|
||||
continue;
|
||||
}
|
||||
let keys = new Set(Object.keys(record));
|
||||
for (let key of Object.keys(profile)) {
|
||||
|
||||
// Ignore computed fields when matching records as they aren't synced at all.
|
||||
let strippedLocalRecord = this._clone(localRecord);
|
||||
this._stripComputedFields(strippedLocalRecord);
|
||||
|
||||
let keys = new Set(Object.keys(remoteRecord));
|
||||
for (let key of Object.keys(strippedLocalRecord)) {
|
||||
keys.add(key);
|
||||
}
|
||||
// Ignore internal and computed fields when matching records. Internal
|
||||
// fields are synced, but almost certainly have different values than the
|
||||
// local record, and we'll update them in `reconcile`. Computed fields
|
||||
// aren't synced at all.
|
||||
// Ignore internal fields when matching records. Internal fields are synced,
|
||||
// but almost certainly have different values than the local record, and
|
||||
// we'll update them in `reconcile`.
|
||||
for (let field of INTERNAL_FIELDS) {
|
||||
keys.delete(field);
|
||||
}
|
||||
for (let field of this.VALID_COMPUTED_FIELDS) {
|
||||
keys.delete(field);
|
||||
}
|
||||
if (!keys.size) {
|
||||
// This shouldn't ever happen; a valid record will always have fields
|
||||
// that aren't computed or internal. Sync can't do anything about that,
|
||||
|
@ -1039,13 +1072,13 @@ class AutofillRecords {
|
|||
// For now, we ensure that both (or neither) records have the field
|
||||
// with matching values. This doesn't account for the version yet
|
||||
// (bug 1377204).
|
||||
same = key in profile == key in record && profile[key] == record[key];
|
||||
same = key in strippedLocalRecord == key in remoteRecord && strippedLocalRecord[key] == remoteRecord[key];
|
||||
if (!same) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (same) {
|
||||
return profile.guid;
|
||||
return strippedLocalRecord.guid;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
@ -1056,6 +1089,10 @@ class AutofillRecords {
|
|||
*/
|
||||
|
||||
_clone(record) {
|
||||
return Object.assign({}, record);
|
||||
}
|
||||
|
||||
_cloneAndCleanUp(record) {
|
||||
let result = {};
|
||||
for (let key in record) {
|
||||
// Do not expose hidden fields and fields with empty value (mainly used
|
||||
|
@ -1069,11 +1106,11 @@ class AutofillRecords {
|
|||
|
||||
_findByGUID(guid, {includeDeleted = false} = {}) {
|
||||
let found = this._findIndexByGUID(guid, {includeDeleted});
|
||||
return found < 0 ? undefined : this._store.data[this._collectionName][found];
|
||||
return found < 0 ? undefined : this.data[found];
|
||||
}
|
||||
|
||||
_findIndexByGUID(guid, {includeDeleted = false} = {}) {
|
||||
return this._store.data[this._collectionName].findIndex(record => {
|
||||
return this.data.findIndex(record => {
|
||||
return record.guid == guid && (!record.deleted || includeDeleted);
|
||||
});
|
||||
}
|
||||
|
@ -1410,7 +1447,7 @@ class Addresses extends AutofillRecords {
|
|||
*/
|
||||
mergeToStorage(targetAddress) {
|
||||
let mergedGUIDs = [];
|
||||
for (let address of this._store.data[this._collectionName]) {
|
||||
for (let address of this.data) {
|
||||
if (!address.deleted && this.mergeIfPossible(address.guid, targetAddress)) {
|
||||
mergedGUIDs.push(address.guid);
|
||||
}
|
||||
|
@ -1425,6 +1462,13 @@ class CreditCards extends AutofillRecords {
|
|||
super(store, "creditCards", VALID_CREDIT_CARD_FIELDS, VALID_CREDIT_CARD_COMPUTED_FIELDS, CREDIT_CARD_SCHEMA_VERSION);
|
||||
}
|
||||
|
||||
_getMaskedCCNumber(ccNumber) {
|
||||
if (ccNumber.length <= 4) {
|
||||
throw new Error(`Invalid credit card number`);
|
||||
}
|
||||
return "*".repeat(ccNumber.length - 4) + ccNumber.substr(-4);
|
||||
}
|
||||
|
||||
_computeFields(creditCard) {
|
||||
// NOTE: Remember to bump the schema version number if any of the existing
|
||||
// computing algorithm changes. (No need to bump when just adding new
|
||||
|
@ -1448,15 +1492,31 @@ class CreditCards extends AutofillRecords {
|
|||
hasNewComputedFields = true;
|
||||
}
|
||||
|
||||
// Encrypt credit card number
|
||||
if (!("cc-number-encrypted" in creditCard)) {
|
||||
let ccNumber = (creditCard["cc-number"] || "").replace(/\s/g, "");
|
||||
if (FormAutofillUtils.isCCNumber(ccNumber)) {
|
||||
creditCard["cc-number"] = this._getMaskedCCNumber(ccNumber);
|
||||
creditCard["cc-number-encrypted"] = MasterPassword.encryptSync(ccNumber);
|
||||
} else {
|
||||
delete creditCard["cc-number"];
|
||||
// Computed fields are always present in the storage no matter it's
|
||||
// empty or not.
|
||||
creditCard["cc-number-encrypted"] = "";
|
||||
}
|
||||
}
|
||||
|
||||
return hasNewComputedFields;
|
||||
}
|
||||
|
||||
_normalizeFields(creditCard) {
|
||||
// Check if cc-number is normalized(normalizeCCNumberFields should be called first).
|
||||
if (!creditCard["cc-number-encrypted"] || !creditCard["cc-number"].includes("*")) {
|
||||
throw new Error("Credit card number needs to be normalized first.");
|
||||
_stripComputedFields(creditCard) {
|
||||
if (creditCard["cc-number-encrypted"]) {
|
||||
creditCard["cc-number"] = MasterPassword.decryptSync(creditCard["cc-number-encrypted"]);
|
||||
}
|
||||
super._stripComputedFields(creditCard);
|
||||
}
|
||||
|
||||
_normalizeFields(creditCard) {
|
||||
// Normalize name
|
||||
if (creditCard["cc-given-name"] || creditCard["cc-additional-name"] || creditCard["cc-family-name"]) {
|
||||
if (!creditCard["cc-name"]) {
|
||||
|
@ -1493,31 +1553,6 @@ class CreditCards extends AutofillRecords {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize credit card number related field for saving. It should always be
|
||||
* called before adding/updating credit card records.
|
||||
*
|
||||
* @param {Object} creditCard
|
||||
* The creditCard record with plaintext number only.
|
||||
*/
|
||||
async normalizeCCNumberFields(creditCard) {
|
||||
// Fields that should not be set by content.
|
||||
delete creditCard["cc-number-encrypted"];
|
||||
|
||||
// Validate and encrypt credit card numbers, and calculate the masked numbers
|
||||
if (creditCard["cc-number"]) {
|
||||
let ccNumber = creditCard["cc-number"].replace(/\s/g, "");
|
||||
delete creditCard["cc-number"];
|
||||
|
||||
if (!FormAutofillUtils.isCCNumber(ccNumber)) {
|
||||
throw new Error("Credit card number contains invalid characters or is under 12 digits.");
|
||||
}
|
||||
|
||||
creditCard["cc-number-encrypted"] = await MasterPassword.encrypt(ccNumber);
|
||||
creditCard["cc-number"] = "*".repeat(ccNumber.length - 4) + ccNumber.substr(-4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ProfileStorage(path) {
|
||||
|
|
|
@ -263,9 +263,12 @@ class EditCreditCard extends EditDialog {
|
|||
this._elements.ccNumber.setCustomValidity(true);
|
||||
return;
|
||||
}
|
||||
let storage = await this.getStorage();
|
||||
await storage.normalizeCCNumberFields(creditCard);
|
||||
await this.saveRecord(creditCard, this._record ? this._record.guid : null);
|
||||
|
||||
// TODO: "MasterPassword.ensureLoggedIn" can be removed after the storage
|
||||
// APIs are refactored to be async functions (bug 1399367).
|
||||
if (await MasterPassword.ensureLoggedIn()) {
|
||||
await this.saveRecord(creditCard, this._record ? this._record.guid : null);
|
||||
}
|
||||
window.close();
|
||||
}
|
||||
|
||||
|
|
|
@ -348,7 +348,7 @@ class ManageCreditCards extends ManageRecords {
|
|||
async openEditDialog(creditCard) {
|
||||
// If master password is set, ask for password if user is trying to edit an
|
||||
// existing credit card.
|
||||
if (!this._hasMasterPassword || !creditCard || await MasterPassword.prompt()) {
|
||||
if (!creditCard || !this._hasMasterPassword || await MasterPassword.ensureLoggedIn(true)) {
|
||||
this.prefWin.gSubDialog.open(EDIT_CREDIT_CARD_URL, null, creditCard);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,9 @@ changeAutofillOptionsOSX = Change Form Autofill Preferences
|
|||
# LOCALIZATION NOTE (addressesSyncCheckbox): If Sync is enabled, this checkbox is displayed on the doorhanger
|
||||
# shown when saving addresses.
|
||||
addressesSyncCheckbox = Share addresses with synced devices
|
||||
# LOCALIZATION NOTE (creditCardsSyncCheckbox): If Sync is enabled and credit card sync is available,
|
||||
# this checkbox is displayed on the doorhanger shown when saving credit card.
|
||||
creditCardsSyncCheckbox = Share credit cards with synced devices
|
||||
# LOCALIZATION NOTE (updateAddressMessage, createAddressLabel, updateAddressLabel): Used on the doorhanger
|
||||
# when an address change is detected.
|
||||
updateAddressMessage = Would you like to update your address with this new information?
|
||||
|
|
|
@ -51,7 +51,6 @@ add_task(async function test_submit_creditCard_saved() {
|
|||
|
||||
await promiseShown;
|
||||
await clickDoorhangerButton(MAIN_BUTTON);
|
||||
await TestUtils.topicObserved("formautofill-storage-changed");
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -163,3 +162,129 @@ add_task(async function test_submit_creditCard_saved_with_mp_enabled_but_cancele
|
|||
is(creditCards.length, 2, "Still 2 credit cards in storage");
|
||||
LoginTestUtils.masterPassword.disable();
|
||||
});
|
||||
|
||||
add_task(async function test_submit_creditCard_unavailable_with_sync_account() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
"set": [
|
||||
[SYNC_USERNAME_PREF, "foo@bar.com"],
|
||||
],
|
||||
});
|
||||
|
||||
await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
|
||||
async function(browser) {
|
||||
let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
|
||||
"popupshown");
|
||||
is(SpecialPowers.getBoolPref(SYNC_CREDITCARDS_AVAILABLE_PREF), false,
|
||||
"creditCards sync should be unavailable by default");
|
||||
await ContentTask.spawn(browser, null, async function() {
|
||||
let form = content.document.getElementById("form");
|
||||
let name = form.querySelector("#cc-name");
|
||||
name.focus();
|
||||
name.setUserInput("User 2");
|
||||
|
||||
let number = form.querySelector("#cc-number");
|
||||
number.setUserInput("1234123412341234");
|
||||
|
||||
// Wait 500ms before submission to make sure the input value applied
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
form.querySelector("input[type=submit]").click();
|
||||
});
|
||||
|
||||
await promiseShown;
|
||||
let cb = getDoorhangerCheckbox();
|
||||
ok(cb.hidden, "Sync checkbox should be hidden");
|
||||
|
||||
await clickDoorhangerButton(SECONDARY_BUTTON);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function test_submit_creditCard_with_sync_account() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
"set": [
|
||||
[SYNC_USERNAME_PREF, "foo@bar.com"],
|
||||
[SYNC_CREDITCARDS_AVAILABLE_PREF, true],
|
||||
],
|
||||
});
|
||||
|
||||
await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
|
||||
async function(browser) {
|
||||
let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
|
||||
"popupshown");
|
||||
await ContentTask.spawn(browser, null, async function() {
|
||||
let form = content.document.getElementById("form");
|
||||
let name = form.querySelector("#cc-name");
|
||||
name.focus();
|
||||
name.setUserInput("User 2");
|
||||
|
||||
let number = form.querySelector("#cc-number");
|
||||
number.setUserInput("1234123412341234");
|
||||
|
||||
// Wait 500ms before submission to make sure the input value applied
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
form.querySelector("input[type=submit]").click();
|
||||
});
|
||||
|
||||
await promiseShown;
|
||||
let cb = getDoorhangerCheckbox();
|
||||
ok(!cb.hidden, "Sync checkbox should be visible");
|
||||
is(SpecialPowers.getBoolPref(SYNC_CREDITCARDS_PREF), false,
|
||||
"creditCards sync should be disabled by default");
|
||||
|
||||
// Verify if the checkbox and button state is changed.
|
||||
let secondaryButton = getDoorhangerButton(SECONDARY_BUTTON);
|
||||
let menuButton = getDoorhangerButton(MENU_BUTTON);
|
||||
is(cb.checked, false, "Checkbox state should match creditCards sync state");
|
||||
is(secondaryButton.disabled, false, "Not saving button should be enabled");
|
||||
is(menuButton.disabled, false, "Never saving menu button should be enabled");
|
||||
// Click the checkbox to enable credit card sync.
|
||||
cb.click();
|
||||
is(SpecialPowers.getBoolPref(SYNC_CREDITCARDS_PREF), true,
|
||||
"creditCards sync should be enabled after checked");
|
||||
is(secondaryButton.disabled, true, "Not saving button should be disabled");
|
||||
is(menuButton.disabled, true, "Never saving menu button should be disabled");
|
||||
// Click the checkbox again to disable credit card sync.
|
||||
cb.click();
|
||||
is(SpecialPowers.getBoolPref(SYNC_CREDITCARDS_PREF), false,
|
||||
"creditCards sync should be disabled after unchecked");
|
||||
is(secondaryButton.disabled, false, "Not saving button should be enabled again");
|
||||
is(menuButton.disabled, false, "Never saving menu button should be enabled again");
|
||||
await clickDoorhangerButton(MAIN_BUTTON);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function test_submit_creditCard_with_synced_already() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
"set": [
|
||||
[SYNC_CREDITCARDS_PREF, true],
|
||||
[SYNC_USERNAME_PREF, "foo@bar.com"],
|
||||
[SYNC_CREDITCARDS_AVAILABLE_PREF, true],
|
||||
],
|
||||
});
|
||||
|
||||
await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
|
||||
async function(browser) {
|
||||
let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
|
||||
"popupshown");
|
||||
await ContentTask.spawn(browser, null, async function() {
|
||||
let form = content.document.getElementById("form");
|
||||
let name = form.querySelector("#cc-name");
|
||||
name.focus();
|
||||
name.setUserInput("User 2");
|
||||
|
||||
let number = form.querySelector("#cc-number");
|
||||
number.setUserInput("1234123412341234");
|
||||
|
||||
// Wait 500ms before submission to make sure the input value applied
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
form.querySelector("input[type=submit]").click();
|
||||
});
|
||||
|
||||
await promiseShown;
|
||||
let cb = getDoorhangerCheckbox();
|
||||
ok(cb.hidden, "Sync checkbox should be hidden");
|
||||
await clickDoorhangerButton(MAIN_BUTTON);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
|
@ -2,10 +2,11 @@
|
|||
BASE_URL, TEST_ADDRESS_1, TEST_ADDRESS_2, TEST_ADDRESS_3, TEST_ADDRESS_4, TEST_ADDRESS_5,
|
||||
TEST_CREDIT_CARD_1, TEST_CREDIT_CARD_2, TEST_CREDIT_CARD_3, FORM_URL, CREDITCARD_FORM_URL,
|
||||
FTU_PREF, ENABLED_AUTOFILL_ADDRESSES_PREF, AUTOFILL_CREDITCARDS_AVAILABLE_PREF, ENABLED_AUTOFILL_CREDITCARDS_PREF,
|
||||
SYNC_USERNAME_PREF, SYNC_ADDRESSES_PREF,
|
||||
SYNC_USERNAME_PREF, SYNC_ADDRESSES_PREF, SYNC_CREDITCARDS_PREF, SYNC_CREDITCARDS_AVAILABLE_PREF,
|
||||
sleep, expectPopupOpen, openPopupOn, expectPopupClose, closePopup, clickDoorhangerButton,
|
||||
getAddresses, saveAddress, removeAddresses, saveCreditCard,
|
||||
getDisplayedPopupItems, getDoorhangerCheckbox, waitForMasterPasswordDialog */
|
||||
getDisplayedPopupItems, getDoorhangerCheckbox, waitForMasterPasswordDialog,
|
||||
getNotification, getDoorhangerButton */
|
||||
|
||||
"use strict";
|
||||
|
||||
|
@ -25,6 +26,8 @@ const AUTOFILL_CREDITCARDS_AVAILABLE_PREF = "extensions.formautofill.creditCards
|
|||
const ENABLED_AUTOFILL_CREDITCARDS_PREF = "extensions.formautofill.creditCards.enabled";
|
||||
const SYNC_USERNAME_PREF = "services.sync.username";
|
||||
const SYNC_ADDRESSES_PREF = "services.sync.engine.addresses";
|
||||
const SYNC_CREDITCARDS_PREF = "services.sync.engine.creditcards";
|
||||
const SYNC_CREDITCARDS_AVAILABLE_PREF = "services.sync.engine.creditcards.available";
|
||||
|
||||
const TEST_ADDRESS_1 = {
|
||||
"given-name": "John",
|
||||
|
@ -248,10 +251,15 @@ async function clickDoorhangerButton(button, index) {
|
|||
await popuphidden;
|
||||
}
|
||||
|
||||
|
||||
function getDoorhangerCheckbox() {
|
||||
return getNotification().checkbox;
|
||||
}
|
||||
|
||||
function getDoorhangerButton(button) {
|
||||
return getNotification()[button];
|
||||
}
|
||||
|
||||
|
||||
// Wait for the master password dialog to popup and enter the password to log in
|
||||
// if "login" is "true" or dismiss it directly if otherwise.
|
||||
|
|
|
@ -90,7 +90,7 @@ function getTempFile(leafName) {
|
|||
return file;
|
||||
}
|
||||
|
||||
async function initProfileStorage(fileName, records) {
|
||||
async function initProfileStorage(fileName, records, collectionName = "addresses") {
|
||||
let {ProfileStorage} = Cu.import("resource://formautofill/ProfileStorage.jsm", {});
|
||||
let path = getTempFile(fileName).path;
|
||||
let profileStorage = new ProfileStorage(path);
|
||||
|
@ -103,7 +103,7 @@ async function initProfileStorage(fileName, records) {
|
|||
let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
|
||||
(subject, data) => data == "add");
|
||||
for (let record of records) {
|
||||
do_check_true(profileStorage.addresses.add(record));
|
||||
do_check_true(profileStorage[collectionName].add(record));
|
||||
await onChanged;
|
||||
}
|
||||
await profileStorage._saveImmediately();
|
||||
|
|
|
@ -52,29 +52,16 @@ const TEST_CREDIT_CARD_WITH_SPACES_BETWEEN_DIGITS = {
|
|||
"cc-number": "1111 2222 3333 4444",
|
||||
};
|
||||
|
||||
const TEST_CREDIT_CARD_WITH_INVALID_NUMBERS = {
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "abcdefg",
|
||||
};
|
||||
|
||||
const TEST_CREDIT_CARD_WITH_SHORT_NUMBERS = {
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1234567890",
|
||||
};
|
||||
|
||||
let prepareTestCreditCards = async function(path) {
|
||||
let profileStorage = new ProfileStorage(path);
|
||||
await profileStorage.initialize();
|
||||
|
||||
let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
|
||||
(subject, data) => data == "add");
|
||||
let encryptedCC_1 = Object.assign({}, TEST_CREDIT_CARD_1);
|
||||
await profileStorage.creditCards.normalizeCCNumberFields(encryptedCC_1);
|
||||
do_check_true(profileStorage.creditCards.add(encryptedCC_1));
|
||||
do_check_true(profileStorage.creditCards.add(TEST_CREDIT_CARD_1));
|
||||
await onChanged;
|
||||
do_check_true(profileStorage.creditCards.add(TEST_CREDIT_CARD_2));
|
||||
await onChanged;
|
||||
let encryptedCC_2 = Object.assign({}, TEST_CREDIT_CARD_2);
|
||||
await profileStorage.creditCards.normalizeCCNumberFields(encryptedCC_2);
|
||||
do_check_true(profileStorage.creditCards.add(encryptedCC_2));
|
||||
await profileStorage._saveImmediately();
|
||||
};
|
||||
|
||||
|
@ -87,6 +74,7 @@ let do_check_credit_card_matches = (creditCardWithMeta, creditCard) => {
|
|||
do_check_neq(matches, null);
|
||||
do_check_eq(creditCardWithMeta["cc-number"].length, creditCard["cc-number"].length);
|
||||
do_check_eq(creditCard["cc-number"].endsWith(matches[2]), true);
|
||||
do_check_neq(creditCard["cc-number-encrypted"], "");
|
||||
} else {
|
||||
do_check_eq(creditCardWithMeta[key], creditCard[key]);
|
||||
}
|
||||
|
@ -182,9 +170,7 @@ add_task(async function test_add() {
|
|||
do_check_eq(creditCards[0].timeLastUsed, 0);
|
||||
do_check_eq(creditCards[0].timesUsed, 0);
|
||||
|
||||
let encryptedCC_invalid = Object.assign({}, TEST_CREDIT_CARD_WITH_INVALID_FIELD);
|
||||
await profileStorage.creditCards.normalizeCCNumberFields(encryptedCC_invalid);
|
||||
Assert.throws(() => profileStorage.creditCards.add(encryptedCC_invalid),
|
||||
Assert.throws(() => profileStorage.creditCards.add(TEST_CREDIT_CARD_WITH_INVALID_FIELD),
|
||||
/"invalidField" is not a valid field\./);
|
||||
});
|
||||
|
||||
|
@ -203,7 +189,6 @@ add_task(async function test_update() {
|
|||
(subject, data) => data == "update");
|
||||
|
||||
do_check_neq(creditCards[1]["cc-name"], undefined);
|
||||
await profileStorage.creditCards.normalizeCCNumberFields(TEST_CREDIT_CARD_3);
|
||||
profileStorage.creditCards.update(guid, TEST_CREDIT_CARD_3);
|
||||
await onChanged;
|
||||
await profileStorage._saveImmediately();
|
||||
|
@ -222,10 +207,8 @@ add_task(async function test_update() {
|
|||
/No matching record\./
|
||||
);
|
||||
|
||||
let encryptedCC_invalid = Object.assign({}, TEST_CREDIT_CARD_WITH_INVALID_FIELD);
|
||||
await profileStorage.creditCards.normalizeCCNumberFields(encryptedCC_invalid);
|
||||
Assert.throws(
|
||||
() => profileStorage.creditCards.update(guid, encryptedCC_invalid),
|
||||
() => profileStorage.creditCards.update(guid, TEST_CREDIT_CARD_WITH_INVALID_FIELD),
|
||||
/"invalidField" is not a valid field\./
|
||||
);
|
||||
});
|
||||
|
@ -236,11 +219,8 @@ add_task(async function test_validate() {
|
|||
let profileStorage = new ProfileStorage(path);
|
||||
await profileStorage.initialize();
|
||||
|
||||
await profileStorage.creditCards.normalizeCCNumberFields(TEST_CREDIT_CARD_WITH_INVALID_EXPIRY_DATE);
|
||||
profileStorage.creditCards.add(TEST_CREDIT_CARD_WITH_INVALID_EXPIRY_DATE);
|
||||
await profileStorage.creditCards.normalizeCCNumberFields(TEST_CREDIT_CARD_WITH_2_DIGITS_YEAR);
|
||||
profileStorage.creditCards.add(TEST_CREDIT_CARD_WITH_2_DIGITS_YEAR);
|
||||
await profileStorage.creditCards.normalizeCCNumberFields(TEST_CREDIT_CARD_WITH_SPACES_BETWEEN_DIGITS);
|
||||
profileStorage.creditCards.add(TEST_CREDIT_CARD_WITH_SPACES_BETWEEN_DIGITS);
|
||||
|
||||
let creditCards = profileStorage.creditCards.getAll();
|
||||
|
@ -256,20 +236,6 @@ add_task(async function test_validate() {
|
|||
do_check_eq(creditCards[1]["cc-exp"], year + "-" + month.toString().padStart(2, "0"));
|
||||
|
||||
do_check_eq(creditCards[2]["cc-number"].length, 16);
|
||||
|
||||
try {
|
||||
await profileStorage.creditCards.normalizeCCNumberFields(TEST_CREDIT_CARD_WITH_INVALID_NUMBERS);
|
||||
throw new Error("Not receiving invalid characters error");
|
||||
} catch (e) {
|
||||
Assert.equal(e.message, "Credit card number contains invalid characters or is under 12 digits.");
|
||||
}
|
||||
|
||||
try {
|
||||
await profileStorage.creditCards.normalizeCCNumberFields(TEST_CREDIT_CARD_WITH_SHORT_NUMBERS);
|
||||
throw new Error("Not receiving invalid characters error");
|
||||
} catch (e) {
|
||||
Assert.equal(e.message, "Credit card number contains invalid characters or is under 12 digits.");
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_notifyUsed() {
|
||||
|
|
|
@ -157,12 +157,6 @@ const TESTCASES = [
|
|||
contactType: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "non-input element",
|
||||
document: `<label id="targetElement">street</label>`,
|
||||
elementId: "targetElement",
|
||||
expectedReturnValue: null,
|
||||
},
|
||||
{
|
||||
description: "input element with \"submit\" type",
|
||||
document: `<input id="targetElement" type="submit" />`,
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
"use strict";
|
||||
|
||||
Cu.import("resource://formautofill/FormAutofillParent.jsm");
|
||||
Cu.import("resource://formautofill/MasterPassword.jsm");
|
||||
Cu.import("resource://formautofill/ProfileStorage.jsm");
|
||||
|
||||
const TEST_ADDRESS_1 = {
|
||||
|
@ -166,13 +167,16 @@ add_task(async function test_getRecords_creditCards() {
|
|||
await formAutofillParent.init();
|
||||
await formAutofillParent.profileStorage.initialize();
|
||||
let collection = profileStorage.creditCards;
|
||||
let decryptedCCNumber = [TEST_CREDIT_CARD_1["cc-number"], TEST_CREDIT_CARD_2["cc-number"]];
|
||||
await collection.normalizeCCNumberFields(TEST_CREDIT_CARD_1);
|
||||
await collection.normalizeCCNumberFields(TEST_CREDIT_CARD_2);
|
||||
sinon.stub(collection, "getAll", () => [Object.assign({}, TEST_CREDIT_CARD_1), Object.assign({}, TEST_CREDIT_CARD_2)]);
|
||||
let encryptedCCRecords = [TEST_CREDIT_CARD_1, TEST_CREDIT_CARD_2].map(record => {
|
||||
let clonedRecord = Object.assign({}, record);
|
||||
clonedRecord["cc-number"] = collection._getMaskedCCNumber(record["cc-number"]);
|
||||
clonedRecord["cc-number-encrypted"] = MasterPassword.encryptSync(record["cc-number"]);
|
||||
return clonedRecord;
|
||||
});
|
||||
sinon.stub(collection, "getAll", () => [Object.assign({}, encryptedCCRecords[0]), Object.assign({}, encryptedCCRecords[1])]);
|
||||
let CreditCardsWithDecryptedNumber = [
|
||||
Object.assign({}, TEST_CREDIT_CARD_1, {"cc-number-decrypted": decryptedCCNumber[0]}),
|
||||
Object.assign({}, TEST_CREDIT_CARD_2, {"cc-number-decrypted": decryptedCCNumber[1]}),
|
||||
Object.assign({}, encryptedCCRecords[0], {"cc-number-decrypted": TEST_CREDIT_CARD_1["cc-number"]}),
|
||||
Object.assign({}, encryptedCCRecords[1], {"cc-number-decrypted": TEST_CREDIT_CARD_2["cc-number"]}),
|
||||
];
|
||||
|
||||
let testCases = [
|
||||
|
@ -229,7 +233,7 @@ add_task(async function test_getRecords_creditCards() {
|
|||
searchString: "John Doe",
|
||||
},
|
||||
mpEnabled: true,
|
||||
expectedResult: [TEST_CREDIT_CARD_1],
|
||||
expectedResult: encryptedCCRecords.slice(0, 1),
|
||||
},
|
||||
{
|
||||
description: "Return all creditCards if focused field is cc number (with masterpassword)",
|
||||
|
@ -239,7 +243,7 @@ add_task(async function test_getRecords_creditCards() {
|
|||
searchString: "123",
|
||||
},
|
||||
mpEnabled: true,
|
||||
expectedResult: [TEST_CREDIT_CARD_1, TEST_CREDIT_CARD_2],
|
||||
expectedResult: encryptedCCRecords,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ const TEST_STORE_FILE_NAME = "test-profile.json";
|
|||
// changed on a remote device)
|
||||
//
|
||||
// To further help understanding this, a few of the testcases are annotated.
|
||||
const RECONCILE_TESTCASES = [
|
||||
const ADDRESS_RECONCILE_TESTCASES = [
|
||||
{
|
||||
description: "Local change",
|
||||
parent: {
|
||||
|
@ -464,6 +464,458 @@ const RECONCILE_TESTCASES = [
|
|||
},
|
||||
];
|
||||
|
||||
const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
{
|
||||
description: "Local change",
|
||||
parent: {
|
||||
// So when we last wrote the record to the server, it had these values.
|
||||
"guid": "2bbd2d8fbc6b",
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
},
|
||||
local: [{
|
||||
// The current local record - by comparing against parent we can see that
|
||||
// only the cc-number has changed locally.
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4444333322221111",
|
||||
}],
|
||||
remote: {
|
||||
// This is the incoming record. It has the same values as "parent", so
|
||||
// we can deduce the record hasn't actually been changed remotely so we
|
||||
// can safely ignore the incoming record and write our local changes.
|
||||
"guid": "2bbd2d8fbc6b",
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
},
|
||||
reconciled: {
|
||||
"guid": "2bbd2d8fbc6b",
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4444333322221111",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Remote change",
|
||||
parent: {
|
||||
"guid": "e3680e9f890d",
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
},
|
||||
local: [{
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
}],
|
||||
remote: {
|
||||
"guid": "e3680e9f890d",
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4444333322221111",
|
||||
},
|
||||
reconciled: {
|
||||
"guid": "e3680e9f890d",
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4444333322221111",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
description: "New local field",
|
||||
parent: {
|
||||
"guid": "0cba738b1be0",
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
},
|
||||
local: [{
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
"cc-exp-month": 12,
|
||||
}],
|
||||
remote: {
|
||||
"guid": "0cba738b1be0",
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
},
|
||||
reconciled: {
|
||||
"guid": "0cba738b1be0",
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
"cc-exp-month": 12,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "New remote field",
|
||||
parent: {
|
||||
"guid": "be3ef97f8285",
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
},
|
||||
local: [{
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
}],
|
||||
remote: {
|
||||
"guid": "be3ef97f8285",
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
"cc-exp-month": 12,
|
||||
},
|
||||
reconciled: {
|
||||
"guid": "be3ef97f8285",
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
"cc-exp-month": 12,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Deleted field locally",
|
||||
parent: {
|
||||
"guid": "9627322248ec",
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
"cc-exp-month": 12,
|
||||
},
|
||||
local: [{
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
}],
|
||||
remote: {
|
||||
"guid": "9627322248ec",
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
"cc-exp-month": 12,
|
||||
},
|
||||
reconciled: {
|
||||
"guid": "9627322248ec",
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Deleted field remotely",
|
||||
parent: {
|
||||
"guid": "7d7509f3eeb2",
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
"cc-exp-month": 12,
|
||||
},
|
||||
local: [{
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
"cc-exp-month": 12,
|
||||
}],
|
||||
remote: {
|
||||
"guid": "7d7509f3eeb2",
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
},
|
||||
reconciled: {
|
||||
"guid": "7d7509f3eeb2",
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Local and remote changes to unrelated fields",
|
||||
parent: {
|
||||
// The last time we wrote this to the server, "cc-exp-month" was 12.
|
||||
"guid": "e087a06dfc57",
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
"cc-exp-month": 12,
|
||||
},
|
||||
local: [{
|
||||
// The current local record - so locally we've changed "cc-number".
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4444333322221111",
|
||||
"cc-exp-month": 12,
|
||||
}],
|
||||
remote: {
|
||||
// Remotely, we've changed "cc-exp-month" to 1.
|
||||
"guid": "e087a06dfc57",
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
"cc-exp-month": 1,
|
||||
},
|
||||
reconciled: {
|
||||
"guid": "e087a06dfc57",
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4444333322221111",
|
||||
"cc-exp-month": 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Multiple local changes",
|
||||
parent: {
|
||||
"guid": "340a078c596f",
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
},
|
||||
local: [{
|
||||
"cc-name": "Skip",
|
||||
"cc-number": "1111222233334444",
|
||||
}, {
|
||||
"cc-name": "Skip",
|
||||
"cc-number": "1111222233334444",
|
||||
"cc-exp-month": 12,
|
||||
}],
|
||||
remote: {
|
||||
"guid": "340a078c596f",
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
"cc-exp-year": 2000,
|
||||
},
|
||||
reconciled: {
|
||||
"guid": "340a078c596f",
|
||||
"cc-name": "Skip",
|
||||
"cc-number": "1111222233334444",
|
||||
"cc-exp-month": 12,
|
||||
"cc-exp-year": 2000,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Local and remote diverged from the shared parent, but the values are the
|
||||
// same, so we shouldn't fork.
|
||||
description: "Same change to local and remote",
|
||||
parent: {
|
||||
"guid": "0b3a72a1bea2",
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
},
|
||||
local: [{
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4444333322221111",
|
||||
}],
|
||||
remote: {
|
||||
"guid": "0b3a72a1bea2",
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4444333322221111",
|
||||
},
|
||||
reconciled: {
|
||||
"guid": "0b3a72a1bea2",
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4444333322221111",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Conflicting changes to single field",
|
||||
parent: {
|
||||
// This is what we last wrote to the sync server.
|
||||
"guid": "62068784d089",
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
},
|
||||
local: [{
|
||||
// The current version of the local record - the cc-number has changed locally.
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111111111111111",
|
||||
}],
|
||||
remote: {
|
||||
// An incoming record has a different cc-number than any of the above!
|
||||
"guid": "62068784d089",
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4444333322221111",
|
||||
},
|
||||
forked: {
|
||||
// So we've forked the local record to a new GUID (and the next sync is
|
||||
// going to write this as a new record)
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111111111111111",
|
||||
},
|
||||
reconciled: {
|
||||
// And we've updated the local version of the record to be the remote version.
|
||||
guid: "62068784d089",
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4444333322221111",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Conflicting changes to multiple fields",
|
||||
parent: {
|
||||
"guid": "244dbb692e94",
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
"cc-exp-month": 12,
|
||||
},
|
||||
local: [{
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111111111111111",
|
||||
"cc-exp-month": 1,
|
||||
}],
|
||||
remote: {
|
||||
"guid": "244dbb692e94",
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4444333322221111",
|
||||
"cc-exp-month": 3,
|
||||
},
|
||||
forked: {
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111111111111111",
|
||||
"cc-exp-month": 1,
|
||||
},
|
||||
reconciled: {
|
||||
"guid": "244dbb692e94",
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4444333322221111",
|
||||
"cc-exp-month": 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Field deleted locally, changed remotely",
|
||||
parent: {
|
||||
"guid": "6fc45e03d19a",
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
"cc-exp-month": 12,
|
||||
},
|
||||
local: [{
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
}],
|
||||
remote: {
|
||||
"guid": "6fc45e03d19a",
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
"cc-exp-month": 3,
|
||||
},
|
||||
forked: {
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
},
|
||||
reconciled: {
|
||||
"guid": "6fc45e03d19a",
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
"cc-exp-month": 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Field changed locally, deleted remotely",
|
||||
parent: {
|
||||
"guid": "fff9fa27fa18",
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
"cc-exp-month": 12,
|
||||
},
|
||||
local: [{
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
"cc-exp-month": 3,
|
||||
}],
|
||||
remote: {
|
||||
"guid": "fff9fa27fa18",
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
},
|
||||
forked: {
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
"cc-exp-month": 3,
|
||||
},
|
||||
reconciled: {
|
||||
"guid": "fff9fa27fa18",
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
},
|
||||
},
|
||||
{
|
||||
// Created, last modified should be synced; last used and times used should
|
||||
// be local. Remote created time older than local, remote modified time
|
||||
// newer than local.
|
||||
description: "Created, last modified time reconciliation without local changes",
|
||||
parent: {
|
||||
"guid": "5113f329c42f",
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
"timeCreated": 1234,
|
||||
"timeLastModified": 5678,
|
||||
"timeLastUsed": 5678,
|
||||
"timesUsed": 6,
|
||||
},
|
||||
local: [],
|
||||
remote: {
|
||||
"guid": "5113f329c42f",
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
"timeCreated": 1200,
|
||||
"timeLastModified": 5700,
|
||||
"timeLastUsed": 5700,
|
||||
"timesUsed": 3,
|
||||
},
|
||||
reconciled: {
|
||||
"guid": "5113f329c42f",
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
"timeCreated": 1200,
|
||||
"timeLastModified": 5700,
|
||||
"timeLastUsed": 5678,
|
||||
"timesUsed": 6,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Local changes, remote created time newer than local, remote modified time
|
||||
// older than local.
|
||||
description: "Created, last modified time reconciliation with local changes",
|
||||
parent: {
|
||||
"guid": "791e5608b80a",
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
"timeCreated": 1234,
|
||||
"timeLastModified": 5678,
|
||||
"timeLastUsed": 5678,
|
||||
"timesUsed": 6,
|
||||
},
|
||||
local: [{
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4444333322221111",
|
||||
}],
|
||||
remote: {
|
||||
"guid": "791e5608b80a",
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1111222233334444",
|
||||
"timeCreated": 1300,
|
||||
"timeLastModified": 5000,
|
||||
"timeLastUsed": 5000,
|
||||
"timesUsed": 3,
|
||||
},
|
||||
reconciled: {
|
||||
"guid": "791e5608b80a",
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4444333322221111",
|
||||
"timeCreated": 1234,
|
||||
"timeLastUsed": 5678,
|
||||
"timesUsed": 6,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
add_task(async function test_reconcile_unknown_version() {
|
||||
let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME);
|
||||
|
||||
|
@ -536,38 +988,47 @@ add_task(async function test_reconcile_idempotent() {
|
|||
});
|
||||
|
||||
add_task(async function test_reconcile_three_way_merge() {
|
||||
let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME);
|
||||
let TESTCASES = {
|
||||
addresses: ADDRESS_RECONCILE_TESTCASES,
|
||||
creditCards: CREDIT_CARD_RECONCILE_TESTCASES,
|
||||
};
|
||||
|
||||
for (let test of RECONCILE_TESTCASES) {
|
||||
do_print(test.description);
|
||||
for (let collectionName in TESTCASES) {
|
||||
do_print(`Start to test reconcile on ${collectionName}`);
|
||||
|
||||
profileStorage.addresses.add(test.parent, {sourceSync: true});
|
||||
let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, null, collectionName);
|
||||
|
||||
for (let updatedRecord of test.local) {
|
||||
profileStorage.addresses.update(test.parent.guid, updatedRecord);
|
||||
}
|
||||
for (let test of TESTCASES[collectionName]) {
|
||||
do_print(test.description);
|
||||
|
||||
let localRecord = profileStorage.addresses.get(test.parent.guid, {
|
||||
rawData: true,
|
||||
});
|
||||
profileStorage[collectionName].add(test.parent, {sourceSync: true});
|
||||
|
||||
let {forkedGUID} = profileStorage.addresses.reconcile(test.remote);
|
||||
let reconciledRecord = profileStorage.addresses.get(test.parent.guid, {
|
||||
rawData: true,
|
||||
});
|
||||
if (forkedGUID) {
|
||||
let forkedRecord = profileStorage.addresses.get(forkedGUID, {
|
||||
for (let updatedRecord of test.local) {
|
||||
profileStorage[collectionName].update(test.parent.guid, updatedRecord);
|
||||
}
|
||||
|
||||
let localRecord = profileStorage[collectionName].get(test.parent.guid, {
|
||||
rawData: true,
|
||||
});
|
||||
|
||||
notEqual(forkedRecord.guid, reconciledRecord.guid);
|
||||
equal(forkedRecord.timeLastModified, localRecord.timeLastModified);
|
||||
ok(objectMatches(forkedRecord, test.forked),
|
||||
`${test.description} should fork record`);
|
||||
} else {
|
||||
ok(!test.forked, `${test.description} should not fork record`);
|
||||
}
|
||||
let {forkedGUID} = profileStorage[collectionName].reconcile(test.remote);
|
||||
let reconciledRecord = profileStorage[collectionName].get(test.parent.guid, {
|
||||
rawData: true,
|
||||
});
|
||||
if (forkedGUID) {
|
||||
let forkedRecord = profileStorage[collectionName].get(forkedGUID, {
|
||||
rawData: true,
|
||||
});
|
||||
|
||||
ok(objectMatches(reconciledRecord, test.reconciled));
|
||||
notEqual(forkedRecord.guid, reconciledRecord.guid);
|
||||
equal(forkedRecord.timeLastModified, localRecord.timeLastModified);
|
||||
ok(objectMatches(forkedRecord, test.forked),
|
||||
`${test.description} should fork record`);
|
||||
} else {
|
||||
ok(!test.forked, `${test.description} should not fork record`);
|
||||
}
|
||||
|
||||
ok(objectMatches(reconciledRecord, test.reconciled));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -45,9 +45,6 @@ function add_storage_task(test_function) {
|
|||
|
||||
for (let [storage, record] of [[profileStorage.addresses, TEST_ADDRESS_1],
|
||||
[profileStorage.creditCards, testCC1]]) {
|
||||
if (storage.normalizeCCNumberFields) {
|
||||
await storage.normalizeCCNumberFields(record);
|
||||
}
|
||||
await test_function(storage, record);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -621,9 +621,7 @@ add_task(async function test_computeCreditCardFields() {
|
|||
await profileStorage.initialize();
|
||||
|
||||
for (let testcase of CREDIT_CARD_COMPUTE_TESTCASES) {
|
||||
let encryptedCC = Object.assign({}, testcase.creditCard);
|
||||
await profileStorage.creditCards.normalizeCCNumberFields(encryptedCC);
|
||||
profileStorage.creditCards.add(encryptedCC);
|
||||
profileStorage.creditCards.add(testcase.creditCard);
|
||||
}
|
||||
await profileStorage._saveImmediately();
|
||||
|
||||
|
@ -645,9 +643,7 @@ add_task(async function test_normalizeCreditCardFields() {
|
|||
await profileStorage.initialize();
|
||||
|
||||
for (let testcase of CREDIT_CARD_NORMALIZE_TESTCASES) {
|
||||
let encryptedCC = Object.assign({}, testcase.creditCard);
|
||||
await profileStorage.creditCards.normalizeCCNumberFields(encryptedCC);
|
||||
profileStorage.creditCards.add(encryptedCC);
|
||||
profileStorage.creditCards.add(testcase.creditCard);
|
||||
}
|
||||
await profileStorage._saveImmediately();
|
||||
|
||||
|
|
|
@ -53,12 +53,13 @@ add_task(async function test_clean_up_uitour_after_closing_overlay() {
|
|||
is(highlight.state, "open", "Should show UITour highlight");
|
||||
is(highlight.getAttribute("targetName"), "library", "UITour should highlight library");
|
||||
|
||||
// Close the overlay by clicking the overlay close button
|
||||
// Close the overlay by clicking the overlay
|
||||
// Should not click the close button here since the close button is hovered by appmenu and can't be clicked on win7
|
||||
highlightClosePromise = promisePopupChange(highlight, "closed");
|
||||
BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-close-btn", {}, tab.linkedBrowser);
|
||||
BrowserTestUtils.synthesizeMouseAtPoint(2, 2, {}, tab.linkedBrowser);
|
||||
await promiseOnboardingOverlayClosed(tab.linkedBrowser);
|
||||
await highlightClosePromise;
|
||||
is(highlight.state, "closed", "Should close UITour highlight after closing the overlay by clicking the overlay close button");
|
||||
is(highlight.state, "closed", "Should close UITour highlight after closing the overlay by clicking the overlay");
|
||||
|
||||
// Trigger UITour showHighlight again
|
||||
highlightOpenPromise = promisePopupChange(highlight, "open");
|
||||
|
|
|
@ -783,7 +783,7 @@
|
|||
"zh-TW": {
|
||||
"default": {
|
||||
"visibleDefaultEngines": [
|
||||
"yahoo-zh-TW", "google", "ddg", "findbook-zh-TW", "wikipedia-zh-TW", "yahoo-zh-TW-HK", "yahoo-bid-zh-TW", "yahoo-answer-zh-TW"
|
||||
"yahoo-zh-TW", "google", "ddg", "wikipedia-zh-TW", "yahoo-zh-TW-HK", "yahoo-bid-zh-TW", "yahoo-answer-zh-TW"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
|
||||
<ShortName>Findbook</ShortName>
|
||||
<Description>Findbook 書籍搜尋</Description>
|
||||
<InputEncoding>UTF-8</InputEncoding>
|
||||
<Image width="16" height="16">data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAQAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAArHEA/6xxAP+scQD/rHEA/6xxAP+scQD/rHEA/6xxAP+scQD/rHEA/6xxAP+scQD/rHEA/7a2tv8AAAAArHEA/LZ3AP/LhgT/1YwF/9SMBP/UjAT/1IwE/9SMBP/UjAT/1IwE/9SMBf/UjAX/1IwF/6xxAP+2trb/ypMqDqxxAP+2eAD/y4YC/9OMBf/QgQD/9uvV////////////uXAA/9WMBP/UjAX/1IwF/9SMBf+scQD/tra2/61zAuKscQD/tngA/8uGAv/UjAX/z4EA//Xmyv///////////7pwAP/VjAT/1IwF/9SMBf/UjAX/rHEA/7a2tv+scQD/rHEA/7Z4AP/LhgT/1IwF/8+BAP/15sr///////////+6cAD/1YwE/9SMBf/UjAX/1IwF/6xxAP+2trb/rHEA/6xxAP+2eAD/y4YC/9OMBf/QgQD/9ebK////////////tGQA/9GCAP/PgAD/0YQA/9SMBf+scQD/tra2/6xxAP+scQD/tngA/8uGBP/UjAX/z4EA//Xmyv/////////////////////////////////QggD/rHEA/7a2tv+scQD/rHEA/7Z4AP/LhgL/1IwF/9CBAP/15sr///////////+5hCL/yJg5/8iYO//BjjD/04kA/6xxAP+2trb/rHEA/6xxAP+2eAD/y4YE/9SMBf/OgQD/9ebK////////////um0A/9aKAP/UiQD/1YoA/9SLA/+scQD/tra2/6xxAP+scQD/tngA/8uGBP/UjAX/0IEA//Xmyf///////////7pwAP/VjAT/1IwF/9SLA//Tiwb/rHEA/7a2tv+scQD/rHEA/7Z4AP/LhgL/1IwE/859AP/15sr///////////+1ZwD/04YA/9GEAP/RgAD/zKVa/6xxAP+2trb/rHEA/6xxAP+2eAD/y4YB/9WMBf////////////////////////////////////////////////+scQD/tra2/6xxAP+scQD/tXUA/8uDAP/SkRj/yJc5/8iXOf/Ilzn/yJc5/8iWN//Ilzn/x5c5/8iXOf/Ilzn/rHEA/7a2tv+scQD/rHEA/7B2CP+scQD/rHEA/6xxAP+scQD/rHEA/6xxAP+scQD/rHEA/6xxAP+scQD/rHEA/61xAP+2trb/rHEA/6xyAfvp+///6PD//+z2///w+///9f////v//////////////////////////////////////////////6xxAP8AAAAArncM8axxAP+scQD/rHEA/6xxAP+scQD/rHEA/6xxAP+scQD/rHEA/6xxAP+scQD/rHEA/691B/mtcwPugAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAA%3D%3D</Image>
|
||||
<Url type="application/x-suggestions+json" method="GET" template="http://findbook.tw/search/suggest?q={searchTerms}&utm_source=ff-bundled&utm_medium=mozsearch&utm_campaign=search" />
|
||||
<Url type="text/html" method="GET" template="http://findbook.tw/search" resultdomain="findbook.tw">
|
||||
<Param name="q" value="{searchTerms}"/>
|
||||
<Param name="utm_source" value="ff-bundled"/>
|
||||
<Param name="utm_medium" value="mozsearch"/>
|
||||
<Param name="utm_campaign" value="search"/>
|
||||
</Url>
|
||||
<SearchForm>http://findbook.tw/search</SearchForm>
|
||||
</SearchPlugin>
|
|
@ -132,9 +132,6 @@
|
|||
#main-window:not([tabsintitlebar]) #TabsToolbar:not([collapsed="true"]) + #nav-bar,
|
||||
#TabsToolbar:not([collapsed="true"]) + #nav-bar:-moz-lwtheme {
|
||||
box-shadow: 0 calc(-1 * var(--tab-toolbar-navbar-overlap)) 0 var(--tabs-border);
|
||||
/* Position the toolbar above the bottom of background tabs */
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* Always draw a border on Yosemite to ensure the border is well-defined there
|
||||
|
@ -148,11 +145,7 @@
|
|||
}
|
||||
|
||||
#main-window[tabsintitlebar] #TabsToolbar:not([collapsed="true"]) + #nav-bar:not(:-moz-lwtheme) {
|
||||
border-top: 1px solid var(--tabs-border);
|
||||
background-clip: padding-box;
|
||||
/* Position the toolbar above the bottom of background tabs */
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
box-shadow: 0 calc(-1 * var(--tab-toolbar-navbar-overlap)) 0 var(--tabs-border);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-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/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60">
|
||||
<defs>
|
||||
<linearGradient id="gradient" gradientUnits="userSpaceOnUse" x1="30" y1="12.85" x2="30" y2="47.15">
|
||||
<stop offset="0" style="stop-color: #e63b2e"/>
|
||||
<stop offset="1" style="stop-color: #c33931"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="url(#gradient)" d="M49.048,17.648H29.004 c-2.289-0.016-2.809-1.142-3.165-2.401c-0.359-1.269-1.076-2.397-3.229-2.397c-5.775,0-5.42,0-6.167,0 c-2.153,0-2.87,1.127-3.229,2.397c-0.359,1.269-0.882,2.403-3.214,2.403h0.94c-0.519,0.008-0.937,0.433-0.937,0.958v27.583 c0,0.53,0.426,0.959,0.952,0.959h38.093c0.526,0,0.952-0.429,0.952-0.959V18.607C50,18.077,49.574,17.648,49.048,17.648z M18.441,27.932c0-2.119,1.705-3.837,3.809-3.837c2.103,0,3.809,1.718,3.809,3.837c0,2.119-1.705,3.837-3.809,3.837 C20.146,31.769,18.441,30.051,18.441,27.932z M36.717,41.83c-1.525,0-1.525-2.305-6.864-2.305c-5.339,0-5.339,2.305-6.864,2.305 c-0.842,0-1.526-0.512-1.526-1.537c0-1.024,1.271-3.842,8.39-3.842c7.119,0,8.39,2.804,8.39,3.842 C38.243,41.331,37.56,41.83,36.717,41.83z M37.485,31.769c-2.104,0-3.809-1.718-3.809-3.837c0-2.119,1.705-3.837,3.809-3.837 c2.104,0,3.809,1.718,3.809,3.837C41.294,30.051,39.588,31.769,37.485,31.769z"/>
|
||||
</svg>
|
||||
<?xml version="1.0"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40">
|
||||
<defs>
|
||||
<linearGradient id="gradient" gradientUnits="userSpaceOnUse" x1="20" y1="2.85" x2="20" y2="37.15">
|
||||
<stop offset="0" stop-color="#e63b2e"/>
|
||||
<stop offset="1" stop-color="#c33931"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="url(#gradient)" d="M39.048,6.798H19.004 C16.715,6.782 16.195,5.656 15.839,4.397 15.48,3.128 14.763,2 12.61,2 6.835,2 7.1899998,2 6.443,2 4.29,2 3.573,3.127 3.214,4.397 2.855,5.666 2.332,6.8 0,6.8H0.94 C0.421,6.808 0.003,7.233 0.003,7.758v27.583c0,0.53 0.426,0.959 0.952,0.959H39.048 C39.574,36.3 40,35.871 40,35.341 V 7.757 C40,7.227 39.574,6.798 39.048,6.798 Z M8.4409998,17.082c0,-2.119 1.7050002,-3.837 3.8090002,-3.837 2.103,0 3.809,1.718 3.809,3.837 0,2.119 -1.705,3.837 -3.809,3.837 -2.104,0 -3.8090002,-1.718 -3.8090002,-3.837z M26.717,30.98c-1.525,0 -1.525,-2.305 -6.864,-2.305 -5.339,0 -5.339,2.305 -6.864,2.305 -0.842,0 -1.526,-0.512 -1.526,-1.537 0,-1.024 1.271,-3.842 8.39,-3.842 7.119,0 8.39,2.804 8.39,3.842 0,1.038 -0.683,1.537 -1.526,1.537z m 0.768,-10.061c-2.104,0 -3.809,-1.718 -3.809,-3.837 0,-2.119 1.705,-3.837 3.809,-3.837 2.104,0 3.809,1.718 3.809,3.837 0,2.119 -1.706,3.837 -3.809,3.837z"/>
|
||||
</svg>
|
||||
|
|
До Ширина: | Высота: | Размер: 1.5 KiB После Ширина: | Высота: | Размер: 1.5 KiB |
|
@ -8,8 +8,7 @@
|
|||
|
||||
define(function (require, exports, module) {
|
||||
const { DOM: dom, createFactory, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
|
||||
const TreeViewClass = require("devtools/client/shared/components/tree/TreeView");
|
||||
const TreeView = createFactory(TreeViewClass);
|
||||
const TreeView = createFactory(require("devtools/client/shared/components/tree/TreeView"));
|
||||
|
||||
const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
|
||||
const { createFactories } = require("devtools/client/shared/react-utils");
|
||||
|
@ -19,8 +18,6 @@ define(function (require, exports, module) {
|
|||
const { Toolbar, ToolbarButton } = createFactories(require("./reps/Toolbar"));
|
||||
|
||||
const { div } = dom;
|
||||
const AUTO_EXPAND_MAX_SIZE = 100 * 1024;
|
||||
const AUTO_EXPAND_MAX_LEVEL = 7;
|
||||
|
||||
function isObject(value) {
|
||||
return Object(value) === value;
|
||||
|
@ -42,7 +39,7 @@ define(function (require, exports, module) {
|
|||
PropTypes.bool,
|
||||
PropTypes.number
|
||||
]),
|
||||
jsonTextLength: PropTypes.number,
|
||||
expandedNodes: PropTypes.instanceOf(Set),
|
||||
searchFilter: PropTypes.string,
|
||||
actions: PropTypes.object,
|
||||
},
|
||||
|
@ -96,15 +93,6 @@ define(function (require, exports, module) {
|
|||
width: "100%"
|
||||
}];
|
||||
|
||||
// Expand the document by default if its size isn't bigger than 100KB.
|
||||
let expandedNodes = new Set();
|
||||
if (this.props.jsonTextLength <= AUTO_EXPAND_MAX_SIZE) {
|
||||
expandedNodes = TreeViewClass.getExpandedNodes(
|
||||
this.props.data,
|
||||
{maxLevel: AUTO_EXPAND_MAX_LEVEL}
|
||||
);
|
||||
}
|
||||
|
||||
// Render tree component.
|
||||
return TreeView({
|
||||
object: this.props.data,
|
||||
|
@ -112,7 +100,7 @@ define(function (require, exports, module) {
|
|||
onFilter: this.onFilter,
|
||||
columns: columns,
|
||||
renderValue: this.renderValue,
|
||||
expandedNodes: expandedNodes,
|
||||
expandedNodes: this.props.expandedNodes,
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -34,7 +34,8 @@ define(function (require, exports, module) {
|
|||
PropTypes.array,
|
||||
PropTypes.bool,
|
||||
PropTypes.number
|
||||
])
|
||||
]),
|
||||
expandedNodes: PropTypes.instanceOf(Set),
|
||||
},
|
||||
|
||||
getInitialState: function () {
|
||||
|
@ -60,7 +61,7 @@ define(function (require, exports, module) {
|
|||
title: JSONView.Locale.$STR("jsonViewer.tab.JSON")},
|
||||
JsonPanel({
|
||||
data: this.props.json,
|
||||
jsonTextLength: this.props.jsonText.length,
|
||||
expandedNodes: this.props.expandedNodes,
|
||||
actions: this.props.actions,
|
||||
searchFilter: this.state.searchFilter
|
||||
})
|
||||
|
|
|
@ -201,24 +201,23 @@ Converter.prototype = {
|
|||
// To save with the proper extension we need the original content type,
|
||||
// which has been replaced by application/vnd.mozilla.json.view
|
||||
function fixSave(request) {
|
||||
let originalType;
|
||||
let match;
|
||||
if (request instanceof Ci.nsIHttpChannel) {
|
||||
try {
|
||||
let header = request.getResponseHeader("Content-Type");
|
||||
originalType = header.split(";")[0];
|
||||
match = header.match(/^(application\/(?:[^;]+\+)?json)(?:;|$)/);
|
||||
} catch (err) {
|
||||
// Handled below
|
||||
}
|
||||
} else {
|
||||
let uri = request.QueryInterface(Ci.nsIChannel).URI.spec;
|
||||
let match = uri.match(/^data:(.*?)[,;]/);
|
||||
if (match) {
|
||||
originalType = match[1];
|
||||
}
|
||||
match = uri.match(/^data:(application\/(?:[^;,]+\+)?json)[;,]/);
|
||||
}
|
||||
const JSON_TYPES = ["application/json", "application/manifest+json"];
|
||||
if (!JSON_TYPES.includes(originalType)) {
|
||||
originalType = JSON_TYPES[0];
|
||||
let originalType;
|
||||
if (match) {
|
||||
originalType = match[1];
|
||||
} else {
|
||||
originalType = "application/json";
|
||||
}
|
||||
request.QueryInterface(Ci.nsIWritablePropertyBag);
|
||||
request.setProperty("contentType", originalType);
|
||||
|
|
|
@ -90,8 +90,7 @@ JsonViewSniffer.prototype = {
|
|||
// Check the response content type and if it's a valid type
|
||||
// such as application/json or application/manifest+json
|
||||
// change it to new internal type consumed by JSON View.
|
||||
const JSON_TYPES = ["application/json", "application/manifest+json"];
|
||||
if (JSON_TYPES.includes(request.contentType)) {
|
||||
if (/^application\/(?:.+\+)?json$/.test(request.contentType)) {
|
||||
return JSON_VIEW_MIME_TYPE;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,8 +10,11 @@ define(function (require, exports, module) {
|
|||
const { render } = require("devtools/client/shared/vendor/react-dom");
|
||||
const { createFactories } = require("devtools/client/shared/react-utils");
|
||||
const { MainTabbedArea } = createFactories(require("./components/MainTabbedArea"));
|
||||
const TreeViewClass = require("devtools/client/shared/components/tree/TreeView");
|
||||
|
||||
const json = document.getElementById("json");
|
||||
const AUTO_EXPAND_MAX_SIZE = 100 * 1024;
|
||||
const AUTO_EXPAND_MAX_LEVEL = 7;
|
||||
|
||||
let prettyURL;
|
||||
|
||||
|
@ -35,6 +38,16 @@ define(function (require, exports, module) {
|
|||
input.json = err;
|
||||
}
|
||||
|
||||
// Expand the document by default if its size isn't bigger than 100KB.
|
||||
if (!(input.json instanceof Error) && input.jsonText.length <= AUTO_EXPAND_MAX_SIZE) {
|
||||
input.expandedNodes = TreeViewClass.getExpandedNodes(
|
||||
input.json,
|
||||
{maxLevel: AUTO_EXPAND_MAX_LEVEL}
|
||||
);
|
||||
} else {
|
||||
input.expandedNodes = new Set();
|
||||
}
|
||||
|
||||
json.remove();
|
||||
|
||||
/**
|
||||
|
|
|
@ -24,6 +24,7 @@ support-files =
|
|||
|
||||
[browser_jsonview_bug_1380828.js]
|
||||
[browser_jsonview_ignore_charset.js]
|
||||
[browser_jsonview_content_type.js]
|
||||
[browser_jsonview_copy_headers.js]
|
||||
subsuite = clipboard
|
||||
skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const mimeSvc = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
|
||||
const handlerSvc = Cc["@mozilla.org/uriloader/handler-service;1"]
|
||||
.getService(Ci.nsIHandlerService);
|
||||
|
||||
let contentTypes = {
|
||||
valid: [
|
||||
"application/json",
|
||||
"application/manifest+json",
|
||||
"application/vnd.api+json",
|
||||
"application/hal+json",
|
||||
"application/json+json",
|
||||
"application/whatever+json",
|
||||
],
|
||||
invalid: [
|
||||
"text/json",
|
||||
"text/hal+json",
|
||||
"application/jsona",
|
||||
"application/whatever+jsona",
|
||||
],
|
||||
};
|
||||
|
||||
add_task(function* () {
|
||||
info("Test JSON content types started");
|
||||
|
||||
// Prevent saving files to disk.
|
||||
let useDownloadDir = SpecialPowers.getBoolPref("browser.download.useDownloadDir");
|
||||
SpecialPowers.setBoolPref("browser.download.useDownloadDir", false);
|
||||
let { MockFilePicker } = SpecialPowers;
|
||||
MockFilePicker.init(window);
|
||||
MockFilePicker.returnValue = MockFilePicker.returnCancel;
|
||||
|
||||
for (let kind of Object.keys(contentTypes)) {
|
||||
let isValid = kind === "valid";
|
||||
for (let type of contentTypes[kind]) {
|
||||
// Prevent "Open or Save" dialogs, which would make the test fail.
|
||||
let mimeInfo = mimeSvc.getFromTypeAndExtension(type, null);
|
||||
let exists = handlerSvc.exists(mimeInfo);
|
||||
let {alwaysAskBeforeHandling} = mimeInfo;
|
||||
mimeInfo.alwaysAskBeforeHandling = false;
|
||||
handlerSvc.store(mimeInfo);
|
||||
|
||||
yield testType(isValid, type);
|
||||
yield testType(isValid, type, ";foo=bar+json");
|
||||
|
||||
// Restore old nsIMIMEInfo
|
||||
if (exists) {
|
||||
Object.assign(mimeInfo, {alwaysAskBeforeHandling});
|
||||
handlerSvc.store(mimeInfo);
|
||||
} else {
|
||||
handlerSvc.remove(mimeInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Restore old pref
|
||||
registerCleanupFunction(function () {
|
||||
MockFilePicker.cleanup();
|
||||
SpecialPowers.setBoolPref("browser.download.useDownloadDir", useDownloadDir);
|
||||
});
|
||||
});
|
||||
|
||||
function testType(isValid, type, params = "") {
|
||||
const TEST_JSON_URL = "data:" + type + params + ",[1,2,3]";
|
||||
return addJsonViewTab(TEST_JSON_URL).then(async function () {
|
||||
ok(isValid, "The JSON Viewer should only load for valid content types.");
|
||||
is(await evalInContent("document.contentType"), type, "Got the right content type");
|
||||
|
||||
let count = await getElementCount(".jsonPanelBox .treeTable .treeRow");
|
||||
is(count, 3, "There must be expected number of rows");
|
||||
}, function () {
|
||||
ok(!isValid, "The JSON Viewer should only not load for invalid content types.");
|
||||
});
|
||||
}
|
|
@ -31,6 +31,11 @@ add_task(function* () {
|
|||
// Clicking the label collapses the auto-expanded node.
|
||||
yield clickJsonNode(".jsonPanelBox .treeTable .treeLabel");
|
||||
is(yield countRows(), 1, "There must be one row");
|
||||
|
||||
// Collapsed nodes are preserved when switching panels.
|
||||
yield selectJsonViewContentTab("headers");
|
||||
yield selectJsonViewContentTab("json");
|
||||
is(yield countRows(), 1, "There must still be one row");
|
||||
});
|
||||
|
||||
function countRows() {
|
||||
|
|
|
@ -114,3 +114,8 @@ addMessageListener("Test:JsonView:WaitForFilter", function (msg) {
|
|||
|
||||
observer.observe(firstRow, { attributes: true });
|
||||
});
|
||||
|
||||
addMessageListener("Test:JsonView:Eval", function (msg) {
|
||||
let result = content.eval(msg.data.code);
|
||||
sendAsyncMessage(msg.name, {result});
|
||||
});
|
||||
|
|
|
@ -47,6 +47,12 @@ function addJsonViewTab(url, timeout = -1) {
|
|||
let frameScriptUrl = rootDir + "doc_frame_script.js";
|
||||
browser.messageManager.loadFrameScript(frameScriptUrl, false);
|
||||
|
||||
// Check if there is a JSONView object.
|
||||
if (!content.window.wrappedJSObject.JSONView) {
|
||||
deferred.reject("JSON Viewer did not load.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Resolve if the JSONView is fully loaded or wait
|
||||
// for an initialization event.
|
||||
if (content.window.wrappedJSObject.JSONView.initialized) {
|
||||
|
@ -171,3 +177,8 @@ function waitForFilter() {
|
|||
function normalizeNewLines(value) {
|
||||
return value.replace("(\r\n|\n)", "\n");
|
||||
}
|
||||
|
||||
function evalInContent(code) {
|
||||
return executeInContent("Test:JsonView:Eval", {code})
|
||||
.then(result => result.result);
|
||||
}
|
||||
|
|
|
@ -24,8 +24,9 @@
|
|||
},
|
||||
"firefox": {
|
||||
"webSocketConnection": false,
|
||||
"proxyHost": "localhost:9000",
|
||||
"webSocketHost": "localhost:6080",
|
||||
"host": "localhost",
|
||||
"webSocketPort": 8116,
|
||||
"tcpPort": 6080,
|
||||
"mcPath": "./firefox"
|
||||
},
|
||||
"development": {
|
||||
|
|
|
@ -9,19 +9,18 @@
|
|||
"codemirror": "^5.24.2",
|
||||
"devtools-config": "=0.0.12",
|
||||
"devtools-contextmenu": "=0.0.3",
|
||||
"devtools-launchpad": "=0.0.96",
|
||||
"devtools-modules": "=0.0.31",
|
||||
"devtools-launchpad": "=0.0.103",
|
||||
"devtools-modules": "=0.0.32",
|
||||
"devtools-source-editor": "=0.0.3",
|
||||
"immutable": "^3.8.1",
|
||||
"jszip": "^3.1.3",
|
||||
"react": "=15.3.2",
|
||||
"react-dom": "=15.3.2",
|
||||
"react": "=15.6.1",
|
||||
"react-dom": "=15.6.1",
|
||||
"react-redux": "=5.0.3",
|
||||
"redux": "^3.6.0",
|
||||
"reselect": "^2.5.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-preset-es2015": "^6.6.0",
|
||||
"babel-register": "^6.24.0",
|
||||
"file-loader": "^0.10.1"
|
||||
},
|
||||
|
|
|
@ -23,21 +23,18 @@ let webpackConfig = {
|
|||
test: /\.(png|svg)$/,
|
||||
loader: "file-loader?name=[path][name].[ext]",
|
||||
},
|
||||
{
|
||||
/*
|
||||
* The version of webpack used in the launchpad seems to have trouble
|
||||
* with the require("raw!${file}") that we use for the properties
|
||||
* file in l10.js.
|
||||
* This loader goes through the whole code and remove the "raw!" prefix
|
||||
* so the raw-loader declared in devtools-launchpad config can load
|
||||
* those files.
|
||||
*/
|
||||
test: /\.js$/,
|
||||
loader: "rewrite-raw",
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
loaders: [
|
||||
/**
|
||||
* The version of webpack used in the launchpad seems to have trouble
|
||||
* with the require("raw!${file}") that we use for the properties
|
||||
* file in l10.js.
|
||||
* This loader goes through the whole code and remove the "raw!" prefix
|
||||
* so the raw-loader declared in devtools-launchpad config can load
|
||||
* those files.
|
||||
*/
|
||||
"rewrite-raw",
|
||||
// Replace all references to this.browserRequire() by require()
|
||||
"rewrite-browser-require",
|
||||
// Replace all references to loader.lazyRequire() by require()
|
||||
|
@ -49,7 +46,7 @@ let webpackConfig = {
|
|||
|
||||
resolveLoader: {
|
||||
modules: [
|
||||
path.resolve("./node_modules"),
|
||||
"node_modules",
|
||||
path.resolve("../shared/webpack"),
|
||||
]
|
||||
},
|
||||
|
@ -64,8 +61,9 @@ let webpackConfig = {
|
|||
resolve: {
|
||||
modules: [
|
||||
// Make sure webpack is always looking for modules in
|
||||
// `webconsole/node_modules` directory first.
|
||||
path.resolve(__dirname, "node_modules"), "node_modules"
|
||||
// `netmonitor/node_modules` directory first.
|
||||
path.resolve(__dirname, "node_modules"),
|
||||
"node_modules",
|
||||
],
|
||||
alias: {
|
||||
"Services": "devtools-modules/src/Services",
|
||||
|
@ -138,9 +136,22 @@ let config = toolboxConfig(webpackConfig, getConfig(), {
|
|||
});
|
||||
|
||||
// Remove loaders from devtools-launchpad's webpack.config.js
|
||||
// * For svg-inline loader:
|
||||
// Netmonitor uses file loader to bundle image assets instead of svg-inline-loader
|
||||
config.module.rules = config.module.rules
|
||||
.filter((rule) => !["svg-inline-loader"].includes(rule.loader));
|
||||
// For svg-inline loader:
|
||||
// Using file loader to bundle image assets instead of svg-inline-loader
|
||||
config.module.rules = config.module.rules.filter((rule) => !["svg-inline-loader"].includes(rule.loader));
|
||||
|
||||
// For PostCSS loader:
|
||||
// Disable PostCSS loader
|
||||
config.module.rules.forEach(rule => {
|
||||
if (Array.isArray(rule.use)) {
|
||||
rule.use.some((use, idx) => {
|
||||
if (use.loader === "postcss-loader") {
|
||||
rule.use = rule.use.slice(0, idx);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = config;
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -196,7 +196,13 @@ skip-if = true # Bug 1403188
|
|||
# old console skip-if = e10s # Bug 1042253 - webconsole e10s tests
|
||||
[browser_jsterm_accessibility.js]
|
||||
[browser_jsterm_add_edited_input_to_history.js]
|
||||
[browser_jsterm_autocomplete_array_no_index.js]
|
||||
[browser_jsterm_autocomplete_escape_key.js]
|
||||
[browser_jsterm_autocomplete_helpers.js]
|
||||
[browser_jsterm_autocomplete_inside_text.js]
|
||||
[browser_jsterm_autocomplete_nav_and_tab_key.js]
|
||||
[browser_jsterm_autocomplete_return_key_no_selection.js]
|
||||
[browser_jsterm_autocomplete_return_key.js]
|
||||
[browser_jsterm_autocomplete-properties-with-non-alphanumeric-names.js]
|
||||
[browser_jsterm_copy_command.js]
|
||||
[browser_jsterm_dollar.js]
|
||||
|
@ -218,8 +224,6 @@ skip-if = true # Bug 1404850
|
|||
skip-if = true # Bug 1408919
|
||||
[browser_webconsole_autocomplete_in_debugger_stackframe.js]
|
||||
skip-if = true # Bug 1408920
|
||||
[browser_webconsole_autocomplete_keys.js]
|
||||
skip-if = true # Bug 1408921
|
||||
[browser_webconsole_autocomplete_popup.js]
|
||||
skip-if = true # Bug 1408922
|
||||
[browser_webconsole_autocomplete_popup_close_on_tab_switch.js]
|
||||
|
|
|
@ -11,12 +11,9 @@
|
|||
|
||||
const TEST_URI = "data:text/html;charset=utf-8,Web Console test for bug 817834";
|
||||
|
||||
add_task(function* () {
|
||||
let hud = yield openNewTabAndConsole(TEST_URI);
|
||||
// Clearing history that might have been set in previous tests.
|
||||
yield hud.jsterm.clearHistory();
|
||||
add_task(async function () {
|
||||
let hud = await openNewTabAndConsole(TEST_URI);
|
||||
testEditedInputHistory(hud);
|
||||
yield hud.jsterm.clearHistory();
|
||||
});
|
||||
|
||||
function testEditedInputHistory(hud) {
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// See Bug 585991.
|
||||
|
||||
const TEST_URI = `data:text/html;charset=utf-8,
|
||||
<head>
|
||||
<script>
|
||||
window.foo = [1,2,3];
|
||||
</script>
|
||||
</head>
|
||||
<body>bug 585991 - Autocomplete popup on array</body>`;
|
||||
|
||||
add_task(async function () {
|
||||
let { jsterm } = await openNewTabAndConsole(TEST_URI);
|
||||
|
||||
const {
|
||||
autocompletePopup: popup,
|
||||
completeNode,
|
||||
inputNode,
|
||||
} = jsterm;
|
||||
|
||||
let onPopUpOpen = popup.once("popup-opened");
|
||||
|
||||
info("wait for popup to show");
|
||||
jsterm.setInputValue("foo");
|
||||
EventUtils.synthesizeKey(".", {});
|
||||
|
||||
await onPopUpOpen;
|
||||
|
||||
let popupItems = popup.getItems().map(e => e.label);
|
||||
is(popupItems.includes("0"), false, "Completing on an array doesn't show numbers.");
|
||||
|
||||
info("press Escape to close the popup");
|
||||
const onPopupClose = popup.once("popup-closed");
|
||||
EventUtils.synthesizeKey("VK_ESCAPE", {});
|
||||
|
||||
await onPopupClose;
|
||||
});
|
|
@ -0,0 +1,55 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// See Bug 585991.
|
||||
|
||||
const TEST_URI = `data:text/html;charset=utf-8,
|
||||
<head>
|
||||
<script>
|
||||
/* Create a prototype-less object so popup does not contain native
|
||||
* Object prototype properties.
|
||||
*/
|
||||
window.foo = Object.create(null);
|
||||
Object.assign(window.foo, {
|
||||
item0: "value0",
|
||||
item1: "value1",
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>bug 585991 - autocomplete popup escape key usage test</body>`;
|
||||
|
||||
add_task(async function () {
|
||||
let { jsterm } = await openNewTabAndConsole(TEST_URI);
|
||||
info("web console opened");
|
||||
|
||||
const {
|
||||
autocompletePopup: popup,
|
||||
completeNode,
|
||||
inputNode,
|
||||
} = jsterm;
|
||||
|
||||
let onPopUpOpen = popup.once("popup-opened");
|
||||
|
||||
info("wait for completion: window.foo.");
|
||||
jsterm.setInputValue("window.foo");
|
||||
EventUtils.synthesizeKey(".", {});
|
||||
|
||||
await onPopUpOpen;
|
||||
|
||||
ok(popup.isOpen, "popup is open");
|
||||
ok(popup.itemCount, "popup has items");
|
||||
|
||||
info("press Escape to close the popup");
|
||||
const onPopupClose = popup.once("popup-closed");
|
||||
EventUtils.synthesizeKey("VK_ESCAPE", {});
|
||||
|
||||
await onPopupClose;
|
||||
|
||||
ok(!popup.isOpen, "popup is not open after VK_ESCAPE");
|
||||
is(jsterm.getInputValue(), "window.foo.", "completion was cancelled");
|
||||
ok(!completeNode.value, "completeNode is empty");
|
||||
});
|
|
@ -0,0 +1,62 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// See Bug 812618.
|
||||
|
||||
const TEST_URI = `data:text/html;charset=utf-8,
|
||||
<head>
|
||||
<script>
|
||||
window.testBugA = "hello world";
|
||||
window.testBugB = "hello world 2";
|
||||
</script>
|
||||
</head>
|
||||
<body>bug 812618 - test completion inside text</body>`;
|
||||
|
||||
add_task(async function () {
|
||||
let { jsterm } = await openNewTabAndConsole(TEST_URI);
|
||||
info("web console opened");
|
||||
|
||||
const {
|
||||
autocompletePopup: popup,
|
||||
completeNode,
|
||||
inputNode,
|
||||
} = jsterm;
|
||||
|
||||
const onPopUpOpen = popup.once("popup-opened");
|
||||
|
||||
const dumpString = "dump(window.testBu)";
|
||||
jsterm.setInputValue(dumpString);
|
||||
inputNode.selectionStart = inputNode.selectionEnd = dumpString.indexOf(")");
|
||||
EventUtils.synthesizeKey("g", {});
|
||||
|
||||
await onPopUpOpen;
|
||||
|
||||
ok(popup.isOpen, "popup is open");
|
||||
is(popup.itemCount, 2, "popup.itemCount is correct");
|
||||
|
||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||
is(popup.selectedIndex, 0, "popup.selectedIndex is correct");
|
||||
ok(!completeNode.value, "completeNode.value is empty");
|
||||
|
||||
let items = popup.getItems().map(e => e.label);
|
||||
let expectedItems = ["testBugB", "testBugA"];
|
||||
is(items.join("-"), expectedItems.join("-"), "getItems returns the items we expect");
|
||||
|
||||
info("press Tab and wait for popup to hide");
|
||||
const onPopupClose = popup.once("popup-closed");
|
||||
EventUtils.synthesizeKey("VK_TAB", {});
|
||||
|
||||
await onPopupClose;
|
||||
|
||||
// At this point the completion suggestion should be accepted.
|
||||
ok(!popup.isOpen, "popup is not open");
|
||||
const expectedInput = "dump(window.testBugB)";
|
||||
is(jsterm.getInputValue(), expectedInput, "completion was successful after VK_TAB");
|
||||
is(inputNode.selectionStart, expectedInput.length - 1, "cursor location is correct");
|
||||
is(inputNode.selectionStart, inputNode.selectionEnd, "cursor location (confirmed)");
|
||||
ok(!completeNode.value, "completeNode is empty");
|
||||
});
|
|
@ -0,0 +1,109 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// See Bug 585991.
|
||||
|
||||
const TEST_URI = `data:text/html;charset=utf-8,
|
||||
<head>
|
||||
<script>
|
||||
/* Create a prototype-less object so popup does not contain native
|
||||
* Object prototype properties.
|
||||
*/
|
||||
window.foo = Object.create(null);
|
||||
Object.assign(window.foo, {
|
||||
item0: "value0",
|
||||
item1: "value1",
|
||||
item2: "value2",
|
||||
item3: "value3",
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>bug 585991 - autocomplete popup navigation and tab key usage test</body>`;
|
||||
|
||||
add_task(async function () {
|
||||
let { jsterm } = await openNewTabAndConsole(TEST_URI);
|
||||
info("web console opened");
|
||||
|
||||
const {
|
||||
autocompletePopup: popup,
|
||||
completeNode,
|
||||
inputNode,
|
||||
} = jsterm;
|
||||
|
||||
ok(!popup.isOpen, "popup is not open");
|
||||
|
||||
const onPopUpOpen = popup.once("popup-opened");
|
||||
jsterm.setInputValue("window.foo");
|
||||
|
||||
// Shows the popup
|
||||
EventUtils.synthesizeKey(".", {});
|
||||
await onPopUpOpen;
|
||||
|
||||
ok(popup.isOpen, "popup is open");
|
||||
|
||||
const popupItems = popup.getItems().map(e => e.label);
|
||||
const expectedPopupItems = [
|
||||
"item3",
|
||||
"item2",
|
||||
"item1",
|
||||
"item0",
|
||||
];
|
||||
|
||||
is(popup.itemCount, expectedPopupItems.length, "popup.itemCount is correct");
|
||||
is(popupItems.join("-"), expectedPopupItems.join("-"),
|
||||
"getItems returns the items we expect");
|
||||
is(popup.selectedIndex, expectedPopupItems.length - 1,
|
||||
"Index of the first item from bottom is selected.");
|
||||
|
||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||
|
||||
let prefix = jsterm.getInputValue().replace(/[\S]/g, " ");
|
||||
is(popup.selectedIndex, 0, "index 0 is selected");
|
||||
is(popup.selectedItem.label, "item3", "item3 is selected");
|
||||
is(completeNode.value, prefix + "item3", "completeNode.value holds item3");
|
||||
|
||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||
|
||||
is(popup.selectedIndex, 1, "index 1 is selected");
|
||||
is(popup.selectedItem.label, "item2", "item2 is selected");
|
||||
is(completeNode.value, prefix + "item2", "completeNode.value holds item2");
|
||||
|
||||
EventUtils.synthesizeKey("VK_UP", {});
|
||||
|
||||
is(popup.selectedIndex, 0, "index 0 is selected");
|
||||
is(popup.selectedItem.label, "item3", "item3 is selected");
|
||||
is(completeNode.value, prefix + "item3", "completeNode.value holds item3");
|
||||
|
||||
let currentSelectionIndex = popup.selectedIndex;
|
||||
|
||||
EventUtils.synthesizeKey("VK_PAGE_DOWN", {});
|
||||
|
||||
ok(popup.selectedIndex > currentSelectionIndex, "Index is greater after PGDN");
|
||||
|
||||
currentSelectionIndex = popup.selectedIndex;
|
||||
EventUtils.synthesizeKey("VK_PAGE_UP", {});
|
||||
|
||||
ok(popup.selectedIndex < currentSelectionIndex, "Index is less after Page UP");
|
||||
|
||||
EventUtils.synthesizeKey("VK_END", {});
|
||||
is(popup.selectedIndex, expectedPopupItems.length - 1, "index is last after End");
|
||||
|
||||
EventUtils.synthesizeKey("VK_HOME", {});
|
||||
is(popup.selectedIndex, 0, "index is first after Home");
|
||||
|
||||
info("press Tab and wait for popup to hide");
|
||||
const onPopupClose = popup.once("popup-closed");
|
||||
EventUtils.synthesizeKey("VK_TAB", {});
|
||||
|
||||
await onPopupClose;
|
||||
|
||||
// At this point the completion suggestion should be accepted.
|
||||
ok(!popup.isOpen, "popup is not open");
|
||||
is(jsterm.getInputValue(), "window.foo.item3",
|
||||
"completion was successful after VK_TAB");
|
||||
ok(!completeNode.value, "completeNode is empty");
|
||||
});
|
|
@ -0,0 +1,81 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// See Bug 585991.
|
||||
|
||||
const TEST_URI = `data:text/html;charset=utf-8,
|
||||
<head>
|
||||
<script>
|
||||
/* Create a prototype-less object so popup does not contain native
|
||||
* Object prototype properties.
|
||||
*/
|
||||
window.foobar = Object.create(null);
|
||||
Object.assign(window.foobar, {
|
||||
item0: "value0",
|
||||
item1: "value1",
|
||||
item2: "value2",
|
||||
item3: "value3",
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>bug 585991 - test pressing return with open popup</body>`;
|
||||
|
||||
// We should turn off auto-multiline editing during these tests
|
||||
const PREF_AUTO_MULTILINE = "devtools.webconsole.autoMultiline";
|
||||
|
||||
add_task(async function () {
|
||||
Services.prefs.setBoolPref(PREF_AUTO_MULTILINE, false);
|
||||
|
||||
let { jsterm } = await openNewTabAndConsole(TEST_URI);
|
||||
const {
|
||||
autocompletePopup: popup,
|
||||
completeNode,
|
||||
inputNode,
|
||||
} = jsterm;
|
||||
|
||||
let onPopUpOpen = popup.once("popup-opened");
|
||||
|
||||
info("wait for completion suggestions: window.foobar.");
|
||||
|
||||
jsterm.setInputValue("window.fooba");
|
||||
EventUtils.synthesizeKey("r", {});
|
||||
EventUtils.synthesizeKey(".", {});
|
||||
|
||||
await onPopUpOpen;
|
||||
|
||||
ok(popup.isOpen, "popup is open");
|
||||
|
||||
const expectedPopupItems = [
|
||||
"item3",
|
||||
"item2",
|
||||
"item1",
|
||||
"item0",
|
||||
];
|
||||
is(popup.itemCount, expectedPopupItems.length, "popup.itemCount is correct");
|
||||
is(popup.selectedIndex, expectedPopupItems.length - 1,
|
||||
"First index from bottom is selected");
|
||||
|
||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||
|
||||
is(popup.selectedIndex, 0, "index 0 is selected");
|
||||
is(popup.selectedItem.label, "item3", "item3 is selected");
|
||||
let prefix = jsterm.getInputValue().replace(/[\S]/g, " ");
|
||||
is(completeNode.value, prefix + "item3", "completeNode.value holds item3");
|
||||
|
||||
info("press Return to accept suggestion. wait for popup to hide");
|
||||
const onPopupClose = popup.once("popup-closed");
|
||||
EventUtils.synthesizeKey("VK_RETURN", {});
|
||||
|
||||
await onPopupClose;
|
||||
|
||||
ok(!popup.isOpen, "popup is not open after VK_RETURN");
|
||||
is(jsterm.getInputValue(), "window.foobar.item3",
|
||||
"completion was successful after VK_RETURN");
|
||||
ok(!completeNode.value, "completeNode is empty");
|
||||
|
||||
Services.prefs.clearUserPref(PREF_AUTO_MULTILINE);
|
||||
});
|
|
@ -0,0 +1,49 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// See Bug 873250.
|
||||
|
||||
const TEST_URI = `data:text/html;charset=utf-8,
|
||||
<head>
|
||||
<script>
|
||||
window.testBugA = "hello world";
|
||||
window.testBugB = "hello world 2";
|
||||
</script>
|
||||
</head>
|
||||
<body>bug 873250 - test pressing return with open popup, but no selection</body>`;
|
||||
|
||||
add_task(async function () {
|
||||
let { jsterm } = await openNewTabAndConsole(TEST_URI);
|
||||
const {
|
||||
autocompletePopup: popup,
|
||||
completeNode,
|
||||
inputNode,
|
||||
} = jsterm;
|
||||
|
||||
const onPopUpOpen = popup.once("popup-opened");
|
||||
|
||||
info("wait for popup to show");
|
||||
jsterm.setInputValue("window.testBu");
|
||||
EventUtils.synthesizeKey("g", {});
|
||||
|
||||
await onPopUpOpen;
|
||||
|
||||
ok(popup.isOpen, "popup is open");
|
||||
is(popup.itemCount, 2, "popup.itemCount is correct");
|
||||
isnot(popup.selectedIndex, -1, "popup.selectedIndex is correct");
|
||||
|
||||
info("press Return and wait for popup to hide");
|
||||
const onPopUpClose = popup.once("popup-closed");
|
||||
executeSoon(() => EventUtils.synthesizeKey("VK_RETURN", {}));
|
||||
await onPopUpClose;
|
||||
|
||||
ok(!popup.isOpen, "popup is not open after VK_RETURN");
|
||||
is(jsterm.getInputValue(), "", "inputNode is empty after VK_RETURN");
|
||||
is(completeNode.value, "", "completeNode is empty");
|
||||
is(jsterm.history[jsterm.history.length - 1], "window.testBug",
|
||||
"jsterm history is correct");
|
||||
});
|
|
@ -11,13 +11,10 @@
|
|||
const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
|
||||
"new-console-output/test/mochitest/test-jsterm-dollar.html";
|
||||
|
||||
add_task(function* () {
|
||||
let hud = yield openNewTabAndConsole(TEST_URI);
|
||||
yield test$(hud);
|
||||
yield test$$(hud);
|
||||
|
||||
// Clear history to not affect next tests.
|
||||
yield hud.jsterm.clearHistory();
|
||||
add_task(async function () {
|
||||
let hud = await openNewTabAndConsole(TEST_URI);
|
||||
await test$(hud);
|
||||
await test$$(hud);
|
||||
});
|
||||
|
||||
async function test$(hud) {
|
||||
|
|
|
@ -14,37 +14,34 @@ const TEST_URI = "data:text/html;charset=utf-8,Web Console test for " +
|
|||
"persisting history - bug 943306";
|
||||
const INPUT_HISTORY_COUNT = 10;
|
||||
|
||||
add_task(function* () {
|
||||
add_task(async function() {
|
||||
info("Setting custom input history pref to " + INPUT_HISTORY_COUNT);
|
||||
Services.prefs.setIntPref("devtools.webconsole.inputHistoryCount", INPUT_HISTORY_COUNT);
|
||||
|
||||
// First tab: run a bunch of commands and then make sure that you can
|
||||
// navigate through their history.
|
||||
let hud1 = yield openNewTabAndConsole(TEST_URI);
|
||||
|
||||
// Clearing history that might have been set in previous tests.
|
||||
yield hud1.jsterm.clearHistory();
|
||||
let hud1 = await openNewTabAndConsole(TEST_URI);
|
||||
|
||||
is(JSON.stringify(hud1.jsterm.history), "[]", "No history on first tab initially");
|
||||
yield populateInputHistory(hud1);
|
||||
await populateInputHistory(hud1);
|
||||
is(JSON.stringify(hud1.jsterm.history),
|
||||
'["0","1","2","3","4","5","6","7","8","9"]',
|
||||
"First tab has populated history");
|
||||
|
||||
// Second tab: Just make sure that you can navigate through the history
|
||||
// generated by the first tab.
|
||||
let hud2 = yield openNewTabAndConsole(TEST_URI);
|
||||
let hud2 = await openNewTabAndConsole(TEST_URI, false);
|
||||
is(JSON.stringify(hud2.jsterm.history),
|
||||
'["0","1","2","3","4","5","6","7","8","9"]',
|
||||
"Second tab has populated history");
|
||||
yield testNavigatingHistoryInUI(hud2);
|
||||
await testNavigatingHistoryInUI(hud2);
|
||||
is(JSON.stringify(hud2.jsterm.history),
|
||||
'["0","1","2","3","4","5","6","7","8","9",""]',
|
||||
"An empty entry has been added in the second tab due to history perusal");
|
||||
|
||||
// Third tab: Should have the same history as first tab, but if we run a
|
||||
// command, then the history of the first and second shouldn't be affected
|
||||
let hud3 = yield openNewTabAndConsole(TEST_URI);
|
||||
let hud3 = await openNewTabAndConsole(TEST_URI, false);
|
||||
is(JSON.stringify(hud3.jsterm.history),
|
||||
'["0","1","2","3","4","5","6","7","8","9"]',
|
||||
"Third tab has populated history");
|
||||
|
@ -67,15 +64,15 @@ add_task(function* () {
|
|||
|
||||
// Fourth tab: Should have the latest command from the third tab, followed
|
||||
// by the rest of the history from the first tab.
|
||||
let hud4 = yield openNewTabAndConsole(TEST_URI);
|
||||
let hud4 = await openNewTabAndConsole(TEST_URI, false);
|
||||
is(JSON.stringify(hud4.jsterm.history),
|
||||
'["1","2","3","4","5","6","7","8","9","\\"hello from third tab\\""]',
|
||||
"Fourth tab has most recent history");
|
||||
|
||||
yield hud4.jsterm.clearHistory();
|
||||
await hud4.jsterm.clearHistory();
|
||||
is(JSON.stringify(hud4.jsterm.history), "[]", "Clearing history for a tab works");
|
||||
|
||||
let hud5 = yield openNewTabAndConsole(TEST_URI);
|
||||
let hud5 = await openNewTabAndConsole(TEST_URI, false);
|
||||
is(JSON.stringify(hud5.jsterm.history), "[]",
|
||||
"Clearing history carries over to a new tab");
|
||||
|
||||
|
@ -87,14 +84,14 @@ add_task(function* () {
|
|||
* Populate the history by running the following commands:
|
||||
* [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
*/
|
||||
function* populateInputHistory(hud) {
|
||||
async function populateInputHistory(hud) {
|
||||
let jsterm = hud.jsterm;
|
||||
|
||||
for (let i = 0; i < INPUT_HISTORY_COUNT; i++) {
|
||||
// Set input value separately from execute so UP arrow accurately navigates
|
||||
// history.
|
||||
jsterm.setInputValue(i);
|
||||
yield jsterm.execute();
|
||||
await jsterm.execute();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
|
||||
"test/test-console.html";
|
||||
|
||||
add_task(function* () {
|
||||
let hud = yield openNewTabAndConsole(TEST_URI);
|
||||
add_task(async function () {
|
||||
let hud = await openNewTabAndConsole(TEST_URI);
|
||||
testCompletion(hud);
|
||||
});
|
||||
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
|
||||
const TEST_URI = "data:text/html,Testing jsterm with no input";
|
||||
|
||||
add_task(function* () {
|
||||
let hud = yield openNewTabAndConsole(TEST_URI);
|
||||
add_task(async function() {
|
||||
let hud = await openNewTabAndConsole(TEST_URI);
|
||||
testCompletion(hud);
|
||||
});
|
||||
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
|
||||
const TEST_URI = "data:text/html,Testing jsterm focus";
|
||||
|
||||
add_task(function* () {
|
||||
let hud = yield openNewTabAndConsole(TEST_URI);
|
||||
add_task(async function() {
|
||||
let hud = await openNewTabAndConsole(TEST_URI);
|
||||
let jsterm = hud.jsterm;
|
||||
let input = jsterm.inputNode;
|
||||
|
||||
|
|
|
@ -1,369 +0,0 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// See Bug 585991.
|
||||
|
||||
const TEST_URI = "data:text/html;charset=utf-8,<p>bug 585991 - autocomplete " +
|
||||
"popup keyboard usage test";
|
||||
|
||||
// We should turn off auto-multiline editing during these tests
|
||||
const PREF_AUTO_MULTILINE = "devtools.webconsole.autoMultiline";
|
||||
var HUD, popup, jsterm, inputNode, completeNode;
|
||||
|
||||
add_task(function* () {
|
||||
Services.prefs.setBoolPref(PREF_AUTO_MULTILINE, false);
|
||||
yield loadTab(TEST_URI);
|
||||
let hud = yield openConsole();
|
||||
|
||||
yield consoleOpened(hud);
|
||||
yield popupHideAfterTab();
|
||||
yield testReturnKey();
|
||||
yield dontShowArrayNumbers();
|
||||
yield testReturnWithNoSelection();
|
||||
yield popupHideAfterReturnWithNoSelection();
|
||||
yield testCompletionInText();
|
||||
yield popupHideAfterCompletionInText();
|
||||
|
||||
HUD = popup = jsterm = inputNode = completeNode = null;
|
||||
Services.prefs.setBoolPref(PREF_AUTO_MULTILINE, true);
|
||||
});
|
||||
|
||||
var consoleOpened = Task.async(function* (hud) {
|
||||
let deferred = defer();
|
||||
HUD = hud;
|
||||
info("web console opened");
|
||||
|
||||
jsterm = HUD.jsterm;
|
||||
|
||||
yield jsterm.execute("window.foobarBug585991={" +
|
||||
"'item0': 'value0'," +
|
||||
"'item1': 'value1'," +
|
||||
"'item2': 'value2'," +
|
||||
"'item3': 'value3'" +
|
||||
"}");
|
||||
yield jsterm.execute("window.testBug873250a = 'hello world';"
|
||||
+ "window.testBug873250b = 'hello world 2';");
|
||||
popup = jsterm.autocompletePopup;
|
||||
completeNode = jsterm.completeNode;
|
||||
inputNode = jsterm.inputNode;
|
||||
|
||||
ok(!popup.isOpen, "popup is not open");
|
||||
|
||||
popup.once("popup-opened", () => {
|
||||
ok(popup.isOpen, "popup is open");
|
||||
|
||||
// 4 values, and the following properties:
|
||||
// __defineGetter__ __defineSetter__ __lookupGetter__ __lookupSetter__
|
||||
// __proto__ hasOwnProperty isPrototypeOf propertyIsEnumerable
|
||||
// toLocaleString toString toSource unwatch valueOf watch constructor.
|
||||
is(popup.itemCount, 19, "popup.itemCount is correct");
|
||||
|
||||
let sameItems = popup.getItems().reverse().map(function (e) {
|
||||
return e.label;
|
||||
});
|
||||
|
||||
ok(sameItems.every(function (prop, index) {
|
||||
return [
|
||||
"__defineGetter__",
|
||||
"__defineSetter__",
|
||||
"__lookupGetter__",
|
||||
"__lookupSetter__",
|
||||
"__proto__",
|
||||
"constructor",
|
||||
"hasOwnProperty",
|
||||
"isPrototypeOf",
|
||||
"item0",
|
||||
"item1",
|
||||
"item2",
|
||||
"item3",
|
||||
"propertyIsEnumerable",
|
||||
"toLocaleString",
|
||||
"toSource",
|
||||
"toString",
|
||||
"unwatch",
|
||||
"valueOf",
|
||||
"watch",
|
||||
][index] === prop;
|
||||
}), "getItems returns the items we expect");
|
||||
|
||||
is(popup.selectedIndex, 18,
|
||||
"Index of the first item from bottom is selected.");
|
||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||
|
||||
let prefix = jsterm.getInputValue().replace(/[\S]/g, " ");
|
||||
|
||||
is(popup.selectedIndex, 0, "index 0 is selected");
|
||||
is(popup.selectedItem.label, "watch", "watch is selected");
|
||||
is(completeNode.value, prefix + "watch",
|
||||
"completeNode.value holds watch");
|
||||
|
||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||
|
||||
is(popup.selectedIndex, 1, "index 1 is selected");
|
||||
is(popup.selectedItem.label, "valueOf", "valueOf is selected");
|
||||
is(completeNode.value, prefix + "valueOf",
|
||||
"completeNode.value holds valueOf");
|
||||
|
||||
EventUtils.synthesizeKey("VK_UP", {});
|
||||
|
||||
is(popup.selectedIndex, 0, "index 0 is selected");
|
||||
is(popup.selectedItem.label, "watch", "watch is selected");
|
||||
is(completeNode.value, prefix + "watch",
|
||||
"completeNode.value holds watch");
|
||||
|
||||
let currentSelectionIndex = popup.selectedIndex;
|
||||
|
||||
EventUtils.synthesizeKey("VK_PAGE_DOWN", {});
|
||||
|
||||
ok(popup.selectedIndex > currentSelectionIndex,
|
||||
"Index is greater after PGDN");
|
||||
|
||||
currentSelectionIndex = popup.selectedIndex;
|
||||
EventUtils.synthesizeKey("VK_PAGE_UP", {});
|
||||
|
||||
ok(popup.selectedIndex < currentSelectionIndex,
|
||||
"Index is less after Page UP");
|
||||
|
||||
EventUtils.synthesizeKey("VK_END", {});
|
||||
is(popup.selectedIndex, 18, "index is last after End");
|
||||
|
||||
EventUtils.synthesizeKey("VK_HOME", {});
|
||||
is(popup.selectedIndex, 0, "index is first after Home");
|
||||
|
||||
info("press Tab and wait for popup to hide");
|
||||
popup.once("popup-closed", () => {
|
||||
deferred.resolve();
|
||||
});
|
||||
EventUtils.synthesizeKey("VK_TAB", {});
|
||||
});
|
||||
|
||||
jsterm.setInputValue("window.foobarBug585991");
|
||||
EventUtils.synthesizeKey(".", {});
|
||||
|
||||
return deferred.promise;
|
||||
});
|
||||
|
||||
function popupHideAfterTab() {
|
||||
let deferred = defer();
|
||||
|
||||
// At this point the completion suggestion should be accepted.
|
||||
ok(!popup.isOpen, "popup is not open");
|
||||
|
||||
is(jsterm.getInputValue(), "window.foobarBug585991.watch",
|
||||
"completion was successful after VK_TAB");
|
||||
|
||||
ok(!completeNode.value, "completeNode is empty");
|
||||
|
||||
popup.once("popup-opened", function onShown() {
|
||||
ok(popup.isOpen, "popup is open");
|
||||
|
||||
is(popup.itemCount, 19, "popup.itemCount is correct");
|
||||
|
||||
is(popup.selectedIndex, 18, "First index from bottom is selected");
|
||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||
|
||||
let prefix = jsterm.getInputValue().replace(/[\S]/g, " ");
|
||||
|
||||
is(popup.selectedIndex, 0, "index 0 is selected");
|
||||
is(popup.selectedItem.label, "watch", "watch is selected");
|
||||
is(completeNode.value, prefix + "watch",
|
||||
"completeNode.value holds watch");
|
||||
|
||||
popup.once("popup-closed", function onHidden() {
|
||||
ok(!popup.isOpen, "popup is not open after VK_ESCAPE");
|
||||
|
||||
is(jsterm.getInputValue(), "window.foobarBug585991.",
|
||||
"completion was cancelled");
|
||||
|
||||
ok(!completeNode.value, "completeNode is empty");
|
||||
|
||||
deferred.resolve();
|
||||
}, false);
|
||||
|
||||
info("press Escape to close the popup");
|
||||
executeSoon(function () {
|
||||
EventUtils.synthesizeKey("VK_ESCAPE", {});
|
||||
});
|
||||
}, false);
|
||||
|
||||
info("wait for completion: window.foobarBug585991.");
|
||||
executeSoon(function () {
|
||||
jsterm.setInputValue("window.foobarBug585991");
|
||||
EventUtils.synthesizeKey(".", {});
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function testReturnKey() {
|
||||
let deferred = defer();
|
||||
|
||||
popup.once("popup-opened", function onShown() {
|
||||
ok(popup.isOpen, "popup is open");
|
||||
|
||||
is(popup.itemCount, 19, "popup.itemCount is correct");
|
||||
|
||||
is(popup.selectedIndex, 18, "First index from bottom is selected");
|
||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||
|
||||
let prefix = jsterm.getInputValue().replace(/[\S]/g, " ");
|
||||
|
||||
is(popup.selectedIndex, 0, "index 0 is selected");
|
||||
is(popup.selectedItem.label, "watch", "watch is selected");
|
||||
is(completeNode.value, prefix + "watch",
|
||||
"completeNode.value holds watch");
|
||||
|
||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||
|
||||
is(popup.selectedIndex, 1, "index 1 is selected");
|
||||
is(popup.selectedItem.label, "valueOf", "valueOf is selected");
|
||||
is(completeNode.value, prefix + "valueOf",
|
||||
"completeNode.value holds valueOf");
|
||||
|
||||
popup.once("popup-closed", function onHidden() {
|
||||
ok(!popup.isOpen, "popup is not open after VK_RETURN");
|
||||
|
||||
is(jsterm.getInputValue(), "window.foobarBug585991.valueOf",
|
||||
"completion was successful after VK_RETURN");
|
||||
|
||||
ok(!completeNode.value, "completeNode is empty");
|
||||
|
||||
deferred.resolve();
|
||||
}, false);
|
||||
|
||||
info("press Return to accept suggestion. wait for popup to hide");
|
||||
|
||||
executeSoon(() => EventUtils.synthesizeKey("VK_RETURN", {}));
|
||||
}, false);
|
||||
|
||||
info("wait for completion suggestions: window.foobarBug585991.");
|
||||
|
||||
executeSoon(function () {
|
||||
jsterm.setInputValue("window.foobarBug58599");
|
||||
EventUtils.synthesizeKey("1", {});
|
||||
EventUtils.synthesizeKey(".", {});
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function* dontShowArrayNumbers() {
|
||||
let deferred = defer();
|
||||
|
||||
info("dontShowArrayNumbers");
|
||||
yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
|
||||
content.wrappedJSObject.foobarBug585991 = ["Sherlock Holmes"];
|
||||
});
|
||||
|
||||
jsterm = HUD.jsterm;
|
||||
popup = jsterm.autocompletePopup;
|
||||
|
||||
popup.once("popup-opened", function onShown() {
|
||||
let sameItems = popup.getItems().map(function (e) {
|
||||
return e.label;
|
||||
});
|
||||
ok(!sameItems.some(function (prop) {
|
||||
prop === "0";
|
||||
}), "Completing on an array doesn't show numbers.");
|
||||
|
||||
popup.once("popup-closed", function popupHidden() {
|
||||
deferred.resolve();
|
||||
}, false);
|
||||
|
||||
info("wait for popup to hide");
|
||||
executeSoon(() => EventUtils.synthesizeKey("VK_ESCAPE", {}));
|
||||
}, false);
|
||||
|
||||
info("wait for popup to show");
|
||||
executeSoon(() => {
|
||||
jsterm.setInputValue("window.foobarBug585991");
|
||||
EventUtils.synthesizeKey(".", {});
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function testReturnWithNoSelection() {
|
||||
let deferred = defer();
|
||||
|
||||
info("test pressing return with open popup, but no selection, see bug 873250");
|
||||
|
||||
popup.once("popup-opened", function onShown() {
|
||||
ok(popup.isOpen, "popup is open");
|
||||
is(popup.itemCount, 2, "popup.itemCount is correct");
|
||||
isnot(popup.selectedIndex, -1, "popup.selectedIndex is correct");
|
||||
|
||||
info("press Return and wait for popup to hide");
|
||||
popup.once("popup-closed", function popupHidden() {
|
||||
deferred.resolve();
|
||||
});
|
||||
executeSoon(() => EventUtils.synthesizeKey("VK_RETURN", {}));
|
||||
});
|
||||
|
||||
executeSoon(() => {
|
||||
info("wait for popup to show");
|
||||
jsterm.setInputValue("window.testBu");
|
||||
EventUtils.synthesizeKey("g", {});
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function popupHideAfterReturnWithNoSelection() {
|
||||
ok(!popup.isOpen, "popup is not open after VK_RETURN");
|
||||
|
||||
is(jsterm.getInputValue(), "", "inputNode is empty after VK_RETURN");
|
||||
is(completeNode.value, "", "completeNode is empty");
|
||||
is(jsterm.history[jsterm.history.length - 1], "window.testBug",
|
||||
"jsterm history is correct");
|
||||
|
||||
return promise.resolve();
|
||||
}
|
||||
|
||||
function testCompletionInText() {
|
||||
info("test that completion works inside text, see bug 812618");
|
||||
|
||||
let deferred = defer();
|
||||
|
||||
popup.once("popup-opened", function onShown() {
|
||||
ok(popup.isOpen, "popup is open");
|
||||
is(popup.itemCount, 2, "popup.itemCount is correct");
|
||||
|
||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||
is(popup.selectedIndex, 0, "popup.selectedIndex is correct");
|
||||
ok(!completeNode.value, "completeNode.value is empty");
|
||||
|
||||
let items = popup.getItems().reverse().map(e => e.label);
|
||||
let sameItems = items.every((prop, index) =>
|
||||
["testBug873250a", "testBug873250b"][index] === prop);
|
||||
ok(sameItems, "getItems returns the items we expect");
|
||||
|
||||
info("press Tab and wait for popup to hide");
|
||||
popup.once("popup-closed", function popupHidden() {
|
||||
deferred.resolve();
|
||||
});
|
||||
EventUtils.synthesizeKey("VK_TAB", {});
|
||||
});
|
||||
|
||||
jsterm.setInputValue("dump(window.testBu)");
|
||||
inputNode.selectionStart = inputNode.selectionEnd = 18;
|
||||
EventUtils.synthesizeKey("g", {});
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function popupHideAfterCompletionInText() {
|
||||
// At this point the completion suggestion should be accepted.
|
||||
ok(!popup.isOpen, "popup is not open");
|
||||
is(jsterm.getInputValue(), "dump(window.testBug873250b)",
|
||||
"completion was successful after VK_TAB");
|
||||
is(inputNode.selectionStart, 26, "cursor location is correct");
|
||||
is(inputNode.selectionStart, inputNode.selectionEnd,
|
||||
"cursor location (confirmed)");
|
||||
ok(!completeNode.value, "completeNode is empty");
|
||||
|
||||
return promise.resolve();
|
||||
}
|
|
@ -10,33 +10,33 @@
|
|||
const TEST_URI = "http://example.com/browser/devtools/client/webconsole/new-console-output/test/mochitest/test-batching.html";
|
||||
const { l10n } = require("devtools/client/webconsole/new-console-output/utils/messages");
|
||||
|
||||
add_task(function* () {
|
||||
let hud = yield openNewTabAndConsole(TEST_URI);
|
||||
add_task(async function() {
|
||||
let hud = await openNewTabAndConsole(TEST_URI);
|
||||
const messageNumber = 100;
|
||||
yield testSimpleBatchLogging(hud, messageNumber);
|
||||
yield testBatchLoggingAndClear(hud, messageNumber);
|
||||
await testSimpleBatchLogging(hud, messageNumber);
|
||||
await testBatchLoggingAndClear(hud, messageNumber);
|
||||
});
|
||||
|
||||
function* testSimpleBatchLogging(hud, messageNumber) {
|
||||
yield ContentTask.spawn(gBrowser.selectedBrowser, messageNumber,
|
||||
async function testSimpleBatchLogging(hud, messageNumber) {
|
||||
await ContentTask.spawn(gBrowser.selectedBrowser, messageNumber,
|
||||
function (numMessages) {
|
||||
content.wrappedJSObject.batchLog(numMessages);
|
||||
}
|
||||
);
|
||||
|
||||
for (let i = 0; i < messageNumber; i++) {
|
||||
let node = yield waitFor(() => findMessageAtIndex(hud, i, i));
|
||||
let node = await waitFor(() => findMessageAtIndex(hud, i, i));
|
||||
is(node.textContent, i.toString(), `message at index "${i}" is the expected one`);
|
||||
}
|
||||
}
|
||||
|
||||
function* testBatchLoggingAndClear(hud, messageNumber) {
|
||||
yield ContentTask.spawn(gBrowser.selectedBrowser, messageNumber,
|
||||
async function testBatchLoggingAndClear(hud, messageNumber) {
|
||||
await ContentTask.spawn(gBrowser.selectedBrowser, messageNumber,
|
||||
function (numMessages) {
|
||||
content.wrappedJSObject.batchLogAndClear(numMessages);
|
||||
}
|
||||
);
|
||||
yield waitFor(() => findMessage(hud, l10n.getStr("consoleCleared")));
|
||||
await waitFor(() => findMessage(hud, l10n.getStr("consoleCleared")));
|
||||
ok(true, "console cleared message is displayed");
|
||||
|
||||
// Passing the text argument as an empty string will returns all the message,
|
||||
|
|
|
@ -28,23 +28,23 @@ const TEST_URI = `data:text/html;charset=utf-8,<script>
|
|||
// Test the Copy menu item of the webconsole copies the expected clipboard text for
|
||||
// different log messages.
|
||||
|
||||
add_task(function* () {
|
||||
add_task(async function() {
|
||||
let observer = new PrefObserver("");
|
||||
let onPrefUpdated = observer.once(PREF_MESSAGE_TIMESTAMP, () => {});
|
||||
Services.prefs.setBoolPref(PREF_MESSAGE_TIMESTAMP, true);
|
||||
yield onPrefUpdated;
|
||||
await onPrefUpdated;
|
||||
|
||||
let hud = yield openNewTabAndConsole(TEST_URI);
|
||||
let hud = await openNewTabAndConsole(TEST_URI);
|
||||
hud.jsterm.clearOutput();
|
||||
|
||||
info("Call the log function defined in the test page");
|
||||
yield ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
|
||||
await ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
|
||||
content.wrappedJSObject.logStuff();
|
||||
});
|
||||
|
||||
info("Test copy menu item for the simple log");
|
||||
let message = yield waitFor(() => findMessage(hud, "simple text message"));
|
||||
let clipboardText = yield copyMessageContent(hud, message);
|
||||
let message = await waitFor(() => findMessage(hud, "simple text message"));
|
||||
let clipboardText = await copyMessageContent(hud, message);
|
||||
ok(true, "Clipboard text was found and saved");
|
||||
|
||||
info("Check copied text for simple log message");
|
||||
|
@ -55,8 +55,8 @@ add_task(function* () {
|
|||
"Log line has the right format:\n" + lines[0]);
|
||||
|
||||
info("Test copy menu item for the stack trace message");
|
||||
message = yield waitFor(() => findMessage(hud, "console.trace"));
|
||||
clipboardText = yield copyMessageContent(hud, message);
|
||||
message = await waitFor(() => findMessage(hud, "console.trace"));
|
||||
clipboardText = await copyMessageContent(hud, message);
|
||||
ok(true, "Clipboard text was found and saved");
|
||||
|
||||
info("Check copied text for stack trace message");
|
||||
|
@ -72,11 +72,11 @@ add_task(function* () {
|
|||
|
||||
onPrefUpdated = observer.once(PREF_MESSAGE_TIMESTAMP, () => {});
|
||||
Services.prefs.setBoolPref(PREF_MESSAGE_TIMESTAMP, false);
|
||||
yield onPrefUpdated;
|
||||
await onPrefUpdated;
|
||||
|
||||
info("Test copy menu item for the simple log");
|
||||
message = yield waitFor(() => findMessage(hud, "simple text message"));
|
||||
clipboardText = yield copyMessageContent(hud, message);
|
||||
message = await waitFor(() => findMessage(hud, "simple text message"));
|
||||
clipboardText = await copyMessageContent(hud, message);
|
||||
ok(true, "Clipboard text was found and saved");
|
||||
|
||||
info("Check copied text for simple log message");
|
||||
|
@ -87,8 +87,8 @@ add_task(function* () {
|
|||
"Log line has the right format:\n" + lines[0]);
|
||||
|
||||
info("Test copy menu item for the stack trace message");
|
||||
message = yield waitFor(() => findMessage(hud, "console.trace"));
|
||||
clipboardText = yield copyMessageContent(hud, message);
|
||||
message = await waitFor(() => findMessage(hud, "console.trace"));
|
||||
clipboardText = await copyMessageContent(hud, message);
|
||||
ok(true, "Clipboard text was found and saved");
|
||||
|
||||
info("Check copied text for stack trace message");
|
||||
|
@ -108,13 +108,13 @@ add_task(function* () {
|
|||
* Simple helper method to open the context menu on a given message, and click on the copy
|
||||
* menu item.
|
||||
*/
|
||||
function* copyMessageContent(hud, message) {
|
||||
let menuPopup = yield openContextMenu(hud, message);
|
||||
async function copyMessageContent(hud, message) {
|
||||
let menuPopup = await openContextMenu(hud, message);
|
||||
let copyMenuItem = menuPopup.querySelector("#console-menu-copy");
|
||||
ok(copyMenuItem, "copy menu item is enabled");
|
||||
|
||||
let clipboardText;
|
||||
yield waitForClipboardPromise(
|
||||
await waitForClipboardPromise(
|
||||
() => copyMenuItem.click(),
|
||||
data => {
|
||||
clipboardText = data;
|
||||
|
|
|
@ -12,48 +12,48 @@ const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
|
|||
"new-console-output/test/mochitest/test-console.html?_date=" + Date.now();
|
||||
const CONTEXT_MENU_ID = "#console-menu-copy-url";
|
||||
|
||||
add_task(function* () {
|
||||
add_task(async function() {
|
||||
// Enable net messages in the console for this test.
|
||||
yield pushPref("devtools.webconsole.filter.net", true);
|
||||
await pushPref("devtools.webconsole.filter.net", true);
|
||||
|
||||
let hud = yield openNewTabAndConsole(TEST_URI);
|
||||
let hud = await openNewTabAndConsole(TEST_URI);
|
||||
hud.jsterm.clearOutput();
|
||||
|
||||
info("Test Copy URL menu item for text log");
|
||||
|
||||
info("Logging a text message in the content window");
|
||||
yield ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
|
||||
await ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
|
||||
content.wrappedJSObject.console.log("simple text message");
|
||||
});
|
||||
let message = yield waitFor(() => findMessage(hud, "simple text message"));
|
||||
let message = await waitFor(() => findMessage(hud, "simple text message"));
|
||||
ok(message, "Text log found in the console");
|
||||
|
||||
info("Open and check the context menu for the logged text message");
|
||||
let menuPopup = yield openContextMenu(hud, message);
|
||||
let menuPopup = await openContextMenu(hud, message);
|
||||
let copyURLItem = menuPopup.querySelector(CONTEXT_MENU_ID);
|
||||
ok(!copyURLItem, "Copy URL menu item is hidden for a simple text message");
|
||||
|
||||
yield hideContextMenu(hud);
|
||||
await hideContextMenu(hud);
|
||||
hud.jsterm.clearOutput();
|
||||
|
||||
info("Test Copy URL menu item for network log");
|
||||
|
||||
info("Reload the content window to produce a network log");
|
||||
yield ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
|
||||
await ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
|
||||
content.wrappedJSObject.location.reload();
|
||||
});
|
||||
|
||||
message = yield waitFor(() => findMessage(hud, "test-console.html"));
|
||||
message = await waitFor(() => findMessage(hud, "test-console.html"));
|
||||
ok(message, "Network log found in the console");
|
||||
|
||||
info("Open and check the context menu for the logged network message");
|
||||
menuPopup = yield openContextMenu(hud, message);
|
||||
menuPopup = await openContextMenu(hud, message);
|
||||
copyURLItem = menuPopup.querySelector(CONTEXT_MENU_ID);
|
||||
ok(copyURLItem, "Copy url menu item is available in context menu");
|
||||
|
||||
info("Click on Copy URL menu item and wait for clipboard to be updated");
|
||||
yield waitForClipboardPromise(() => copyURLItem.click(), TEST_URI);
|
||||
await waitForClipboardPromise(() => copyURLItem.click(), TEST_URI);
|
||||
ok(true, "Expected text was copied to the clipboard.");
|
||||
|
||||
yield hideContextMenu(hud);
|
||||
await hideContextMenu(hud);
|
||||
});
|
||||
|
|
|
@ -22,22 +22,22 @@ const TEST_URI = `data:text/html;charset=utf-8,<script>
|
|||
</script>`;
|
||||
const copyObjectMenuItemId = "#console-menu-copy-object";
|
||||
|
||||
add_task(function* () {
|
||||
let hud = yield openNewTabAndConsole(TEST_URI);
|
||||
add_task(async function() {
|
||||
let hud = await openNewTabAndConsole(TEST_URI);
|
||||
|
||||
let [msgWithText, msgWithObj, msgNested] =
|
||||
yield waitFor(() => findMessages(hud, "foo"));
|
||||
await waitFor(() => findMessages(hud, "foo"));
|
||||
ok(msgWithText && msgWithObj && msgNested, "Three messages should have appeared");
|
||||
|
||||
let [groupMsgObj] = yield waitFor(() => findMessages(hud, "group", ".message-body"));
|
||||
let [collapsedGroupMsgObj] = yield waitFor(() =>
|
||||
let [groupMsgObj] = await waitFor(() => findMessages(hud, "group", ".message-body"));
|
||||
let [collapsedGroupMsgObj] = await waitFor(() =>
|
||||
findMessages(hud, "collapsed", ".message-body"));
|
||||
let [numberMsgObj] = yield waitFor(() => findMessages(hud, `532`, ".message-body"));
|
||||
let [trueMsgObj] = yield waitFor(() => findMessages(hud, `true`, ".message-body"));
|
||||
let [falseMsgObj] = yield waitFor(() => findMessages(hud, `false`, ".message-body"));
|
||||
let [undefinedMsgObj] = yield waitFor(() => findMessages(hud, `undefined`,
|
||||
let [numberMsgObj] = await waitFor(() => findMessages(hud, `532`, ".message-body"));
|
||||
let [trueMsgObj] = await waitFor(() => findMessages(hud, `true`, ".message-body"));
|
||||
let [falseMsgObj] = await waitFor(() => findMessages(hud, `false`, ".message-body"));
|
||||
let [undefinedMsgObj] = await waitFor(() => findMessages(hud, `undefined`,
|
||||
".message-body"));
|
||||
let [nullMsgObj] = yield waitFor(() => findMessages(hud, `null`, ".message-body"));
|
||||
let [nullMsgObj] = await waitFor(() => findMessages(hud, `null`, ".message-body"));
|
||||
ok(nullMsgObj, "One message with null value should have appeared");
|
||||
|
||||
let text = msgWithText.querySelector(".objectBox-string");
|
||||
|
@ -49,50 +49,50 @@ add_task(function* () {
|
|||
let topObjInMsg = msgNested.querySelector(".objectBox-array");
|
||||
let nestedObjInMsg = msgNested.querySelector(".objectBox-object");
|
||||
|
||||
let consoleMessages = yield waitFor(() => findMessages(hud, "console.log(\"foo\");",
|
||||
let consoleMessages = await waitFor(() => findMessages(hud, "console.log(\"foo\");",
|
||||
".message-location"));
|
||||
yield testCopyObjectMenuItemDisabled(hud, consoleMessages[0]);
|
||||
await testCopyObjectMenuItemDisabled(hud, consoleMessages[0]);
|
||||
|
||||
info(`Check "Copy object" is enabled for text only messages
|
||||
thus copying the text`);
|
||||
yield testCopyObject(hud, text, `foo`, false);
|
||||
await testCopyObject(hud, text, `foo`, false);
|
||||
|
||||
info(`Check "Copy object" is enabled for text in complex messages
|
||||
thus copying the text`);
|
||||
yield testCopyObject(hud, textInMsgWithObj, `foo`, false);
|
||||
await testCopyObject(hud, textInMsgWithObj, `foo`, false);
|
||||
|
||||
info("Check `Copy object` is enabled for objects in complex messages");
|
||||
yield testCopyObject(hud, objInMsgWithObj, `{"baz":1}`, true);
|
||||
await testCopyObject(hud, objInMsgWithObj, `{"baz":1}`, true);
|
||||
|
||||
info("Check `Copy object` is enabled for top object in nested messages");
|
||||
yield testCopyObject(hud, topObjInMsg, `["foo",{"baz":1},2]`, true);
|
||||
await testCopyObject(hud, topObjInMsg, `["foo",{"baz":1},2]`, true);
|
||||
|
||||
info("Check `Copy object` is enabled for nested object in nested messages");
|
||||
yield testCopyObject(hud, nestedObjInMsg, `{"baz":1}`, true);
|
||||
await testCopyObject(hud, nestedObjInMsg, `{"baz":1}`, true);
|
||||
|
||||
info("Check `Copy object` is disabled on `console.group('group')` messages");
|
||||
yield testCopyObjectMenuItemDisabled(hud, groupMsgObj);
|
||||
await testCopyObjectMenuItemDisabled(hud, groupMsgObj);
|
||||
|
||||
info(`Check "Copy object" is disabled in "console.groupCollapsed('collapsed')"
|
||||
messages`);
|
||||
yield testCopyObjectMenuItemDisabled(hud, collapsedGroupMsgObj);
|
||||
await testCopyObjectMenuItemDisabled(hud, collapsedGroupMsgObj);
|
||||
|
||||
// Check for primitive objects
|
||||
info("Check `Copy object` is enabled for numbers");
|
||||
yield testCopyObject(hud, numberMsgObj, `532`, false);
|
||||
await testCopyObject(hud, numberMsgObj, `532`, false);
|
||||
|
||||
info("Check `Copy object` is enabled for booleans");
|
||||
yield testCopyObject(hud, trueMsgObj, `true`, false);
|
||||
yield testCopyObject(hud, falseMsgObj, `false`, false);
|
||||
await testCopyObject(hud, trueMsgObj, `true`, false);
|
||||
await testCopyObject(hud, falseMsgObj, `false`, false);
|
||||
|
||||
info("Check `Copy object` is enabled for undefined and null");
|
||||
yield testCopyObject(hud, undefinedMsgObj, `undefined`, false);
|
||||
yield testCopyObject(hud, nullMsgObj, `null`, false);
|
||||
await testCopyObject(hud, undefinedMsgObj, `undefined`, false);
|
||||
await testCopyObject(hud, nullMsgObj, `null`, false);
|
||||
});
|
||||
|
||||
function* testCopyObject(hud, element, expectedMessage, objectInput) {
|
||||
async function testCopyObject(hud, element, expectedMessage, objectInput) {
|
||||
info("Check `Copy object` is enabled");
|
||||
let menuPopup = yield openContextMenu(hud, element);
|
||||
let menuPopup = await openContextMenu(hud, element);
|
||||
let copyObjectMenuItem = menuPopup.querySelector(copyObjectMenuItemId);
|
||||
ok(!copyObjectMenuItem.disabled,
|
||||
"`Copy object` is enabled for object in complex message");
|
||||
|
@ -103,19 +103,19 @@ function* testCopyObject(hud, element, expectedMessage, objectInput) {
|
|||
};
|
||||
|
||||
info("Click on `Copy object`");
|
||||
yield waitForClipboardPromise(() => copyObjectMenuItem.click(), validatorFn);
|
||||
await waitForClipboardPromise(() => copyObjectMenuItem.click(), validatorFn);
|
||||
|
||||
info("`Copy object` by using the access-key O");
|
||||
menuPopup = yield openContextMenu(hud, element);
|
||||
yield waitForClipboardPromise(() => synthesizeKeyShortcut("O"), validatorFn);
|
||||
menuPopup = await openContextMenu(hud, element);
|
||||
await waitForClipboardPromise(() => synthesizeKeyShortcut("O"), validatorFn);
|
||||
}
|
||||
|
||||
function* testCopyObjectMenuItemDisabled(hud, element) {
|
||||
let menuPopup = yield openContextMenu(hud, element);
|
||||
async function testCopyObjectMenuItemDisabled(hud, element) {
|
||||
let menuPopup = await openContextMenu(hud, element);
|
||||
let copyObjectMenuItem = menuPopup.querySelector(copyObjectMenuItemId);
|
||||
ok(copyObjectMenuItem.disabled, `"Copy object" is disabled for messages
|
||||
with no variables/objects`);
|
||||
yield hideContextMenu(hud);
|
||||
await hideContextMenu(hud);
|
||||
}
|
||||
|
||||
function prettyPrintMessage(message, isObject) {
|
||||
|
|
|
@ -11,41 +11,41 @@
|
|||
const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
|
||||
"new-console-output/test/mochitest/test-console.html";
|
||||
|
||||
add_task(function* () {
|
||||
add_task(async function() {
|
||||
// Enable net messages in the console for this test.
|
||||
yield pushPref("devtools.webconsole.filter.net", true);
|
||||
await pushPref("devtools.webconsole.filter.net", true);
|
||||
|
||||
let hud = yield openNewTabAndConsole(TEST_URI);
|
||||
let hud = await openNewTabAndConsole(TEST_URI);
|
||||
hud.jsterm.clearOutput();
|
||||
|
||||
info("Test Open URL menu item for text log");
|
||||
|
||||
info("Logging a text message in the content window");
|
||||
yield ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
|
||||
await ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
|
||||
content.wrappedJSObject.console.log("simple text message");
|
||||
});
|
||||
let message = yield waitFor(() => findMessage(hud, "simple text message"));
|
||||
let message = await waitFor(() => findMessage(hud, "simple text message"));
|
||||
ok(message, "Text log found in the console");
|
||||
|
||||
info("Open and check the context menu for the logged text message");
|
||||
let menuPopup = yield openContextMenu(hud, message);
|
||||
let menuPopup = await openContextMenu(hud, message);
|
||||
let openUrlItem = menuPopup.querySelector("#console-menu-open-url");
|
||||
ok(!openUrlItem, "Open URL menu item is not available");
|
||||
|
||||
yield hideContextMenu(hud);
|
||||
await hideContextMenu(hud);
|
||||
hud.jsterm.clearOutput();
|
||||
|
||||
info("Test Open URL menu item for network log");
|
||||
|
||||
info("Reload the content window to produce a network log");
|
||||
yield ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
|
||||
await ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
|
||||
content.wrappedJSObject.location.reload();
|
||||
});
|
||||
message = yield waitFor(() => findMessage(hud, "test-console.html"));
|
||||
message = await waitFor(() => findMessage(hud, "test-console.html"));
|
||||
ok(message, "Network log found in the console");
|
||||
|
||||
info("Open and check the context menu for the logged network message");
|
||||
menuPopup = yield openContextMenu(hud, message);
|
||||
menuPopup = await openContextMenu(hud, message);
|
||||
openUrlItem = menuPopup.querySelector("#console-menu-open-url");
|
||||
ok(openUrlItem, "Open URL menu item is available");
|
||||
|
||||
|
@ -53,8 +53,8 @@ add_task(function* () {
|
|||
let tabLoaded = listenToTabLoad();
|
||||
info("Click on Open URL menu item and wait for new tab to open");
|
||||
openUrlItem.click();
|
||||
yield hideContextMenu(hud);
|
||||
let newTab = yield tabLoaded;
|
||||
await hideContextMenu(hud);
|
||||
let newTab = await tabLoaded;
|
||||
let newTabHref = newTab.linkedBrowser._contentWindow.location.href;
|
||||
is(newTabHref, TEST_URI, "Tab was opened with the expected URL");
|
||||
|
||||
|
|
|
@ -15,11 +15,11 @@ const TEST_URI = `data:text/html;charset=utf-8,<script>
|
|||
console.log(["foo", window.bar, 2]);
|
||||
</script>`;
|
||||
|
||||
add_task(function* () {
|
||||
let hud = yield openNewTabAndConsole(TEST_URI);
|
||||
add_task(async function() {
|
||||
let hud = await openNewTabAndConsole(TEST_URI);
|
||||
|
||||
let [msgWithText, msgWithObj, msgNested] =
|
||||
yield waitFor(() => findMessages(hud, "foo"));
|
||||
await waitFor(() => findMessages(hud, "foo"));
|
||||
ok(msgWithText && msgWithObj && msgNested, "Three messages should have appeared");
|
||||
|
||||
let text = msgWithText.querySelector(".objectBox-string");
|
||||
|
@ -32,49 +32,49 @@ add_task(function* () {
|
|||
let nestedObjInMsg = msgNested.querySelector(".objectBox-object");
|
||||
|
||||
info("Check store as global variable is disabled for text only messages");
|
||||
let menuPopup = yield openContextMenu(hud, text);
|
||||
let menuPopup = await openContextMenu(hud, text);
|
||||
let storeMenuItem = menuPopup.querySelector("#console-menu-store");
|
||||
ok(storeMenuItem.disabled, "store as global variable is disabled for text message");
|
||||
yield hideContextMenu(hud);
|
||||
await hideContextMenu(hud);
|
||||
|
||||
info("Check store as global variable is disabled for text in complex messages");
|
||||
menuPopup = yield openContextMenu(hud, textInMsgWithObj);
|
||||
menuPopup = await openContextMenu(hud, textInMsgWithObj);
|
||||
storeMenuItem = menuPopup.querySelector("#console-menu-store");
|
||||
ok(storeMenuItem.disabled,
|
||||
"store as global variable is disabled for text in complex message");
|
||||
yield hideContextMenu(hud);
|
||||
await hideContextMenu(hud);
|
||||
|
||||
info("Check store as global variable is enabled for objects in complex messages");
|
||||
yield storeAsVariable(hud, objInMsgWithObj);
|
||||
await storeAsVariable(hud, objInMsgWithObj);
|
||||
|
||||
is(hud.jsterm.getInputValue(), "temp0", "Input was set");
|
||||
|
||||
let executedResult = yield hud.jsterm.execute();
|
||||
let executedResult = await hud.jsterm.execute();
|
||||
ok(executedResult.textContent.includes("{ baz: 1 }"),
|
||||
"Correct variable assigned into console");
|
||||
|
||||
info("Check store as global variable is enabled for top object in nested messages");
|
||||
yield storeAsVariable(hud, topObjInMsg);
|
||||
await storeAsVariable(hud, topObjInMsg);
|
||||
|
||||
is(hud.jsterm.getInputValue(), "temp1", "Input was set");
|
||||
|
||||
executedResult = yield hud.jsterm.execute();
|
||||
executedResult = await hud.jsterm.execute();
|
||||
ok(executedResult.textContent.includes(`[ "foo", {\u2026}, 2 ]`),
|
||||
"Correct variable assigned into console " + executedResult.textContent);
|
||||
|
||||
info("Check store as global variable is enabled for nested object in nested messages");
|
||||
yield storeAsVariable(hud, nestedObjInMsg);
|
||||
await storeAsVariable(hud, nestedObjInMsg);
|
||||
|
||||
is(hud.jsterm.getInputValue(), "temp2", "Input was set");
|
||||
|
||||
executedResult = yield hud.jsterm.execute();
|
||||
executedResult = await hud.jsterm.execute();
|
||||
ok(executedResult.textContent.includes("{ baz: 1 }"),
|
||||
"Correct variable assigned into console " + executedResult.textContent);
|
||||
});
|
||||
|
||||
function* storeAsVariable(hud, element) {
|
||||
async function storeAsVariable(hud, element) {
|
||||
info("Check store as global variable is enabled");
|
||||
let menuPopup = yield openContextMenu(hud, element);
|
||||
let menuPopup = await openContextMenu(hud, element);
|
||||
let storeMenuItem = menuPopup.querySelector("#console-menu-store");
|
||||
ok(!storeMenuItem.disabled,
|
||||
"store as global variable is enabled for object in complex message");
|
||||
|
@ -84,8 +84,8 @@ function* storeAsVariable(hud, element) {
|
|||
storeMenuItem.click();
|
||||
|
||||
info("Wait for console input to be updated with the temp variable");
|
||||
yield onceInputSet;
|
||||
await onceInputSet;
|
||||
|
||||
info("Wait for context menu to be hidden");
|
||||
yield hideContextMenu(hud);
|
||||
await hideContextMenu(hud);
|
||||
}
|
||||
|
|
|
@ -8,16 +8,16 @@
|
|||
"use strict";
|
||||
const { MESSAGE_LEVEL } = require("devtools/client/webconsole/new-console-output/constants");
|
||||
const TEST_URI = "http://example.com/browser/devtools/client/webconsole/new-console-output/test/mochitest/test-console-filters.html";
|
||||
add_task(function* () {
|
||||
let hud = yield openNewTabAndConsole(TEST_URI);
|
||||
add_task(async function () {
|
||||
let hud = await openNewTabAndConsole(TEST_URI);
|
||||
const outputNode = hud.ui.outputNode;
|
||||
const toolbar = yield waitFor(() => {
|
||||
const toolbar = await waitFor(() => {
|
||||
return outputNode.querySelector(".webconsole-filterbar-primary");
|
||||
});
|
||||
ok(toolbar, "Toolbar found");
|
||||
// Show the filter bar
|
||||
toolbar.querySelector(".devtools-filter-icon").click();
|
||||
const filterBar = yield waitFor(() => {
|
||||
const filterBar = await waitFor(() => {
|
||||
return outputNode.querySelector(".webconsole-filterbar-secondary");
|
||||
});
|
||||
ok(filterBar, "Filter bar is shown when filter icon is clicked.");
|
||||
|
@ -39,20 +39,22 @@ add_task(function* () {
|
|||
|
||||
// Check that messages are not shown when their filter is turned off.
|
||||
filterBar.querySelector(".error").click();
|
||||
yield waitFor(() => findMessages(hud, "").length == 4);
|
||||
await waitFor(() => findMessages(hud, "").length == 4);
|
||||
ok(true, "When a filter is turned off, its messages are not shown.");
|
||||
|
||||
// Check that the ui settings were persisted.
|
||||
yield closeTabAndToolbox();
|
||||
yield testFilterPersistence();
|
||||
await closeTabAndToolbox();
|
||||
await testFilterPersistence();
|
||||
});
|
||||
|
||||
function filterIsEnabled(button) {
|
||||
return button.classList.contains("checked");
|
||||
}
|
||||
function* testFilterPersistence() {
|
||||
let hud = yield openNewTabAndConsole(TEST_URI);
|
||||
|
||||
async function testFilterPersistence() {
|
||||
let hud = await openNewTabAndConsole(TEST_URI);
|
||||
const outputNode = hud.ui.outputNode;
|
||||
const filterBar = yield waitFor(() => {
|
||||
const filterBar = await waitFor(() => {
|
||||
return outputNode.querySelector(".webconsole-filterbar-secondary");
|
||||
});
|
||||
ok(filterBar, "Filter bar ui setting is persisted.");
|
||||
|
|
|
@ -9,10 +9,10 @@
|
|||
|
||||
const TEST_URI = "http://example.com/browser/devtools/client/webconsole/new-console-output/test/mochitest/test-console-filters.html";
|
||||
|
||||
add_task(function* () {
|
||||
let hud = yield openNewTabAndConsole(TEST_URI);
|
||||
add_task(async function() {
|
||||
let hud = await openNewTabAndConsole(TEST_URI);
|
||||
|
||||
let filterButtons = yield getFilterButtons(hud);
|
||||
let filterButtons = await getFilterButtons(hud);
|
||||
info("Disable all filters");
|
||||
filterButtons.forEach(filterButton => {
|
||||
if (filterIsEnabled(filterButton)) {
|
||||
|
@ -21,32 +21,33 @@ add_task(function* () {
|
|||
});
|
||||
|
||||
info("Close and re-open the console");
|
||||
yield closeTabAndToolbox();
|
||||
hud = yield openNewTabAndConsole(TEST_URI);
|
||||
await closeTabAndToolbox();
|
||||
hud = await openNewTabAndConsole(TEST_URI);
|
||||
|
||||
info("Check that all filters are disabled, and enable them");
|
||||
filterButtons = yield getFilterButtons(hud);
|
||||
filterButtons = await getFilterButtons(hud);
|
||||
filterButtons.forEach(filterButton => {
|
||||
ok(!filterIsEnabled(filterButton), "filter is disabled");
|
||||
filterButton.click();
|
||||
});
|
||||
|
||||
info("Close and re-open the console");
|
||||
yield closeTabAndToolbox();
|
||||
hud = yield openNewTabAndConsole(TEST_URI);
|
||||
await closeTabAndToolbox();
|
||||
hud = await openNewTabAndConsole(TEST_URI);
|
||||
|
||||
info("Check that all filters are enabled");
|
||||
filterButtons = yield getFilterButtons(hud);
|
||||
filterButtons = await getFilterButtons(hud);
|
||||
filterButtons.forEach(filterButton => {
|
||||
ok(filterIsEnabled(filterButton), "filter is enabled");
|
||||
});
|
||||
// Check that the ui settings were persisted.
|
||||
yield closeTabAndToolbox();
|
||||
await closeTabAndToolbox();
|
||||
});
|
||||
function* getFilterButtons(hud) {
|
||||
|
||||
async function getFilterButtons(hud) {
|
||||
const outputNode = hud.ui.outputNode;
|
||||
info("Wait for console toolbar to appear");
|
||||
const toolbar = yield waitFor(() => {
|
||||
const toolbar = await waitFor(() => {
|
||||
return outputNode.querySelector(".webconsole-filterbar-primary");
|
||||
});
|
||||
// Show the filter bar if it is hidden
|
||||
|
@ -55,7 +56,7 @@ function* getFilterButtons(hud) {
|
|||
}
|
||||
|
||||
info("Wait for console filterbar to appear");
|
||||
const filterBar = yield waitFor(() => {
|
||||
const filterBar = await waitFor(() => {
|
||||
return outputNode.querySelector(".webconsole-filterbar-secondary");
|
||||
});
|
||||
ok(filterBar, "Filter bar is shown when filter icon is clicked.");
|
||||
|
|
|
@ -13,8 +13,8 @@ const TEST_URI =
|
|||
console.log("console message 1");
|
||||
</script>`;
|
||||
|
||||
add_task(function* () {
|
||||
let hud = yield openNewTabAndConsole(TEST_URI);
|
||||
add_task(async function() {
|
||||
let hud = await openNewTabAndConsole(TEST_URI);
|
||||
|
||||
let inputNode = hud.jsterm.inputNode;
|
||||
info("Focus after console is opened");
|
||||
|
@ -24,19 +24,19 @@ add_task(function* () {
|
|||
ok(hasFocus(inputNode), "input node is focused after output is cleared");
|
||||
|
||||
info("Focus during message logging");
|
||||
ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
|
||||
ContentTask.spawn(gBrowser.selectedBrowser, {}, function() {
|
||||
content.wrappedJSObject.console.log("console message 2");
|
||||
});
|
||||
let msg = yield waitFor(() => findMessage(hud, "console message 2"));
|
||||
let msg = await waitFor(() => findMessage(hud, "console message 2"));
|
||||
ok(hasFocus(inputNode, "input node is focused, first time"));
|
||||
|
||||
info("Focus after clicking in the output area");
|
||||
yield waitForBlurredInput(hud);
|
||||
await waitForBlurredInput(hud);
|
||||
EventUtils.sendMouseEvent({type: "click"}, msg);
|
||||
ok(hasFocus(inputNode), "input node is focused, second time");
|
||||
|
||||
info("Setting a text selection and making sure a click does not re-focus");
|
||||
yield waitForBlurredInput(hud);
|
||||
await waitForBlurredInput(hud);
|
||||
let selection = hud.iframeWindow.getSelection();
|
||||
selection.selectAllChildren(msg.querySelector(".message-body"));
|
||||
EventUtils.sendMouseEvent({type: "click"}, msg);
|
||||
|
|
|
@ -16,11 +16,11 @@ const TEST_URI =
|
|||
</script>
|
||||
`;
|
||||
|
||||
add_task(function* () {
|
||||
let hud = yield openNewTabAndConsole(TEST_URI);
|
||||
add_task(async function () {
|
||||
let hud = await openNewTabAndConsole(TEST_URI);
|
||||
info("Web Console opened");
|
||||
const outputScroller = hud.ui.outputScroller;
|
||||
yield waitFor(() => findMessages(hud, "").length == 100);
|
||||
await waitFor(() => findMessages(hud, "").length == 100);
|
||||
let currentPosition = outputScroller.scrollTop;
|
||||
const bottom = currentPosition;
|
||||
hud.jsterm.inputNode.focus();
|
||||
|
@ -53,7 +53,7 @@ add_task(function* () {
|
|||
clearShortcut = WCUL10n.getStr("webconsole.clear.key");
|
||||
}
|
||||
synthesizeKeyShortcut(clearShortcut);
|
||||
yield waitFor(() => findMessages(hud, "").length == 0);
|
||||
await waitFor(() => findMessages(hud, "").length == 0);
|
||||
ok(hasFocus(hud.jsterm.inputNode), "jsterm input is focused");
|
||||
|
||||
// Focus filter
|
||||
|
|
|
@ -12,12 +12,12 @@ requestLongerTimeout(2);
|
|||
const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
|
||||
"new-console-output/test/mochitest/test-location-debugger-link.html";
|
||||
|
||||
add_task(function* () {
|
||||
add_task(async function() {
|
||||
// Force the new debugger UI, in case this gets uplifted with the old
|
||||
// debugger still turned on
|
||||
yield pushPref("devtools.debugger.new-debugger-frontend", true);
|
||||
yield pushPref("devtools.webconsole.filter.error", true);
|
||||
yield pushPref("devtools.webconsole.filter.log", true);
|
||||
await pushPref("devtools.debugger.new-debugger-frontend", true);
|
||||
await pushPref("devtools.webconsole.filter.error", true);
|
||||
await pushPref("devtools.webconsole.filter.log", true);
|
||||
|
||||
// On e10s, the exception thrown in test-location-debugger-link-errors.js
|
||||
// is triggered in child process and is ignored by test harness
|
||||
|
@ -25,18 +25,18 @@ add_task(function* () {
|
|||
expectUncaughtException();
|
||||
}
|
||||
|
||||
let hud = yield openNewTabAndConsole(TEST_URI);
|
||||
let hud = await openNewTabAndConsole(TEST_URI);
|
||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
let toolbox = gDevTools.getToolbox(target);
|
||||
|
||||
yield testOpenInDebugger(hud, toolbox, "document.bar");
|
||||
await testOpenInDebugger(hud, toolbox, "document.bar");
|
||||
|
||||
info("Selecting the console again");
|
||||
yield toolbox.selectTool("webconsole");
|
||||
yield testOpenInDebugger(hud, toolbox, "Blah Blah");
|
||||
await toolbox.selectTool("webconsole");
|
||||
await testOpenInDebugger(hud, toolbox, "Blah Blah");
|
||||
|
||||
// // check again the first node.
|
||||
info("Selecting the console again");
|
||||
yield toolbox.selectTool("webconsole");
|
||||
yield testOpenInDebugger(hud, toolbox, "document.bar");
|
||||
await toolbox.selectTool("webconsole");
|
||||
await testOpenInDebugger(hud, toolbox, "document.bar");
|
||||
});
|
||||
|
|
|
@ -7,22 +7,22 @@
|
|||
|
||||
const TEST_URI = "http://example.com/browser/devtools/client/webconsole/new-console-output/test/mochitest/test-location-styleeditor-link.html";
|
||||
|
||||
add_task(function* () {
|
||||
yield pushPref("devtools.webconsole.filter.css", true);
|
||||
let hud = yield openNewTabAndConsole(TEST_URI);
|
||||
add_task(async function () {
|
||||
await pushPref("devtools.webconsole.filter.css", true);
|
||||
let hud = await openNewTabAndConsole(TEST_URI);
|
||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
let toolbox = gDevTools.getToolbox(target);
|
||||
|
||||
yield testViewSource(hud, toolbox, "\u2018font-weight\u2019");
|
||||
await testViewSource(hud, toolbox, "\u2018font-weight\u2019");
|
||||
|
||||
info("Selecting the console again");
|
||||
yield toolbox.selectTool("webconsole");
|
||||
yield testViewSource(hud, toolbox, "\u2018color\u2019");
|
||||
await toolbox.selectTool("webconsole");
|
||||
await testViewSource(hud, toolbox, "\u2018color\u2019");
|
||||
});
|
||||
|
||||
function* testViewSource(hud, toolbox, text) {
|
||||
async function testViewSource(hud, toolbox, text) {
|
||||
info(`Testing message with text "${text}"`);
|
||||
let messageNode = yield waitFor(() => findMessage(hud, text));
|
||||
let messageNode = await waitFor(() => findMessage(hud, text));
|
||||
let frameLinkNode = messageNode.querySelector(".message-location .frame-link");
|
||||
ok(frameLinkNode, "The message does have a location link");
|
||||
|
||||
|
@ -35,10 +35,10 @@ function* testViewSource(hud, toolbox, text) {
|
|||
EventUtils.sendMouseEvent({ type: "click" },
|
||||
messageNode.querySelector(".frame-link-filename"));
|
||||
|
||||
let panel = yield onStyleEditorSelected;
|
||||
let panel = await onStyleEditorSelected;
|
||||
ok(true, "The style editor is selected when clicking on the location element");
|
||||
|
||||
yield onStyleEditorReady(panel);
|
||||
await onStyleEditorReady(panel);
|
||||
|
||||
info("style editor window focused");
|
||||
let href = frameLinkNode.getAttribute("data-url");
|
||||
|
@ -47,10 +47,10 @@ function* testViewSource(hud, toolbox, text) {
|
|||
|
||||
let editor = getEditorForHref(panel.UI, href);
|
||||
ok(editor, "found style editor for " + href);
|
||||
yield performLineCheck(panel.UI, editor, line - 1);
|
||||
await performLineCheck(panel.UI, editor, line - 1);
|
||||
}
|
||||
|
||||
function* onStyleEditorReady(panel) {
|
||||
async function onStyleEditorReady(panel) {
|
||||
let win = panel.panelWindow;
|
||||
ok(win, "Style Editor Window is defined");
|
||||
ok(panel.UI, "Style Editor UI is defined");
|
||||
|
@ -74,10 +74,10 @@ function getEditorForHref(styleEditorUI, href) {
|
|||
return foundEditor;
|
||||
}
|
||||
|
||||
function* performLineCheck(styleEditorUI, editor, line) {
|
||||
async function performLineCheck(styleEditorUI, editor, line) {
|
||||
info("wait for source editor to load");
|
||||
// Get out of the styleeditor-selected event loop.
|
||||
yield waitForTick();
|
||||
await waitForTick();
|
||||
|
||||
is(editor.sourceEditor.getCursor().line, line,
|
||||
"correct line is selected");
|
||||
|
|
|
@ -21,15 +21,15 @@ const HTML = `
|
|||
`;
|
||||
const TEST_URI = "data:text/html;charset=utf-8," + encodeURI(HTML);
|
||||
|
||||
add_task(function* () {
|
||||
const hud = yield openNewTabAndConsole(TEST_URI);
|
||||
add_task(async function() {
|
||||
const hud = await openNewTabAndConsole(TEST_URI);
|
||||
const toolbox = gDevTools.getToolbox(hud.target);
|
||||
|
||||
yield ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
|
||||
await ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
|
||||
content.wrappedJSObject.logNode("h1");
|
||||
});
|
||||
|
||||
let msg = yield waitFor(() => findMessage(hud, "<h1>"));
|
||||
let msg = await waitFor(() => findMessage(hud, "<h1>"));
|
||||
let node = msg.querySelector(".objectBox-node");
|
||||
ok(node !== null, "Node was logged as expected");
|
||||
const view = node.ownerDocument.defaultView;
|
||||
|
@ -38,7 +38,7 @@ add_task(function* () {
|
|||
let onNodeHighlight = toolbox.once("node-highlight");
|
||||
EventUtils.synthesizeMouseAtCenter(node, {type: "mousemove"}, view);
|
||||
|
||||
let nodeFront = yield onNodeHighlight;
|
||||
let nodeFront = await onNodeHighlight;
|
||||
is(nodeFront.displayName, "h1", "The correct node was highlighted");
|
||||
|
||||
info("Unhighlight the node by moving away from the node");
|
||||
|
@ -46,6 +46,6 @@ add_task(function* () {
|
|||
let btn = toolbox.doc.querySelector(".toolbox-dock-button");
|
||||
EventUtils.synthesizeMouseAtCenter(btn, {type: "mousemove"}, view);
|
||||
|
||||
yield onNodeUnhighlight;
|
||||
await onNodeUnhighlight;
|
||||
ok(true, "node-unhighlight event was fired when moving away from the node");
|
||||
});
|
||||
|
|
|
@ -11,13 +11,13 @@ const TEST_URI = "data:text/html;charset=utf-8,<p>Web Console test for " +
|
|||
let created = false;
|
||||
let destroyed = false;
|
||||
|
||||
add_task(function* () {
|
||||
add_task(async function() {
|
||||
setupObserver();
|
||||
yield openNewTabAndConsole(TEST_URI);
|
||||
yield waitFor(() => created);
|
||||
await openNewTabAndConsole(TEST_URI);
|
||||
await waitFor(() => created);
|
||||
|
||||
yield closeTabAndToolbox(gBrowser.selectedTab);
|
||||
yield waitFor(() => destroyed);
|
||||
await closeTabAndToolbox(gBrowser.selectedTab);
|
||||
await waitFor(() => destroyed);
|
||||
});
|
||||
|
||||
function setupObserver() {
|
||||
|
|
|
@ -27,18 +27,18 @@ const PAGE_URL = `data:text/html,
|
|||
|
||||
</html>`;
|
||||
|
||||
add_task(function* () {
|
||||
yield pushPref("devtools.source-map.client-service.enabled", true);
|
||||
yield pushPref("devtools.webconsole.filter.css", true);
|
||||
add_task(async function() {
|
||||
await pushPref("devtools.source-map.client-service.enabled", true);
|
||||
await pushPref("devtools.webconsole.filter.css", true);
|
||||
|
||||
const hud = yield openNewTabAndConsole(PAGE_URL);
|
||||
const hud = await openNewTabAndConsole(PAGE_URL);
|
||||
|
||||
info("Waiting for css warning");
|
||||
let node = yield waitFor(() => findMessage(hud, "octopus"));
|
||||
let node = await waitFor(() => findMessage(hud, "octopus"));
|
||||
ok(!!node, "css warning seen");
|
||||
|
||||
info("Waiting for source map to be applied");
|
||||
let found = yield waitFor(() => {
|
||||
let found = await waitFor(() => {
|
||||
let frameLinkNode = node.querySelector(".message-location .frame-link");
|
||||
let url = frameLinkNode.getAttribute("data-url");
|
||||
return url.includes("scss");
|
||||
|
|
|
@ -24,11 +24,11 @@ const PAGE_URL = `data:text/html,
|
|||
|
||||
</html>`;
|
||||
|
||||
add_task(function* () {
|
||||
yield pushPref("devtools.source-map.client-service.enabled", true);
|
||||
add_task(async function() {
|
||||
await pushPref("devtools.source-map.client-service.enabled", true);
|
||||
|
||||
const hud = yield openNewTabAndConsole(PAGE_URL);
|
||||
const hud = await openNewTabAndConsole(PAGE_URL);
|
||||
|
||||
const node = yield waitFor(() => findMessage(hud, "Source map error"));
|
||||
const node = await waitFor(() => findMessage(hud, "Source map error"));
|
||||
ok(node, "source map error is displayed in web console");
|
||||
});
|
||||
|
|
|
@ -24,17 +24,17 @@ const PAGE_URL = `data:text/html,
|
|||
|
||||
</html>`;
|
||||
|
||||
add_task(function* () {
|
||||
add_task(async function() {
|
||||
// Force the new debugger UI, in case this gets uplifted with the old
|
||||
// debugger still turned on
|
||||
yield pushPref("devtools.debugger.new-debugger-frontend", true);
|
||||
yield pushPref("devtools.source-map.client-service.enabled", true);
|
||||
await pushPref("devtools.debugger.new-debugger-frontend", true);
|
||||
await pushPref("devtools.source-map.client-service.enabled", true);
|
||||
|
||||
const hud = yield openNewTabAndConsole(PAGE_URL);
|
||||
const hud = await openNewTabAndConsole(PAGE_URL);
|
||||
const toolbox = hud.ui.newConsoleOutput.toolbox;
|
||||
|
||||
info("Finding \"here\" message and waiting for source map to be applied");
|
||||
yield waitFor(() => {
|
||||
await waitFor(() => {
|
||||
let node = findMessage(hud, "here");
|
||||
if (!node) {
|
||||
return false;
|
||||
|
@ -44,12 +44,12 @@ add_task(function* () {
|
|||
return url.includes("nosuchfile");
|
||||
});
|
||||
|
||||
yield testOpenInDebugger(hud, toolbox, "here");
|
||||
await testOpenInDebugger(hud, toolbox, "here");
|
||||
|
||||
info("Selecting the console again");
|
||||
yield toolbox.selectTool("webconsole");
|
||||
await toolbox.selectTool("webconsole");
|
||||
|
||||
const node = yield waitFor(() => findMessage(hud, "original source"));
|
||||
const node = await waitFor(() => findMessage(hud, "original source"));
|
||||
ok(node, "source map error is displayed in web console");
|
||||
|
||||
ok(!!node.querySelector(".learn-more-link"), "source map error has learn more link");
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
|
||||
"new-console-output/test/mochitest/test-stacktrace-location-debugger-link.html";
|
||||
|
||||
add_task(function* () {
|
||||
add_task(async function() {
|
||||
// Force the new debugger UI, in case this gets uplifted with the old
|
||||
// debugger still turned on
|
||||
Services.prefs.setBoolPref("devtools.debugger.new-debugger-frontend", true);
|
||||
|
@ -21,29 +21,29 @@ add_task(function* () {
|
|||
Services.prefs.clearUserPref("devtools.webconsole.filter.log");
|
||||
});
|
||||
|
||||
let hud = yield openNewTabAndConsole(TEST_URI);
|
||||
let hud = await openNewTabAndConsole(TEST_URI);
|
||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
let toolbox = gDevTools.getToolbox(target);
|
||||
|
||||
yield testOpenInDebugger(hud, toolbox, "console.trace()");
|
||||
await testOpenInDebugger(hud, toolbox, "console.trace()");
|
||||
});
|
||||
|
||||
function* testOpenInDebugger(hud, toolbox, text) {
|
||||
async function testOpenInDebugger(hud, toolbox, text) {
|
||||
info(`Testing message with text "${text}"`);
|
||||
let messageNode = yield waitFor(() => findMessage(hud, text));
|
||||
let messageNode = await waitFor(() => findMessage(hud, text));
|
||||
let frameLinksNode = messageNode.querySelectorAll(".stack-trace .frame-link");
|
||||
is(frameLinksNode.length, 3,
|
||||
"The message does have the expected number of frames in the stacktrace");
|
||||
|
||||
for (let frameLinkNode of frameLinksNode) {
|
||||
yield checkClickOnNode(hud, toolbox, frameLinkNode);
|
||||
await checkClickOnNode(hud, toolbox, frameLinkNode);
|
||||
|
||||
info("Selecting the console again");
|
||||
yield toolbox.selectTool("webconsole");
|
||||
await toolbox.selectTool("webconsole");
|
||||
}
|
||||
}
|
||||
|
||||
function* checkClickOnNode(hud, toolbox, frameLinkNode) {
|
||||
async function checkClickOnNode(hud, toolbox, frameLinkNode) {
|
||||
info("checking click on node location");
|
||||
|
||||
let onSourceInDebuggerOpened = once(hud.ui, "source-in-debugger-opened");
|
||||
|
@ -51,7 +51,7 @@ function* checkClickOnNode(hud, toolbox, frameLinkNode) {
|
|||
EventUtils.sendMouseEvent({ type: "click" },
|
||||
frameLinkNode.querySelector(".frame-link-source"));
|
||||
|
||||
yield onSourceInDebuggerOpened;
|
||||
await onSourceInDebuggerOpened;
|
||||
|
||||
let url = frameLinkNode.getAttribute("data-url");
|
||||
let dbg = toolbox.getPanel("jsdebugger");
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
|
||||
const TEST_URI = "http://example.com/browser/devtools/client/webconsole/new-console-output/test/mochitest/test-console.html";
|
||||
|
||||
add_task(function* () {
|
||||
let hud = yield openNewTabAndConsole(TEST_URI);
|
||||
add_task(async function() {
|
||||
let hud = await openNewTabAndConsole(TEST_URI);
|
||||
|
||||
info("console.log with a string argument");
|
||||
let receivedMessages = waitForMessages({
|
||||
|
@ -19,16 +19,16 @@ add_task(function* () {
|
|||
}],
|
||||
});
|
||||
|
||||
yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function () {
|
||||
await ContentTask.spawn(gBrowser.selectedBrowser, {}, function () {
|
||||
content.wrappedJSObject.stringLog();
|
||||
});
|
||||
|
||||
yield receivedMessages;
|
||||
await receivedMessages;
|
||||
|
||||
info("evaluating a string constant");
|
||||
let jsterm = hud.jsterm;
|
||||
yield jsterm.execute("\"string\\nconstant\"");
|
||||
let msg = yield waitFor(() => findMessage(hud, "constant"));
|
||||
await jsterm.execute("\"string\\nconstant\"");
|
||||
let msg = await waitFor(() => findMessage(hud, "constant"));
|
||||
let body = msg.querySelector(".message-body");
|
||||
// On the other hand, a string constant result should be quoted, but
|
||||
// newlines should be let through.
|
||||
|
|
|
@ -19,36 +19,36 @@ const TEST_URI = `data:text/html;charset=utf-8,
|
|||
</script>`;
|
||||
const PREF_MESSAGE_TIMESTAMP = "devtools.webconsole.timestampMessages";
|
||||
|
||||
add_task(function* () {
|
||||
let hud = yield openNewTabAndConsole(TEST_URI);
|
||||
add_task(async function() {
|
||||
let hud = await openNewTabAndConsole(TEST_URI);
|
||||
|
||||
info("Call the log function defined in the test page");
|
||||
yield ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
|
||||
await ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
|
||||
content.wrappedJSObject.logMessage();
|
||||
});
|
||||
|
||||
yield testPrefDefaults(hud);
|
||||
await testPrefDefaults(hud);
|
||||
|
||||
let observer = new PrefObserver("");
|
||||
let toolbox = gDevTools.getToolbox(hud.target);
|
||||
let optionsPanel = yield toolbox.selectTool("options");
|
||||
yield togglePref(optionsPanel, observer);
|
||||
let optionsPanel = await toolbox.selectTool("options");
|
||||
await togglePref(optionsPanel, observer);
|
||||
observer.destroy();
|
||||
|
||||
yield testChangedPref(hud);
|
||||
await testChangedPref(hud);
|
||||
|
||||
Services.prefs.clearUserPref(PREF_MESSAGE_TIMESTAMP);
|
||||
});
|
||||
|
||||
function* testPrefDefaults(hud) {
|
||||
async function testPrefDefaults(hud) {
|
||||
let prefValue = Services.prefs.getBoolPref(PREF_MESSAGE_TIMESTAMP);
|
||||
ok(!prefValue, "Messages should have no timestamp by default (pref check)");
|
||||
let message = yield waitFor(() => findMessage(hud, "simple text message"));
|
||||
let message = await waitFor(() => findMessage(hud, "simple text message"));
|
||||
is(message.querySelectorAll(".timestamp").length, 0,
|
||||
"Messages should have no timestamp by default (element check)");
|
||||
}
|
||||
|
||||
function* togglePref(panel, observer) {
|
||||
async function togglePref(panel, observer) {
|
||||
info("Options panel opened");
|
||||
|
||||
info("Changing pref");
|
||||
|
@ -56,13 +56,13 @@ function* togglePref(panel, observer) {
|
|||
let checkbox = panel.panelDoc.getElementById("webconsole-timestamp-messages");
|
||||
checkbox.click();
|
||||
|
||||
yield prefChanged;
|
||||
await prefChanged;
|
||||
}
|
||||
|
||||
function* testChangedPref(hud) {
|
||||
async function testChangedPref(hud) {
|
||||
let prefValue = Services.prefs.getBoolPref(PREF_MESSAGE_TIMESTAMP);
|
||||
ok(prefValue, "Messages should have timestamps (pref check)");
|
||||
let message = yield waitFor(() => findMessage(hud, "simple text message"));
|
||||
let message = await waitFor(() => findMessage(hud, "simple text message"));
|
||||
is(message.querySelectorAll(".timestamp").length, 1,
|
||||
"Messages should have timestamp (element check)");
|
||||
}
|
||||
|
|
|
@ -42,16 +42,24 @@ registerCleanupFunction(function* () {
|
|||
*
|
||||
* @param string url
|
||||
* The URL for the tab to be opened.
|
||||
* @param Boolean clearJstermHistory
|
||||
* true (default) if the jsterm history should be cleared.
|
||||
* @return Promise
|
||||
* Resolves when the tab has been added, loaded and the toolbox has been opened.
|
||||
* Resolves to the toolbox.
|
||||
*/
|
||||
var openNewTabAndConsole = Task.async(function* (url) {
|
||||
let toolbox = yield openNewTabAndToolbox(url, "webconsole");
|
||||
async function openNewTabAndConsole(url, clearJstermHistory = true) {
|
||||
let toolbox = await openNewTabAndToolbox(url, "webconsole");
|
||||
let hud = toolbox.getCurrentPanel().hud;
|
||||
hud.jsterm._lazyVariablesView = false;
|
||||
|
||||
if (clearJstermHistory) {
|
||||
// Clearing history that might have been set in previous tests.
|
||||
await hud.jsterm.clearHistory();
|
||||
}
|
||||
|
||||
return hud;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for messages in the web console output, resolving once they are received.
|
||||
|
@ -220,18 +228,18 @@ function waitForNodeMutation(node, observeConfig = {}) {
|
|||
* The text to search for. This should be contained in the
|
||||
* message. The searching is done with @see findMessage.
|
||||
*/
|
||||
function* testOpenInDebugger(hud, toolbox, text) {
|
||||
async function testOpenInDebugger(hud, toolbox, text) {
|
||||
info(`Finding message for open-in-debugger test; text is "${text}"`);
|
||||
let messageNode = yield waitFor(() => findMessage(hud, text));
|
||||
let messageNode = await waitFor(() => findMessage(hud, text));
|
||||
let frameLinkNode = messageNode.querySelector(".message-location .frame-link");
|
||||
ok(frameLinkNode, "The message does have a location link");
|
||||
yield checkClickOnNode(hud, toolbox, frameLinkNode);
|
||||
await checkClickOnNode(hud, toolbox, frameLinkNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for testOpenInDebugger.
|
||||
*/
|
||||
function* checkClickOnNode(hud, toolbox, frameLinkNode) {
|
||||
async function checkClickOnNode(hud, toolbox, frameLinkNode) {
|
||||
info("checking click on node location");
|
||||
|
||||
let url = frameLinkNode.getAttribute("data-url");
|
||||
|
@ -245,7 +253,7 @@ function* checkClickOnNode(hud, toolbox, frameLinkNode) {
|
|||
EventUtils.sendMouseEvent({ type: "click" },
|
||||
frameLinkNode.querySelector(".frame-link-filename"));
|
||||
|
||||
yield onSourceInDebuggerOpened;
|
||||
await onSourceInDebuggerOpened;
|
||||
|
||||
let dbg = toolbox.getPanel("jsdebugger");
|
||||
is(
|
||||
|
|
|
@ -322,7 +322,10 @@ waitForAllPaints(function() {
|
|||
var parentElement = addDiv(null,
|
||||
{ style: 'overflow-y: scroll; height: 100px;' });
|
||||
var div = addDiv(null,
|
||||
{ style: 'animation: background-color 100s; position: relative; top: 100px;' });
|
||||
{ style: 'animation: background-color 100s; ' +
|
||||
'position: relative; ' +
|
||||
'top: 60px;' }); // This element is in-view in the parent, but
|
||||
// out of view in the grandparent.
|
||||
grandParent.appendChild(parentElement);
|
||||
parentElement.appendChild(div);
|
||||
var animation = div.getAnimations()[0];
|
||||
|
|
|
@ -2019,21 +2019,21 @@ MediaFormatReader::DecodeDemuxedSamples(TrackType aTrack,
|
|||
decoder.mFlushed = false;
|
||||
decoder.mDecoder->Decode(aSample)
|
||||
->Then(mTaskQueue, __func__,
|
||||
[self, this, aTrack, &decoder]
|
||||
[self, aTrack, &decoder]
|
||||
(const MediaDataDecoder::DecodedData& aResults) {
|
||||
decoder.mDecodeRequest.Complete();
|
||||
NotifyNewOutput(aTrack, aResults);
|
||||
self->NotifyNewOutput(aTrack, aResults);
|
||||
|
||||
// When we recovered from a GPU crash and get the first decoded
|
||||
// frame, report the recovery time telemetry.
|
||||
if (aTrack == TrackType::kVideoTrack) {
|
||||
GPUProcessCrashTelemetryLogger::ReportTelemetry(
|
||||
mMediaDecoderOwnerID, decoder.mDecoder.get());
|
||||
self->mMediaDecoderOwnerID, decoder.mDecoder.get());
|
||||
}
|
||||
},
|
||||
[self, this, aTrack, &decoder](const MediaResult& aError) {
|
||||
[self, aTrack, &decoder](const MediaResult& aError) {
|
||||
decoder.mDecodeRequest.Complete();
|
||||
NotifyError(aTrack, aError);
|
||||
self->NotifyError(aTrack, aError);
|
||||
})
|
||||
->Track(decoder.mDecodeRequest);
|
||||
}
|
||||
|
@ -2208,21 +2208,21 @@ MediaFormatReader::DrainDecoder(TrackType aTrack)
|
|||
RefPtr<MediaFormatReader> self = this;
|
||||
decoder.mDecoder->Drain()
|
||||
->Then(mTaskQueue, __func__,
|
||||
[self, this, aTrack, &decoder]
|
||||
[self, aTrack, &decoder]
|
||||
(const MediaDataDecoder::DecodedData& aResults) {
|
||||
decoder.mDrainRequest.Complete();
|
||||
if (aResults.IsEmpty()) {
|
||||
decoder.mDrainState = DrainState::DrainCompleted;
|
||||
} else {
|
||||
NotifyNewOutput(aTrack, aResults);
|
||||
self->NotifyNewOutput(aTrack, aResults);
|
||||
// Let's see if we have any more data available to drain.
|
||||
decoder.mDrainState = DrainState::PartialDrainPending;
|
||||
}
|
||||
ScheduleUpdate(aTrack);
|
||||
self->ScheduleUpdate(aTrack);
|
||||
},
|
||||
[self, this, aTrack, &decoder](const MediaResult& aError) {
|
||||
[self, aTrack, &decoder](const MediaResult& aError) {
|
||||
decoder.mDrainRequest.Complete();
|
||||
NotifyError(aTrack, aError);
|
||||
self->NotifyError(aTrack, aError);
|
||||
})
|
||||
->Track(decoder.mDrainRequest);
|
||||
LOG("Requesting %s decoder to drain", TrackTypeToStr(aTrack));
|
||||
|
|
|
@ -56,7 +56,7 @@ RemoteVideoDecoder::Init()
|
|||
RefPtr<RemoteVideoDecoder> self = this;
|
||||
return InvokeAsync(VideoDecoderManagerChild::GetManagerAbstractThread(),
|
||||
__func__,
|
||||
[self, this]() { return mActor->Init(); })
|
||||
[self]() { return self->mActor->Init(); })
|
||||
->Then(VideoDecoderManagerChild::GetManagerAbstractThread(),
|
||||
__func__,
|
||||
[self, this](TrackType aTrack) {
|
||||
|
@ -79,7 +79,7 @@ RemoteVideoDecoder::Decode(MediaRawData* aSample)
|
|||
RefPtr<MediaRawData> sample = aSample;
|
||||
return InvokeAsync(VideoDecoderManagerChild::GetManagerAbstractThread(),
|
||||
__func__,
|
||||
[self, this, sample]() { return mActor->Decode(sample); });
|
||||
[self, sample]() { return self->mActor->Decode(sample); });
|
||||
}
|
||||
|
||||
RefPtr<MediaDataDecoder::FlushPromise>
|
||||
|
@ -87,7 +87,7 @@ RemoteVideoDecoder::Flush()
|
|||
{
|
||||
RefPtr<RemoteVideoDecoder> self = this;
|
||||
return InvokeAsync(VideoDecoderManagerChild::GetManagerAbstractThread(),
|
||||
__func__, [self, this]() { return mActor->Flush(); });
|
||||
__func__, [self]() { return self->mActor->Flush(); });
|
||||
}
|
||||
|
||||
RefPtr<MediaDataDecoder::DecodePromise>
|
||||
|
@ -95,7 +95,7 @@ RemoteVideoDecoder::Drain()
|
|||
{
|
||||
RefPtr<RemoteVideoDecoder> self = this;
|
||||
return InvokeAsync(VideoDecoderManagerChild::GetManagerAbstractThread(),
|
||||
__func__, [self, this]() { return mActor->Drain(); });
|
||||
__func__, [self]() { return self->mActor->Drain(); });
|
||||
}
|
||||
|
||||
RefPtr<ShutdownPromise>
|
||||
|
@ -103,8 +103,8 @@ RemoteVideoDecoder::Shutdown()
|
|||
{
|
||||
RefPtr<RemoteVideoDecoder> self = this;
|
||||
return InvokeAsync(VideoDecoderManagerChild::GetManagerAbstractThread(),
|
||||
__func__, [self, this]() {
|
||||
mActor->Shutdown();
|
||||
__func__, [self]() {
|
||||
self->mActor->Shutdown();
|
||||
return ShutdownPromise::CreateAndResolve(true, __func__);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -166,7 +166,7 @@ VideoDecoderParent::RecvInput(const MediaRawDataIPDL& aData)
|
|||
ProcessDecodedData(aResults);
|
||||
Unused << SendInputExhausted();
|
||||
},
|
||||
[self, this](const MediaResult& aError) { Error(aError); });
|
||||
[self](const MediaResult& aError) { self->Error(aError); });
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
|
@ -224,12 +224,12 @@ VideoDecoderParent::RecvFlush()
|
|||
RefPtr<VideoDecoderParent> self = this;
|
||||
mDecoder->Flush()->Then(
|
||||
mManagerTaskQueue, __func__,
|
||||
[self, this]() {
|
||||
if (!mDestroyed) {
|
||||
Unused << SendFlushComplete();
|
||||
[self]() {
|
||||
if (!self->mDestroyed) {
|
||||
Unused << self->SendFlushComplete();
|
||||
}
|
||||
},
|
||||
[self, this](const MediaResult& aError) { Error(aError); });
|
||||
[self](const MediaResult& aError) { self->Error(aError); });
|
||||
|
||||
return IPC_OK();
|
||||
}
|
||||
|
@ -248,7 +248,7 @@ VideoDecoderParent::RecvDrain()
|
|||
Unused << SendDrainComplete();
|
||||
}
|
||||
},
|
||||
[self, this](const MediaResult& aError) { Error(aError); });
|
||||
[self](const MediaResult& aError) { self->Error(aError); });
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
|
|
|
@ -13,10 +13,11 @@
|
|||
#include "mp4_demuxer/MoofParser.h"
|
||||
#include "mozilla/Logging.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/Result.h"
|
||||
#include "MediaData.h"
|
||||
#ifdef MOZ_FMP4
|
||||
#include "mp4_demuxer/AtomType.h"
|
||||
#include "mp4_demuxer/ByteReader.h"
|
||||
#include "mp4_demuxer/BufferReader.h"
|
||||
#include "mp4_demuxer/Stream.h"
|
||||
#endif
|
||||
#include "nsAutoPtr.h"
|
||||
|
@ -477,9 +478,15 @@ private:
|
|||
|
||||
AtomParser(const MediaContainerType& aType, const MediaByteBuffer* aData,
|
||||
StopAt aStop = StopAt::eEnd)
|
||||
{
|
||||
mValid = Init(aType, aData, aStop).isOk();
|
||||
}
|
||||
|
||||
Result<Ok, nsresult> Init(const MediaContainerType& aType, const MediaByteBuffer* aData,
|
||||
StopAt aStop)
|
||||
{
|
||||
const MediaContainerType mType(aType); // for logging macro.
|
||||
mp4_demuxer::ByteReader reader(aData);
|
||||
mp4_demuxer::BufferReader reader(aData);
|
||||
mp4_demuxer::AtomType initAtom("moov");
|
||||
mp4_demuxer::AtomType mediaAtom("moof");
|
||||
mp4_demuxer::AtomType dataAtom("mdat");
|
||||
|
@ -496,9 +503,12 @@ private:
|
|||
};
|
||||
|
||||
while (reader.Remaining() >= 8) {
|
||||
uint64_t size = reader.ReadU32();
|
||||
uint32_t tmp;
|
||||
MOZ_TRY_VAR(tmp, reader.ReadU32());
|
||||
uint64_t size = tmp;
|
||||
const uint8_t* typec = reader.Peek(4);
|
||||
mp4_demuxer::AtomType type(reader.ReadU32());
|
||||
MOZ_TRY_VAR(tmp, reader.ReadU32());
|
||||
mp4_demuxer::AtomType type(tmp);
|
||||
MSE_DEBUGV(AtomParser ,"Checking atom:'%c%c%c%c' @ %u",
|
||||
typec[0], typec[1], typec[2], typec[3],
|
||||
(uint32_t)reader.Offset() - 8);
|
||||
|
@ -510,8 +520,7 @@ private:
|
|||
mLastInvalidBox[2] = typec[2];
|
||||
mLastInvalidBox[3] = typec[3];
|
||||
mLastInvalidBox[4] = '\0';
|
||||
mValid = false;
|
||||
break;
|
||||
return Err(NS_ERROR_FAILURE);
|
||||
}
|
||||
if (mInitOffset.isNothing() &&
|
||||
mp4_demuxer::AtomType(type) == initAtom) {
|
||||
|
@ -527,10 +536,7 @@ private:
|
|||
}
|
||||
if (size == 1) {
|
||||
// 64 bits size.
|
||||
if (!reader.CanReadType<uint64_t>()) {
|
||||
break;
|
||||
}
|
||||
size = reader.ReadU64();
|
||||
MOZ_TRY_VAR(size, reader.ReadU64());
|
||||
} else if (size == 0) {
|
||||
// Atom extends to the end of the buffer, it can't have what we're
|
||||
// looking for.
|
||||
|
@ -556,6 +562,8 @@ private:
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
bool StartWithInitSegment() const
|
||||
|
@ -574,7 +582,7 @@ private:
|
|||
Maybe<size_t> mInitOffset;
|
||||
Maybe<size_t> mMediaOffset;
|
||||
Maybe<size_t> mDataOffset;
|
||||
bool mValid = true;
|
||||
bool mValid;
|
||||
char mLastInvalidBox[5];
|
||||
};
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ extern mozilla::LazyLogModule gMediaDemuxerLog;
|
|||
using mozilla::media::TimeUnit;
|
||||
using mozilla::media::TimeInterval;
|
||||
using mozilla::media::TimeIntervals;
|
||||
using mp4_demuxer::ByteReader;
|
||||
using mp4_demuxer::BufferReader;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
|
@ -538,9 +538,10 @@ MP3TrackDemuxer::FindNextFrame()
|
|||
break;
|
||||
}
|
||||
|
||||
ByteReader reader(buffer, read);
|
||||
BufferReader reader(buffer, read);
|
||||
uint32_t bytesToSkip = 0;
|
||||
foundFrame = mParser.Parse(&reader, &bytesToSkip);
|
||||
auto res = mParser.Parse(&reader, &bytesToSkip);
|
||||
foundFrame = res.isOk() ? res.unwrap() : false;
|
||||
frameHeaderOffset =
|
||||
mOffset + reader.Offset() - FrameParser::FrameHeader::SIZE;
|
||||
|
||||
|
@ -638,7 +639,7 @@ MP3TrackDemuxer::GetNextFrame(const MediaByteRange& aRange)
|
|||
|
||||
if (mNumParsedFrames == 1) {
|
||||
// First frame parsed, let's read VBR info if available.
|
||||
ByteReader reader(frame->Data(), frame->Size());
|
||||
BufferReader reader(frame->Data(), frame->Size());
|
||||
mParser.ParseVBRHeader(&reader);
|
||||
mFirstFrameOffset = frame->mOffset;
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/EndianUtils.h"
|
||||
#include "mozilla/Pair.h"
|
||||
#include "mozilla/ResultExtensions.h"
|
||||
#include "VideoUtils.h"
|
||||
|
||||
extern mozilla::LazyLogModule gMediaDemuxerLog;
|
||||
|
@ -19,7 +21,7 @@ extern mozilla::LazyLogModule gMediaDemuxerLog;
|
|||
#define MP3LOGV(msg, ...) \
|
||||
MOZ_LOG(gMediaDemuxerLog, LogLevel::Verbose, ("MP3Demuxer " msg, ##__VA_ARGS__))
|
||||
|
||||
using mp4_demuxer::ByteReader;
|
||||
using mp4_demuxer::BufferReader;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
|
@ -93,8 +95,8 @@ FrameParser::VBRInfo() const
|
|||
return mVBRHeader;
|
||||
}
|
||||
|
||||
bool
|
||||
FrameParser::Parse(ByteReader* aReader, uint32_t* aBytesToSkip)
|
||||
Result<bool, nsresult>
|
||||
FrameParser::Parse(BufferReader* aReader, uint32_t* aBytesToSkip)
|
||||
{
|
||||
MOZ_ASSERT(aReader && aBytesToSkip);
|
||||
*aBytesToSkip = 0;
|
||||
|
@ -104,8 +106,9 @@ FrameParser::Parse(ByteReader* aReader, uint32_t* aBytesToSkip)
|
|||
// ID3v1 tags may only be at file end.
|
||||
// TODO: should we try to read ID3 tags at end of file/mid-stream, too?
|
||||
const size_t prevReaderOffset = aReader->Offset();
|
||||
const uint32_t tagSize = mID3Parser.Parse(aReader);
|
||||
if (tagSize) {
|
||||
uint32_t tagSize;
|
||||
MOZ_TRY_VAR(tagSize, mID3Parser.Parse(aReader));
|
||||
if (!!tagSize) {
|
||||
// ID3 tag found, skip past it.
|
||||
const uint32_t skipSize = tagSize - ID3Parser::ID3Header::SIZE;
|
||||
|
||||
|
@ -128,7 +131,9 @@ FrameParser::Parse(ByteReader* aReader, uint32_t* aBytesToSkip)
|
|||
}
|
||||
}
|
||||
|
||||
while (aReader->CanRead8() && !mFrame.ParseNext(aReader->ReadU8())) { }
|
||||
for (auto res = aReader->ReadU8();
|
||||
res.isOk() && !mFrame.ParseNext(res.unwrap()); res = aReader->ReadU8())
|
||||
{}
|
||||
|
||||
if (mFrame.Length()) {
|
||||
// MP3 frame found.
|
||||
|
@ -438,8 +443,8 @@ FrameParser::VBRHeader::Offset(float aDurationFac) const
|
|||
return offset;
|
||||
}
|
||||
|
||||
bool
|
||||
FrameParser::VBRHeader::ParseXing(ByteReader* aReader)
|
||||
Result<bool, nsresult>
|
||||
FrameParser::VBRHeader::ParseXing(BufferReader* aReader)
|
||||
{
|
||||
static const uint32_t XING_TAG = BigEndian::readUint32("Xing");
|
||||
static const uint32_t INFO_TAG = BigEndian::readUint32("Info");
|
||||
|
@ -456,25 +461,28 @@ FrameParser::VBRHeader::ParseXing(ByteReader* aReader)
|
|||
const size_t prevReaderOffset = aReader->Offset();
|
||||
|
||||
// We have to search for the Xing header as its position can change.
|
||||
while (aReader->CanRead32() &&
|
||||
aReader->PeekU32() != XING_TAG && aReader->PeekU32() != INFO_TAG) {
|
||||
for (auto res = aReader->PeekU32();
|
||||
res.isOk() && res.unwrap() != XING_TAG && res.unwrap() != INFO_TAG;) {
|
||||
aReader->Read(1);
|
||||
res = aReader->PeekU32();
|
||||
}
|
||||
|
||||
if (aReader->CanRead32()) {
|
||||
// Skip across the VBR header ID tag.
|
||||
aReader->ReadU32();
|
||||
mType = XING;
|
||||
// Skip across the VBR header ID tag.
|
||||
MOZ_TRY(aReader->ReadU32());
|
||||
mType = XING;
|
||||
|
||||
uint32_t flags;
|
||||
MOZ_TRY_VAR(flags, aReader->ReadU32());
|
||||
|
||||
if (flags & NUM_FRAMES) {
|
||||
uint32_t frames;
|
||||
MOZ_TRY_VAR(frames, aReader->ReadU32());
|
||||
mNumAudioFrames = Some(frames);
|
||||
}
|
||||
uint32_t flags = 0;
|
||||
if (aReader->CanRead32()) {
|
||||
flags = aReader->ReadU32();
|
||||
}
|
||||
if (flags & NUM_FRAMES && aReader->CanRead32()) {
|
||||
mNumAudioFrames = Some(aReader->ReadU32());
|
||||
}
|
||||
if (flags & NUM_BYTES && aReader->CanRead32()) {
|
||||
mNumBytes = Some(aReader->ReadU32());
|
||||
if (flags & NUM_BYTES) {
|
||||
uint32_t bytes;
|
||||
MOZ_TRY_VAR(bytes, aReader->ReadU32());
|
||||
mNumBytes = Some(bytes);
|
||||
}
|
||||
if (flags & TOC && aReader->Remaining() >= vbr_header::TOC_SIZE) {
|
||||
if (!mNumBytes) {
|
||||
|
@ -483,21 +491,25 @@ FrameParser::VBRHeader::ParseXing(ByteReader* aReader)
|
|||
} else {
|
||||
mTOC.clear();
|
||||
mTOC.reserve(vbr_header::TOC_SIZE);
|
||||
uint8_t data;
|
||||
for (size_t i = 0; i < vbr_header::TOC_SIZE; ++i) {
|
||||
mTOC.push_back(1.0f / 256.0f * aReader->ReadU8() * mNumBytes.value());
|
||||
MOZ_TRY_VAR(data, aReader->ReadU8());
|
||||
mTOC.push_back(1.0f / 256.0f * data * mNumBytes.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (flags & VBR_SCALE && aReader->CanRead32()) {
|
||||
mScale = Some(aReader->ReadU32());
|
||||
if (flags & VBR_SCALE) {
|
||||
uint32_t scale;
|
||||
MOZ_TRY_VAR(scale, aReader->ReadU32());
|
||||
mScale = Some(scale);
|
||||
}
|
||||
|
||||
aReader->Seek(prevReaderOffset);
|
||||
return mType == XING;
|
||||
}
|
||||
|
||||
bool
|
||||
FrameParser::VBRHeader::ParseVBRI(ByteReader* aReader)
|
||||
Result<bool, nsresult>
|
||||
FrameParser::VBRHeader::ParseVBRI(BufferReader* aReader)
|
||||
{
|
||||
static const uint32_t TAG = BigEndian::readUint32("VBRI");
|
||||
static const uint32_t OFFSET = 32 + FrameParser::FrameHeader::SIZE;
|
||||
|
@ -508,15 +520,21 @@ FrameParser::VBRHeader::ParseVBRI(ByteReader* aReader)
|
|||
// ParseVBRI assumes that the ByteReader offset points to the beginning of a
|
||||
// frame, therefore as a simple check, we look for the presence of a frame
|
||||
// sync at that position.
|
||||
MOZ_ASSERT((aReader->PeekU16() & 0xFFE0) == 0xFFE0);
|
||||
auto sync = aReader->PeekU16();
|
||||
if (sync.isOk()) { // To avoid compiler complains 'set but unused'.
|
||||
MOZ_ASSERT((sync.unwrap() & 0xFFE0) == 0xFFE0);
|
||||
}
|
||||
const size_t prevReaderOffset = aReader->Offset();
|
||||
|
||||
// VBRI have a fixed relative position, so let's check for it there.
|
||||
if (aReader->Remaining() > MIN_FRAME_SIZE) {
|
||||
aReader->Seek(prevReaderOffset + OFFSET);
|
||||
if (aReader->ReadU32() == TAG) {
|
||||
uint32_t tag, frames;
|
||||
MOZ_TRY_VAR(tag, aReader->ReadU32());
|
||||
if (tag == TAG) {
|
||||
aReader->Seek(prevReaderOffset + FRAME_COUNT_OFFSET);
|
||||
mNumAudioFrames = Some(aReader->ReadU32());
|
||||
MOZ_TRY_VAR(frames, aReader->ReadU32());
|
||||
mNumAudioFrames = Some(frames);
|
||||
mType = VBRI;
|
||||
aReader->Seek(prevReaderOffset);
|
||||
return true;
|
||||
|
@ -527,9 +545,11 @@ FrameParser::VBRHeader::ParseVBRI(ByteReader* aReader)
|
|||
}
|
||||
|
||||
bool
|
||||
FrameParser::VBRHeader::Parse(ByteReader* aReader)
|
||||
FrameParser::VBRHeader::Parse(BufferReader* aReader)
|
||||
{
|
||||
const bool rv = ParseVBRI(aReader) || ParseXing(aReader);
|
||||
auto res = MakePair(ParseVBRI(aReader), ParseXing(aReader));
|
||||
const bool rv = (res.first().isOk() && res.first().unwrap()) ||
|
||||
(res.second().isOk() && res.second().unwrap());
|
||||
if (rv) {
|
||||
MP3LOG("VBRHeader::Parse found valid VBR/CBR header: type=%s"
|
||||
" NumAudioFrames=%u NumBytes=%u Scale=%u TOC-size=%zu",
|
||||
|
@ -574,7 +594,7 @@ FrameParser::Frame::Header() const
|
|||
}
|
||||
|
||||
bool
|
||||
FrameParser::ParseVBRHeader(ByteReader* aReader)
|
||||
FrameParser::ParseVBRHeader(BufferReader* aReader)
|
||||
{
|
||||
return mVBRHeader.Parse(aReader);
|
||||
}
|
||||
|
@ -599,12 +619,14 @@ static const uint8_t MIN_MAJOR_VER = 2;
|
|||
static const uint8_t MAX_MAJOR_VER = 4;
|
||||
} // namespace id3_header
|
||||
|
||||
uint32_t
|
||||
ID3Parser::Parse(ByteReader* aReader)
|
||||
Result<uint32_t, nsresult>
|
||||
ID3Parser::Parse(BufferReader* aReader)
|
||||
{
|
||||
MOZ_ASSERT(aReader);
|
||||
|
||||
while (aReader->CanRead8() && !mHeader.ParseNext(aReader->ReadU8())) { }
|
||||
for (auto res = aReader->ReadU8();
|
||||
res.isOk() && !mHeader.ParseNext(res.unwrap()); res = aReader->ReadU8())
|
||||
{}
|
||||
|
||||
return mHeader.TotalTagSize();
|
||||
}
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
#include <vector>
|
||||
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mp4_demuxer/ByteReader.h"
|
||||
#include "mozilla/Result.h"
|
||||
#include "mp4_demuxer/BufferReader.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
|
@ -81,9 +82,9 @@ public:
|
|||
// Returns the parsed ID3 header. Note: check for validity.
|
||||
const ID3Header& Header() const;
|
||||
|
||||
// Parses contents of given ByteReader for a valid ID3v2 header.
|
||||
// Parses contents of given BufferReader for a valid ID3v2 header.
|
||||
// Returns the total ID3v2 tag size if successful and zero otherwise.
|
||||
uint32_t Parse(mp4_demuxer::ByteReader* aReader);
|
||||
Result<uint32_t, nsresult> Parse(mp4_demuxer::BufferReader* aReader);
|
||||
|
||||
// Resets the state to allow for a new parsing session.
|
||||
void Reset();
|
||||
|
@ -227,20 +228,20 @@ public:
|
|||
// The offset of the passed ByteReader needs to point to an MPEG frame
|
||||
// begin, as a VBRI-style header is searched at a fixed offset relative to
|
||||
// frame begin. Returns whether a valid VBR header was found in the range.
|
||||
bool Parse(mp4_demuxer::ByteReader* aReader);
|
||||
bool Parse(mp4_demuxer::BufferReader* aReader);
|
||||
|
||||
private:
|
||||
// Parses contents of given ByteReader for a valid Xing header.
|
||||
// The initial ByteReader offset will be preserved.
|
||||
// Returns whether a valid Xing header was found in the range.
|
||||
bool ParseXing(mp4_demuxer::ByteReader* aReader);
|
||||
Result<bool, nsresult> ParseXing(mp4_demuxer::BufferReader* aReader);
|
||||
|
||||
// Parses contents of given ByteReader for a valid VBRI header.
|
||||
// The initial ByteReader offset will be preserved. It also needs to point
|
||||
// to the beginning of a valid MPEG frame, as VBRI headers are searched
|
||||
// at a fixed offset relative to frame begin.
|
||||
// Returns whether a valid VBRI header was found in the range.
|
||||
bool ParseVBRI(mp4_demuxer::ByteReader* aReader);
|
||||
Result<bool, nsresult> ParseVBRI(mp4_demuxer::BufferReader* aReader);
|
||||
|
||||
// The total number of frames expected as parsed from a VBR header.
|
||||
Maybe<uint32_t> mNumAudioFrames;
|
||||
|
@ -311,17 +312,17 @@ public:
|
|||
// - resets ID3Header if no valid header was parsed yet
|
||||
void EndFrameSession();
|
||||
|
||||
// Parses contents of given ByteReader for a valid frame header and returns
|
||||
// Parses contents of given BufferReader for a valid frame header and returns
|
||||
// true if one was found. After returning, the variable passed to
|
||||
// 'aBytesToSkip' holds the amount of bytes to be skipped (if any) in order to
|
||||
// jump across a large ID3v2 tag spanning multiple buffers.
|
||||
bool Parse(mp4_demuxer::ByteReader* aReader, uint32_t* aBytesToSkip);
|
||||
Result<bool, nsresult> Parse(mp4_demuxer::BufferReader* aReader, uint32_t* aBytesToSkip);
|
||||
|
||||
// Parses contents of given ByteReader for a valid VBR header.
|
||||
// The offset of the passed ByteReader needs to point to an MPEG frame begin,
|
||||
// Parses contents of given BufferReader for a valid VBR header.
|
||||
// The offset of the passed BufferReader needs to point to an MPEG frame begin,
|
||||
// as a VBRI-style header is searched at a fixed offset relative to frame
|
||||
// begin. Returns whether a valid VBR header was found.
|
||||
bool ParseVBRHeader(mp4_demuxer::ByteReader* aReader);
|
||||
bool ParseVBRHeader(mp4_demuxer::BufferReader* aReader);
|
||||
|
||||
private:
|
||||
// ID3 header parser.
|
||||
|
|
|
@ -80,8 +80,8 @@ RefPtr<ShutdownPromise>
|
|||
AOMDecoder::Shutdown()
|
||||
{
|
||||
RefPtr<AOMDecoder> self = this;
|
||||
return InvokeAsync(mTaskQueue, __func__, [self, this]() {
|
||||
auto res = aom_codec_destroy(&mCodec);
|
||||
return InvokeAsync(mTaskQueue, __func__, [self]() {
|
||||
auto res = aom_codec_destroy(&self->mCodec);
|
||||
if (res != AOM_CODEC_OK) {
|
||||
LOG_RESULT(res, "aom_codec_destroy");
|
||||
}
|
||||
|
|
|
@ -86,9 +86,9 @@ RefPtr<ShutdownPromise>
|
|||
VPXDecoder::Shutdown()
|
||||
{
|
||||
RefPtr<VPXDecoder> self = this;
|
||||
return InvokeAsync(mTaskQueue, __func__, [self, this]() {
|
||||
vpx_codec_destroy(&mVPX);
|
||||
vpx_codec_destroy(&mVPXAlpha);
|
||||
return InvokeAsync(mTaskQueue, __func__, [self]() {
|
||||
vpx_codec_destroy(&self->mVPX);
|
||||
vpx_codec_destroy(&self->mVPXAlpha);
|
||||
return ShutdownPromise::CreateAndResolve(true, __func__);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -276,12 +276,12 @@ RefPtr<MediaDataDecoder::FlushPromise>
|
|||
VorbisDataDecoder::Flush()
|
||||
{
|
||||
RefPtr<VorbisDataDecoder> self = this;
|
||||
return InvokeAsync(mTaskQueue, __func__, [self, this]() {
|
||||
return InvokeAsync(mTaskQueue, __func__, [self]() {
|
||||
// Ignore failed results from vorbis_synthesis_restart. They
|
||||
// aren't fatal and it fails when ResetDecode is called at a
|
||||
// time when no vorbis data has been read.
|
||||
vorbis_synthesis_restart(&mVorbisDsp);
|
||||
mLastFrameTime.reset();
|
||||
vorbis_synthesis_restart(&self->mVorbisDsp);
|
||||
self->mLastFrameTime.reset();
|
||||
return FlushPromise::CreateAndResolve(true, __func__);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -57,12 +57,12 @@ public:
|
|||
RefPtr<EMEDecryptor> self = this;
|
||||
mSamplesWaitingForKey->WaitIfKeyNotUsable(aSample)
|
||||
->Then(mTaskQueue, __func__,
|
||||
[self, this](RefPtr<MediaRawData> aSample) {
|
||||
mKeyRequest.Complete();
|
||||
ThrottleDecode(aSample);
|
||||
[self](RefPtr<MediaRawData> aSample) {
|
||||
self->mKeyRequest.Complete();
|
||||
self->ThrottleDecode(aSample);
|
||||
},
|
||||
[self, this]() {
|
||||
mKeyRequest.Complete();
|
||||
[self]() {
|
||||
self->mKeyRequest.Complete();
|
||||
})
|
||||
->Track(mKeyRequest);
|
||||
|
||||
|
@ -74,12 +74,12 @@ public:
|
|||
RefPtr<EMEDecryptor> self = this;
|
||||
mThroughputLimiter.Throttle(aSample)
|
||||
->Then(mTaskQueue, __func__,
|
||||
[self, this] (RefPtr<MediaRawData> aSample) {
|
||||
mThrottleRequest.Complete();
|
||||
AttemptDecode(aSample);
|
||||
[self] (RefPtr<MediaRawData> aSample) {
|
||||
self->mThrottleRequest.Complete();
|
||||
self->AttemptDecode(aSample);
|
||||
},
|
||||
[self, this]() {
|
||||
mThrottleRequest.Complete();
|
||||
[self]() {
|
||||
self->mThrottleRequest.Complete();
|
||||
})
|
||||
->Track(mThrottleRequest);
|
||||
}
|
||||
|
@ -144,13 +144,13 @@ public:
|
|||
RefPtr<EMEDecryptor> self = this;
|
||||
mDecoder->Decode(aDecrypted.mSample)
|
||||
->Then(mTaskQueue, __func__,
|
||||
[self, this](const DecodedData& aResults) {
|
||||
mDecodeRequest.Complete();
|
||||
mDecodePromise.ResolveIfExists(aResults, __func__);
|
||||
[self](const DecodedData& aResults) {
|
||||
self->mDecodeRequest.Complete();
|
||||
self->mDecodePromise.ResolveIfExists(aResults, __func__);
|
||||
},
|
||||
[self, this](const MediaResult& aError) {
|
||||
mDecodeRequest.Complete();
|
||||
mDecodePromise.RejectIfExists(aError, __func__);
|
||||
[self](const MediaResult& aError) {
|
||||
self->mDecodeRequest.Complete();
|
||||
self->mDecodePromise.RejectIfExists(aError, __func__);
|
||||
})
|
||||
->Track(mDecodeRequest);
|
||||
}
|
||||
|
@ -286,8 +286,8 @@ EMEMediaDataDecoderProxy::Decode(MediaRawData* aSample)
|
|||
})
|
||||
->Track(mDecodeRequest);
|
||||
},
|
||||
[self, this]() {
|
||||
mKeyRequest.Complete();
|
||||
[self]() {
|
||||
self->mKeyRequest.Complete();
|
||||
MOZ_CRASH("Should never get here");
|
||||
})
|
||||
->Track(mKeyRequest);
|
||||
|
|
|
@ -550,7 +550,7 @@ RemoteDataDecoder::Decode(MediaRawData* aSample)
|
|||
|
||||
RefPtr<RemoteDataDecoder> self = this;
|
||||
RefPtr<MediaRawData> sample = aSample;
|
||||
return InvokeAsync(mTaskQueue, __func__, [self, sample, this]() {
|
||||
return InvokeAsync(mTaskQueue, __func__, [self, sample]() {
|
||||
jni::ByteBuffer::LocalRef bytes = jni::ByteBuffer::New(
|
||||
const_cast<uint8_t*>(sample->Data()), sample->Size());
|
||||
|
||||
|
@ -562,9 +562,9 @@ RemoteDataDecoder::Decode(MediaRawData* aSample)
|
|||
}
|
||||
bufferInfo->Set(0, sample->Size(), sample->mTime.ToMicroseconds(), 0);
|
||||
|
||||
mDrainStatus = DrainStatus::DRAINABLE;
|
||||
return mJavaDecoder->Input(bytes, bufferInfo, GetCryptoInfoFromSample(sample))
|
||||
? mDecodePromise.Ensure(__func__)
|
||||
self->mDrainStatus = DrainStatus::DRAINABLE;
|
||||
return self->mJavaDecoder->Input(bytes, bufferInfo, GetCryptoInfoFromSample(sample))
|
||||
? self->mDecodePromise.Ensure(__func__)
|
||||
: DecodePromise::CreateAndReject(
|
||||
MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__), __func__);
|
||||
|
||||
|
|
|
@ -73,8 +73,8 @@ AppleATDecoder::Decode(MediaRawData* aSample)
|
|||
(unsigned long long)aSample->Size());
|
||||
RefPtr<AppleATDecoder> self = this;
|
||||
RefPtr<MediaRawData> sample = aSample;
|
||||
return InvokeAsync(mTaskQueue, __func__, [self, this, sample] {
|
||||
return ProcessDecode(sample);
|
||||
return InvokeAsync(mTaskQueue, __func__, [self, sample] {
|
||||
return self->ProcessDecode(sample);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -121,8 +121,8 @@ RefPtr<ShutdownPromise>
|
|||
AppleATDecoder::Shutdown()
|
||||
{
|
||||
RefPtr<AppleATDecoder> self = this;
|
||||
return InvokeAsync(mTaskQueue, __func__, [self, this]() {
|
||||
ProcessShutdown();
|
||||
return InvokeAsync(mTaskQueue, __func__, [self]() {
|
||||
self->ProcessShutdown();
|
||||
return ShutdownPromise::CreateAndResolve(true, __func__);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -113,8 +113,8 @@ AppleVTDecoder::Shutdown()
|
|||
{
|
||||
if (mTaskQueue) {
|
||||
RefPtr<AppleVTDecoder> self = this;
|
||||
return InvokeAsync(mTaskQueue, __func__, [self, this]() {
|
||||
ProcessShutdown();
|
||||
return InvokeAsync(mTaskQueue, __func__, [self]() {
|
||||
self->ProcessShutdown();
|
||||
return ShutdownPromise::CreateAndResolve(true, __func__);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -90,8 +90,8 @@ FFmpegDataDecoder<LIBAV_VER>::Shutdown()
|
|||
{
|
||||
if (mTaskQueue) {
|
||||
RefPtr<FFmpegDataDecoder<LIBAV_VER>> self = this;
|
||||
return InvokeAsync(mTaskQueue, __func__, [self, this]() {
|
||||
ProcessShutdown();
|
||||
return InvokeAsync(mTaskQueue, __func__, [self]() {
|
||||
self->ProcessShutdown();
|
||||
return ShutdownPromise::CreateAndResolve(true, __func__);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -217,8 +217,8 @@ FFmpegVideoDecoder<LIBAV_VER>::DoDecode(MediaRawData* aSample, bool* aGotFrame,
|
|||
#endif
|
||||
)) {
|
||||
while (inputSize) {
|
||||
uint8_t* data;
|
||||
int size;
|
||||
uint8_t* data = inputData;
|
||||
int size = inputSize;
|
||||
int len = mLib->av_parser_parse2(
|
||||
mCodecParser, mCodecContext, &data, &size, inputData, inputSize,
|
||||
aSample->mTime.ToMicroseconds(), aSample->mTimecode.ToMicroseconds(),
|
||||
|
|
|
@ -204,9 +204,9 @@ OmxDataDecoder::Drain()
|
|||
LOG("");
|
||||
|
||||
RefPtr<OmxDataDecoder> self = this;
|
||||
return InvokeAsync(mOmxTaskQueue, __func__, [self, this]() {
|
||||
RefPtr<DecodePromise> p = mDrainPromise.Ensure(__func__);
|
||||
SendEosBuffer();
|
||||
return InvokeAsync(mOmxTaskQueue, __func__, [self]() {
|
||||
RefPtr<DecodePromise> p = self->mDrainPromise.Ensure(__func__);
|
||||
self->SendEosBuffer();
|
||||
return p;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ MediaDataDecoderProxy::Init()
|
|||
}
|
||||
RefPtr<MediaDataDecoderProxy> self = this;
|
||||
return InvokeAsync(mProxyThread, __func__,
|
||||
[self, this]() { return mProxyDecoder->Init(); });
|
||||
[self]() { return self->mProxyDecoder->Init(); });
|
||||
}
|
||||
|
||||
RefPtr<MediaDataDecoder::DecodePromise>
|
||||
|
@ -31,8 +31,8 @@ MediaDataDecoderProxy::Decode(MediaRawData* aSample)
|
|||
}
|
||||
RefPtr<MediaDataDecoderProxy> self = this;
|
||||
RefPtr<MediaRawData> sample = aSample;
|
||||
return InvokeAsync(mProxyThread, __func__, [self, this, sample]() {
|
||||
return mProxyDecoder->Decode(sample);
|
||||
return InvokeAsync(mProxyThread, __func__, [self, sample]() {
|
||||
return self->mProxyDecoder->Decode(sample);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -46,7 +46,7 @@ MediaDataDecoderProxy::Flush()
|
|||
}
|
||||
RefPtr<MediaDataDecoderProxy> self = this;
|
||||
return InvokeAsync(mProxyThread, __func__,
|
||||
[self, this]() { return mProxyDecoder->Flush(); });
|
||||
[self]() { return self->mProxyDecoder->Flush(); });
|
||||
}
|
||||
|
||||
RefPtr<MediaDataDecoder::DecodePromise>
|
||||
|
@ -59,7 +59,7 @@ MediaDataDecoderProxy::Drain()
|
|||
}
|
||||
RefPtr<MediaDataDecoderProxy> self = this;
|
||||
return InvokeAsync(mProxyThread, __func__,
|
||||
[self, this]() { return mProxyDecoder->Drain(); });
|
||||
[self]() { return self->mProxyDecoder->Drain(); });
|
||||
}
|
||||
|
||||
RefPtr<ShutdownPromise>
|
||||
|
@ -76,7 +76,7 @@ MediaDataDecoderProxy::Shutdown()
|
|||
}
|
||||
RefPtr<MediaDataDecoderProxy> self = this;
|
||||
return InvokeAsync(mProxyThread, __func__,
|
||||
[self, this]() { return mProxyDecoder->Shutdown(); });
|
||||
[self]() { return self->mProxyDecoder->Shutdown(); });
|
||||
}
|
||||
|
||||
nsCString
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/EndianUtils.h"
|
||||
#include "mp4_demuxer/ByteReader.h"
|
||||
#include "mp4_demuxer/BufferReader.h"
|
||||
#include "nsAutoPtr.h"
|
||||
#include "VideoUtils.h"
|
||||
#include "TimeUnits.h"
|
||||
|
@ -166,8 +166,8 @@ WAVTrackDemuxer::RIFFParserInit()
|
|||
if (!riffHeader) {
|
||||
return false;
|
||||
}
|
||||
ByteReader RIFFReader(riffHeader->Data(), 12);
|
||||
mRIFFParser.Parse(RIFFReader);
|
||||
BufferReader RIFFReader(riffHeader->Data(), 12);
|
||||
Unused << mRIFFParser.Parse(RIFFReader);
|
||||
return mRIFFParser.RiffHeader().IsValid(11);
|
||||
}
|
||||
|
||||
|
@ -178,8 +178,8 @@ WAVTrackDemuxer::HeaderParserInit()
|
|||
if (!header) {
|
||||
return false;
|
||||
}
|
||||
ByteReader HeaderReader(header->Data(), 8);
|
||||
mHeaderParser.Parse(HeaderReader);
|
||||
BufferReader HeaderReader(header->Data(), 8);
|
||||
Unused << mHeaderParser.Parse(HeaderReader);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -190,9 +190,9 @@ WAVTrackDemuxer::FmtChunkParserInit()
|
|||
if (!fmtChunk) {
|
||||
return false;
|
||||
}
|
||||
ByteReader fmtReader(fmtChunk->Data(),
|
||||
mHeaderParser.GiveHeader().ChunkSize());
|
||||
mFmtParser.Parse(fmtReader);
|
||||
BufferReader fmtReader(fmtChunk->Data(),
|
||||
mHeaderParser.GiveHeader().ChunkSize());
|
||||
Unused << mFmtParser.Parse(fmtReader);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -205,8 +205,10 @@ WAVTrackDemuxer::ListChunkParserInit(uint32_t aChunkSize)
|
|||
if (!infoTag) {
|
||||
return false;
|
||||
}
|
||||
ByteReader infoTagReader(infoTag->Data(), 4);
|
||||
if (!infoTagReader.CanRead32() || infoTagReader.ReadU32() != INFO_CODE) {
|
||||
|
||||
BufferReader infoTagReader(infoTag->Data(), 4);
|
||||
auto res = infoTagReader.ReadU32();
|
||||
if (res.isErr() || (res.isOk() && res.unwrap() != INFO_CODE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -619,10 +621,12 @@ WAVTrackDemuxer::Read(uint8_t* aBuffer, int64_t aOffset, int32_t aSize)
|
|||
|
||||
// RIFFParser
|
||||
|
||||
uint32_t
|
||||
RIFFParser::Parse(ByteReader& aReader)
|
||||
Result<uint32_t, nsresult>
|
||||
RIFFParser::Parse(BufferReader& aReader)
|
||||
{
|
||||
while (aReader.CanRead8() && !mRiffHeader.ParseNext(aReader.ReadU8())) { }
|
||||
for (auto res = aReader.ReadU8();
|
||||
res.isOk() && !mRiffHeader.ParseNext(res.unwrap()); res = aReader.ReadU8())
|
||||
{}
|
||||
|
||||
if (mRiffHeader.IsValid()) {
|
||||
return RIFF_CHUNK_SIZE;
|
||||
|
@ -698,10 +702,12 @@ RIFFParser::RIFFHeader::Update(uint8_t c)
|
|||
|
||||
// HeaderParser
|
||||
|
||||
uint32_t
|
||||
HeaderParser::Parse(ByteReader& aReader)
|
||||
Result<uint32_t, nsresult>
|
||||
HeaderParser::Parse(BufferReader& aReader)
|
||||
{
|
||||
while (aReader.CanRead8() && !mHeader.ParseNext(aReader.ReadU8())) { }
|
||||
for (auto res = aReader.ReadU8();
|
||||
res.isOk() && !mHeader.ParseNext(res.unwrap()); res = aReader.ReadU8())
|
||||
{}
|
||||
|
||||
if (mHeader.IsValid()) {
|
||||
return CHUNK_HEAD_SIZE;
|
||||
|
@ -777,10 +783,12 @@ HeaderParser::ChunkHeader::Update(uint8_t c)
|
|||
|
||||
// FormatParser
|
||||
|
||||
uint32_t
|
||||
FormatParser::Parse(ByteReader& aReader)
|
||||
Result<uint32_t, nsresult>
|
||||
FormatParser::Parse(BufferReader& aReader)
|
||||
{
|
||||
while (aReader.CanRead8() && !mFmtChunk.ParseNext(aReader.ReadU8())) { }
|
||||
for (auto res = aReader.ReadU8();
|
||||
res.isOk() && !mFmtChunk.ParseNext(res.unwrap()); res = aReader.ReadU8())
|
||||
{}
|
||||
|
||||
if (mFmtChunk.IsValid()) {
|
||||
return FMT_CHUNK_MIN_SIZE;
|
||||
|
|
|
@ -9,9 +9,9 @@
|
|||
#include "MediaResource.h"
|
||||
|
||||
namespace mp4_demuxer {
|
||||
class ByteReader;
|
||||
class BufferReader;
|
||||
}
|
||||
using mp4_demuxer::ByteReader;
|
||||
using mp4_demuxer::BufferReader;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
|
@ -56,7 +56,7 @@ private:
|
|||
public:
|
||||
const RIFFHeader& RiffHeader() const;
|
||||
|
||||
uint32_t Parse(ByteReader& aReader);
|
||||
Result<uint32_t, nsresult> Parse(BufferReader& aReader);
|
||||
|
||||
void Reset();
|
||||
|
||||
|
@ -90,7 +90,7 @@ private:
|
|||
public:
|
||||
const ChunkHeader& GiveHeader() const;
|
||||
|
||||
uint32_t Parse(ByteReader& aReader);
|
||||
Result<uint32_t, nsresult> Parse(BufferReader& aReader);
|
||||
|
||||
void Reset();
|
||||
|
||||
|
@ -126,7 +126,7 @@ private:
|
|||
public:
|
||||
const FormatChunk& FmtChunk() const;
|
||||
|
||||
uint32_t Parse(ByteReader& aReader);
|
||||
Result<uint32_t, nsresult> Parse(BufferReader& aReader);
|
||||
|
||||
void Reset();
|
||||
|
||||
|
|
|
@ -10777,10 +10777,12 @@ nsIFrame::GetPseudoElement(CSSPseudoElementType aType)
|
|||
}
|
||||
|
||||
static bool
|
||||
IsFrameScrolledOutOfView(nsIFrame *aFrame)
|
||||
IsFrameScrolledOutOfView(nsIFrame* aTarget,
|
||||
const nsRect& aTargetRect,
|
||||
nsIFrame* aParent)
|
||||
{
|
||||
nsIScrollableFrame* scrollableFrame =
|
||||
nsLayoutUtils::GetNearestScrollableFrame(aFrame,
|
||||
nsLayoutUtils::GetNearestScrollableFrame(aParent,
|
||||
nsLayoutUtils::SCROLLABLE_SAME_DOC |
|
||||
nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN);
|
||||
if (!scrollableFrame) {
|
||||
|
@ -10788,11 +10790,10 @@ IsFrameScrolledOutOfView(nsIFrame *aFrame)
|
|||
}
|
||||
|
||||
nsIFrame *scrollableParent = do_QueryFrame(scrollableFrame);
|
||||
nsRect rect = aFrame->GetVisualOverflowRectRelativeToSelf();
|
||||
|
||||
nsRect transformedRect =
|
||||
nsLayoutUtils::TransformFrameRectToAncestor(aFrame,
|
||||
rect,
|
||||
nsLayoutUtils::TransformFrameRectToAncestor(aTarget,
|
||||
aTargetRect,
|
||||
scrollableParent);
|
||||
|
||||
nsRect scrollableRect = scrollableParent->GetVisualOverflowRect();
|
||||
|
@ -10814,13 +10815,14 @@ IsFrameScrolledOutOfView(nsIFrame *aFrame)
|
|||
return false;
|
||||
}
|
||||
|
||||
return IsFrameScrolledOutOfView(parent);
|
||||
return IsFrameScrolledOutOfView(aTarget, aTargetRect, parent);
|
||||
}
|
||||
|
||||
bool
|
||||
nsIFrame::IsScrolledOutOfView()
|
||||
{
|
||||
return IsFrameScrolledOutOfView(this);
|
||||
nsRect rect = GetVisualOverflowRectRelativeToSelf();
|
||||
return IsFrameScrolledOutOfView(this, rect, this);
|
||||
}
|
||||
|
||||
gfx::Matrix
|
||||
|
|
|
@ -727,11 +727,15 @@ RetainedDisplayListBuilder::AttemptPartialUpdate(nscolor aBackstop)
|
|||
|
||||
if (mPreviousCaret != mBuilder.GetCaretFrame()) {
|
||||
if (mPreviousCaret) {
|
||||
mBuilder.MarkFrameModifiedDuringBuilding(mPreviousCaret);
|
||||
if (mBuilder.MarkFrameModifiedDuringBuilding(mPreviousCaret)) {
|
||||
modifiedFrames.AppendElement(mPreviousCaret);
|
||||
}
|
||||
}
|
||||
|
||||
if (mBuilder.GetCaretFrame()) {
|
||||
mBuilder.MarkFrameModifiedDuringBuilding(mBuilder.GetCaretFrame());
|
||||
if (mBuilder.MarkFrameModifiedDuringBuilding(mBuilder.GetCaretFrame())) {
|
||||
modifiedFrames.AppendElement(mBuilder.GetCaretFrame());
|
||||
}
|
||||
}
|
||||
|
||||
mPreviousCaret = mBuilder.GetCaretFrame();
|
||||
|
@ -760,7 +764,7 @@ RetainedDisplayListBuilder::AttemptPartialUpdate(nscolor aBackstop)
|
|||
|
||||
//printf_stderr("Painting --- Modified list (dirty %d,%d,%d,%d):\n",
|
||||
// modifiedDirty.x, modifiedDirty.y, modifiedDirty.width, modifiedDirty.height);
|
||||
//nsFrame::PrintDisplayList(&builder, modifiedDL);
|
||||
//nsFrame::PrintDisplayList(&mBuilder, modifiedDL);
|
||||
|
||||
} else {
|
||||
// TODO: We can also skip layer building and painting if
|
||||
|
@ -777,7 +781,7 @@ RetainedDisplayListBuilder::AttemptPartialUpdate(nscolor aBackstop)
|
|||
MergeDisplayLists(&modifiedDL, &mList, &mList);
|
||||
|
||||
//printf_stderr("Painting --- Merged list:\n");
|
||||
//nsFrame::PrintDisplayList(&builder, list);
|
||||
//nsFrame::PrintDisplayList(&mBuilder, mList);
|
||||
|
||||
merged = true;
|
||||
}
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче