Merge inbound to central, a=merge

MozReview-Commit-ID: 1D2zbWPC8TT
This commit is contained in:
Wes Kocher 2017-02-14 13:12:20 -08:00
commit cac6cb6a10
319 changed files with 10603 additions and 4971 deletions

View File

@ -131,8 +131,9 @@ public:
if (parent) {
aChildDoc->Parent()->ClearChildDoc(aChildDoc);
}
mChildDocs.RemoveElement(aChildDoc);
DebugOnly<bool> result = mChildDocs.RemoveElement(aChildDoc);
aChildDoc->mParentDoc = nullptr;
MOZ_ASSERT(result);
MOZ_ASSERT(aChildDoc->mChildDocs.Length() == 0);
}

View File

@ -3,6 +3,7 @@
/certutil
/firefox-bin
/gtest/***
/pingsender
/pk12util
/ssltunnel
/xpcshell

View File

@ -415,6 +415,7 @@ class TestFirefoxRefresh(MarionetteTestCase):
self.assertTrue(os.path.isdir(self.reset_profile_path), "Reset profile path should be present")
self.assertTrue(os.path.isdir(self.desktop_backup_path), "Backup profile path should be present")
self.assertTrue(self.profileNameToRemove in self.reset_profile_path, "Reset profile path should contain profile name to remove")
def testReset(self):
self.checkProfile()

View File

@ -0,0 +1,346 @@
/* 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/. */
/*
* Form Autofill content process module.
*/
/* eslint-disable no-use-before-define */
"use strict";
this.EXPORTED_SYMBOLS = ["FormAutofillContent"];
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr, manager: Cm} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ProfileAutoCompleteResult",
"resource://formautofill/ProfileAutoCompleteResult.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FormAutofillHandler",
"resource://formautofill/FormAutofillHandler.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FormLikeFactory",
"resource://gre/modules/FormLikeFactory.jsm");
const formFillController = Cc["@mozilla.org/satchel/form-fill-controller;1"]
.getService(Ci.nsIFormFillController);
const AUTOFILL_FIELDS_THRESHOLD = 3;
// Register/unregister a constructor as a factory.
function AutocompleteFactory() {}
AutocompleteFactory.prototype = {
register(targetConstructor) {
let proto = targetConstructor.prototype;
this._classID = proto.classID;
let factory = XPCOMUtils._getFactory(targetConstructor);
this._factory = factory;
let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
registrar.registerFactory(proto.classID, proto.classDescription,
proto.contractID, factory);
if (proto.classID2) {
this._classID2 = proto.classID2;
registrar.registerFactory(proto.classID2, proto.classDescription,
proto.contractID2, factory);
}
},
unregister() {
let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
registrar.unregisterFactory(this._classID, this._factory);
if (this._classID2) {
registrar.unregisterFactory(this._classID2, this._factory);
}
this._factory = null;
},
};
/**
* @constructor
*
* @implements {nsIAutoCompleteSearch}
*/
function AutofillProfileAutoCompleteSearch() {
}
AutofillProfileAutoCompleteSearch.prototype = {
classID: Components.ID("4f9f1e4c-7f2c-439e-9c9e-566b68bc187d"),
contractID: "@mozilla.org/autocomplete/search;1?name=autofill-profiles",
classDescription: "AutofillProfileAutoCompleteSearch",
QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompleteSearch]),
// Begin nsIAutoCompleteSearch implementation
/**
* Searches for a given string and notifies a listener (either synchronously
* or asynchronously) of the result
*
* @param {string} searchString the string to search for
* @param {string} searchParam
* @param {Object} previousResult a previous result to use for faster searchinig
* @param {Object} listener the listener to notify when the search is complete
*/
startSearch(searchString, searchParam, previousResult, listener) {
let focusedInput = formFillController.focusedInput;
this.forceStop = false;
let info = this._serializeInfo(FormAutofillContent.getInputDetails(focusedInput));
this._getProfiles({info, searchString}).then((profiles) => {
if (this.forceStop) {
return;
}
let allFieldNames = FormAutofillContent.getAllFieldNames(focusedInput);
let result = new ProfileAutoCompleteResult(searchString,
info.fieldName,
allFieldNames,
profiles,
{});
listener.onSearchResult(this, result);
ProfileAutocomplete.setProfileAutoCompleteResult(result);
});
},
/**
* Stops an asynchronous search that is in progress
*/
stopSearch() {
ProfileAutocomplete.setProfileAutoCompleteResult(null);
this.forceStop = true;
},
/**
* Get the profile data from parent process for AutoComplete result.
*
* @private
* @param {Object} data
* Parameters for querying the corresponding result.
* @param {string} data.searchString
* The typed string for filtering out the matched profile.
* @param {string} data.info
* The input autocomplete property's information.
* @returns {Promise}
* Promise that resolves when profiles returned from parent process.
*/
_getProfiles(data) {
return new Promise((resolve) => {
Services.cpmm.addMessageListener("FormAutofill:Profiles", function getResult(result) {
Services.cpmm.removeMessageListener("FormAutofill:Profiles", getResult);
resolve(result.data);
});
Services.cpmm.sendAsyncMessage("FormAutofill:GetProfiles", data);
});
},
_serializeInfo(detail) {
let info = Object.assign({}, detail);
delete info.element;
return info;
},
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([AutofillProfileAutoCompleteSearch]);
let ProfileAutocomplete = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
_lastAutoCompleteResult: null,
_registered: false,
_factory: null,
ensureRegistered() {
if (this._registered) {
return;
}
this._factory = new AutocompleteFactory();
this._factory.register(AutofillProfileAutoCompleteSearch);
this._registered = true;
Services.obs.addObserver(this, "autocomplete-will-enter-text", false);
},
ensureUnregistered() {
if (!this._registered) {
return;
}
this._factory.unregister();
this._factory = null;
this._registered = false;
this._lastAutoCompleteResult = null;
Services.obs.removeObserver(this, "autocomplete-will-enter-text");
},
setProfileAutoCompleteResult(result) {
this._lastAutoCompleteResult = result;
},
observe(subject, topic, data) {
switch (topic) {
case "autocomplete-will-enter-text": {
if (!formFillController.focusedInput) {
// The observer notification is for autocomplete in a different process.
break;
}
this._fillFromAutocompleteRow(formFillController.focusedInput);
break;
}
}
},
_frameMMFromWindow(contentWindow) {
return contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShell)
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIContentFrameMessageManager);
},
_fillFromAutocompleteRow(focusedInput) {
let formDetails = FormAutofillContent.getFormDetails(focusedInput);
if (!formDetails) {
// The observer notification is for a different frame.
return;
}
let mm = this._frameMMFromWindow(focusedInput.ownerGlobal);
let selectedIndexResult = mm.sendSyncMessage("FormAutoComplete:GetSelectedIndex", {});
if (selectedIndexResult.length != 1 || !Number.isInteger(selectedIndexResult[0])) {
throw new Error("Invalid autocomplete selectedIndex");
}
let selectedIndex = selectedIndexResult[0];
if (selectedIndex == -1 ||
!this._lastAutoCompleteResult ||
this._lastAutoCompleteResult.getStyleAt(selectedIndex) != "autofill-profile") {
return;
}
let profile = JSON.parse(this._lastAutoCompleteResult.getCommentAt(selectedIndex));
// TODO: FormAutofillHandler.autofillFormFields will be used for filling
// fields logic eventually.
for (let inputInfo of formDetails) {
// Skip filling the value of focused input which is filled in
// FormFillController.
if (inputInfo.element === focusedInput) {
continue;
}
let value = profile[inputInfo.fieldName];
if (value) {
inputInfo.element.setUserInput(value);
}
}
},
};
/**
* Handles content's interactions for the process.
*
* NOTE: Declares it by "var" to make it accessible in unit tests.
*/
var FormAutofillContent = {
/**
* @type {WeakMap} mapping FormLike root HTML elements to form details.
*/
_formsDetails: new WeakMap(),
init() {
Services.cpmm.addMessageListener("FormAutofill:enabledStatus", (result) => {
if (result.data) {
ProfileAutocomplete.ensureRegistered();
} else {
ProfileAutocomplete.ensureUnregistered();
}
});
Services.cpmm.sendAsyncMessage("FormAutofill:getEnabledStatus");
// TODO: use initialProcessData:
// Services.cpmm.initialProcessData.autofillEnabled
},
/**
* Get the input's information from cache which is created after page identified.
*
* @param {HTMLInputElement} element Focused input which triggered profile searching
* @returns {Object|null}
* Return target input's information that cloned from content cache
* (or return null if the information is not found in the cache).
*/
getInputDetails(element) {
let formDetails = this.getFormDetails(element);
for (let detail of formDetails) {
if (element == detail.element) {
return detail;
}
}
return null;
},
/**
* Get the form's information from cache which is created after page identified.
*
* @param {HTMLInputElement} element Focused input which triggered profile searching
* @returns {Array<Object>|null}
* Return target form's information that cloned from content cache
* (or return null if the information is not found in the cache).
*
*/
getFormDetails(element) {
let rootElement = FormLikeFactory.findRootForField(element);
let formDetails = this._formsDetails.get(rootElement);
return formDetails ? formDetails.fieldDetails : null;
},
getAllFieldNames(element) {
let formDetails = this.getFormDetails(element);
return formDetails.map(record => record.fieldName);
},
_identifyAutofillFields(doc) {
let forms = [];
// Collects root forms from inputs.
for (let field of doc.getElementsByTagName("input")) {
// We only consider text-like fields for now until we support radio and
// checkbox buttons in the future.
if (!field.mozIsTextField(true)) {
continue;
}
let formLike = FormLikeFactory.createFromField(field);
if (!forms.some(form => form.rootElement === formLike.rootElement)) {
forms.push(formLike);
}
}
// Collects the fields that can be autofilled from each form and marks them
// as autofill fields if the amount is above the threshold.
forms.forEach(form => {
let formHandler = new FormAutofillHandler(form);
formHandler.collectFormFields();
if (formHandler.fieldDetails.length < AUTOFILL_FIELDS_THRESHOLD) {
return;
}
this._formsDetails.set(form.rootElement, formHandler);
formHandler.fieldDetails.forEach(
detail => this._markAsAutofillField(detail.element));
});
},
_markAsAutofillField(field) {
formFillController.markAsAutofillField(field);
},
};
FormAutofillContent.init();

View File

@ -0,0 +1,138 @@
/* 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/. */
/*
* Defines a handler object to represent forms that autofill can handle.
*/
"use strict";
this.EXPORTED_SYMBOLS = ["FormAutofillHandler"];
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FormAutofillHeuristics",
"resource://formautofill/FormAutofillHeuristics.jsm");
/**
* Handles profile autofill for a DOM Form element.
* @param {FormLike} form Form that need to be auto filled
*/
function FormAutofillHandler(form) {
this.form = form;
this.fieldDetails = [];
}
FormAutofillHandler.prototype = {
/**
* DOM Form element to which this object is attached.
*/
form: null,
/**
* Array of collected data about relevant form fields. Each item is an object
* storing the identifying details of the field and a reference to the
* originally associated element from the form.
*
* The "section", "addressType", "contactType", and "fieldName" values are
* used to identify the exact field when the serializable data is received
* from the backend. There cannot be multiple fields which have
* the same exact combination of these values.
*
* A direct reference to the associated element cannot be sent to the user
* interface because processing may be done in the parent process.
*/
fieldDetails: null,
/**
* Returns information from the form about fields that can be autofilled, and
* populates the fieldDetails array on this object accordingly.
*
* @returns {Array<Object>} Serializable data structure that can be sent to the user
* interface, or null if the operation failed because the constraints
* on the allowed fields were not honored.
*/
collectFormFields() {
let autofillData = [];
for (let element of this.form.elements) {
// Exclude elements to which no autocomplete field has been assigned.
let info = FormAutofillHeuristics.getInfo(element);
if (!info) {
continue;
}
// Store the association between the field metadata and the element.
if (this.fieldDetails.some(f => f.section == info.section &&
f.addressType == info.addressType &&
f.contactType == info.contactType &&
f.fieldName == info.fieldName)) {
// A field with the same identifier already exists.
return null;
}
let inputFormat = {
section: info.section,
addressType: info.addressType,
contactType: info.contactType,
fieldName: info.fieldName,
};
// Clone the inputFormat for caching the fields and elements together
let formatWithElement = Object.assign({}, inputFormat);
inputFormat.index = autofillData.length;
autofillData.push(inputFormat);
formatWithElement.element = element;
this.fieldDetails.push(formatWithElement);
}
return autofillData;
},
/**
* Processes form fields that can be autofilled, and populates them with the
* data provided by backend.
*
* @param {Array<Object>} autofillResult
* Data returned by the user interface.
* [{
* section: Value originally provided to the user interface.
* addressType: Value originally provided to the user interface.
* contactType: Value originally provided to the user interface.
* fieldName: Value originally provided to the user interface.
* value: String with which the field should be updated.
* index: Index to match the input in fieldDetails
* }],
* }
*/
autofillFormFields(autofillResult) {
for (let field of autofillResult) {
// TODO: Skip filling the value of focused input which is filled in
// FormFillController.
// Get the field details, if it was processed by the user interface.
let fieldDetail = this.fieldDetails[field.index];
// Avoid the invalid value set
if (!fieldDetail || !field.value) {
continue;
}
let info = FormAutofillHeuristics.getInfo(fieldDetail.element);
if (!info ||
field.section != info.section ||
field.addressType != info.addressType ||
field.contactType != info.contactType ||
field.fieldName != info.fieldName) {
Cu.reportError("Autocomplete tokens mismatched");
continue;
}
fieldDetail.element.setUserInput(field.value);
}
},
};

View File

@ -0,0 +1,43 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* Form Autofill field heuristics.
*/
"use strict";
this.EXPORTED_SYMBOLS = ["FormAutofillHeuristics"];
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
/**
* Returns the autocomplete information of fields according to heuristics.
*/
this.FormAutofillHeuristics = {
VALID_FIELDS: [
"organization",
"street-address",
"address-level2",
"address-level1",
"postal-code",
"country",
"tel",
"email",
],
getInfo(element) {
if (!(element instanceof Ci.nsIDOMHTMLInputElement)) {
return null;
}
let info = element.getAutocompleteInfo();
if (!info || !info.fieldName ||
!this.VALID_FIELDS.includes(info.fieldName)) {
return null;
}
return info;
},
};

View File

@ -75,7 +75,7 @@ FormAutofillParent.prototype = {
// Force to trigger the onStatusChanged function for setting listeners properly
// while initizlization
this._onStatusChanged();
Services.mm.addMessageListener("FormAutofill:getEnabledStatus", this);
Services.ppmm.addMessageListener("FormAutofill:getEnabledStatus", this);
},
observe(subject, topic, data) {
@ -112,14 +112,12 @@ FormAutofillParent.prototype = {
*/
_onStatusChanged() {
if (this._enabled) {
Services.mm.addMessageListener("FormAutofill:PopulateFieldValues", this);
Services.mm.addMessageListener("FormAutofill:GetProfiles", this);
Services.ppmm.addMessageListener("FormAutofill:GetProfiles", this);
} else {
Services.mm.removeMessageListener("FormAutofill:PopulateFieldValues", this);
Services.mm.removeMessageListener("FormAutofill:GetProfiles", this);
Services.ppmm.removeMessageListener("FormAutofill:GetProfiles", this);
}
Services.mm.broadcastAsyncMessage("FormAutofill:enabledStatus", this._enabled);
Services.ppmm.broadcastAsyncMessage("FormAutofill:enabledStatus", this._enabled);
},
/**
@ -141,14 +139,11 @@ FormAutofillParent.prototype = {
*/
receiveMessage({name, data, target}) {
switch (name) {
case "FormAutofill:PopulateFieldValues":
this._populateFieldValues(data, target);
break;
case "FormAutofill:GetProfiles":
this._getProfiles(data, target);
break;
case "FormAutofill:getEnabledStatus":
target.messageManager.sendAsyncMessage("FormAutofill:enabledStatus",
Services.ppmm.broadcastAsyncMessage("FormAutofill:enabledStatus",
this._enabled);
break;
}
@ -176,30 +171,11 @@ FormAutofillParent.prototype = {
this._profileStore = null;
}
Services.mm.removeMessageListener("FormAutofill:PopulateFieldValues", this);
Services.mm.removeMessageListener("FormAutofill:GetProfiles", this);
Services.ppmm.removeMessageListener("FormAutofill:GetProfiles", this);
Services.obs.removeObserver(this, "advanced-pane-loaded");
Services.prefs.removeObserver(ENABLED_PREF, this);
},
/**
* Populates the field values and notifies content to fill in. Exception will
* be thrown if there's no matching profile.
*
* @private
* @param {string} data.guid
* Indicates which profile to populate
* @param {Fields} data.fields
* The "fields" array collected from content.
* @param {nsIFrameMessageManager} target
* Content's message manager.
*/
_populateFieldValues({guid, fields}, target) {
this._profileStore.notifyUsed(guid);
this._fillInFields(this._profileStore.get(guid), fields);
target.sendAsyncMessage("FormAutofill:fillForm", {fields});
},
/**
* Get the profile data from profile store and return profiles back to content process.
*
@ -220,43 +196,7 @@ FormAutofillParent.prototype = {
profiles = this._profileStore.getAll();
}
target.messageManager.sendAsyncMessage("FormAutofill:Profiles", profiles);
},
/**
* Get the corresponding value from the specified profile according to a valid
* @autocomplete field name.
*
* Note that the field name doesn't need to match the property name defined in
* Profile object. This method can transform the raw data to fulfill it. (e.g.
* inputting "country-name" as "fieldName" will get a full name transformed
* from the country code that is recorded in "country" field.)
*
* @private
* @param {Profile} profile The specified profile.
* @param {string} fieldName A valid @autocomplete field name.
* @returns {string} The corresponding value. Returns "undefined" if there's
* no matching field.
*/
_getDataByFieldName(profile, fieldName) {
// TODO: Transform the raw profile data to fulfill "fieldName" here.
return profile[fieldName];
},
/**
* Fills in the "fields" array by the specified profile.
*
* @private
* @param {Profile} profile The specified profile to fill in.
* @param {Fields} fields The "fields" array collected from content.
*/
_fillInFields(profile, fields) {
for (let field of fields) {
let value = this._getDataByFieldName(profile, field.fieldName);
if (value !== undefined) {
field.value = value;
}
}
target.sendAsyncMessage("FormAutofill:Profiles", profiles);
},
};

View File

@ -0,0 +1,22 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
this.EXPORTED_SYMBOLS = ["FormAutofillUtils"];
this.FormAutofillUtils = {
generateFullName(firstName, lastName, middleName) {
// TODO: The implementation should depend on the L10N spec, but a simplified
// rule is used here.
let fullName = firstName;
if (middleName) {
fullName += " " + middleName;
}
if (lastName) {
fullName += " " + lastName;
}
return fullName;
},
};

View File

@ -10,12 +10,17 @@ const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FormAutofillUtils",
"resource://formautofill/FormAutofillUtils.jsm");
this.ProfileAutoCompleteResult = function(searchString,
fieldName,
focusedFieldName,
allFieldNames,
matchingProfiles,
{resultCode = null}) {
this.searchString = searchString;
this._fieldName = fieldName;
this._focusedFieldName = focusedFieldName;
this._allFieldNames = allFieldNames;
this._matchingProfiles = matchingProfiles;
if (resultCode) {
@ -25,6 +30,10 @@ this.ProfileAutoCompleteResult = function(searchString,
} else {
this.searchResult = Ci.nsIAutoCompleteResult.RESULT_NOMATCH;
}
this._popupLabels = this._generateLabels(this._focusedFieldName,
this._allFieldNames,
this._matchingProfiles);
};
ProfileAutoCompleteResult.prototype = {
@ -41,12 +50,18 @@ ProfileAutoCompleteResult.prototype = {
// The result code of this result object.
searchResult: null,
// The autocomplete attribute of the focused input field
_fieldName: "",
// The field name of the focused input.
_focusedFieldName: "",
// All field names in the form which contains the focused input.
_allFieldNames: null,
// The matching profiles contains the information for filling forms.
_matchingProfiles: null,
// An array of primary and secondary labels for each profiles.
_popupLabels: null,
/**
* @returns {number} The number of results
*/
@ -60,6 +75,61 @@ ProfileAutoCompleteResult.prototype = {
}
},
/**
* Get the secondary label based on the focused field name and related field names
* in the same form.
* @param {string} focusedFieldName The field name of the focused input
* @param {Array<Object>} allFieldNames The field names in the same section
* @param {object} profile The profile providing the labels to show.
* @returns {string} The secondary label
*/
_getSecondaryLabel(focusedFieldName, allFieldNames, profile) {
/* TODO: Since "name" is a special case here, so the secondary "name" label
will be refined when the handling rule for "name" is ready.
*/
const possibleNameFields = ["given-name", "additional-name", "family-name"];
focusedFieldName = possibleNameFields.includes(focusedFieldName) ?
"name" : focusedFieldName;
if (!profile.name) {
profile.name = FormAutofillUtils.generateFullName(profile["given-name"],
profile["family-name"],
profile["additional-name"]);
}
const secondaryLabelOrder = [
"street-address", // Street address
"name", // Full name if needed
"address-level2", // City/Town
"organization", // Company or organization name
"address-level1", // Province/State (Standardized code if possible)
"country", // Country
"postal-code", // Postal code
"tel", // Phone number
"email", // Email address
];
for (const currentFieldName of secondaryLabelOrder) {
if (focusedFieldName != currentFieldName &&
allFieldNames.includes(currentFieldName) &&
profile[currentFieldName]) {
return profile[currentFieldName];
}
}
return ""; // Nothing matched.
},
_generateLabels(focusedFieldName, allFieldNames, profiles) {
return profiles.map(profile => {
return {
primary: profile[focusedFieldName],
secondary: this._getSecondaryLabel(focusedFieldName,
allFieldNames,
profile),
};
});
},
/**
* Retrieves a result
* @param {number} index The index of the result requested
@ -67,12 +137,12 @@ ProfileAutoCompleteResult.prototype = {
*/
getValueAt(index) {
this._checkIndexBounds(index);
return this._matchingProfiles[index].guid;
return this._popupLabels[index].primary;
},
getLabelAt(index) {
this._checkIndexBounds(index);
return this._matchingProfiles[index].organization;
return JSON.stringify(this._popupLabels[index]);
},
/**
@ -82,7 +152,7 @@ ProfileAutoCompleteResult.prototype = {
*/
getCommentAt(index) {
this._checkIndexBounds(index);
return this._matchingProfiles[index].streetAddress;
return JSON.stringify(this._matchingProfiles[index]);
},
/**

View File

@ -23,7 +23,7 @@ function startup() {
let parent = new FormAutofillParent();
parent.init();
Services.mm.loadFrameScript("chrome://formautofill/content/FormAutofillContent.js", true);
Services.mm.loadFrameScript("chrome://formautofill/content/FormAutofillFrameScript.js", true);
}
function shutdown() {}

View File

@ -1,455 +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/. */
/* eslint-disable no-use-before-define */
/*
* Form Autofill frame script.
*/
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr, manager: Cm} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ProfileAutoCompleteResult",
"resource://formautofill/ProfileAutoCompleteResult.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FormLikeFactory",
"resource://gre/modules/FormLikeFactory.jsm");
const formFillController = Cc["@mozilla.org/satchel/form-fill-controller;1"]
.getService(Ci.nsIFormFillController);
const AUTOFILL_FIELDS_THRESHOLD = 3;
/**
* Returns the autocomplete information of fields according to heuristics.
*/
let FormAutofillHeuristics = {
VALID_FIELDS: [
"organization",
"street-address",
"address-level2",
"address-level1",
"postal-code",
"country",
"tel",
"email",
],
getInfo(element) {
if (!(element instanceof Ci.nsIDOMHTMLInputElement)) {
return null;
}
let info = element.getAutocompleteInfo();
if (!info || !info.fieldName ||
!this.VALID_FIELDS.includes(info.fieldName)) {
return null;
}
return info;
},
};
/**
* Handles profile autofill for a DOM Form element.
* @param {HTMLFormElement} form Form that need to be auto filled
*/
function FormAutofillHandler(form) {
this.form = form;
this.fieldDetails = [];
}
FormAutofillHandler.prototype = {
/**
* DOM Form element to which this object is attached.
*/
form: null,
/**
* Array of collected data about relevant form fields. Each item is an object
* storing the identifying details of the field and a reference to the
* originally associated element from the form.
*
* The "section", "addressType", "contactType", and "fieldName" values are
* used to identify the exact field when the serializable data is received
* from the backend. There cannot be multiple fields which have
* the same exact combination of these values.
*
* A direct reference to the associated element cannot be sent to the user
* interface because processing may be done in the parent process.
*/
fieldDetails: null,
/**
* Returns information from the form about fields that can be autofilled, and
* populates the fieldDetails array on this object accordingly.
*
* @returns {Array<Object>} Serializable data structure that can be sent to the user
* interface, or null if the operation failed because the constraints
* on the allowed fields were not honored.
*/
collectFormFields() {
let autofillData = [];
for (let element of this.form.elements) {
// Exclude elements to which no autocomplete field has been assigned.
let info = FormAutofillHeuristics.getInfo(element);
if (!info) {
continue;
}
// Store the association between the field metadata and the element.
if (this.fieldDetails.some(f => f.section == info.section &&
f.addressType == info.addressType &&
f.contactType == info.contactType &&
f.fieldName == info.fieldName)) {
// A field with the same identifier already exists.
return null;
}
let inputFormat = {
section: info.section,
addressType: info.addressType,
contactType: info.contactType,
fieldName: info.fieldName,
};
// Clone the inputFormat for caching the fields and elements together
let formatWithElement = Object.assign({}, inputFormat);
inputFormat.index = autofillData.length;
autofillData.push(inputFormat);
formatWithElement.element = element;
this.fieldDetails.push(formatWithElement);
}
return autofillData;
},
/**
* Processes form fields that can be autofilled, and populates them with the
* data provided by backend.
*
* @param {Array<Object>} autofillResult
* Data returned by the user interface.
* [{
* section: Value originally provided to the user interface.
* addressType: Value originally provided to the user interface.
* contactType: Value originally provided to the user interface.
* fieldName: Value originally provided to the user interface.
* value: String with which the field should be updated.
* index: Index to match the input in fieldDetails
* }],
* }
*/
autofillFormFields(autofillResult) {
for (let field of autofillResult) {
// Get the field details, if it was processed by the user interface.
let fieldDetail = this.fieldDetails[field.index];
// Avoid the invalid value set
if (!fieldDetail || !field.value) {
continue;
}
let info = FormAutofillHeuristics.getInfo(fieldDetail.element);
if (!info ||
field.section != info.section ||
field.addressType != info.addressType ||
field.contactType != info.contactType ||
field.fieldName != info.fieldName) {
Cu.reportError("Autocomplete tokens mismatched");
continue;
}
fieldDetail.element.setUserInput(field.value);
}
},
};
// Register/unregister a constructor as a factory.
function AutocompleteFactory() {}
AutocompleteFactory.prototype = {
register(targetConstructor) {
let proto = targetConstructor.prototype;
this._classID = proto.classID;
let factory = XPCOMUtils._getFactory(targetConstructor);
this._factory = factory;
let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
registrar.registerFactory(proto.classID, proto.classDescription,
proto.contractID, factory);
if (proto.classID2) {
this._classID2 = proto.classID2;
registrar.registerFactory(proto.classID2, proto.classDescription,
proto.contractID2, factory);
}
},
unregister() {
let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
registrar.unregisterFactory(this._classID, this._factory);
if (this._classID2) {
registrar.unregisterFactory(this._classID2, this._factory);
}
this._factory = null;
},
};
/**
* @constructor
*
* @implements {nsIAutoCompleteSearch}
*/
function AutofillProfileAutoCompleteSearch() {
}
AutofillProfileAutoCompleteSearch.prototype = {
classID: Components.ID("4f9f1e4c-7f2c-439e-9c9e-566b68bc187d"),
contractID: "@mozilla.org/autocomplete/search;1?name=autofill-profiles",
classDescription: "AutofillProfileAutoCompleteSearch",
QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompleteSearch]),
// Begin nsIAutoCompleteSearch implementation
/**
* Searches for a given string and notifies a listener (either synchronously
* or asynchronously) of the result
*
* @param {string} searchString the string to search for
* @param {string} searchParam
* @param {Object} previousResult a previous result to use for faster searchinig
* @param {Object} listener the listener to notify when the search is complete
*/
startSearch(searchString, searchParam, previousResult, listener) {
this.forceStop = false;
let info = this.getInputDetails();
this.getProfiles({info, searchString}).then((profiles) => {
if (this.forceStop) {
return;
}
// TODO: Set formInfo for ProfileAutoCompleteResult
// let formInfo = this.getFormDetails();
let result = new ProfileAutoCompleteResult(searchString, info, profiles, {});
listener.onSearchResult(this, result);
});
},
/**
* Stops an asynchronous search that is in progress
*/
stopSearch() {
this.forceStop = true;
},
/**
* Get the profile data from parent process for AutoComplete result.
*
* @private
* @param {Object} data
* Parameters for querying the corresponding result.
* @param {string} data.searchString
* The typed string for filtering out the matched profile.
* @param {string} data.info
* The input autocomplete property's information.
* @returns {Promise}
* Promise that resolves when profiles returned from parent process.
*/
getProfiles(data) {
return new Promise((resolve) => {
addMessageListener("FormAutofill:Profiles", function getResult(result) {
removeMessageListener("FormAutofill:Profiles", getResult);
resolve(result.data);
});
sendAsyncMessage("FormAutofill:GetProfiles", data);
});
},
/**
* Get the input's information from FormAutofillContent's cache.
*
* @returns {Object}
* Target input's information that cached in FormAutofillContent.
*/
getInputDetails() {
// TODO: Maybe we'll need to wait for cache ready if detail is empty.
return FormAutofillContent.getInputDetails(formFillController.focusedInput);
},
/**
* Get the form's information from FormAutofillContent's cache.
*
* @returns {Array<Object>}
* Array of the inputs' information for the target form.
*/
getFormDetails() {
// TODO: Maybe we'll need to wait for cache ready if details is empty.
return FormAutofillContent.getFormDetails(formFillController.focusedInput);
},
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([AutofillProfileAutoCompleteSearch]);
let ProfileAutocomplete = {
_registered: false,
_factory: null,
ensureRegistered() {
if (this._registered) {
return;
}
this._factory = new AutocompleteFactory();
this._factory.register(AutofillProfileAutoCompleteSearch);
this._registered = true;
},
ensureUnregistered() {
if (!this._registered) {
return;
}
this._factory.unregister();
this._factory = null;
this._registered = false;
},
};
/**
* Handles content's interactions.
*
* NOTE: Declares it by "var" to make it accessible in unit tests.
*/
var FormAutofillContent = {
init() {
addEventListener("DOMContentLoaded", this);
addMessageListener("FormAutofill:enabledStatus", (result) => {
if (result.data) {
ProfileAutocomplete.ensureRegistered();
} else {
ProfileAutocomplete.ensureUnregistered();
}
});
sendAsyncMessage("FormAutofill:getEnabledStatus");
},
handleEvent(evt) {
if (!evt.isTrusted) {
return;
}
switch (evt.type) {
case "DOMContentLoaded":
let doc = evt.target;
if (!(doc instanceof Ci.nsIDOMHTMLDocument)) {
return;
}
this._identifyAutofillFields(doc);
break;
}
},
/**
* Get the input's information from cache which is created after page identified.
*
* @param {HTMLInputElement} element Focused input which triggered profile searching
* @returns {Object|null}
* Return target input's information that cloned from content cache
* (or return null if the information is not found in the cache).
*/
getInputDetails(element) {
for (let formDetails of this._formsDetails) {
for (let detail of formDetails) {
if (element == detail.element) {
return this._serializeInfo(detail);
}
}
}
return null;
},
/**
* Get the form's information from cache which is created after page identified.
*
* @param {HTMLInputElement} element Focused input which triggered profile searching
* @returns {Array<Object>|null}
* Return target form's information that cloned from content cache
* (or return null if the information is not found in the cache).
*
*/
getFormDetails(element) {
for (let formDetails of this._formsDetails) {
if (formDetails.some((detail) => detail.element == element)) {
return formDetails.map((detail) => this._serializeInfo(detail));
}
}
return null;
},
/**
* Create a clone the information object without element reference.
*
* @param {Object} detail Profile autofill information for specific input.
* @returns {Object}
* Return a copy of cached information object without element reference
* since it's not needed for creating result.
*/
_serializeInfo(detail) {
let info = Object.assign({}, detail);
delete info.element;
return info;
},
_identifyAutofillFields(doc) {
let forms = [];
this._formsDetails = [];
// Collects root forms from inputs.
for (let field of doc.getElementsByTagName("input")) {
// We only consider text-like fields for now until we support radio and
// checkbox buttons in the future.
if (!field.mozIsTextField(true)) {
continue;
}
let formLike = FormLikeFactory.createFromField(field);
if (!forms.some(form => form.rootElement === formLike.rootElement)) {
forms.push(formLike);
}
}
// Collects the fields that can be autofilled from each form and marks them
// as autofill fields if the amount is above the threshold.
forms.forEach(form => {
let formHandler = new FormAutofillHandler(form);
formHandler.collectFormFields();
if (formHandler.fieldDetails.length < AUTOFILL_FIELDS_THRESHOLD) {
return;
}
this._formsDetails.push(formHandler.fieldDetails);
formHandler.fieldDetails.forEach(
detail => this._markAsAutofillField(detail.element));
});
},
_markAsAutofillField(field) {
formFillController.markAsAutofillField(field);
},
};
FormAutofillContent.init();

View File

@ -0,0 +1,51 @@
/* 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/. */
/*
* Form Autofill frame script.
*/
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
/**
* Handles content's interactions for the frame.
*
* NOTE: Declares it by "var" to make it accessible in unit tests.
*/
var FormAutofillFrameScript = {
init() {
addEventListener("DOMContentLoaded", this);
},
handleEvent(evt) {
if (!evt.isTrusted) {
return;
}
if (!Services.prefs.getBoolPref("browser.formautofill.enabled")) {
return;
}
switch (evt.type) {
case "DOMContentLoaded": {
let doc = evt.target;
if (!(doc instanceof Ci.nsIDOMHTMLDocument)) {
return;
}
this.FormAutofillContent._identifyAutofillFields(doc);
break;
}
}
},
};
XPCOMUtils.defineLazyModuleGetter(FormAutofillFrameScript, "FormAutofillContent",
"resource://formautofill/FormAutofillContent.jsm");
FormAutofillFrameScript.init();

View File

@ -45,21 +45,6 @@ Components.manager.addBootstrappedManifestLocation(extensionDir);
// with a file that is still pending deletion highly unlikely.
let gFileCounter = Math.floor(Math.random() * 1000000);
function loadFormAutofillContent() {
let facGlobal = {
addEventListener() {},
addMessageListener() {},
sendAsyncMessage() {},
};
let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader);
loader.loadSubScriptWithOptions("chrome://formautofill/content/FormAutofillContent.js", {
target: facGlobal,
});
return facGlobal;
}
/**
* Returns a reference to a temporary file, that is guaranteed not to exist, and
* to have never been created before.

View File

@ -4,7 +4,7 @@
"use strict";
let {FormAutofillHandler} = loadFormAutofillContent();
Cu.import("resource://formautofill/FormAutofillHandler.jsm");
const TESTCASES = [
{

View File

@ -4,7 +4,7 @@
"use strict";
let {FormAutofillHandler} = loadFormAutofillContent();
Cu.import("resource://formautofill/FormAutofillHandler.jsm");
const TESTCASES = [
{

View File

@ -1,11 +1,11 @@
"use strict";
let {FormAutofillContent} = loadFormAutofillContent();
Cu.import("resource://formautofill/FormAutofillContent.jsm");
const TESTCASES = [
{
description: "Form containing 5 fields with autocomplete attribute.",
document: `<form>
document: `<form id="form1">
<input id="street-addr" autocomplete="street-address">
<input id="city" autocomplete="address-level2">
<input id="country" autocomplete="country">
@ -15,6 +15,7 @@ const TESTCASES = [
targetInput: ["street-addr", "country"],
expectedResult: [{
input: {"section": "", "addressType": "", "contactType": "", "fieldName": "street-address"},
formId: "form1",
form: [
{"section": "", "addressType": "", "contactType": "", "fieldName": "street-address"},
{"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"},
@ -25,6 +26,7 @@ const TESTCASES = [
},
{
input: {"section": "", "addressType": "", "contactType": "", "fieldName": "country"},
formId: "form1",
form: [
{"section": "", "addressType": "", "contactType": "", "fieldName": "street-address"},
{"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"},
@ -36,12 +38,12 @@ const TESTCASES = [
},
{
description: "2 forms that are able to be auto filled",
document: `<form>
document: `<form id="form2">
<input id="home-addr" autocomplete="street-address">
<input id="city" autocomplete="address-level2">
<input id="country" autocomplete="country">
</form>
<form>
<form id="form3">
<input id="office-addr" autocomplete="street-address">
<input id="email" autocomplete="email">
<input id="tel" autocomplete="tel">
@ -49,6 +51,7 @@ const TESTCASES = [
targetInput: ["home-addr", "office-addr"],
expectedResult: [{
input: {"section": "", "addressType": "", "contactType": "", "fieldName": "street-address"},
formId: "form2",
form: [
{"section": "", "addressType": "", "contactType": "", "fieldName": "street-address"},
{"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"},
@ -57,6 +60,7 @@ const TESTCASES = [
},
{
input: {"section": "", "addressType": "", "contactType": "", "fieldName": "street-address"},
formId: "form3",
form: [
{"section": "", "addressType": "", "contactType": "", "fieldName": "street-address"},
{"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
@ -78,12 +82,22 @@ TESTCASES.forEach(testcase => {
for (let i in testcase.targetInput) {
let input = doc.getElementById(testcase.targetInput[i]);
// Put the input element reference to `element` to make sure the result of
// `getInputDetails` contains the same input element.
testcase.expectedResult[i].input.element = input;
Assert.deepEqual(FormAutofillContent.getInputDetails(input),
testcase.expectedResult[i].input,
"Check if returned input information is correct.");
let formDetails = testcase.expectedResult[i].form;
for (let formDetail of formDetails) {
// Compose a query string to get the exact reference of the input
// element, e.g. #form1 > input[autocomplete="street-address"]
let queryString = "#" + testcase.expectedResult[i].formId + " > input[autocomplete=" + formDetail.fieldName + "]";
formDetail.element = doc.querySelector(queryString);
}
Assert.deepEqual(FormAutofillContent.getFormDetails(input),
testcase.expectedResult[i].form,
formDetails,
"Check if returned form information is correct.");
}
});

View File

@ -1,6 +1,6 @@
"use strict";
let {FormAutofillContent} = loadFormAutofillContent();
Cu.import("resource://formautofill/FormAutofillContent.jsm");
const TESTCASES = [
{

View File

@ -1,102 +0,0 @@
/*
* Test for populating field values in Form Autofill Parent.
*/
"use strict";
Cu.import("resource://formautofill/FormAutofillParent.jsm");
do_get_profile();
const TEST_FIELDS = [
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "organization"},
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address"},
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level2"},
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level1"},
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "postal-code"},
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "country"},
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel"},
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email"},
];
const TEST_GUID = "test-guid";
const TEST_PROFILE = {
guid: TEST_GUID,
organization: "World Wide Web Consortium",
"street-address": "32 Vassar Street\nMIT Room 32-G524",
"address-level2": "Cambridge",
"address-level1": "MA",
postalCode: "02139",
country: "US",
tel: "+1 617 253 5702",
email: "timbl@w3.org",
};
add_task(function* test_populateFieldValues() {
let formAutofillParent = new FormAutofillParent();
formAutofillParent.init();
let store = formAutofillParent.getProfileStore();
do_check_neq(store, null);
store.get = function(guid) {
do_check_eq(guid, TEST_GUID);
return store._clone(TEST_PROFILE);
};
let notifyUsedCalledCount = 0;
store.notifyUsed = function(guid) {
do_check_eq(guid, TEST_GUID);
notifyUsedCalledCount++;
};
yield new Promise((resolve) => {
formAutofillParent.receiveMessage({
name: "FormAutofill:PopulateFieldValues",
data: {
guid: TEST_GUID,
fields: TEST_FIELDS,
},
target: {
sendAsyncMessage(name, data) {
do_check_eq(name, "FormAutofill:fillForm");
let fields = data.fields;
do_check_eq(fields.length, TEST_FIELDS.length);
for (let i = 0; i < fields.length; i++) {
do_check_eq(fields[i].fieldName, TEST_FIELDS[i].fieldName);
do_check_eq(fields[i].value,
TEST_PROFILE[fields[i].fieldName]);
}
resolve();
},
},
});
});
do_check_eq(notifyUsedCalledCount, 1);
formAutofillParent._uninit();
do_check_null(formAutofillParent.getProfileStore());
});
add_task(function* test_populateFieldValues_with_invalid_guid() {
let formAutofillParent = new FormAutofillParent();
formAutofillParent.init();
Assert.throws(() => {
formAutofillParent.receiveMessage({
name: "FormAutofill:PopulateFieldValues",
data: {
guid: "invalid-guid",
fields: TEST_FIELDS,
},
target: {},
});
}, /No matching profile\./);
formAutofillParent._uninit();
});

View File

@ -5,34 +5,108 @@ Cu.import("resource://formautofill/ProfileAutoCompleteResult.jsm");
let matchingProfiles = [{
guid: "test-guid-1",
organization: "Sesame Street",
streetAddress: "123 Sesame Street.",
"street-address": "123 Sesame Street.",
tel: "1-345-345-3456.",
}, {
guid: "test-guid-2",
organization: "Mozilla",
streetAddress: "331 E. Evelyn Avenue",
"street-address": "331 E. Evelyn Avenue",
tel: "1-650-903-0800",
}];
let allFieldNames = ["street-address", "organization", "tel"];
let testCases = [{
options: {},
matchingProfiles,
allFieldNames,
searchString: "",
fieldName: "",
fieldName: "organization",
expected: {
searchResult: Ci.nsIAutoCompleteResult.RESULT_SUCCESS,
defaultIndex: 0,
items: [{
value: "Sesame Street",
style: "autofill-profile",
comment: JSON.stringify(matchingProfiles[0]),
label: JSON.stringify({
primary: "Sesame Street",
secondary: "123 Sesame Street.",
}),
image: "",
}, {
value: "Mozilla",
style: "autofill-profile",
comment: JSON.stringify(matchingProfiles[1]),
label: JSON.stringify({
primary: "Mozilla",
secondary: "331 E. Evelyn Avenue",
}),
image: "",
}],
},
}, {
options: {},
matchingProfiles,
allFieldNames,
searchString: "",
fieldName: "tel",
expected: {
searchResult: Ci.nsIAutoCompleteResult.RESULT_SUCCESS,
defaultIndex: 0,
items: [{
value: "1-345-345-3456.",
style: "autofill-profile",
comment: JSON.stringify(matchingProfiles[0]),
label: JSON.stringify({
primary: "1-345-345-3456.",
secondary: "123 Sesame Street.",
}),
image: "",
}, {
value: "1-650-903-0800",
style: "autofill-profile",
comment: JSON.stringify(matchingProfiles[1]),
label: JSON.stringify({
primary: "1-650-903-0800",
secondary: "331 E. Evelyn Avenue",
}),
image: "",
}],
},
}, {
options: {},
matchingProfiles,
allFieldNames,
searchString: "",
fieldName: "street-address",
expected: {
searchResult: Ci.nsIAutoCompleteResult.RESULT_SUCCESS,
defaultIndex: 0,
items: [{
value: "123 Sesame Street.",
style: "autofill-profile",
comment: JSON.stringify(matchingProfiles[0]),
label: JSON.stringify({
primary: "123 Sesame Street.",
secondary: "Sesame Street",
}),
image: "",
}, {
value: "331 E. Evelyn Avenue",
style: "autofill-profile",
comment: JSON.stringify(matchingProfiles[1]),
label: JSON.stringify({
primary: "331 E. Evelyn Avenue",
secondary: "Mozilla",
}),
image: "",
}],
},
}, {
options: {},
matchingProfiles: [],
allFieldNames,
searchString: "",
fieldName: "",
expected: {
@ -43,6 +117,7 @@ let testCases = [{
}, {
options: {resultCode: Ci.nsIAutoCompleteResult.RESULT_FAILURE},
matchingProfiles: [],
allFieldNames,
searchString: "",
fieldName: "",
expected: {
@ -56,6 +131,7 @@ add_task(function* test_all_patterns() {
testCases.forEach(pattern => {
let actual = new ProfileAutoCompleteResult(pattern.searchString,
pattern.fieldName,
pattern.allFieldNames,
pattern.matchingProfiles,
pattern.options);
let expectedValue = pattern.expected;
@ -63,7 +139,9 @@ add_task(function* test_all_patterns() {
equal(actual.defaultIndex, expectedValue.defaultIndex);
equal(actual.matchCount, expectedValue.items.length);
expectedValue.items.forEach((item, index) => {
// TODO: getValueAt, getLabelAt, and getCommentAt should be verified here.
equal(actual.getValueAt(index), item.value);
equal(actual.getCommentAt(index), item.comment);
equal(actual.getLabelAt(index), item.label);
equal(actual.getStyleAt(index), item.style);
equal(actual.getImageAt(index), item.image);
});

View File

@ -8,6 +8,5 @@ support-files =
[test_enabledStatus.js]
[test_getFormInputDetails.js]
[test_markAsAutofillField.js]
[test_populateFieldValues.js]
[test_profileAutocompleteResult.js]
[test_profileStorage.js]

View File

@ -770,8 +770,8 @@ bin/libfreebl_32int64_3.so
@BINPATH@/crashreporter.app/
#else
@BINPATH@/crashreporter@BIN_SUFFIX@
@BINPATH@/minidump-analyzer@BIN_SUFFIX@
@RESPATH@/crashreporter.ini
@BINPATH@/minidump-analyzer@BIN_SUFFIX@
#ifdef XP_UNIX
@RESPATH@/Throbber-small.gif
#endif
@ -782,6 +782,10 @@ bin/libfreebl_32int64_3.so
#endif
#endif
; [ Ping Sender ]
;
@BINPATH@/pingsender@BIN_SUFFIX@
@RESPATH@/components/dom_audiochannel.xpt
; Shutdown Terminator

View File

@ -1135,6 +1135,7 @@
Push "xpcom.dll"
Push "crashreporter.exe"
Push "minidump-analyzer.exe"
Push "pingsender.exe"
Push "updater.exe"
Push "${FileMainEXE}"
!macroend

View File

@ -9,11 +9,11 @@
}
:root:-moz-lwtheme-darktext {
--urlbar-dropmarker-url: url("chrome://browser/skin/devedition/urlbar-history-dropmarker.svg");
--urlbar-dropmarker-url: url("chrome://browser/skin/compacttheme/urlbar-history-dropmarker.svg");
--urlbar-dropmarker-region: rect(0px, 11px, 14px, 0px);
--urlbar-dropmarker-hover-region: rect(0, 22px, 14px, 11px);
--urlbar-dropmarker-active-region: rect(0px, 33px, 14px, 22px);
--urlbar-dropmarker-2x-url: url("chrome://browser/skin/devedition/urlbar-history-dropmarker.svg");
--urlbar-dropmarker-2x-url: url("chrome://browser/skin/compacttheme/urlbar-history-dropmarker.svg");
--urlbar-dropmarker-2x-region: rect(0px, 11px, 14px, 0px);
--urlbar-dropmarker-hover-2x-region: rect(0, 22px, 14px, 11px);
--urlbar-dropmarker-active-2x-region: rect(0px, 33px, 14px, 22px);

View File

@ -39,11 +39,11 @@
/* Url and search bars */
--url-and-searchbar-background-color: #171B1F;
--urlbar-separator-color: #5F6670;
--urlbar-dropmarker-url: url("chrome://browser/skin/devedition/urlbar-history-dropmarker.svg");
--urlbar-dropmarker-url: url("chrome://browser/skin/compacttheme/urlbar-history-dropmarker.svg");
--urlbar-dropmarker-region: rect(0px, 11px, 14px, 0px);
--urlbar-dropmarker-hover-region: rect(0, 22px, 14px, 11px);
--urlbar-dropmarker-active-region: rect(0px, 33px, 14px, 22px);
--urlbar-dropmarker-2x-url: url("chrome://browser/skin/devedition/urlbar-history-dropmarker.svg");
--urlbar-dropmarker-2x-url: url("chrome://browser/skin/compacttheme/urlbar-history-dropmarker.svg");
--urlbar-dropmarker-2x-region: rect(0px, 11px, 14px, 0px);
--urlbar-dropmarker-hover-2x-region: rect(0, 22px, 14px, 11px);
--urlbar-dropmarker-active-2x-region: rect(0px, 33px, 14px, 22px);

View File

Before

Width:  |  Height:  |  Size: 831 B

After

Width:  |  Height:  |  Size: 831 B

View File

@ -138,6 +138,6 @@
skin/classic/browser/privatebrowsing/private-browsing.svg (../shared/privatebrowsing/private-browsing.svg)
skin/classic/browser/privatebrowsing/tracking-protection-off.svg (../shared/privatebrowsing/tracking-protection-off.svg)
skin/classic/browser/privatebrowsing/tracking-protection.svg (../shared/privatebrowsing/tracking-protection.svg)
skin/classic/browser/devedition/urlbar-history-dropmarker.svg (../shared/devedition/urlbar-history-dropmarker.svg)
skin/classic/browser/compacttheme/urlbar-history-dropmarker.svg (../shared/compacttheme/urlbar-history-dropmarker.svg)
skin/classic/browser/urlbar-star.svg (../shared/urlbar-star.svg)
skin/classic/browser/urlbar-tab.svg (../shared/urlbar-tab.svg)

View File

@ -33,6 +33,23 @@ namespace mozilla {
using dom::URLParams;
bool OriginAttributes::sFirstPartyIsolation = false;
bool OriginAttributes::sRestrictedOpenerAccess = false;
void
OriginAttributes::InitPrefs()
{
MOZ_ASSERT(NS_IsMainThread());
static bool sInited = false;
if (!sInited) {
sInited = true;
Preferences::AddBoolVarCache(&sFirstPartyIsolation,
"privacy.firstparty.isolate");
Preferences::AddBoolVarCache(&sRestrictedOpenerAccess,
"privacy.firstparty.isolate.restrict_opener_access");
}
}
void
OriginAttributes::Inherit(const OriginAttributes& aAttrs)
{
@ -269,46 +286,6 @@ OriginAttributes::SyncAttributesWithPrivateBrowsing(bool aInPrivateBrowsing)
mPrivateBrowsingId = aInPrivateBrowsing ? 1 : 0;
}
/* static */
bool
OriginAttributes::IsFirstPartyEnabled()
{
// Cache the privacy.firstparty.isolate pref.
static bool sFirstPartyIsolation = false;
static bool sCachedFirstPartyPref = false;
if (!sCachedFirstPartyPref) {
sCachedFirstPartyPref = true;
Preferences::AddBoolVarCache(&sFirstPartyIsolation, "privacy.firstparty.isolate");
}
return sFirstPartyIsolation;
}
/* static */
bool
OriginAttributes::IsRestrictOpenerAccessForFPI()
{
bool isFirstPartyEnabled = IsFirstPartyEnabled();
// Cache the privacy.firstparty.isolate.restrict_opener_access pref.
static bool sRestrictedOpenerAccess = false;
static bool sCachedRestrictedAccessPref = false;
if (!sCachedRestrictedAccessPref) {
MOZ_ASSERT(NS_IsMainThread());
sCachedRestrictedAccessPref = true;
Preferences::AddBoolVarCache(&sRestrictedOpenerAccess,
"privacy.firstparty.isolate.restrict_opener_access");
}
// We always want to restrict window.opener if first party isolation is
// disabled.
if (!isFirstPartyEnabled) {
return true;
}
return isFirstPartyEnabled && sRestrictedOpenerAccess;
}
/* static */
bool
OriginAttributes::IsPrivateBrowsing(const nsACString& aOrigin)

View File

@ -101,16 +101,30 @@ public:
void SyncAttributesWithPrivateBrowsing(bool aInPrivateBrowsing);
// check if "privacy.firstparty.isolate" is enabled.
static bool IsFirstPartyEnabled();
static inline bool IsFirstPartyEnabled()
{
return sFirstPartyIsolation;
}
// check if the access of window.opener across different FPDs is restricted.
// We only restrict the access of window.opener when first party isolation
// is enabled and "privacy.firstparty.isolate.restrict_opener_access" is on.
static bool IsRestrictOpenerAccessForFPI();
static inline bool IsRestrictOpenerAccessForFPI()
{
// We always want to restrict window.opener if first party isolation is
// disabled.
return !sFirstPartyIsolation || sRestrictedOpenerAccess;
}
// returns true if the originAttributes suffix has mPrivateBrowsingId value
// different than 0.
static bool IsPrivateBrowsing(const nsACString& aOrigin);
static void InitPrefs();
private:
static bool sFirstPartyIsolation;
static bool sRestrictedOpenerAccess;
};
class OriginAttributesPattern : public dom::OriginAttributesPatternDictionary

View File

@ -1500,6 +1500,8 @@ nsScriptSecurityManager::InitPrefs()
// set observer callbacks in case the value of the prefs change
Preferences::AddStrongObservers(this, kObservedPrefs);
OriginAttributes::InitPrefs();
return NS_OK;
}

File diff suppressed because it is too large Load Diff

View File

@ -121,9 +121,9 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
#define SQLITE_VERSION "3.16.2"
#define SQLITE_VERSION_NUMBER 3016002
#define SQLITE_SOURCE_ID "2017-01-06 16:32:41 a65a62893ca8319e89e48b8a38cf8a59c69a8209"
#define SQLITE_VERSION "3.17.0"
#define SQLITE_VERSION_NUMBER 3017000
#define SQLITE_SOURCE_ID "2017-02-13 16:02:40 ada05cfa86ad7f5645450ac7a2a21c9aa6e57d2c"
/*
** CAPI3REF: Run-Time Library Version Numbers
@ -259,7 +259,11 @@ typedef struct sqlite3 sqlite3;
*/
#ifdef SQLITE_INT64_TYPE
typedef SQLITE_INT64_TYPE sqlite_int64;
# ifdef SQLITE_UINT64_TYPE
typedef SQLITE_UINT64_TYPE sqlite_uint64;
# else
typedef unsigned SQLITE_INT64_TYPE sqlite_uint64;
# endif
#elif defined(_MSC_VER) || defined(__BORLANDC__)
typedef __int64 sqlite_int64;
typedef unsigned __int64 sqlite_uint64;
@ -572,7 +576,7 @@ SQLITE_API int sqlite3_exec(
** file that were written at the application level might have changed
** and that adjacent bytes, even bytes within the same sector are
** guaranteed to be unchanged. The SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN
** flag indicate that a file cannot be deleted when open. The
** flag indicates that a file cannot be deleted when open. The
** SQLITE_IOCAP_IMMUTABLE flag indicates that the file is on
** read-only media and cannot be changed even by processes with
** elevated privileges.
@ -722,6 +726,9 @@ struct sqlite3_file {
** <li> [SQLITE_IOCAP_ATOMIC64K]
** <li> [SQLITE_IOCAP_SAFE_APPEND]
** <li> [SQLITE_IOCAP_SEQUENTIAL]
** <li> [SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN]
** <li> [SQLITE_IOCAP_POWERSAFE_OVERWRITE]
** <li> [SQLITE_IOCAP_IMMUTABLE]
** </ul>
**
** The SQLITE_IOCAP_ATOMIC property means that all writes of
@ -5410,7 +5417,7 @@ SQLITE_API void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*);
** ^The update hook is not invoked when [WITHOUT ROWID] tables are modified.
**
** ^In the current implementation, the update hook
** is not invoked when duplication rows are deleted because of an
** is not invoked when conflicting rows are deleted because of an
** [ON CONFLICT | ON CONFLICT REPLACE] clause. ^Nor is the update hook
** invoked when rows are deleted using the [truncate optimization].
** The exceptions defined in this paragraph might change in a future
@ -6192,6 +6199,12 @@ typedef struct sqlite3_blob sqlite3_blob;
** [database connection] error code and message accessible via
** [sqlite3_errcode()] and [sqlite3_errmsg()] and related functions.
**
** A BLOB referenced by sqlite3_blob_open() may be read using the
** [sqlite3_blob_read()] interface and modified by using
** [sqlite3_blob_write()]. The [BLOB handle] can be moved to a
** different row of the same table using the [sqlite3_blob_reopen()]
** interface. However, the column, table, or database of a [BLOB handle]
** cannot be changed after the [BLOB handle] is opened.
**
** ^(If the row that a BLOB handle points to is modified by an
** [UPDATE], [DELETE], or by [ON CONFLICT] side-effects
@ -6215,6 +6228,10 @@ typedef struct sqlite3_blob sqlite3_blob;
**
** To avoid a resource leak, every open [BLOB handle] should eventually
** be released by a call to [sqlite3_blob_close()].
**
** See also: [sqlite3_blob_close()],
** [sqlite3_blob_reopen()], [sqlite3_blob_read()],
** [sqlite3_blob_bytes()], [sqlite3_blob_write()].
*/
SQLITE_API int sqlite3_blob_open(
sqlite3*,
@ -6230,11 +6247,11 @@ SQLITE_API int sqlite3_blob_open(
** CAPI3REF: Move a BLOB Handle to a New Row
** METHOD: sqlite3_blob
**
** ^This function is used to move an existing blob handle so that it points
** ^This function is used to move an existing [BLOB handle] so that it points
** to a different row of the same database table. ^The new row is identified
** by the rowid value passed as the second argument. Only the row can be
** changed. ^The database, table and column on which the blob handle is open
** remain the same. Moving an existing blob handle to a new row can be
** remain the same. Moving an existing [BLOB handle] to a new row is
** faster than closing the existing handle and opening a new one.
**
** ^(The new row must meet the same criteria as for [sqlite3_blob_open()] -
@ -8163,7 +8180,7 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*);
**
** ^The [sqlite3_preupdate_hook()] interface registers a callback function
** that is invoked prior to each [INSERT], [UPDATE], and [DELETE] operation
** on a [rowid table].
** on a database table.
** ^At most one preupdate hook may be registered at a time on a single
** [database connection]; each call to [sqlite3_preupdate_hook()] overrides
** the previous setting.
@ -8172,9 +8189,9 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*);
** ^The third parameter to [sqlite3_preupdate_hook()] is passed through as
** the first parameter to callbacks.
**
** ^The preupdate hook only fires for changes to [rowid tables]; the preupdate
** hook is not invoked for changes to [virtual tables] or [WITHOUT ROWID]
** tables.
** ^The preupdate hook only fires for changes to real database tables; the
** preupdate hook is not invoked for changes to [virtual tables] or to
** system tables like sqlite_master or sqlite_stat1.
**
** ^The second parameter to the preupdate callback is a pointer to
** the [database connection] that registered the preupdate hook.
@ -8188,12 +8205,16 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*);
** databases.)^
** ^The fifth parameter to the preupdate callback is the name of the
** table that is being modified.
** ^The sixth parameter to the preupdate callback is the initial [rowid] of the
** row being changes for SQLITE_UPDATE and SQLITE_DELETE changes and is
** undefined for SQLITE_INSERT changes.
** ^The seventh parameter to the preupdate callback is the final [rowid] of
** the row being changed for SQLITE_UPDATE and SQLITE_INSERT changes and is
** undefined for SQLITE_DELETE changes.
**
** For an UPDATE or DELETE operation on a [rowid table], the sixth
** parameter passed to the preupdate callback is the initial [rowid] of the
** row being modified or deleted. For an INSERT operation on a rowid table,
** or any operation on a WITHOUT ROWID table, the value of the sixth
** parameter is undefined. For an INSERT or UPDATE on a rowid table the
** seventh parameter is the final rowid value of the row being inserted
** or updated. The value of the seventh parameter passed to the callback
** function is not defined for operations on WITHOUT ROWID tables, or for
** INSERT operations on rowid tables.
**
** The [sqlite3_preupdate_old()], [sqlite3_preupdate_new()],
** [sqlite3_preupdate_count()], and [sqlite3_preupdate_depth()] interfaces
@ -8629,7 +8650,7 @@ typedef struct sqlite3_changeset_iter sqlite3_changeset_iter;
** attached database. It is not an error if database zDb is not attached
** to the database when the session object is created.
*/
int sqlite3session_create(
SQLITE_API int sqlite3session_create(
sqlite3 *db, /* Database handle */
const char *zDb, /* Name of db (e.g. "main") */
sqlite3_session **ppSession /* OUT: New session object */
@ -8647,7 +8668,7 @@ int sqlite3session_create(
** are attached is closed. Refer to the documentation for
** [sqlite3session_create()] for details.
*/
void sqlite3session_delete(sqlite3_session *pSession);
SQLITE_API void sqlite3session_delete(sqlite3_session *pSession);
/*
@ -8667,7 +8688,7 @@ void sqlite3session_delete(sqlite3_session *pSession);
** The return value indicates the final state of the session object: 0 if
** the session is disabled, or 1 if it is enabled.
*/
int sqlite3session_enable(sqlite3_session *pSession, int bEnable);
SQLITE_API int sqlite3session_enable(sqlite3_session *pSession, int bEnable);
/*
** CAPI3REF: Set Or Clear the Indirect Change Flag
@ -8696,7 +8717,7 @@ int sqlite3session_enable(sqlite3_session *pSession, int bEnable);
** The return value indicates the final state of the indirect flag: 0 if
** it is clear, or 1 if it is set.
*/
int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect);
SQLITE_API int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect);
/*
** CAPI3REF: Attach A Table To A Session Object
@ -8726,7 +8747,7 @@ int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect);
** SQLITE_OK is returned if the call completes without error. Or, if an error
** occurs, an SQLite error code (e.g. SQLITE_NOMEM) is returned.
*/
int sqlite3session_attach(
SQLITE_API int sqlite3session_attach(
sqlite3_session *pSession, /* Session object */
const char *zTab /* Table name */
);
@ -8740,7 +8761,7 @@ int sqlite3session_attach(
** If xFilter returns 0, changes is not tracked. Note that once a table is
** attached, xFilter will not be called again.
*/
void sqlite3session_table_filter(
SQLITE_API void sqlite3session_table_filter(
sqlite3_session *pSession, /* Session object */
int(*xFilter)(
void *pCtx, /* Copy of third arg to _filter_table() */
@ -8853,7 +8874,7 @@ void sqlite3session_table_filter(
** another field of the same row is updated while the session is enabled, the
** resulting changeset will contain an UPDATE change that updates both fields.
*/
int sqlite3session_changeset(
SQLITE_API int sqlite3session_changeset(
sqlite3_session *pSession, /* Session object */
int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */
void **ppChangeset /* OUT: Buffer containing changeset */
@ -8897,7 +8918,8 @@ int sqlite3session_changeset(
** the from-table, a DELETE record is added to the session object.
**
** <li> For each row (primary key) that exists in both tables, but features
** different in each, an UPDATE record is added to the session.
** different non-PK values in each, an UPDATE record is added to the
** session.
** </ul>
**
** To clarify, if this function is called and then a changeset constructed
@ -8914,7 +8936,7 @@ int sqlite3session_changeset(
** message. It is the responsibility of the caller to free this buffer using
** sqlite3_free().
*/
int sqlite3session_diff(
SQLITE_API int sqlite3session_diff(
sqlite3_session *pSession,
const char *zFromDb,
const char *zTbl,
@ -8950,7 +8972,7 @@ int sqlite3session_diff(
** a single table are grouped together, tables appear in the order in which
** they were attached to the session object).
*/
int sqlite3session_patchset(
SQLITE_API int sqlite3session_patchset(
sqlite3_session *pSession, /* Session object */
int *pnPatchset, /* OUT: Size of buffer at *ppChangeset */
void **ppPatchset /* OUT: Buffer containing changeset */
@ -8971,7 +8993,7 @@ int sqlite3session_patchset(
** guaranteed that a call to sqlite3session_changeset() will return a
** changeset containing zero changes.
*/
int sqlite3session_isempty(sqlite3_session *pSession);
SQLITE_API int sqlite3session_isempty(sqlite3_session *pSession);
/*
** CAPI3REF: Create An Iterator To Traverse A Changeset
@ -9006,7 +9028,7 @@ int sqlite3session_isempty(sqlite3_session *pSession);
** the applies to table X, then one for table Y, and then later on visit
** another change for table X.
*/
int sqlite3changeset_start(
SQLITE_API int sqlite3changeset_start(
sqlite3_changeset_iter **pp, /* OUT: New changeset iterator handle */
int nChangeset, /* Size of changeset blob in bytes */
void *pChangeset /* Pointer to blob containing changeset */
@ -9035,7 +9057,7 @@ int sqlite3changeset_start(
** codes include SQLITE_CORRUPT (if the changeset buffer is corrupt) or
** SQLITE_NOMEM.
*/
int sqlite3changeset_next(sqlite3_changeset_iter *pIter);
SQLITE_API int sqlite3changeset_next(sqlite3_changeset_iter *pIter);
/*
** CAPI3REF: Obtain The Current Operation From A Changeset Iterator
@ -9063,7 +9085,7 @@ int sqlite3changeset_next(sqlite3_changeset_iter *pIter);
** SQLite error code is returned. The values of the output variables may not
** be trusted in this case.
*/
int sqlite3changeset_op(
SQLITE_API int sqlite3changeset_op(
sqlite3_changeset_iter *pIter, /* Iterator object */
const char **pzTab, /* OUT: Pointer to table name */
int *pnCol, /* OUT: Number of columns in table */
@ -9096,7 +9118,7 @@ int sqlite3changeset_op(
** SQLITE_OK is returned and the output variables populated as described
** above.
*/
int sqlite3changeset_pk(
SQLITE_API int sqlite3changeset_pk(
sqlite3_changeset_iter *pIter, /* Iterator object */
unsigned char **pabPK, /* OUT: Array of boolean - true for PK cols */
int *pnCol /* OUT: Number of entries in output array */
@ -9126,7 +9148,7 @@ int sqlite3changeset_pk(
** If some other error occurs (e.g. an OOM condition), an SQLite error code
** is returned and *ppValue is set to NULL.
*/
int sqlite3changeset_old(
SQLITE_API int sqlite3changeset_old(
sqlite3_changeset_iter *pIter, /* Changeset iterator */
int iVal, /* Column number */
sqlite3_value **ppValue /* OUT: Old value (or NULL pointer) */
@ -9159,7 +9181,7 @@ int sqlite3changeset_old(
** If some other error occurs (e.g. an OOM condition), an SQLite error code
** is returned and *ppValue is set to NULL.
*/
int sqlite3changeset_new(
SQLITE_API int sqlite3changeset_new(
sqlite3_changeset_iter *pIter, /* Changeset iterator */
int iVal, /* Column number */
sqlite3_value **ppValue /* OUT: New value (or NULL pointer) */
@ -9186,7 +9208,7 @@ int sqlite3changeset_new(
** If some other error occurs (e.g. an OOM condition), an SQLite error code
** is returned and *ppValue is set to NULL.
*/
int sqlite3changeset_conflict(
SQLITE_API int sqlite3changeset_conflict(
sqlite3_changeset_iter *pIter, /* Changeset iterator */
int iVal, /* Column number */
sqlite3_value **ppValue /* OUT: Value from conflicting row */
@ -9202,7 +9224,7 @@ int sqlite3changeset_conflict(
**
** In all other cases this function returns SQLITE_MISUSE.
*/
int sqlite3changeset_fk_conflicts(
SQLITE_API int sqlite3changeset_fk_conflicts(
sqlite3_changeset_iter *pIter, /* Changeset iterator */
int *pnOut /* OUT: Number of FK violations */
);
@ -9235,7 +9257,7 @@ int sqlite3changeset_fk_conflicts(
** // An error has occurred
** }
*/
int sqlite3changeset_finalize(sqlite3_changeset_iter *pIter);
SQLITE_API int sqlite3changeset_finalize(sqlite3_changeset_iter *pIter);
/*
** CAPI3REF: Invert A Changeset
@ -9265,7 +9287,7 @@ int sqlite3changeset_finalize(sqlite3_changeset_iter *pIter);
** WARNING/TODO: This function currently assumes that the input is a valid
** changeset. If it is not, the results are undefined.
*/
int sqlite3changeset_invert(
SQLITE_API int sqlite3changeset_invert(
int nIn, const void *pIn, /* Input changeset */
int *pnOut, void **ppOut /* OUT: Inverse of input */
);
@ -9294,7 +9316,7 @@ int sqlite3changeset_invert(
**
** Refer to the sqlite3_changegroup documentation below for details.
*/
int sqlite3changeset_concat(
SQLITE_API int sqlite3changeset_concat(
int nA, /* Number of bytes in buffer pA */
void *pA, /* Pointer to buffer containing changeset A */
int nB, /* Number of bytes in buffer pB */
@ -9482,7 +9504,7 @@ void sqlite3changegroup_delete(sqlite3_changegroup*);
** <ul>
** <li> The table has the same name as the name recorded in the
** changeset, and
** <li> The table has the same number of columns as recorded in the
** <li> The table has at least as many columns as recorded in the
** changeset, and
** <li> The table has primary key columns in the same position as
** recorded in the changeset.
@ -9527,7 +9549,11 @@ void sqlite3changegroup_delete(sqlite3_changegroup*);
** If a row with matching primary key values is found, but one or more of
** the non-primary key fields contains a value different from the original
** row value stored in the changeset, the conflict-handler function is
** invoked with [SQLITE_CHANGESET_DATA] as the second argument.
** invoked with [SQLITE_CHANGESET_DATA] as the second argument. If the
** database table has more columns than are recorded in the changeset,
** only the values of those non-primary key fields are compared against
** the current database contents - any trailing database table columns
** are ignored.
**
** If no row with matching primary key values is found in the database,
** the conflict-handler function is invoked with [SQLITE_CHANGESET_NOTFOUND]
@ -9542,7 +9568,9 @@ void sqlite3changegroup_delete(sqlite3_changegroup*);
**
** <dt>INSERT Changes<dd>
** For each INSERT change, an attempt is made to insert the new row into
** the database.
** the database. If the changeset row contains fewer fields than the
** database table, the trailing fields are populated with their default
** values.
**
** If the attempt to insert the row fails because the database already
** contains a row with the same primary key values, the conflict handler
@ -9560,13 +9588,13 @@ void sqlite3changegroup_delete(sqlite3_changegroup*);
** For each UPDATE change, this function checks if the target database
** contains a row with the same primary key value (or values) as the
** original row values stored in the changeset. If it does, and the values
** stored in all non-primary key columns also match the values stored in
** the changeset the row is updated within the target database.
** stored in all modified non-primary key columns also match the values
** stored in the changeset the row is updated within the target database.
**
** If a row with matching primary key values is found, but one or more of
** the non-primary key fields contains a value different from an original
** row value stored in the changeset, the conflict-handler function is
** invoked with [SQLITE_CHANGESET_DATA] as the second argument. Since
** the modified non-primary key fields contains a value different from an
** original row value stored in the changeset, the conflict-handler function
** is invoked with [SQLITE_CHANGESET_DATA] as the second argument. Since
** UPDATE changes only contain values for non-primary key fields that are
** to be modified, only those fields need to match the original values to
** avoid the SQLITE_CHANGESET_DATA conflict-handler callback.
@ -9594,7 +9622,7 @@ void sqlite3changegroup_delete(sqlite3_changegroup*);
** rolled back, restoring the target database to its original state, and an
** SQLite error code returned.
*/
int sqlite3changeset_apply(
SQLITE_API int sqlite3changeset_apply(
sqlite3 *db, /* Apply change to "main" db of this handle */
int nChangeset, /* Size of changeset in bytes */
void *pChangeset, /* Changeset blob */
@ -9795,7 +9823,7 @@ int sqlite3changeset_apply(
** parameter set to a value less than or equal to zero. Other than this,
** no guarantees are made as to the size of the chunks of data returned.
*/
int sqlite3changeset_apply_strm(
SQLITE_API int sqlite3changeset_apply_strm(
sqlite3 *db, /* Apply change to "main" db of this handle */
int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */
void *pIn, /* First arg for xInput */
@ -9810,7 +9838,7 @@ int sqlite3changeset_apply_strm(
),
void *pCtx /* First argument passed to xConflict */
);
int sqlite3changeset_concat_strm(
SQLITE_API int sqlite3changeset_concat_strm(
int (*xInputA)(void *pIn, void *pData, int *pnData),
void *pInA,
int (*xInputB)(void *pIn, void *pData, int *pnData),
@ -9818,23 +9846,23 @@ int sqlite3changeset_concat_strm(
int (*xOutput)(void *pOut, const void *pData, int nData),
void *pOut
);
int sqlite3changeset_invert_strm(
SQLITE_API int sqlite3changeset_invert_strm(
int (*xInput)(void *pIn, void *pData, int *pnData),
void *pIn,
int (*xOutput)(void *pOut, const void *pData, int nData),
void *pOut
);
int sqlite3changeset_start_strm(
SQLITE_API int sqlite3changeset_start_strm(
sqlite3_changeset_iter **pp,
int (*xInput)(void *pIn, void *pData, int *pnData),
void *pIn
);
int sqlite3session_changeset_strm(
SQLITE_API int sqlite3session_changeset_strm(
sqlite3_session *pSession,
int (*xOutput)(void *pOut, const void *pData, int nData),
void *pOut
);
int sqlite3session_patchset_strm(
SQLITE_API int sqlite3session_patchset_strm(
sqlite3_session *pSession,
int (*xOutput)(void *pOut, const void *pData, int nData),
void *pOut

View File

@ -29,6 +29,7 @@ static int32_t gMinTimeoutValue = 0;
static int32_t gMinBackgroundTimeoutValue = 0;
static int32_t gMinTrackingTimeoutValue = 0;
static int32_t gMinTrackingBackgroundTimeoutValue = 0;
static int32_t gTrackingTimeoutThrottlingDelay = 0;
static bool gAnnotateTrackingChannels = false;
int32_t
TimeoutManager::DOMMinTimeoutValue(bool aIsTracking) const {
@ -39,7 +40,8 @@ TimeoutManager::DOMMinTimeoutValue(bool aIsTracking) const {
// The original behavior was implemented in bug 11811073.
bool isBackground = !mWindow.AsInner()->IsPlayingAudio() &&
mWindow.IsBackgroundInternal();
auto minValue = aIsTracking ? (isBackground ? gMinTrackingBackgroundTimeoutValue
bool throttleTracking = aIsTracking && mThrottleTrackingTimeouts;
auto minValue = throttleTracking ? (isBackground ? gMinTrackingBackgroundTimeoutValue
: gMinTrackingTimeoutValue)
: (isBackground ? gMinBackgroundTimeoutValue
: gMinTimeoutValue);
@ -53,6 +55,9 @@ TimeoutManager::DOMMinTimeoutValue(bool aIsTracking) const {
#define RANDOM_TIMEOUT_BUCKETING_STRATEGY 3 // Put timeouts into either the normal or tracking timeouts list randomly
static int32_t gTimeoutBucketingStrategy = 0;
#define DEFAULT_TRACKING_TIMEOUT_THROTTLING_DELAY -1 // Only positive integers cause us to introduce a delay for tracking
// timeout throttling.
// The number of nested timeouts before we start clamping. HTML5 says 1, WebKit
// uses 5.
#define DOM_CLAMP_TIMEOUT_NESTING_LEVEL 5
@ -121,7 +126,8 @@ TimeoutManager::TimeoutManager(nsGlobalWindow& aWindow)
mTimeoutFiringDepth(0),
mRunningTimeout(nullptr),
mIdleCallbackTimeoutCounter(1),
mBackPressureDelayMS(0)
mBackPressureDelayMS(0),
mThrottleTrackingTimeouts(gTrackingTimeoutThrottlingDelay <= 0)
{
MOZ_DIAGNOSTIC_ASSERT(aWindow.IsInnerWindow());
@ -132,6 +138,8 @@ TimeoutManager::TimeoutManager(nsGlobalWindow& aWindow)
TimeoutManager::~TimeoutManager()
{
MOZ_DIAGNOSTIC_ASSERT(!mThrottleTrackingTimeoutsTimer);
MOZ_LOG(gLog, LogLevel::Debug,
("TimeoutManager %p destroyed\n", this));
}
@ -155,6 +163,9 @@ TimeoutManager::Initialize()
Preferences::AddIntVarCache(&gTimeoutBucketingStrategy,
"dom.timeout_bucketing_strategy",
TRACKING_SEPARATE_TIMEOUT_BUCKETING_STRATEGY);
Preferences::AddIntVarCache(&gTrackingTimeoutThrottlingDelay,
"dom.timeout.tracking_throttling_delay",
DEFAULT_TRACKING_TIMEOUT_THROTTLING_DELAY);
Preferences::AddBoolVarCache(&gAnnotateTrackingChannels,
"privacy.trackingprotection.annotate_channels",
false);
@ -328,12 +339,15 @@ TimeoutManager::SetTimeout(nsITimeoutHandler* aHandler,
*aReturn = timeout->mTimeoutId;
MOZ_LOG(gLog, LogLevel::Debug,
("Set%s(TimeoutManager=%p, timeout=%p, "
"delay=%i, minimum=%i, background=%d, realInterval=%i) "
("Set%s(TimeoutManager=%p, timeout=%p, delay=%i, "
"minimum=%i, throttling=%s, background=%d, realInterval=%i) "
"returned %stracking timeout ID %u\n",
aIsInterval ? "Interval" : "Timeout",
this, timeout.get(), interval,
DOMMinTimeoutValue(timeout->mIsTracking),
mThrottleTrackingTimeouts ? "yes"
: (mThrottleTrackingTimeoutsTimer ?
"pending" : "no"),
int(mWindow.IsBackgroundInternal()), realInterval,
timeout->mIsTracking ? "" : "non-",
timeout->mTimeoutId));
@ -1214,3 +1228,79 @@ TimeoutManager::IsTimeoutTracking(uint32_t aTimeoutId)
return aTimeout->mTimeoutId == aTimeoutId;
});
}
namespace {
class ThrottleTrackingTimeoutsCallback final : public nsITimerCallback
{
public:
explicit ThrottleTrackingTimeoutsCallback(nsGlobalWindow* aWindow)
: mWindow(aWindow)
{
MOZ_DIAGNOSTIC_ASSERT(aWindow->IsInnerWindow());
}
NS_DECL_ISUPPORTS
NS_DECL_NSITIMERCALLBACK
private:
~ThrottleTrackingTimeoutsCallback() {}
private:
// The strong reference here keeps the Window and hence the TimeoutManager
// object itself alive.
RefPtr<nsGlobalWindow> mWindow;
};
NS_IMPL_ISUPPORTS(ThrottleTrackingTimeoutsCallback, nsITimerCallback)
NS_IMETHODIMP
ThrottleTrackingTimeoutsCallback::Notify(nsITimer* aTimer)
{
mWindow->AsInner()->TimeoutManager().StartThrottlingTrackingTimeouts();
mWindow = nullptr;
return NS_OK;
}
}
void
TimeoutManager::StartThrottlingTrackingTimeouts()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_DIAGNOSTIC_ASSERT(mThrottleTrackingTimeoutsTimer);
MOZ_LOG(gLog, LogLevel::Debug,
("TimeoutManager %p started to throttle tracking timeouts\n", this));
MOZ_DIAGNOSTIC_ASSERT(!mThrottleTrackingTimeouts);
mThrottleTrackingTimeouts = true;
mThrottleTrackingTimeoutsTimer = nullptr;
}
void
TimeoutManager::OnDocumentLoaded()
{
if (gTrackingTimeoutThrottlingDelay <= 0) {
return;
}
MOZ_DIAGNOSTIC_ASSERT(!mThrottleTrackingTimeouts);
MOZ_LOG(gLog, LogLevel::Debug,
("TimeoutManager %p delaying tracking timeout throttling by %dms\n",
this, gTrackingTimeoutThrottlingDelay));
mThrottleTrackingTimeoutsTimer =
do_CreateInstance("@mozilla.org/timer;1");
if (!mThrottleTrackingTimeoutsTimer) {
return;
}
nsCOMPtr<nsITimerCallback> callback =
new ThrottleTrackingTimeoutsCallback(&mWindow);
mThrottleTrackingTimeoutsTimer->InitWithCallback(callback,
gTrackingTimeoutThrottlingDelay,
nsITimer::TYPE_ONE_SHOT);
}

View File

@ -91,6 +91,10 @@ public:
// Exposed only for testing
bool IsTimeoutTracking(uint32_t aTimeoutId);
// The document finished loading
void OnDocumentLoaded();
void StartThrottlingTrackingTimeouts();
// Run some code for each Timeout in our list. Note that this function
// doesn't guarantee that Timeouts are iterated in any particular order.
template <class Callable>
@ -209,6 +213,9 @@ private:
int32_t mBackPressureDelayMS;
nsCOMPtr<nsITimer> mThrottleTrackingTimeoutsTimer;
bool mThrottleTrackingTimeouts;
static uint32_t sNestingLevel;
};

View File

@ -689,6 +689,23 @@ nsGlobalWindow::ScheduleIdleRequestDispatch()
mIdleRequestExecutor->MaybeDispatch();
}
void
nsGlobalWindow::SuspendIdleRequests()
{
if (mIdleRequestExecutor) {
mIdleRequestExecutor->Cancel();
mIdleRequestExecutor = nullptr;
}
}
void
nsGlobalWindow::ResumeIdleRequests()
{
MOZ_ASSERT(!mIdleRequestExecutor);
ScheduleIdleRequestDispatch();
}
void
nsGlobalWindow::InsertIdleCallback(IdleRequest* aRequest)
{
@ -816,9 +833,10 @@ nsGlobalWindow::RequestIdleCallback(JSContext* aCx,
}
// If the list of idle callback requests is not empty it means that
// we've already dispatched the first idle request. It is the
// responsibility of that to dispatch the next.
bool needsScheduling = mIdleRequestCallbacks.isEmpty();
// we've already dispatched the first idle request. If we're
// suspended we should only queue the idle callback and not schedule
// it to run, that will be done in ResumeIdleRequest.
bool needsScheduling = !IsSuspended() && mIdleRequestCallbacks.isEmpty();
// mIdleRequestCallbacks now owns request
InsertIdleCallback(request);
@ -3754,6 +3772,8 @@ nsGlobalWindow::PostHandleEvent(EventChainPostVisitor& aVisitor)
// @see nsDocument::GetEventTargetParent.
mIsDocumentLoaded = true;
mTimeoutManager->OnDocumentLoaded();
nsCOMPtr<Element> element = GetOuterWindow()->GetFrameElementInternal();
nsIDocShell* docShell = GetDocShell();
if (element && GetParentInternal() &&
@ -12146,6 +12166,8 @@ nsGlobalWindow::Suspend()
mozilla::dom::workers::SuspendWorkersForWindow(AsInner());
SuspendIdleRequests();
mTimeoutManager->Suspend();
// Suspend all of the AudioContexts for this window
@ -12200,6 +12222,8 @@ nsGlobalWindow::Resume()
mTimeoutManager->Resume();
ResumeIdleRequests();
// Resume all of the workers for this window. We must do this
// after timeouts since workers may have queued events that can trigger
// a setTimeout().

View File

@ -1803,6 +1803,8 @@ public:
DOMHighResTimeStamp aDeadline, bool aDidTimeout);
nsresult ExecuteIdleRequest(TimeStamp aDeadline);
void ScheduleIdleRequestDispatch();
void SuspendIdleRequests();
void ResumeIdleRequests();
typedef mozilla::LinkedList<mozilla::dom::IdleRequest> IdleRequests;
void InsertIdleCallback(mozilla::dom::IdleRequest* aRequest);

View File

@ -36,12 +36,14 @@ function* runTest(url) {
let before = new Date();
content.window.setTimeout(function() {
let after = new Date();
resolve(after - before);
// Sometimes due to rounding errors, we may get a result of 9ms here, so
// let's round up by 1 to protect against such intermittent failures.
resolve(after - before + 1);
}, 0);
});
});
ok(timeout >= kMinTimeoutForeground &&
timeout <= kMinTimeoutBackground, `Got the correct timeout (${timeout})`);
timeout < kMinTimeoutBackground, `Got the correct timeout (${timeout})`);
// All done.
yield BrowserTestUtils.removeTab(newTab);

View File

@ -201,6 +201,7 @@ skip-if = toolkit != 'cocoa'
disabled = bug 407107
[test_2d.strokeRect.zero.5.html]
[test_bitmaprenderer.html]
skip-if = android_version == '25' # bug 1336581
[test_bug232227.html]
[test_bug613794.html]
[test_bug764125.html]
@ -227,16 +228,21 @@ support-files = captureStream_common.js
[test_drawWindow.html]
support-files = file_drawWindow_source.html file_drawWindow_common.js
[test_imagebitmap.html]
skip-if = android_version == '17' || android_version == '19' # bug 1336581
tags = imagebitmap
[test_imagebitmap_close.html]
tags = imagebitmap
[test_imagebitmap_cropping.html]
skip-if = android_version >= '17' # bug 1336581
tags = imagebitmap
[test_imagebitmap_extensions.html]
skip-if = android_version >= '17' # bug 1336581
tags = imagebitmap
[test_imagebitmap_extensions_on_worker.html]
skip-if = android_version == '19' # bug 1338256
tags = imagebitmap
[test_imagebitmap_on_worker.html]
skip-if = android_version >= '17' # bug 1336581
tags = imagebitmap
[test_imagebitmap_structuredclone.html]
tags = imagebitmap

View File

@ -93,8 +93,10 @@ PointerEvent::Constructor(EventTarget* aOwner,
widgetEvent->mWidth = aParam.mWidth;
widgetEvent->mHeight = aParam.mHeight;
widgetEvent->pressure = aParam.mPressure;
widgetEvent->tangentialPressure = aParam.mTangentialPressure;
widgetEvent->tiltX = aParam.mTiltX;
widgetEvent->tiltY = aParam.mTiltY;
widgetEvent->twist = aParam.mTwist;
widgetEvent->inputSource = ConvertStringToPointerType(aParam.mPointerType);
widgetEvent->mIsPrimary = aParam.mIsPrimary;
widgetEvent->buttons = aParam.mButtons;
@ -145,6 +147,12 @@ PointerEvent::Pressure()
return mEvent->AsPointerEvent()->pressure;
}
float
PointerEvent::TangentialPressure()
{
return mEvent->AsPointerEvent()->tangentialPressure;
}
int32_t
PointerEvent::TiltX()
{
@ -157,6 +165,12 @@ PointerEvent::TiltY()
return mEvent->AsPointerEvent()->tiltY;
}
int32_t
PointerEvent::Twist()
{
return mEvent->AsPointerEvent()->twist;
}
bool
PointerEvent::IsPrimary()
{

View File

@ -44,8 +44,10 @@ public:
int32_t Width();
int32_t Height();
float Pressure();
float TangentialPressure();
int32_t TiltX();
int32_t TiltY();
int32_t Twist();
bool IsPrimary();
void GetPointerType(nsAString& aPointerType);
};

View File

@ -0,0 +1,102 @@
<!doctype html>
<html>
<head>
<title>Pointer Event: touch-action test for two-finger interaction</title>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
<link rel="author" title="Google" href="http://www.google.com "/>
<link rel="help" href="https://compat.spec.whatwg.org/#touch-action" />
<meta name="assert" content="Tests that a two-finger pan gesture is cancelled in 'touch-action: pan-x pan-y' but is allowed in 'touch-action: pinch-zoom'"/>
<link rel="stylesheet" type="text/css" href="../pointerevent_styles.css">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script type="text/javascript" src="../pointerevent_support.js"></script>
<script type="text/javascript">
var event_log = [];
var active_pointers = 0;
function resetTestState() {
event_log = [];
active_pointers = 0;
}
function run() {
var test_pointer_events = [
setup_pointerevent_test("two-finger pan on 'touch-action: pan-x pan-y'", ["touch"]),
setup_pointerevent_test("two-finger pan on 'touch-action: pinch-zoom'", ["touch"])
];
var expected_events = [
"pointerdown@black, pointerdown@black, pointerup@black, pointerup@black",
"pointerdown@grey, pointerdown@grey, pointercancel@grey, pointercancel@grey"
];
var current_test_index = 0;
on_event(document.getElementById("done"), "click", function() {
test_pointer_events[current_test_index].step(function () {
assert_equals(active_pointers, 0);
assert_equals(event_log.join(", "), expected_events[current_test_index]);
});
event_log = [];
test_pointer_events[current_test_index++].done();
});
var targets = [document.getElementById("black"), document.getElementById("grey")];
["pointerdown", "pointerup", "pointercancel"].forEach(function(eventName) {
targets.forEach(function(target){
on_event(target, eventName, function (event) {
event_log.push(event.type + "@" + event.target.id);
if (event.type == "pointerdown") {
active_pointers++;
} else {
active_pointers--;
}
});
});
});
}
</script>
<style>
.box {
width: 250px;
height: 150px;
float: left;
margin: 10px;
}
#black {
touch-action: pan-x pan-y;
background-color: black;
}
#grey {
touch-action: pinch-zoom;
background-color: grey;
}
#done {
float: left;
padding: 20px;
}
</style>
</head>
<body onload="run()">
<h1>Pointer Event: touch-action test for two-finger interaction</h1>
<h2 id="pointerTypeDescription"></h2>
<h4>
Tests that a two-finger pan gesture is cancelled in 'touch-action: pan-x pan-y' but is allowed in 'touch-action: pinch-zoom'
</h4>
<ol>
<li>Touch on Black with two fingers and drag both fingers down at same speed.</li>
<li>Tap on Done.</li>
<li>Touch on Grey with two fingers and drag both fingers down at same speed.</li>
<li>Tap on Done.</li>
</ol>
<div class="box" id="black"></div>
<input type="button" id="done" value="Done" />
<div class="box" id="grey"></div>
<div id="log"></div>
</body>
</html>

View File

@ -0,0 +1,104 @@
<!doctype html>
<meta charset=utf-8>
<title>idlharness test</title>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<script src="/resources/WebIDLParser.js"></script>
<script src="/resources/idlharness.js"></script>
<pre id='untested_idl' style='display:none'>
[PrimaryGlobal]
interface Window {
};
[TreatNonObjectAsNull]
callback EventHandlerNonNull = any (Event event);
typedef EventHandlerNonNull? EventHandler;
[NoInterfaceObject]
interface GlobalEventHandlers {
};
Window implements GlobalEventHandlers;
interface Navigator {
};
interface Element {
};
interface HTMLElement : Element {
};
HTMLElement implements GlobalEventHandlers;
interface Document {
};
Document implements GlobalEventHandlers;
interface MouseEvent {
};
</pre>
<pre id='idl'>
dictionary PointerEventInit : MouseEventInit {
long pointerId = 0;
double width = 1;
double height = 1;
float pressure = 0;
float tangentialPressure = 0;
long tiltX = 0;
long tiltY = 0;
long twist = 0;
DOMString pointerType = "";
boolean isPrimary = false;
};
[Constructor(DOMString type, optional PointerEventInit eventInitDict)]
interface PointerEvent : MouseEvent {
readonly attribute long pointerId;
readonly attribute double width;
readonly attribute double height;
readonly attribute float pressure;
readonly attribute float tangentialPressure;
readonly attribute long tiltX;
readonly attribute long tiltY;
readonly attribute long twist;
readonly attribute DOMString pointerType;
readonly attribute boolean isPrimary;
};
partial interface Element {
void setPointerCapture(long pointerId);
void releasePointerCapture(long pointerId);
boolean hasPointerCapture(long pointerId);
};
partial interface GlobalEventHandlers {
attribute EventHandler ongotpointercapture;
attribute EventHandler onlostpointercapture;
attribute EventHandler onpointerdown;
attribute EventHandler onpointermove;
attribute EventHandler onpointerup;
attribute EventHandler onpointercancel;
attribute EventHandler onpointerover;
attribute EventHandler onpointerout;
attribute EventHandler onpointerenter;
attribute EventHandler onpointerleave;
};
partial interface Navigator {
readonly attribute long maxTouchPoints;
};
</pre>
<script>
var idl_array = new IdlArray();
idl_array.add_untested_idls(document.getElementById("untested_idl").textContent);
idl_array.add_idls(document.getElementById("idl").textContent);
// Note that I don't bother including Document here because there are still
// a bunch of differences between browsers around Document vs HTMLDocument.
idl_array.add_objects({
Window: ["window"],
Navigator: ["navigator"]});
idl_array.test();
</script>

View File

@ -28,8 +28,6 @@ support-files =
support-files = pointerevent_element_haspointercapture-manual.html
[test_pointerevent_element_haspointercapture_release_pending_capture-manual.html]
support-files = pointerevent_element_haspointercapture_release_pending_capture-manual.html
[test_pointerevent_gotpointercapture_before_first_pointerevent-manual.html]
support-files = pointerevent_gotpointercapture_before_first_pointerevent-manual.html
[test_pointerevent_lostpointercapture_for_disconnected_node-manual.html]
support-files = pointerevent_lostpointercapture_for_disconnected_node-manual.html
[test_pointerevent_lostpointercapture_is_first-manual.html]

View File

@ -0,0 +1,97 @@
<!doctype html>
<html>
<head>
<title>Pointer Events boundary events in capturing tests</title>
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<!-- Additional helper script for common checks across event types -->
<script type="text/javascript" src="pointerevent_support.js"></script>
<script>
var detected_pointertypes = {};
var eventList = All_Pointer_Events;
PhaseEnum = {
WaitingForDown: "down",
WaitingForFirstMove: "firstMove",
WaitingForSecondMove: "secondMove",
WaitingForUp: "up"
}
var phase = PhaseEnum.WaitingForDown;
var eventsRecieved = [];
function resetTestState() {
eventsRecieved = [];
phase = PhaseEnum.WaitingForDown;
}
function run() {
var test_pointerEvent = setup_pointerevent_test("pointerevent boundary events in capturing", ALL_POINTERS);
var target = document.getElementById("target0");
var listener = document.getElementById("listener");
eventList.forEach(function(eventName) {
on_event(target, eventName, function (event) {
if (phase == PhaseEnum.WaitingForDown) {
if (eventName == 'pointerdown') {
listener.setPointerCapture(event.pointerId);
phase = PhaseEnum.WaitingForFirstMove;
}
} else if (phase == PhaseEnum.WaitingForUp) {
if (event.type == 'pointerup')
test_pointerEvent.done();
} else {
eventsRecieved.push(event.type + '@target');
if (phase == PhaseEnum.WaitingForSecondMove && event.type == 'pointermove') {
test(function () {
checkPointerEventType(event);
assert_array_equals(eventsRecieved, ['lostpointercapture@listener', 'pointerout@listener', 'pointerleave@listener', 'pointerover@target', 'pointerenter@target', 'pointermove@target'],
'lostpointercapture and pointerout/leave should be dispatched to the capturing target and pointerover/enter should be dispatched to the hit-test element before the first pointermove event after releasing pointer capture');
}, expectedPointerType + " pointer events boundary events when releasing capture");
phase = PhaseEnum.WaitingForUp;
}
}
});
on_event(listener, eventName, function (event) {
if (phase == PhaseEnum.WaitingForDown)
return;
eventsRecieved.push(event.type + '@listener');
if (phase == PhaseEnum.WaitingForFirstMove && eventName == 'pointermove') {
test(function () {
checkPointerEventType(event);
assert_array_equals(eventsRecieved, ['pointerout@target', 'pointerleave@target', 'pointerover@listener', 'pointerenter@listener', 'gotpointercapture@listener', 'pointermove@listener'],
'pointerout/leave should be dispatched to the previous target and pointerover/enter and gotpointercapture should be dispatched to the capturing element before the first captured pointermove event');
}, expectedPointerType + " pointer events boundary events when receiving capture");
listener.releasePointerCapture(event.pointerId);
eventsRecieved = [];
phase = PhaseEnum.WaitingForSecondMove;
}
});
});
}
</script>
</head>
<body onload="run()">
<h1>Pointer Events boundary events in capturing</h1>
<h2 id="pointerTypeDescription"></h2>
<h4>
Test Description: This test checks the boundary events of pointer events while the capturing changes. If you are using hoverable pen don't leave the range of digitizer while doing the instructions.
<ol>
<li>Move your pointer over the black square</li>
<li>Press down the pointer (i.e. press left button with mouse or touch the screen with finger or pen).</li>
<li>Drag the pointer within the black square.</li>
<li>Release the pointer.</li>
</ol>
Test passes if the proper behavior of the events is observed.
</h4>
<div id="target0" class="touchActionNone">
</div>
<div id="listener">Do not hover over or touch this element. </div>
<div id="complete-notice">
<p>The following pointer types were detected: <span id="pointertype-log"></span>.</p>
<p>Refresh the page to run the tests again with a different pointer type.</p>
</div>
<div id="log"></div>
</body>
</html>

View File

@ -1,97 +0,0 @@
<!doctype html>
<html>
<head>
<title>Pointer Event: gotpiontercapture is fired first and asynchronously.</title>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
<link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
<meta name="assert" content="After setting capture, the gotpointercapture dispatched to the capturing element before any other event is fired." />
<link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
<!-- /resources/testharness.js -->
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<!-- Additional helper script for common checks across event types -->
<script type="text/javascript" src="pointerevent_support.js"></script>
<script type="text/javascript">
var detected_pointertypes = {};
var test_pointerEvent = async_test("gotpointercapture event"); // set up test harness
// showPointerTypes is defined in pointerevent_support.js
// Requirements: the callback function will reference the test_pointerEvent object and
// will fail unless the async_test is created with the var name "test_pointerEvent".
add_completion_callback(showPointerTypes);
var target0;
var listener;
var pointerdown_event;
var detected_pointerEvents = new Array();
var eventRcvd = false;
var isWaiting = false;
function run() {
target0 = document.getElementById("target0");
target0.style["touchAction"] = "none";
listener = document.getElementById("listener");
// listen to all events
for (var i = 0; i < All_Pointer_Events.length; i++) {
on_event(listener, All_Pointer_Events[i], function (event) {
if (event.type == "gotpointercapture") {
check_PointerEvent(event);
// TA: 10.2
assert_true(isWaiting, "gotpointercapture must be fired asynchronously");
isWaiting = false;
// if any events have been received with same pointerId before gotpointercapture, then fail
var eventsRcvd_str = "";
if (eventRcvd) {
eventsRcvd_str = "Events received before gotpointercapture: ";
for (var i = 0; i < detected_pointerEvents.length; i++) {
eventsRcvd_str += detected_pointerEvents[i] + ", ";
}
}
test_pointerEvent.step(function () {
assert_false(eventRcvd, "no other events should be received before gotpointercapture." + eventsRcvd_str);
assert_equals(event.pointerId, pointerdown_event.pointerId, "pointerID is same for pointerdown and gotpointercapture");
});
}
else {
if (pointerdown_event.pointerId === event.pointerId) {
assert_false(isWaiting, event.type + " must be fired after gotpointercapture");
detected_pointerEvents.push(event.type);
eventRcvd = true;
test_pointerEvent.done(); // complete test
}
}
});
}
// set pointer capture
on_event(target0, "pointerdown", function (event) {
detected_pointertypes[event.pointerType] = true;
pointerdown_event = event;
listener.setPointerCapture(event.pointerId);
isWaiting = true;
});
}
</script>
</head>
<body onload="run()">
<h1>Pointer Event: Dispatch gotpointercapture event</h1>
<h4>Test Description:
After pointer capture is set for a pointer, and prior to dispatching the first event for the pointer, the gotpointercapture
event must be dispatched to the element that is receiving the pointer capture. The gotpointercapture event must be dispatched asynchronously.
</h4>
<br />
<div id="target0">
Use the mouse, touch or pen to tap/click this box.
</div>
<div id="listener">Do not hover over or touch this element. </div>
<div id="complete-notice">
<p>Test complete: Scroll to Summary to view Pass/Fail Results.</p>
<p>The following pointer types were detected: <span id="pointertype-log"></span>.</p>
<p>Refresh the page to run the tests again with a different pointer type.</p>
</div>
<div id="log"></div>
</body>
</html>

View File

@ -46,7 +46,7 @@
log("gotpointercapture", target1);
setTimeout(function() {
isDisconnected = true;
target0.remove();
target0.parentNode.removeChild(target0);
}, 250);
});

View File

@ -0,0 +1,67 @@
<!doctype html>
<html>
<head>
<title>PointerEvent: Constructor test</title>
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<!-- Additional helper script for common checks across event types -->
<script type="text/javascript" src="pointerevent_support.js"></script>
</head>
<body>
<h1>PointerEvent: Dispatch custom event</h1>
<h4>Test Description: This test checks if on pointer event handlers through attributes works properly using synthetic pointerevents. For valid results, this test must be run without generating real (trusted) events on the black rectangle below.</h4>
<div id="target0"
onpointercancel="window.eventHappened = 'pointercancel';"
onpointerdown="window.eventHappened = 'pointerdown';"
onpointerup="window.eventHappened = 'pointerup';"
onpointermove="window.eventHappened = 'pointermove';"
onpointerout="window.eventHappened = 'pointerout';"
onpointerover="window.eventHappened = 'pointerover';"
onpointerleave="window.eventHappened = 'pointerleave';"
onpointerenter="window.eventHappened = 'pointerenter';"
ongotpointercapture="window.eventHappened = 'gotpointercapture';"
onlostpointercapture="window.eventHappened = 'lostpointercapture';"
></div>
<script>
window.eventHappened = '';
All_Pointer_Events.forEach(function(event) {
var on_event = "on" + event;
test(function() {
const htmlElement = document.createElement("span");
const svgElement = document.createElementNS("http://www.w3.org/2000/svg", "g");
for (const location of [window, htmlElement, svgElement, document]) {
assert_equals(location[on_event], null,
`The default value of the property is null for a ${location.constructor.name} instance`);
}
}, "The default value of " + on_event + " is always null");
test(function() {
window.eventHappened = '';
const element = document.querySelector("#target0");
const compiledHandler = element[on_event];
assert_equals(typeof compiledHandler, "function", "The " + on_event + " property must be a function");
compiledHandler();
assert_equals(window.eventHappened, event, "Calling the handler must run the code");
}, "The " + on_event + " content attribute must be compiled into the " + on_event + " property");
var handlerTest = async_test("dispatching a " + event + " event must trigger element." + on_event);
const element = document.createElement("meta");
element[on_event] = function(e) {
handlerTest.step(function() {
assert_equals(e.currentTarget, element, "The event must be fired at the <meta> element");
});
handlerTest.done();
};
element.dispatchEvent(new Event(event));
});
</script>
<div id="complete-notice">
<p>The following pointer types were detected: <span id="pointertype-log"></span>.</p>
</div>
<div id="log"></div>
</body>
</html>

View File

@ -0,0 +1,83 @@
<!doctype html>
<html>
<head>
<title>Pointer Event: Event sequence at implicit release on click</title>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
<link rel="author" title="Google" href="http://www.google.com "/>
<meta name="assert" content="When a captured pointer is implicitly released after a click, the boundary events should follow the lostpointercapture event."/>
<link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script type="text/javascript" src="pointerevent_support.js"></script>
<script type="text/javascript">
var detected_pointertypes = {};
var event_log = [];
var start_logging = false;
function resetTestState() {
detected_eventTypes = {};
event_log = [];
start_logging = false;
}
function run() {
var test_pointer_event = setup_pointerevent_test("Event sequence at implicit release on click", ALL_POINTERS);
on_event(document.getElementById("done"), "click", function() {
test_pointer_event.step(function () {
var expected_events = "pointerup, lostpointercapture, pointerout, pointerleave";
assert_equals(event_log.join(", "), expected_events);
});
test_pointer_event.done();
});
var target = document.getElementById("target");
All_Pointer_Events.forEach(function(eventName) {
on_event(target, eventName, function (event) {
detected_pointertypes[event.pointerType] = true;
if (event.type == "pointerdown") {
event.target.setPointerCapture(event.pointerId);
} else if (event.type == "gotpointercapture") {
start_logging = true;
} else if (event.type != "pointermove" && start_logging) {
event_log.push(event.type);
}
});
});
}
</script>
<style>
#target {
margin: 20px;
background-color: black;
}
#done {
margin: 20px;
background-color: green;
}
</style>
</head>
<body onload="run()">
<h1>Pointer Event: Event sequence at implicit release on click<h1>
<h2 id="pointerTypeDescription"></h2>
<h4>
When a captured pointer is implicitly released after a click, the boundary events should follow the lostpointercapture event.
</h4>
<ol>
<li>Click or tap on Black.</li>
<li>Click or tap on Green.</li>
</ol>
<div id="target"></div>
<div id="done"></div>
<div id="complete-notice">
<p>The following pointer types were detected: <span id="pointertype-log"></span>.</p>
<p>The following events were logged: <span id="event-log"></span>.</p>
</div>
<div id="log"></div>
</body>
</html>

View File

@ -0,0 +1,84 @@
<!doctype html>
<html>
<head>
<title>Pointer Event: Event sequence at implicit release on drag</title>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
<link rel="author" title="Google" href="http://www.google.com "/>
<meta name="assert" content="When a captured pointer is implicitly released after a drag, the boundary events should follow the lostpointercapture event."/>
<link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script type="text/javascript" src="pointerevent_support.js"></script>
<script type="text/javascript">
var detected_pointertypes = {};
var event_log = [];
var start_logging = false;
function resetTestState() {
detected_eventTypes = {};
event_log = [];
start_logging = false;
}
function run() {
var test_pointer_event = setup_pointerevent_test("Event sequence at implicit release on drag", ["touch"]);
on_event(document.getElementById("done"), "click", function() {
test_pointer_event.step(function () {
var expected_events = "pointercancel, lostpointercapture, pointerout, pointerleave";
assert_equals(event_log.join(", "), expected_events);
});
test_pointer_event.done();
});
var target = document.getElementById("target");
All_Pointer_Events.forEach(function(eventName) {
on_event(target, eventName, function (event) {
detected_pointertypes[event.pointerType] = true;
if (event.type == "pointerdown") {
event.target.setPointerCapture(event.pointerId);
} else if (event.type == "gotpointercapture") {
start_logging = true;
} else if (event.type != "pointermove" && start_logging) {
event_log.push(event.type);
}
});
});
}
</script>
<style>
#target {
margin: 20px;
background-color: black;
touch-action: auto;
}
#done {
margin: 20px;
background-color: green;
}
</style>
</head>
<body onload="run()">
<h1>Pointer Event: Event sequence at implicit release on drag<h1>
<h2 id="pointerTypeDescription"></h2>
<h4>
When a captured pointer is implicitly released after a drag, the boundary events should follow the lostpointercapture event.
</h4>
<ol>
<li>Drag quickly down starting on Black.</li>
<li>Click or tap on Green.</li>
</ol>
<div id="target"></div>
<div id="done"></div>
<div id="complete-notice">
<p>The following pointer types were detected: <span id="pointertype-log"></span>.</p>
<p>The following events were logged: <span id="event-log"></span>.</p>
</div>
<div id="log"></div>
</body>
</html>

View File

@ -16,7 +16,7 @@
var target0 = document.getElementById("target0");
var target1 = document.getElementById("target1");
target1.remove();
target1.parentNode.removeChild(target1);
on_event(target0, "pointerdown", function (event) {
detected_pointertypes[ event.pointerType ] = true;

View File

@ -21,7 +21,7 @@ function check_PointerEvent(event, testNamePrefix) {
if (expectedPointerType != null) {
test(function () {
assert_equals(event.pointerType, expectedPointerType, "pointerType should be the same as the requested device.");
assert_equals(event.pointerType, expectedPointerType, "pointerType should be the one specified in the test page.");
}, pointerTestName + " event pointerType is correct.");
}
@ -201,6 +201,7 @@ function rPointerCapture(e) {
var globalPointerEventTest = null;
var expectedPointerType = null;
const ALL_POINTERS = ['mouse', 'touch', 'pen'];
const HOVERABLE_POINTERS = ['mouse', 'pen'];
const NOHOVER_POINTERS = ['touch'];
@ -225,6 +226,10 @@ MultiPointerTypeTest.prototype.done = function() {
prevTest.done();
}
MultiPointerTypeTest.prototype.step = function(stepFunction) {
this.currentTest.step(stepFunction);
}
MultiPointerTypeTest.prototype.createNextTest = function() {
if (this.currentTypeIndex < this.types.length) {
var pointerTypeDescription = document.getElementById('pointerTypeDescription');
@ -242,3 +247,7 @@ MultiPointerTypeTest.prototype.createNextTest = function() {
function setup_pointerevent_test(testName, supportedPointerTypes) {
return globalPointerEventTest = new MultiPointerTypeTest(testName, supportedPointerTypes);
}
function checkPointerEventType(event) {
assert_equals(event.pointerType, expectedPointerType, "pointerType should be the same as the requested device.");
}

View File

@ -1,27 +0,0 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1000870
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 1000870</title>
<meta name="author" content="Maksim Lebedev" />
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<script type="text/javascript" src="mochitest_support_external.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="text/javascript">
SimpleTest.waitForExplicitFinish();
function startTest() {
runTestInNewWindow("pointerevent_gotpointercapture_before_first_pointerevent-manual.html");
}
function executeTest(int_win) {
sendMouseEvent(int_win, "target0", "mousedown");
sendMouseEvent(int_win, "target0", "mouseup");
}
</script>
</head>
<body>
</body>
</html>

View File

@ -13,7 +13,6 @@
#include "nsIOutputStream.h"
#include "nsIHttpChannel.h"
#include "nsIHttpChannelInternal.h"
#include "nsIHttpHeaderVisitor.h"
#include "nsIScriptSecurityManager.h"
#include "nsIThreadRetargetableRequest.h"
#include "nsIUploadChannel2.h"
@ -416,38 +415,6 @@ FetchDriver::FailWithNetworkError()
}
}
namespace {
class FillResponseHeaders final : public nsIHttpHeaderVisitor {
InternalResponse* mResponse;
~FillResponseHeaders()
{ }
public:
NS_DECL_ISUPPORTS
explicit FillResponseHeaders(InternalResponse* aResponse)
: mResponse(aResponse)
{
}
NS_IMETHOD
VisitHeader(const nsACString & aHeader, const nsACString & aValue) override
{
ErrorResult result;
mResponse->Headers()->Append(aHeader, aValue, result);
if (result.Failed()) {
NS_WARNING(nsPrintfCString("Fetch ignoring illegal header - '%s': '%s'",
PromiseFlatCString(aHeader).get(),
PromiseFlatCString(aValue).get()).get());
result.SuppressException();
}
return NS_OK;
}
};
NS_IMPL_ISUPPORTS(FillResponseHeaders, nsIHttpHeaderVisitor)
} // namespace
NS_IMETHODIMP
FetchDriver::OnStartRequest(nsIRequest* aRequest,
nsISupports* aContext)
@ -501,11 +468,7 @@ FetchDriver::OnStartRequest(nsIRequest* aRequest,
response = new InternalResponse(responseStatus, statusText);
RefPtr<FillResponseHeaders> visitor = new FillResponseHeaders(response);
rv = httpChannel->VisitResponseHeaders(visitor);
if (NS_WARN_IF(NS_FAILED(rv))) {
NS_WARNING("Failed to visit all headers.");
}
response->Headers()->FillResponseHeaders(httpChannel);
// If Content-Encoding or Transfer-Encoding headers are set, then the actual
// Content-Length (which refer to the decoded data) is obscured behind the encodings.

View File

@ -11,6 +11,7 @@
#include "nsCharSeparatedTokenizer.h"
#include "nsContentUtils.h"
#include "nsIHttpHeaderVisitor.h"
#include "nsNetUtil.h"
#include "nsReadableUtils.h"
@ -314,6 +315,48 @@ InternalHeaders::Fill(const MozMap<nsCString>& aInit, ErrorResult& aRv)
}
}
namespace {
class FillHeaders final : public nsIHttpHeaderVisitor
{
RefPtr<InternalHeaders> mInternalHeaders;
~FillHeaders() = default;
public:
NS_DECL_ISUPPORTS
explicit FillHeaders(InternalHeaders* aInternalHeaders)
: mInternalHeaders(aInternalHeaders)
{
MOZ_DIAGNOSTIC_ASSERT(mInternalHeaders);
}
NS_IMETHOD
VisitHeader(const nsACString& aHeader, const nsACString& aValue) override
{
IgnoredErrorResult result;
mInternalHeaders->Append(aHeader, aValue, result);
return NS_OK;
}
};
NS_IMPL_ISUPPORTS(FillHeaders, nsIHttpHeaderVisitor)
} // namespace
void
InternalHeaders::FillResponseHeaders(nsIRequest* aRequest)
{
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
if (!httpChannel) {
return;
}
RefPtr<FillHeaders> visitor = new FillHeaders(this);
httpChannel->VisitResponseHeaders(visitor);
}
bool
InternalHeaders::HasOnlySimpleHeaders() const
{

View File

@ -102,6 +102,7 @@ public:
void Fill(const InternalHeaders& aInit, ErrorResult& aRv);
void Fill(const Sequence<Sequence<nsCString>>& aInit, ErrorResult& aRv);
void Fill(const MozMap<nsCString>& aInit, ErrorResult& aRv);
void FillResponseHeaders(nsIRequest* aRequest);
bool HasOnlySimpleHeaders() const;

View File

@ -63,7 +63,8 @@ public:
}
void Decrypt(GMPBuffer* aBuffer,
GMPEncryptedBufferMetadata* aMetadata) override
GMPEncryptedBufferMetadata* aMetadata,
uint64_t aDurationUses) override
{
}

View File

@ -712,7 +712,7 @@ GMPCDMProxy::gmp_Decrypt(RefPtr<DecryptJob> aJob)
aJob->mId = ++mDecryptionJobCount;
nsTArray<uint8_t> data;
data.AppendElements(aJob->mSample->Data(), aJob->mSample->Size());
mCDM->Decrypt(aJob->mId, aJob->mSample->mCrypto, data);
mCDM->Decrypt(aJob->mId, aJob->mSample->mCrypto, data, aJob->mSample->mDuration);
mDecryptionJobs.AppendElement(aJob.forget());
}

View File

@ -332,7 +332,8 @@ GMPDecryptorChild::RecvSetServerCertificate(const uint32_t& aPromiseId,
mozilla::ipc::IPCResult
GMPDecryptorChild::RecvDecrypt(const uint32_t& aId,
InfallibleTArray<uint8_t>&& aBuffer,
const GMPDecryptionData& aMetadata)
const GMPDecryptionData& aMetadata,
const uint64_t& aDurationUsecs)
{
if (!mSession) {
return IPC_FAIL_NO_REASON(this);
@ -346,7 +347,7 @@ GMPDecryptorChild::RecvDecrypt(const uint32_t& aId,
GMPEncryptedBufferDataImpl* metadata = new GMPEncryptedBufferDataImpl(aMetadata);
buffer->SetMetadata(metadata);
mSession->Decrypt(buffer, metadata);
mSession->Decrypt(buffer, metadata, aDurationUsecs);
return IPC_OK();
}

View File

@ -103,7 +103,8 @@ private:
mozilla::ipc::IPCResult RecvDecrypt(const uint32_t& aId,
InfallibleTArray<uint8_t>&& aBuffer,
const GMPDecryptionData& aMetadata) override;
const GMPDecryptionData& aMetadata,
const uint64_t& aDurationUsecs) override;
// Resolve/reject promise on completion.
mozilla::ipc::IPCResult RecvSetServerCertificate(const uint32_t& aPromiseId,

View File

@ -172,7 +172,8 @@ GMPDecryptorParent::SetServerCertificate(uint32_t aPromiseId,
void
GMPDecryptorParent::Decrypt(uint32_t aId,
const CryptoSample& aCrypto,
const nsTArray<uint8_t>& aBuffer)
const nsTArray<uint8_t>& aBuffer,
uint64_t aDurationUsecs)
{
LOGV(("GMPDecryptorParent[%p]::Decrypt(id=%d)", this, aId));
@ -191,10 +192,10 @@ GMPDecryptorParent::Decrypt(uint32_t aId,
aCrypto.mEncryptedSizes,
aCrypto.mSessionIds);
Unused << SendDecrypt(aId, aBuffer, data);
Unused << SendDecrypt(aId, aBuffer, data, aDurationUsecs);
} else {
GMPDecryptionData data;
Unused << SendDecrypt(aId, aBuffer, data);
Unused << SendDecrypt(aId, aBuffer, data, aDurationUsecs);
}
}

View File

@ -61,7 +61,8 @@ public:
void Decrypt(uint32_t aId,
const CryptoSample& aCrypto,
const nsTArray<uint8_t>& aBuffer) override;
const nsTArray<uint8_t>& aBuffer,
uint64_t aDurationUsecs) override;
void Close() override;

View File

@ -55,7 +55,8 @@ public:
virtual void Decrypt(uint32_t aId,
const mozilla::CryptoSample& aCrypto,
const nsTArray<uint8_t>& aBuffer) = 0;
const nsTArray<uint8_t>& aBuffer,
uint64_t aDurationUsecs) = 0;
virtual void Close() = 0;
};

View File

@ -8,6 +8,10 @@
#include "GMPTimerChild.h"
#include "mozilla/Monitor.h"
#include "GMPChild.h"
#include "mozilla/Mutex.h"
#include "base/thread.h"
#include "mozilla/ReentrantMonitor.h"
#include <ctime>
namespace mozilla {
@ -100,6 +104,21 @@ private:
Monitor mMonitor;
};
class GMPThreadImpl : public GMPThread
{
public:
GMPThreadImpl();
virtual ~GMPThreadImpl();
// GMPThread
void Post(GMPTask* aTask) override;
void Join() override;
private:
Mutex mMutex;
base::Thread mThread;
};
GMPErr
CreateThread(GMPThread** aThread)
{
@ -139,6 +158,21 @@ SyncRunOnMainThread(GMPTask* aTask)
return GMPNoErr;
}
class GMPMutexImpl : public GMPMutex
{
public:
GMPMutexImpl();
virtual ~GMPMutexImpl();
// GMPMutex
void Acquire() override;
void Release() override;
void Destroy() override;
private:
ReentrantMonitor mMonitor;
};
GMPErr
CreateMutex(GMPMutex** aMutex)
{
@ -280,5 +314,32 @@ GMPMutexImpl::Release()
mMonitor.Exit();
}
GMPTask*
NewGMPTask(std::function<void()>&& aFunction)
{
class Task : public GMPTask
{
public:
explicit Task(std::function<void()>&& aFunction)
: mFunction(Move(aFunction))
{
}
void Destroy() override
{
delete this;
}
~Task() override
{
}
void Run() override
{
mFunction();
}
private:
std::function<void()> mFunction;
};
return new Task(Move(aFunction));
}
} // namespace gmp
} // namespace mozilla

View File

@ -6,10 +6,8 @@
#ifndef GMPPlatform_h_
#define GMPPlatform_h_
#include "mozilla/Mutex.h"
#include "gmp-platform.h"
#include "base/thread.h"
#include "mozilla/ReentrantMonitor.h"
#include <functional>
namespace mozilla {
namespace gmp {
@ -20,35 +18,11 @@ void InitPlatformAPI(GMPPlatformAPI& aPlatformAPI, GMPChild* aChild);
GMPErr RunOnMainThread(GMPTask* aTask);
class GMPThreadImpl : public GMPThread
{
public:
GMPThreadImpl();
virtual ~GMPThreadImpl();
GMPTask*
NewGMPTask(std::function<void()>&& aFunction);
// GMPThread
void Post(GMPTask* aTask) override;
void Join() override;
private:
Mutex mMutex;
base::Thread mThread;
};
class GMPMutexImpl : public GMPMutex
{
public:
GMPMutexImpl();
virtual ~GMPMutexImpl();
// GMPMutex
void Acquire() override;
void Release() override;
void Destroy() override;
private:
ReentrantMonitor mMonitor;
};
GMPErr
SetTimerOnMainThread(GMPTask* aTask, int64_t aTimeoutMS);
} // namespace gmp
} // namespace mozilla

View File

@ -46,7 +46,8 @@ child:
async Decrypt(uint32_t aId,
uint8_t[] aBuffer,
GMPDecryptionData aMetadata);
GMPDecryptionData aMetadata,
uint64_t aDurationUsecs);
async DecryptingComplete();

View File

@ -324,7 +324,8 @@ public:
// is deleted. Don't forget to call Decrypted(), as otherwise aBuffer's
// memory will leak!
virtual void Decrypt(GMPBuffer* aBuffer,
GMPEncryptedBufferMetadata* aMetadata) = 0;
GMPEncryptedBufferMetadata* aMetadata,
uint64_t aDurationUsecs) = 0;
// Called when the decryption operations are complete.
// Do not call the GMPDecryptorCallback's functions after this is called.

View File

@ -8,8 +8,10 @@
#include "WidevineAdapter.h"
#include "WidevineUtils.h"
#include "WidevineFileIO.h"
#include "GMPPlatform.h"
#include <mozilla/SizePrintfMacros.h>
#include <stdarg.h>
#include "TimeUnits.h"
using namespace cdm;
using namespace std;
@ -196,35 +198,124 @@ private:
int64_t mTimestamp;
};
cdm::Time
WidevineDecryptor::ThrottleDecrypt(cdm::Time aWallTime, cdm::Time aSampleDuration)
{
const cdm::Time WindowSize = 1.0;
const cdm::Time MaxThroughput = 2.0;
// Forget decrypts that happened before the start of our window.
while (!mDecrypts.empty() && mDecrypts.front().mWallTime < aWallTime - WindowSize) {
mDecrypts.pop_front();
}
// How much time duration of the media would we have decrypted inside the
// time window if we did decrypt this block?
cdm::Time durationDecrypted = aSampleDuration;
for (const DecryptJob& job : mDecrypts) {
durationDecrypted += job.mSampleDuration;
}
if (durationDecrypted > MaxThroughput) {
// If we decrypted a sample of this duration, we would have decrypted more than
// our threshold for max throughput, over the preceding wall time window.
return durationDecrypted - MaxThroughput;
}
return 0.0;
}
void
WidevineDecryptor::Decrypt(GMPBuffer* aBuffer,
GMPEncryptedBufferMetadata* aMetadata)
GMPEncryptedBufferMetadata* aMetadata,
uint64_t aDurationUsecs)
{
if (!mCallback) {
Log("WidevineDecryptor::Decrypt() this=%p FAIL; !mCallback", this);
return;
}
const GMPEncryptedBufferMetadata* crypto = aMetadata;
cdm::Time duration = double(aDurationUsecs) / USECS_PER_S;
mPendingDecrypts.push({aBuffer, aMetadata, duration});
ProcessDecrypts();
}
void
WidevineDecryptor::ProcessDecryptsFromTimer()
{
MOZ_ASSERT(mPendingDecryptTimerSet);
mPendingDecryptTimerSet = false;
ProcessDecrypts();
}
void
WidevineDecryptor::ProcessDecrypts()
{
while (!mPendingDecrypts.empty()) {
PendingDecrypt job = mPendingDecrypts.front();
// We throttle our decrypt so that we don't decrypt more than a certain
// duration of samples per second. This is to work around bugs in the
// Widevine CDM. See bug 1338924.
cdm::Time now = GetCurrentWallTime();
cdm::Time delay = ThrottleDecrypt(now, job.mSampleDuration);
if (delay > 0.0) {
// If we decrypted this sample now, we'd decrypt more than our threshold
// per second of samples. Enqueue the sample, and wait until we'd be able
// to decrypt it without breaking our throughput threshold.
if (!mPendingDecryptTimerSet) {
mPendingDecryptTimerSet = true;
RefPtr<WidevineDecryptor> self = this;
GMPTask* task = gmp::NewGMPTask(
[self]() {
self->ProcessDecryptsFromTimer();
});
gmp::SetTimerOnMainThread(task, delay * 1000);
}
return;
}
DecryptBuffer(job);
mDecrypts.push_back(DecryptJob(now, job.mSampleDuration));
mPendingDecrypts.pop();
}
}
void
WidevineDecryptor::DecryptBuffer(const PendingDecrypt& aJob)
{
GMPBuffer* buffer = aJob.mBuffer;
const GMPEncryptedBufferMetadata* crypto = aJob.mMetadata;
InputBuffer sample;
nsTArray<SubsampleEntry> subsamples;
InitInputBuffer(crypto, aBuffer->Id(), aBuffer->Data(), aBuffer->Size(), sample, subsamples);
InitInputBuffer(crypto, buffer->Id(), buffer->Data(), buffer->Size(), sample, subsamples);
WidevineDecryptedBlock decrypted;
Status rv = CDM()->Decrypt(sample, &decrypted);
Log("Decryptor::Decrypt(timestamp=%lld) rv=%d sz=%d",
sample.timestamp, rv, decrypted.DecryptedBuffer()->Size());
if (rv == kSuccess) {
aBuffer->Resize(decrypted.DecryptedBuffer()->Size());
memcpy(aBuffer->Data(),
buffer->Resize(decrypted.DecryptedBuffer()->Size());
memcpy(buffer->Data(),
decrypted.DecryptedBuffer()->Data(),
decrypted.DecryptedBuffer()->Size());
}
mCallback->Decrypted(aBuffer, ToGMPErr(rv));
mCallback->Decrypted(buffer, ToGMPErr(rv));
}
void
WidevineDecryptor::DecryptingComplete()
{
Log("WidevineDecryptor::DecryptingComplete() this=%p, instanceId=%u", this, mInstanceId);
// Ensure buffers are freed.
while (!mPendingDecrypts.empty()) {
PendingDecrypt& job = mPendingDecrypts.front();
if (mCallback) {
mCallback->Decrypted(job.mBuffer, GMPAbortedErr);
}
mPendingDecrypts.pop();
}
// Drop our references to the CDMWrapper. When any other references
// held elsewhere are dropped (for example references held by a
// WidevineVideoDecoder, or a runnable), the CDMWrapper destroys

View File

@ -12,6 +12,8 @@
#include "mozilla/RefPtr.h"
#include "WidevineUtils.h"
#include <map>
#include <deque>
#include <queue>
namespace mozilla {
@ -64,7 +66,8 @@ public:
uint32_t aServerCertSize) override;
void Decrypt(GMPBuffer* aBuffer,
GMPEncryptedBufferMetadata* aMetadata) override;
GMPEncryptedBufferMetadata* aMetadata,
uint64_t aDurationUsecs) override;
void DecryptingComplete() override;
@ -118,6 +121,45 @@ public:
GMPDecryptorCallback* Callback() const { return mCallback; }
RefPtr<CDMWrapper> GetCDMWrapper() const { return mCDM; }
private:
struct PendingDecrypt
{
PendingDecrypt(GMPBuffer* aBuffer,
GMPEncryptedBufferMetadata* aMetadata,
cdm::Time aSampleDuration)
: mBuffer(aBuffer)
, mMetadata(aMetadata)
, mSampleDuration(aSampleDuration)
{
}
GMPBuffer* mBuffer;
GMPEncryptedBufferMetadata* mMetadata;
cdm::Time mSampleDuration;
};
// Queue of buffers waiting to be decrypted.
std::queue<PendingDecrypt> mPendingDecrypts;
void DecryptBuffer(const PendingDecrypt& aJob);
cdm::Time ThrottleDecrypt(cdm::Time aWallClock, cdm::Time aSampleDuration);
void ProcessDecrypts();
void ProcessDecryptsFromTimer();
struct DecryptJob
{
DecryptJob(cdm::Time aWallTime, cdm::Time aSampleDuration)
: mWallTime(aWallTime)
, mSampleDuration(aSampleDuration)
{}
cdm::Time mWallTime;
cdm::Time mSampleDuration;
};
// Queue of durations of buffers that have been decrypted, along with the
// wall-clock timestamp when they were decrypted. This enables us to
// throttle the throughput against the wall-clock.
std::deque<DecryptJob> mDecrypts;
~WidevineDecryptor();
RefPtr<CDMWrapper> mCDM;
cdm::ContentDecryptionModule_8* CDM() { return mCDM->GetCDM(); }
@ -127,6 +169,7 @@ private:
bool mDistinctiveIdentifierRequired = false;
bool mPersistentStateRequired = false;
uint32_t mInstanceId = 0;
bool mPendingDecryptTimerSet = false;
};
} // namespace mozilla

View File

@ -26,6 +26,8 @@ this.EXPORTED_SYMBOLS = ["WebVTT"];
* limitations under the License.
*/
var Cu = Components.utils;
Cu.import('resource://gre/modules/Services.jsm');
(function(global) {
@ -1347,6 +1349,7 @@ this.EXPORTED_SYMBOLS = ["WebVTT"];
// Create the region, using default values for any values that were not
// specified.
if (settings.has("id")) {
try {
var region = new self.window.VTTRegion();
region.width = settings.get("width", 100);
region.lines = settings.get("lines", 3);
@ -1363,6 +1366,11 @@ this.EXPORTED_SYMBOLS = ["WebVTT"];
id: settings.get("id"),
region: region
});
} catch(e) {
dump("VTTRegion Error " + e + "\n");
var regionPref = Services.prefs.getBoolPref("media.webvtt.regions.enabled");
dump("regionPref " + regionPref + "\n");
}
}
}

View File

@ -275,7 +275,11 @@ CSPService::AsyncOnChannelRedirect(nsIChannel *oldChannel,
*/
nsCOMPtr<nsIURI> originalUri;
rv = oldChannel->GetOriginalURI(getter_AddRefs(originalUri));
NS_ENSURE_SUCCESS(rv, rv);
if (NS_FAILED(rv)) {
autoCallback.DontCallback();
oldChannel->Cancel(NS_ERROR_DOM_BAD_URI);
return rv;
}
bool isPreload = nsContentUtils::IsPreloadType(policyType);
@ -306,6 +310,7 @@ CSPService::AsyncOnChannelRedirect(nsIChannel *oldChannel,
// is no point in checking the real policy
if (NS_CP_REJECTED(aDecision)) {
autoCallback.DontCallback();
oldChannel->Cancel(NS_ERROR_DOM_BAD_URI);
return NS_BINDING_FAILED;
}
}
@ -329,6 +334,7 @@ CSPService::AsyncOnChannelRedirect(nsIChannel *oldChannel,
// if ShouldLoad doesn't accept the load, cancel the request
if (!NS_CP_ACCEPTED(aDecision)) {
autoCallback.DontCallback();
oldChannel->Cancel(NS_ERROR_DOM_BAD_URI);
return NS_BINDING_FAILED;
}
return NS_OK;

View File

@ -0,0 +1,13 @@
<!DOCTYPE HTML>
<html>
<script>
addEventListener('message', evt => {
let url = '/tests/dom/security/test/csp/file_redirects_resource.sjs?redir=other&res=xhr-resp';
fetch(url).then(response => {
parent.postMessage('RESOLVED', '*');
}).catch(error => {
parent.postMessage('REJECTED', '*');
});
}, { once: true });
</script>
</html>

View File

@ -0,0 +1,2 @@
Content-Type: text/html
Content-Security-Policy: connect-src 'self'

View File

@ -1,6 +1,8 @@
[DEFAULT]
support-files =
fetch_test_framework.js
file_fetch_csp_block_frame.html
file_fetch_csp_block_frame.html^headers^
test_fetch_basic.js
test_fetch_basic_http.js
test_fetch_cors.js
@ -31,6 +33,7 @@ support-files =
!/dom/xhr/tests/temporaryFileBlob.sjs
!/dom/html/test/form_submit_server.sjs
!/dom/security/test/cors/file_CrossSiteXHR_server.sjs
!/dom/security/test/csp/file_redirects_resource.sjs
!/dom/base/test/referrer_helper.js
!/dom/base/test/referrer_testserver.sjs
@ -49,6 +52,7 @@ skip-if = toolkit == 'android' && debug # Bug 1210282
skip-if = toolkit == 'android' && debug # Bug 1210282
[test_fetch_cors_sw_empty_reroute.html]
skip-if = toolkit == 'android' && debug # Bug 1210282
[test_fetch_csp_block.html]
[test_fetch_user_control_rp.html]
[test_formdataparsing.html]
[test_formdataparsing_sw_reroute.html]

View File

@ -0,0 +1,50 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test fetch() rejects when CSP blocks</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<script type="application/javascript">
SimpleTest.waitForExplicitFinish();
function withFrame(url) {
return new Promise(resolve => {
let frame = document.createElement('iframe');
frame.addEventListener('load', _ => {
resolve(frame);
}, { once: true });
frame.src = url;
document.body.appendChild(frame);
});
}
function asyncTest(frame) {
return new Promise((resolve, reject) => {
addEventListener('message', evt => {
if (evt.data === 'REJECTED') {
resolve();
} else {
reject();
}
}, { once: true });
frame.contentWindow.postMessage('GO', '*');
});
}
withFrame('file_fetch_csp_block_frame.html').then(frame => {
asyncTest(frame).then(_ => {
ok(true, 'fetch rejected correctly');
}).catch(e => {
ok(false, 'fetch resolved when it should have been CSP blocked');
}).then(_ => {
frame.remove();
SimpleTest.finish();
});
});
</script>
</body>
</html>

View File

@ -39,6 +39,7 @@ support-files =
resource_timing.js
navigation_timing.html
test_bug1012662_common.js
test_interfaces.js
frameStorageAllowed.html
frameStoragePrevented.html
frameStorageChrome.html
@ -93,6 +94,7 @@ skip-if = toolkit == 'android' #TIMED_OUT
[test_frameElementWrapping.html]
[test_img_mutations.html]
[test_interfaces.html]
[test_interfaces_secureContext.html]
scheme = https
[test_navigation_timing.html]
[test_network_events.html]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,26 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=766694
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 766694</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=766694">Mozilla Bug 766694</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script>
ok(self.isSecureContext, "The test should be running in a secure context");
</script>
<script type="text/javascript" src="test_interfaces.js"></script>
<span id="span" style="-moz-binding: url(file_interfaces.xml)"></span>
</pre>
</body>
</html>

View File

@ -17,8 +17,10 @@ interface PointerEvent : MouseEvent
readonly attribute long width;
readonly attribute long height;
readonly attribute float pressure;
readonly attribute float tangentialPressure;
readonly attribute long tiltX;
readonly attribute long tiltY;
readonly attribute long twist;
readonly attribute DOMString pointerType;
readonly attribute boolean isPrimary;
};
@ -29,8 +31,10 @@ dictionary PointerEventInit : MouseEventInit
long width = 1;
long height = 1;
float pressure = 0;
float tangentialPressure = 0;
long tiltX = 0;
long tiltY = 0;
long twist = 0;
DOMString pointerType = "";
boolean isPrimary = false;
};

View File

@ -470,6 +470,8 @@ private:
nsCOMPtr<nsIURI> mBaseURI;
mozilla::dom::ChannelInfo mChannelInfo;
UniquePtr<PrincipalInfo> mPrincipalInfo;
nsCString mCSPHeaderValue;
nsCString mCSPReportOnlyHeaderValue;
};
NS_IMPL_ISUPPORTS(CacheScriptLoader, nsIStreamLoaderObserver)
@ -669,6 +671,7 @@ private:
RefPtr<mozilla::dom::InternalResponse> ir =
new mozilla::dom::InternalResponse(200, NS_LITERAL_CSTRING("OK"));
ir->SetBody(loadInfo.mCacheReadStream, InternalResponse::UNKNOWN_BODY_SIZE);
// Drop our reference to the stream now that we've passed it along, so it
// doesn't hang around once the cache is done with it and keep data alive.
loadInfo.mCacheReadStream = nullptr;
@ -697,6 +700,7 @@ private:
}
ir->SetPrincipalInfo(Move(principalInfo));
ir->Headers()->FillResponseHeaders(loadInfo.mChannel);
RefPtr<mozilla::dom::Response> response =
new mozilla::dom::Response(mCacheCreator->Global(), ir);
@ -1134,49 +1138,10 @@ private:
// We did inherit CSP in bug 1223647. If we do not already have a CSP, we
// should get it from the HTTP headers on the worker script.
if (!mWorkerPrivate->GetCSP() && CSPService::sCSPEnabled) {
NS_ConvertASCIItoUTF16 cspHeaderValue(tCspHeaderValue);
NS_ConvertASCIItoUTF16 cspROHeaderValue(tCspROHeaderValue);
nsIPrincipal* principal = mWorkerPrivate->GetPrincipal();
MOZ_ASSERT(principal, "Should not be null");
nsCOMPtr<nsIContentSecurityPolicy> csp;
rv = principal->EnsureCSP(nullptr, getter_AddRefs(csp));
if (csp) {
// If there's a CSP header, apply it.
if (!cspHeaderValue.IsEmpty()) {
rv = CSP_AppendCSPFromHeader(csp, cspHeaderValue, false);
rv = mWorkerPrivate->SetCSPFromHeaderValues(tCspHeaderValue,
tCspROHeaderValue);
NS_ENSURE_SUCCESS(rv, rv);
}
// If there's a report-only CSP header, apply it.
if (!cspROHeaderValue.IsEmpty()) {
rv = CSP_AppendCSPFromHeader(csp, cspROHeaderValue, true);
NS_ENSURE_SUCCESS(rv, rv);
}
// Set evalAllowed, default value is set in GetAllowsEval
bool evalAllowed = false;
bool reportEvalViolations = false;
rv = csp->GetAllowsEval(&reportEvalViolations, &evalAllowed);
NS_ENSURE_SUCCESS(rv, rv);
mWorkerPrivate->SetCSP(csp);
mWorkerPrivate->SetEvalAllowed(evalAllowed);
mWorkerPrivate->SetReportCSPViolations(reportEvalViolations);
// Set ReferrerPolicy, default value is set in GetReferrerPolicy
bool hasReferrerPolicy = false;
uint32_t rp = mozilla::net::RP_Unset;
rv = csp->GetReferrerPolicy(&rp, &hasReferrerPolicy);
NS_ENSURE_SUCCESS(rv, rv);
if (hasReferrerPolicy) { //FIXME bug 1307366: move RP out of CSP code
mWorkerPrivate->SetReferrerPolicy(static_cast<net::ReferrerPolicy>(rp));
}
}
}
WorkerPrivate* parent = mWorkerPrivate->GetParent();
if (parent) {
// XHR Params Allowed
@ -1201,7 +1166,9 @@ private:
DataReceivedFromCache(uint32_t aIndex, const uint8_t* aString,
uint32_t aStringLen,
const mozilla::dom::ChannelInfo& aChannelInfo,
UniquePtr<PrincipalInfo> aPrincipalInfo)
UniquePtr<PrincipalInfo> aPrincipalInfo,
const nsACString& aCSPHeaderValue,
const nsACString& aCSPReportOnlyHeaderValue)
{
AssertIsOnMainThread();
MOZ_ASSERT(aIndex < mLoadInfos.Length());
@ -1210,6 +1177,7 @@ private:
nsCOMPtr<nsIPrincipal> responsePrincipal =
PrincipalInfoToPrincipal(*aPrincipalInfo);
MOZ_DIAGNOSTIC_ASSERT(responsePrincipal);
nsIPrincipal* principal = mWorkerPrivate->GetPrincipal();
if (!principal) {
@ -1237,18 +1205,35 @@ private:
mWorkerPrivate->SetBaseURI(finalURI);
}
mozilla::DebugOnly<nsIPrincipal*> principal = mWorkerPrivate->GetPrincipal();
MOZ_ASSERT(principal);
nsILoadGroup* loadGroup = mWorkerPrivate->GetLoadGroup();
MOZ_ASSERT(loadGroup);
MOZ_DIAGNOSTIC_ASSERT(loadGroup);
mozilla::DebugOnly<bool> equal = false;
MOZ_ASSERT(responsePrincipal && NS_SUCCEEDED(responsePrincipal->Equals(principal, &equal)));
MOZ_ASSERT(equal);
#if defined(DEBUG) || !defined(RELEASE_OR_BETA)
nsIPrincipal* principal = mWorkerPrivate->GetPrincipal();
MOZ_DIAGNOSTIC_ASSERT(principal);
bool equal = false;
MOZ_ALWAYS_SUCCEEDS(responsePrincipal->Equals(principal, &equal));
MOZ_DIAGNOSTIC_ASSERT(equal);
nsCOMPtr<nsIContentSecurityPolicy> csp;
MOZ_ALWAYS_SUCCEEDS(responsePrincipal->GetCsp(getter_AddRefs(csp)));
MOZ_DIAGNOSTIC_ASSERT(!csp);
#endif
mWorkerPrivate->InitChannelInfo(aChannelInfo);
// Override the principal on the WorkerPrivate. We just asserted that
// this is the same as our current WorkerPrivate principal, so this is
// almost a no-op. We must do, it though, in order to avoid accidentally
// propagating the CSP object back to the ServiceWorkerRegistration
// principal. If bug 965637 is fixed then this can be removed.
rv = mWorkerPrivate->SetPrincipalOnMainThread(responsePrincipal, loadGroup);
MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
rv = mWorkerPrivate->SetCSPFromHeaderValues(aCSPHeaderValue,
aCSPReportOnlyHeaderValue);
MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
}
if (NS_SUCCEEDED(rv)) {
@ -1657,6 +1642,14 @@ CacheScriptLoader::ResolvedCallback(JSContext* aCx,
return;
}
InternalHeaders* headers = response->GetInternalHeaders();
IgnoredErrorResult ignored;
headers->Get(NS_LITERAL_CSTRING("content-security-policy"),
mCSPHeaderValue, ignored);
headers->Get(NS_LITERAL_CSTRING("content-security-policy-report-only"),
mCSPReportOnlyHeaderValue, ignored);
nsCOMPtr<nsIInputStream> inputStream;
response->GetBody(getter_AddRefs(inputStream));
mChannelInfo = response->GetChannelInfo();
@ -1668,7 +1661,8 @@ CacheScriptLoader::ResolvedCallback(JSContext* aCx,
if (!inputStream) {
mLoadInfo.mCacheStatus = ScriptLoadInfo::Cached;
mRunnable->DataReceivedFromCache(mIndex, (uint8_t*)"", 0, mChannelInfo,
Move(mPrincipalInfo));
Move(mPrincipalInfo), mCSPHeaderValue,
mCSPReportOnlyHeaderValue);
return;
}
@ -1728,7 +1722,8 @@ CacheScriptLoader::OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aCont
MOZ_ASSERT(mPrincipalInfo);
mRunnable->DataReceivedFromCache(mIndex, aString, aStringLen, mChannelInfo,
Move(mPrincipalInfo));
Move(mPrincipalInfo), mCSPHeaderValue,
mCSPReportOnlyHeaderValue);
return NS_OK;
}

View File

@ -3206,10 +3206,12 @@ ServiceWorkerManager::CreateNewRegistration(const nsCString& aScope,
nsIPrincipal* aPrincipal,
nsLoadFlags aLoadFlags)
{
nsresult rv;
#ifdef DEBUG
AssertIsOnMainThread();
nsCOMPtr<nsIURI> scopeURI;
nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aScope, nullptr, nullptr);
rv = NS_NewURI(getter_AddRefs(scopeURI), aScope, nullptr, nullptr);
MOZ_ASSERT(NS_SUCCEEDED(rv));
RefPtr<ServiceWorkerRegistrationInfo> tmp =
@ -3217,8 +3219,35 @@ ServiceWorkerManager::CreateNewRegistration(const nsCString& aScope,
MOZ_ASSERT(!tmp);
#endif
// The environment that registers the document may have some CSP applied
// to its principal. This should not be inherited by the registration
// itself or the worker it creates. To avoid confusion in callsites
// downstream we strip the CSP from the principal now.
//
// Unfortunately there is no API to clone a principal without its CSP. To
// achieve the same thing we serialize to the IPC PrincipalInfo type and
// back to an nsIPrincipal.
PrincipalInfo principalInfo;
rv = PrincipalToPrincipalInfo(aPrincipal, &principalInfo);
if (NS_WARN_IF(NS_FAILED(rv))) {
return nullptr;
}
nsCOMPtr<nsIPrincipal> cleanPrincipal =
PrincipalInfoToPrincipal(principalInfo, &rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return nullptr;
}
// Verify that we do not have any CSP set on our principal "clone".
#if defined(DEBUG) || !defined(RELEASE_OR_BETA)
nsCOMPtr<nsIContentSecurityPolicy> csp;
MOZ_ALWAYS_SUCCEEDS(cleanPrincipal->GetCsp(getter_AddRefs(csp)));
MOZ_DIAGNOSTIC_ASSERT(!csp);
#endif
RefPtr<ServiceWorkerRegistrationInfo> registration =
new ServiceWorkerRegistrationInfo(aScope, aPrincipal, aLoadFlags);
new ServiceWorkerRegistrationInfo(aScope, cleanPrincipal, aLoadFlags);
// From now on ownership of registration is with
// mServiceWorkerRegistrationInfos.
AddScopeAndRegistration(aScope, registration);

View File

@ -1743,25 +1743,30 @@ ServiceWorkerPrivate::SpawnWorkerIfNeeded(WakeUpReason aWhy,
info.mStorageAllowed = access > nsContentUtils::StorageAccess::ePrivateBrowsing;
info.mOriginAttributes = mInfo->GetOriginAttributes();
// The ServiceWorkerRegistration principal should never have any CSP
// set. The CSP from the page that registered the SW should not be
// inherited. Verify this is the case in non-release builds
#if defined(DEBUG) || !defined(RELEASE_OR_BETA)
nsCOMPtr<nsIContentSecurityPolicy> csp;
rv = info.mPrincipal->GetCsp(getter_AddRefs(csp));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
info.mCSP = csp;
if (info.mCSP) {
rv = info.mCSP->GetAllowsEval(&info.mReportCSPViolations,
&info.mEvalAllowed);
MOZ_DIAGNOSTIC_ASSERT(!csp);
#endif
// Default CSP permissions for now. These will be overrided if necessary
// based on the script CSP headers during load in ScriptLoader.
info.mEvalAllowed = true;
info.mReportCSPViolations = false;
WorkerPrivate::OverrideLoadInfoLoadGroup(info);
rv = info.SetPrincipalOnMainThread(info.mPrincipal, info.mLoadGroup);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
} else {
info.mEvalAllowed = true;
info.mReportCSPViolations = false;
}
WorkerPrivate::OverrideLoadInfoLoadGroup(info);
AutoJSAPI jsapi;
jsapi.Init();
@ -1926,54 +1931,80 @@ ServiceWorkerPrivate::IsIdle() const
return mTokenCount == 0 || (mTokenCount == 1 && mIdleKeepAliveToken);
}
/* static */ void
ServiceWorkerPrivate::NoteIdleWorkerCallback(nsITimer* aTimer, void* aPrivate)
namespace {
class ServiceWorkerPrivateTimerCallback final : public nsITimerCallback
{
public:
typedef void (ServiceWorkerPrivate::*Method)(nsITimer*);
ServiceWorkerPrivateTimerCallback(ServiceWorkerPrivate* aServiceWorkerPrivate,
Method aMethod)
: mServiceWorkerPrivate(aServiceWorkerPrivate)
, mMethod(aMethod)
{
}
NS_IMETHOD
Notify(nsITimer* aTimer) override
{
(mServiceWorkerPrivate->*mMethod)(aTimer);
mServiceWorkerPrivate = nullptr;
return NS_OK;
}
private:
~ServiceWorkerPrivateTimerCallback() = default;
RefPtr<ServiceWorkerPrivate> mServiceWorkerPrivate;
Method mMethod;
NS_DECL_THREADSAFE_ISUPPORTS
};
NS_IMPL_ISUPPORTS(ServiceWorkerPrivateTimerCallback, nsITimerCallback);
} // anonymous namespace
void
ServiceWorkerPrivate::NoteIdleWorkerCallback(nsITimer* aTimer)
{
AssertIsOnMainThread();
MOZ_ASSERT(aPrivate);
RefPtr<ServiceWorkerPrivate> swp = static_cast<ServiceWorkerPrivate*>(aPrivate);
MOZ_ASSERT(aTimer == swp->mIdleWorkerTimer, "Invalid timer!");
MOZ_ASSERT(aTimer == mIdleWorkerTimer, "Invalid timer!");
// Release ServiceWorkerPrivate's token, since the grace period has ended.
swp->mIdleKeepAliveToken = nullptr;
mIdleKeepAliveToken = nullptr;
if (swp->mWorkerPrivate) {
if (mWorkerPrivate) {
// If we still have a workerPrivate at this point it means there are pending
// waitUntil promises. Wait a bit more until we forcibly terminate the
// worker.
uint32_t timeout = Preferences::GetInt("dom.serviceWorkers.idle_extended_timeout");
nsCOMPtr<nsITimerCallback> cb = new ServiceWorkerPrivateTimerCallback(
this, &ServiceWorkerPrivate::TerminateWorkerCallback);
DebugOnly<nsresult> rv =
swp->mIdleWorkerTimer->InitWithFuncCallback(ServiceWorkerPrivate::TerminateWorkerCallback,
aPrivate,
timeout,
nsITimer::TYPE_ONE_SHOT);
mIdleWorkerTimer->InitWithCallback(cb, timeout, nsITimer::TYPE_ONE_SHOT);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
}
/* static */ void
ServiceWorkerPrivate::TerminateWorkerCallback(nsITimer* aTimer, void *aPrivate)
void
ServiceWorkerPrivate::TerminateWorkerCallback(nsITimer* aTimer)
{
AssertIsOnMainThread();
MOZ_ASSERT(aPrivate);
RefPtr<ServiceWorkerPrivate> serviceWorkerPrivate =
static_cast<ServiceWorkerPrivate*>(aPrivate);
MOZ_ASSERT(aTimer == serviceWorkerPrivate->mIdleWorkerTimer,
"Invalid timer!");
MOZ_ASSERT(aTimer == this->mIdleWorkerTimer, "Invalid timer!");
// mInfo must be non-null at this point because NoteDeadServiceWorkerInfo
// which zeroes it calls TerminateWorker which cancels our timer which will
// ensure we don't get invoked even if the nsTimerEvent is in the event queue.
ServiceWorkerManager::LocalizeAndReportToAllClients(
serviceWorkerPrivate->mInfo->Scope(),
mInfo->Scope(),
"ServiceWorkerGraceTimeoutTermination",
nsTArray<nsString> { NS_ConvertUTF8toUTF16(serviceWorkerPrivate->mInfo->Scope()) });
nsTArray<nsString> { NS_ConvertUTF8toUTF16(mInfo->Scope()) });
serviceWorkerPrivate->TerminateWorker();
TerminateWorker();
}
void
@ -1998,10 +2029,10 @@ void
ServiceWorkerPrivate::ResetIdleTimeout()
{
uint32_t timeout = Preferences::GetInt("dom.serviceWorkers.idle_timeout");
nsCOMPtr<nsITimerCallback> cb = new ServiceWorkerPrivateTimerCallback(
this, &ServiceWorkerPrivate::NoteIdleWorkerCallback);
DebugOnly<nsresult> rv =
mIdleWorkerTimer->InitWithFuncCallback(ServiceWorkerPrivate::NoteIdleWorkerCallback,
this, timeout,
nsITimer::TYPE_ONE_SHOT);
mIdleWorkerTimer->InitWithCallback(cb, timeout, nsITimer::TYPE_ONE_SHOT);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}

View File

@ -173,11 +173,11 @@ private:
};
// Timer callbacks
static void
NoteIdleWorkerCallback(nsITimer* aTimer, void* aPrivate);
void
NoteIdleWorkerCallback(nsITimer* aTimer);
static void
TerminateWorkerCallback(nsITimer* aTimer, void *aPrivate);
void
TerminateWorkerCallback(nsITimer* aTimer);
void
RenewKeepAliveToken(WakeUpReason aWhy);

View File

@ -57,6 +57,10 @@ ServiceWorkerRegisterJob::AsyncExecute()
} else {
registration = swm->CreateNewRegistration(mScope, mPrincipal,
GetLoadFlags());
if (!registration) {
FailUpdateJob(NS_ERROR_DOM_ABORT_ERR);
return;
}
}
SetRegistration(registration);

View File

@ -240,6 +240,7 @@ public:
CompareCallback* aCallback)
: mRegistration(aRegistration)
, mCallback(aCallback)
, mInternalHeaders(new InternalHeaders())
, mState(WaitingForOpen)
, mNetworkFinished(false)
, mCacheFinished(false)
@ -434,10 +435,19 @@ public:
return mCacheStorage;
}
void
InitChannelInfo(nsIChannel* aChannel)
nsresult
OnStartRequest(nsIChannel* aChannel)
{
nsresult rv = SetPrincipalInfo(aChannel);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
mChannelInfo.InitFromChannel(aChannel);
mInternalHeaders->FillResponseHeaders(aChannel);
return NS_OK;
}
nsresult
@ -562,6 +572,9 @@ private:
ir->SetPrincipalInfo(Move(mPrincipalInfo));
}
IgnoredErrorResult ignored;
ir->Headers()->Fill(*mInternalHeaders, ignored);
RefPtr<Response> response = new Response(aCache->GetGlobalObject(), ir);
RequestOrUSVString request;
@ -594,6 +607,7 @@ private:
nsString mNewCacheName;
ChannelInfo mChannelInfo;
RefPtr<InternalHeaders> mInternalHeaders;
UniquePtr<mozilla::ipc::PrincipalInfo> mPrincipalInfo;
@ -693,8 +707,7 @@ CompareNetwork::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
MOZ_ASSERT(channel == mChannel);
#endif
mManager->InitChannelInfo(mChannel);
nsresult rv = mManager->SetPrincipalInfo(mChannel);
nsresult rv = mManager->OnStartRequest(mChannel);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}

View File

@ -58,6 +58,7 @@
#include "mozilla/dom/MessageEventBinding.h"
#include "mozilla/dom/MessagePort.h"
#include "mozilla/dom/MessagePortBinding.h"
#include "mozilla/dom/nsCSPUtils.h"
#include "mozilla/dom/Performance.h"
#include "mozilla/dom/PMessagePort.h"
#include "mozilla/dom/Promise.h"
@ -1842,7 +1843,6 @@ WorkerLoadInfo::SetPrincipalOnMainThread(nsIPrincipal* aPrincipal,
{
AssertIsOnMainThread();
MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(aLoadGroup, aPrincipal));
MOZ_ASSERT(!mPrincipalInfo);
mPrincipal = aPrincipal;
mPrincipalIsSystem = nsContentUtils::IsSystemPrincipal(aPrincipal);
@ -1992,6 +1992,14 @@ WorkerLoadInfo::FinalChannelPrincipalIsValid(nsIChannel* aChannel)
return false;
}
bool
WorkerLoadInfo::PrincipalIsValid() const
{
return mPrincipal && mPrincipalInfo &&
mPrincipalInfo->type() != PrincipalInfo::T__None &&
mPrincipalInfo->type() <= PrincipalInfo::T__Last;
}
#endif // defined(DEBUG) || !defined(RELEASE_OR_BETA)
bool
@ -2596,6 +2604,57 @@ WorkerPrivateParent<Derived>::GetDocument() const
return nullptr;
}
template <class Derived>
nsresult
WorkerPrivateParent<Derived>::SetCSPFromHeaderValues(const nsACString& aCSPHeaderValue,
const nsACString& aCSPReportOnlyHeaderValue)
{
AssertIsOnMainThread();
MOZ_DIAGNOSTIC_ASSERT(!mLoadInfo.mCSP);
NS_ConvertASCIItoUTF16 cspHeaderValue(aCSPHeaderValue);
NS_ConvertASCIItoUTF16 cspROHeaderValue(aCSPReportOnlyHeaderValue);
nsCOMPtr<nsIContentSecurityPolicy> csp;
nsresult rv = mLoadInfo.mPrincipal->EnsureCSP(nullptr, getter_AddRefs(csp));
if (!csp) {
return NS_OK;
}
// If there's a CSP header, apply it.
if (!cspHeaderValue.IsEmpty()) {
rv = CSP_AppendCSPFromHeader(csp, cspHeaderValue, false);
NS_ENSURE_SUCCESS(rv, rv);
}
// If there's a report-only CSP header, apply it.
if (!cspROHeaderValue.IsEmpty()) {
rv = CSP_AppendCSPFromHeader(csp, cspROHeaderValue, true);
NS_ENSURE_SUCCESS(rv, rv);
}
// Set evalAllowed, default value is set in GetAllowsEval
bool evalAllowed = false;
bool reportEvalViolations = false;
rv = csp->GetAllowsEval(&reportEvalViolations, &evalAllowed);
NS_ENSURE_SUCCESS(rv, rv);
// Set ReferrerPolicy, default value is set in GetReferrerPolicy
bool hasReferrerPolicy = false;
uint32_t rp = mozilla::net::RP_Unset;
rv = csp->GetReferrerPolicy(&rp, &hasReferrerPolicy);
NS_ENSURE_SUCCESS(rv, rv);
mLoadInfo.mCSP = csp;
mLoadInfo.mEvalAllowed = evalAllowed;
mLoadInfo.mReportCSPViolations = reportEvalViolations;
if (hasReferrerPolicy) {
mLoadInfo.mReferrerPolicy = static_cast<net::ReferrerPolicy>(rp);
}
return NS_OK;
}
// Can't use NS_IMPL_CYCLE_COLLECTION_CLASS(WorkerPrivateParent) because of the
// templates.
@ -3965,6 +4024,15 @@ WorkerPrivateParent<Derived>::AssertInnerWindowIsCorrect() const
#endif
#if defined(DEBUG) || !defined(RELEASE_OR_BETA)
template <class Derived>
bool
WorkerPrivateParent<Derived>::PrincipalIsValid() const
{
return mLoadInfo.PrincipalIsValid();
}
#endif
class PostDebuggerMessageRunnable final : public Runnable
{
WorkerDebugger *mDebugger;
@ -4537,6 +4605,8 @@ WorkerPrivate::Constructor(JSContext* aCx,
worker->EnableDebugger();
MOZ_DIAGNOSTIC_ASSERT(worker->PrincipalIsValid());
RefPtr<CompileScriptRunnable> compiler =
new CompileScriptRunnable(worker, aScriptURL);
if (!compiler->Dispatch()) {
@ -4806,6 +4876,8 @@ WorkerPrivate::GetLoadInfo(JSContext* aCx, nsPIDOMWindowInner* aWindow,
NS_ENSURE_SUCCESS(rv, rv);
}
MOZ_DIAGNOSTIC_ASSERT(loadInfo.PrincipalIsValid());
aLoadInfo->StealFrom(loadInfo);
return NS_OK;
}

View File

@ -665,6 +665,10 @@ public:
mLoadInfo.mCSP = aCSP;
}
nsresult
SetCSPFromHeaderValues(const nsACString& aCSPHeaderValue,
const nsACString& aCSPReportOnlyHeaderValue);
net::ReferrerPolicy
GetReferrerPolicy() const
{
@ -863,6 +867,11 @@ public:
AssertInnerWindowIsCorrect() const
{ }
#endif
#if defined(DEBUG) || !defined(RELEASE_OR_BETA)
bool
PrincipalIsValid() const;
#endif
};
class WorkerDebugger : public nsIWorkerDebugger {

View File

@ -293,6 +293,9 @@ struct WorkerLoadInfo
#if defined(DEBUG) || !defined(RELEASE_OR_BETA)
bool
FinalChannelPrincipalIsValid(nsIChannel* aChannel);
bool
PrincipalIsValid() const;
#endif
bool

View File

@ -61,6 +61,7 @@ support-files =
terminate_worker.js
test_csp.html^headers^
test_csp.js
test_navigator.js
referrer_worker.html
threadErrors_worker1.js
threadErrors_worker2.js
@ -170,6 +171,8 @@ tags = mcb
[test_multi_sharedWorker.html]
[test_multi_sharedWorker_lifetimes.html]
[test_navigator.html]
[test_navigator_secureContext.html]
scheme=https
[test_navigator_languages.html]
[test_newError.html]
[test_notification.html]
@ -210,6 +213,8 @@ skip-if = toolkit == 'android' #bug 982828
[test_webSocket_sharedWorker.html]
skip-if = toolkit == 'android' #bug 982828
[test_worker_interfaces.html]
[test_worker_interfaces_secureContext.html]
scheme=https
[test_workersDisabled.html]
[test_referrer.html]
[test_referrer_header_worker.html]

View File

@ -18,59 +18,10 @@ Tests of DOM Worker Navigator
</div>
<pre id="test">
<script class="testbody" language="javascript">
function runTest() {
var worker = new Worker("navigator_worker.js");
worker.onmessage = function(event) {
var args = JSON.parse(event.data);
if (args.name == "testFinished") {
SimpleTest.finish();
return;
}
if (typeof navigator[args.name] == "undefined") {
ok(false, "Navigator has no '" + args.name + "' property!");
return;
}
if (args.name === "languages") {
is(navigator.languages.toString(), args.value.toString(), "languages matches");
return;
}
if (args.name === "storage") {
is(typeof navigator.storage, typeof args.value, "storage type matches");
return;
}
if (args.name === "connection") {
is(typeof navigator.connection, typeof args.value, "connection type matches");
return;
}
is(navigator[args.name], args.value,
"Mismatched navigator string for " + args.name + "!");
};
worker.onerror = function(event) {
ok(false, "Worker had an error: " + event.message);
SimpleTest.finish();
}
var version = SpecialPowers.Cc["@mozilla.org/xre/app-info;1"].getService(SpecialPowers.Ci.nsIXULAppInfo).version;
var isNightly = version.endsWith("a1");
var isRelease = !version.includes("a");
worker.postMessage({ isNightly, isRelease });
}
SpecialPowers.pushPrefEnv({"set": [["dom.netinfo.enabled", true]]}, runTest);
SimpleTest.waitForExplicitFinish();
<script>
ok(!self.isSecureContext, "This test should not be running in a secure context");
</script>
<script type="text/javascript" src="test_navigator.js"></script>
</pre>
</body>
</html>

View File

@ -0,0 +1,49 @@
function runTest() {
var worker = new Worker("navigator_worker.js");
worker.onmessage = function(event) {
var args = JSON.parse(event.data);
if (args.name == "testFinished") {
SimpleTest.finish();
return;
}
if (typeof navigator[args.name] == "undefined") {
ok(false, "Navigator has no '" + args.name + "' property!");
return;
}
if (args.name === "languages") {
is(navigator.languages.toString(), args.value.toString(), "languages matches");
return;
}
if (args.name === "storage") {
is(typeof navigator.storage, typeof args.value, "storage type matches");
return;
}
if (args.name === "connection") {
is(typeof navigator.connection, typeof args.value, "connection type matches");
return;
}
is(navigator[args.name], args.value,
"Mismatched navigator string for " + args.name + "!");
};
worker.onerror = function(event) {
ok(false, "Worker had an error: " + event.message);
SimpleTest.finish();
}
var version = SpecialPowers.Cc["@mozilla.org/xre/app-info;1"].getService(SpecialPowers.Ci.nsIXULAppInfo).version;
var isNightly = version.endsWith("a1");
var isRelease = !version.includes("a");
worker.postMessage({ isNightly, isRelease });
}
SpecialPowers.pushPrefEnv({"set": [["dom.netinfo.enabled", true]]}, runTest);
SimpleTest.waitForExplicitFinish();

View File

@ -0,0 +1,27 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<!DOCTYPE HTML>
<html>
<!--
Tests of DOM Worker Navigator
-->
<head>
<title>Test for DOM Worker Navigator</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script>
ok(self.isSecureContext, "This test should be running in a secure context");
</script>
<script type="text/javascript" src="test_navigator.js"></script>
</pre>
</body>
</html>

View File

@ -9,6 +9,9 @@
<script type="text/javascript" src="worker_driver.js"></script>
</head>
<body>
<script>
ok(!self.isSecureContext, "This test should not be running in a secure context");
</script>
<script class="testbody" type="text/javascript">
workerTestExec("test_worker_interfaces.js");
</script>

View File

@ -0,0 +1,19 @@
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!DOCTYPE HTML>
<html>
<head>
<title>Validate Interfaces Exposed to Workers</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<script type="text/javascript" src="worker_driver.js"></script>
</head>
<body>
<script>
ok(self.isSecureContext, "This test should be running in a secure context");
</script>
<script class="testbody" type="text/javascript">
workerTestExec("test_worker_interfaces.js");
</script>
</body>
</html>

View File

@ -160,8 +160,8 @@ static bool UploadData(IDirect3DDevice9* aDevice,
return FinishTextures(aDevice, aTexture, surf);
}
TextureClient*
IMFYCbCrImage::GetD3D9TextureClient(KnowsCompositor* aForwarder)
DXGIYCbCrTextureData*
IMFYCbCrImage::GetD3D9TextureData(Data aData, gfx::IntSize aSize)
{
RefPtr<IDirect3DDevice9> device = DeviceManagerD3D9::GetDevice();
if (!device) {
@ -171,21 +171,21 @@ IMFYCbCrImage::GetD3D9TextureClient(KnowsCompositor* aForwarder)
RefPtr<IDirect3DTexture9> textureY;
HANDLE shareHandleY = 0;
if (!UploadData(device, textureY, shareHandleY,
mData.mYChannel, mData.mYSize, mData.mYStride)) {
aData.mYChannel, aData.mYSize, aData.mYStride)) {
return nullptr;
}
RefPtr<IDirect3DTexture9> textureCb;
HANDLE shareHandleCb = 0;
if (!UploadData(device, textureCb, shareHandleCb,
mData.mCbChannel, mData.mCbCrSize, mData.mCbCrStride)) {
aData.mCbChannel, aData.mCbCrSize, aData.mCbCrStride)) {
return nullptr;
}
RefPtr<IDirect3DTexture9> textureCr;
HANDLE shareHandleCr = 0;
if (!UploadData(device, textureCr, shareHandleCr,
mData.mCrChannel, mData.mCbCrSize, mData.mCbCrStride)) {
aData.mCrChannel, aData.mCbCrSize, aData.mCbCrStride)) {
return nullptr;
}
@ -212,43 +212,26 @@ IMFYCbCrImage::GetD3D9TextureClient(KnowsCompositor* aForwarder)
return nullptr;
}
mTextureClient = TextureClient::CreateWithData(
DXGIYCbCrTextureData::Create(TextureFlags::DEFAULT,
textureY, textureCb, textureCr,
shareHandleY, shareHandleCb, shareHandleCr,
GetSize(), mData.mYSize, mData.mCbCrSize),
TextureFlags::DEFAULT,
aForwarder->GetTextureForwarder()
);
return mTextureClient;
return DXGIYCbCrTextureData::Create(TextureFlags::DEFAULT, textureY,
textureCb, textureCr, shareHandleY,
shareHandleCb, shareHandleCr, aSize,
aData.mYSize, aData.mCbCrSize);
}
TextureClient*
IMFYCbCrImage::GetTextureClient(KnowsCompositor* aForwarder)
DXGIYCbCrTextureData*
IMFYCbCrImage::GetD3D11TextureData(Data aData, gfx::IntSize aSize)
{
if (mTextureClient) {
return mTextureClient;
}
HRESULT hr;
RefPtr<ID3D10Multithread> mt;
RefPtr<ID3D11Device> device =
gfx::DeviceManagerDx::Get()->GetContentDevice();
if (!device) {
device =
gfx::DeviceManagerDx::Get()->GetCompositorDevice();
}
LayersBackend backend = aForwarder->GetCompositorBackendType();
if (!device || backend != LayersBackend::LAYERS_D3D11) {
if (backend == LayersBackend::LAYERS_D3D9 ||
backend == LayersBackend::LAYERS_D3D11) {
return GetD3D9TextureClient(aForwarder);
}
return nullptr;
}
HRESULT hr;
RefPtr<ID3D10Multithread> mt;
hr = device->QueryInterface((ID3D10Multithread**)getter_AddRefs(mt));
if (FAILED(hr)) {
@ -263,13 +246,13 @@ IMFYCbCrImage::GetTextureClient(KnowsCompositor* aForwarder)
return nullptr;
}
if (mData.mYStride < 0 || mData.mCbCrStride < 0) {
if (aData.mYStride < 0 || aData.mCbCrStride < 0) {
// D3D11 only supports unsigned stride values.
return nullptr;
}
CD3D11_TEXTURE2D_DESC newDesc(DXGI_FORMAT_R8_UNORM,
mData.mYSize.width, mData.mYSize.height, 1, 1);
aData.mYSize.width, aData.mYSize.height, 1, 1);
if (device == gfx::DeviceManagerDx::Get()->GetCompositorDevice()) {
newDesc.MiscFlags = D3D11_RESOURCE_MISC_SHARED;
@ -281,8 +264,8 @@ IMFYCbCrImage::GetTextureClient(KnowsCompositor* aForwarder)
hr = device->CreateTexture2D(&newDesc, nullptr, getter_AddRefs(textureY));
NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
newDesc.Width = mData.mCbCrSize.width;
newDesc.Height = mData.mCbCrSize.height;
newDesc.Width = aData.mCbCrSize.width;
newDesc.Height = aData.mCbCrSize.height;
RefPtr<ID3D11Texture2D> textureCb;
hr = device->CreateTexture2D(&newDesc, nullptr, getter_AddRefs(textureCb));
@ -313,28 +296,80 @@ IMFYCbCrImage::GetTextureClient(KnowsCompositor* aForwarder)
D3D11_BOX box;
box.front = box.top = box.left = 0;
box.back = 1;
box.right = mData.mYSize.width;
box.bottom = mData.mYSize.height;
ctx->UpdateSubresource(textureY, 0, &box, mData.mYChannel, mData.mYStride, 0);
box.right = aData.mYSize.width;
box.bottom = aData.mYSize.height;
ctx->UpdateSubresource(textureY, 0, &box, aData.mYChannel, aData.mYStride, 0);
box.right = mData.mCbCrSize.width;
box.bottom = mData.mCbCrSize.height;
ctx->UpdateSubresource(textureCb, 0, &box, mData.mCbChannel, mData.mCbCrStride, 0);
ctx->UpdateSubresource(textureCr, 0, &box, mData.mCrChannel, mData.mCbCrStride, 0);
box.right = aData.mCbCrSize.width;
box.bottom = aData.mCbCrSize.height;
ctx->UpdateSubresource(textureCb, 0, &box, aData.mCbChannel, aData.mCbCrStride, 0);
ctx->UpdateSubresource(textureCr, 0, &box, aData.mCrChannel, aData.mCbCrStride, 0);
mt->Leave();
}
return DXGIYCbCrTextureData::Create(TextureFlags::DEFAULT, textureY,
textureCb, textureCr, aSize, aData.mYSize,
aData.mCbCrSize);
}
TextureClient*
IMFYCbCrImage::GetD3D9TextureClient(KnowsCompositor* aForwarder)
{
DXGIYCbCrTextureData* textureData = GetD3D9TextureData(mData, GetSize());
if (textureData == nullptr) {
return nullptr;
}
mTextureClient = TextureClient::CreateWithData(
DXGIYCbCrTextureData::Create(TextureFlags::DEFAULT,
textureY, textureCb, textureCr,
GetSize(), mData.mYSize, mData.mCbCrSize),
TextureFlags::DEFAULT,
textureData, TextureFlags::DEFAULT,
aForwarder->GetTextureForwarder()
);
return mTextureClient;
}
TextureClient*
IMFYCbCrImage::GetD3D11TextureClient(KnowsCompositor* aForwarder)
{
DXGIYCbCrTextureData* textureData = GetD3D11TextureData(mData, GetSize());
if (textureData == nullptr) {
return nullptr;
}
mTextureClient = TextureClient::CreateWithData(
textureData, TextureFlags::DEFAULT,
aForwarder->GetTextureForwarder()
);
return mTextureClient;
}
TextureClient*
IMFYCbCrImage::GetTextureClient(KnowsCompositor* aForwarder)
{
if (mTextureClient) {
return mTextureClient;
}
RefPtr<ID3D11Device> device =
gfx::DeviceManagerDx::Get()->GetContentDevice();
if (!device) {
device =
gfx::DeviceManagerDx::Get()->GetCompositorDevice();
}
LayersBackend backend = aForwarder->GetCompositorBackendType();
if (!device || backend != LayersBackend::LAYERS_D3D11) {
if (backend == LayersBackend::LAYERS_D3D9 ||
backend == LayersBackend::LAYERS_D3D11) {
return GetD3D9TextureClient(aForwarder);
}
return nullptr;
}
return GetD3D11TextureClient(aForwarder);
}
} // namespace layers
} // namespace mozilla

View File

@ -6,6 +6,7 @@
#ifndef GFX_IMFYCBCRIMAGE_H
#define GFX_IMFYCBCRIMAGE_H
#include "mozilla/layers/TextureD3D11.h"
#include "mozilla/RefPtr.h"
#include "ImageContainer.h"
#include "mfidl.h"
@ -22,9 +23,14 @@ public:
virtual TextureClient* GetTextureClient(KnowsCompositor* aForwarder) override;
static DXGIYCbCrTextureData* GetD3D9TextureData(Data aData,
gfx::IntSize aSize);
static DXGIYCbCrTextureData* GetD3D11TextureData(Data aData,
gfx::IntSize aSize);
protected:
TextureClient* GetD3D9TextureClient(KnowsCompositor* aForwarder);
TextureClient* GetD3D11TextureClient(KnowsCompositor* aForwarder);
~IMFYCbCrImage();

View File

@ -543,10 +543,20 @@ RotatedContentBuffer::BeginPaint(PaintedLayer* aLayer,
return result;
if (HaveBuffer()) {
if (LockBuffers()) {
// Do not modify result.mRegionToDraw or result.mContentType after this call.
// Do not modify mBufferRect, mBufferRotation, or mDidSelfCopy,
// or call CreateBuffer before this call.
FinalizeFrame(result.mRegionToDraw);
} else {
// Abandon everything and redraw it all. Ideally we'd reallocate and copy
// the old to the new and then call FinalizeFrame on the new buffer so that
// we only need to draw the latest bits, but we need a big refactor to support
// that ordering.
result.mRegionToDraw = neededRegion;
canReuseBuffer = false;
Clear();
}
}
IntRect drawBounds = result.mRegionToDraw.GetBounds();

View File

@ -203,6 +203,7 @@ public:
*/
void Clear()
{
UnlockBuffers();
mDTBuffer = nullptr;
mDTBufferOnWhite = nullptr;
mBufferProvider = nullptr;
@ -410,6 +411,9 @@ protected:
*/
virtual void FinalizeFrame(const nsIntRegion& aRegionToDraw) {}
virtual bool LockBuffers() { return true; }
virtual void UnlockBuffers() {}
RefPtr<gfx::DrawTarget> mDTBuffer;
RefPtr<gfx::DrawTarget> mDTBufferOnWhite;

View File

@ -30,7 +30,7 @@ namespace layers {
using namespace mozilla::gfx;
void
ClientPaintedLayer::PaintThebes()
ClientPaintedLayer::PaintThebes(nsTArray<ReadbackProcessor::Update>* aReadbackUpdates)
{
PROFILER_LABEL("ClientPaintedLayer", "PaintThebes",
js::ProfileEntry::Category::GRAPHICS);
@ -38,6 +38,8 @@ ClientPaintedLayer::PaintThebes()
NS_ASSERTION(ClientManager()->InDrawing(),
"Can only draw in drawing phase");
mContentClient->BeginPaint();
uint32_t flags = RotatedContentBuffer::PAINT_CAN_DRAW_ROTATED;
#ifndef MOZ_IGNORE_PAINT_WILL_RESAMPLE
if (ClientManager()->CompositorMightResample()) {
@ -93,6 +95,8 @@ ClientPaintedLayer::PaintThebes()
didUpdate = true;
}
mContentClient->EndPaint(aReadbackUpdates);
if (didUpdate) {
Mutated();
@ -132,10 +136,7 @@ ClientPaintedLayer::RenderLayerWithReadback(ReadbackProcessor *aReadback)
aReadback->GetPaintedLayerUpdates(this, &readbackUpdates);
}
IntPoint origin(mVisibleRegion.GetBounds().x, mVisibleRegion.GetBounds().y);
mContentClient->BeginPaint();
PaintThebes();
mContentClient->EndPaint(&readbackUpdates);
PaintThebes(&readbackUpdates);
}
already_AddRefed<PaintedLayer>

View File

@ -108,7 +108,7 @@ public:
}
protected:
void PaintThebes();
void PaintThebes(nsTArray<ReadbackProcessor::Update>* aReadbackUpdates);
virtual void PrintInfo(std::stringstream& aStream, const char* aPrefix) override;

View File

@ -83,9 +83,7 @@ ContentClient::CreateContentClient(CompositableForwarder* aForwarder)
!gfxVars::UseXRender())
#endif
{
useDoubleBuffering = (LayerManagerComposite::SupportsDirectTexturing() &&
backend != LayersBackend::LAYERS_D3D9) ||
backend == LayersBackend::LAYERS_BASIC;
useDoubleBuffering = backend == LayersBackend::LAYERS_BASIC;
}
if (useDoubleBuffering || gfxEnv::ForceDoubleBuffering()) {
@ -322,6 +320,7 @@ ContentClientRemoteBuffer::CreateBackBuffer(const IntRect& aBufferRect)
AbortTextureClientCreation();
return;
}
mTextureClient->EnableBlockingReadLock();
if (mTextureFlags & TextureFlags::COMPONENT_ALPHA) {
mTextureClientOnWhite = mTextureClient->CreateSimilar(
@ -333,6 +332,7 @@ ContentClientRemoteBuffer::CreateBackBuffer(const IntRect& aBufferRect)
AbortTextureClientCreation();
return;
}
mTextureClientOnWhite->EnableBlockingReadLock();
}
}
@ -410,10 +410,14 @@ ContentClientRemoteBuffer::Updated(const nsIntRegion& aRegionToDraw,
t->mPictureRect = nsIntRect(0, 0, size.width, size.height);
GetForwarder()->UseTextures(this, textures);
}
// This forces a synchronous transaction, so we can swap buffers now
// and know that we'll have sole ownership of the old front buffer
// by the time we paint next.
mForwarder->UpdateTextureRegion(this,
ThebesBufferData(BufferRect(),
BufferRotation()),
updatedRegion);
SwapBuffers(updatedRegion);
}
void
@ -422,6 +426,36 @@ ContentClientRemoteBuffer::SwapBuffers(const nsIntRegion& aFrontUpdatedRegion)
mFrontAndBackBufferDiffer = true;
}
bool
ContentClientRemoteBuffer::LockBuffers()
{
if (mTextureClient) {
bool locked = mTextureClient->Lock(OpenMode::OPEN_READ_WRITE);
if (!locked) {
return false;
}
}
if (mTextureClientOnWhite) {
bool locked = mTextureClientOnWhite->Lock(OpenMode::OPEN_READ_WRITE);
if (!locked) {
UnlockBuffers();
return false;
}
}
return true;
}
void
ContentClientRemoteBuffer::UnlockBuffers()
{
if (mTextureClient && mTextureClient->IsLocked()) {
mTextureClient->Unlock();
}
if (mTextureClientOnWhite && mTextureClientOnWhite->IsLocked()) {
mTextureClientOnWhite->Unlock();
}
}
void
ContentClientRemoteBuffer::Dump(std::stringstream& aStream,
const char* aPrefix,
@ -525,15 +559,6 @@ ContentClientDoubleBuffered::BeginPaint()
void
ContentClientDoubleBuffered::FinalizeFrame(const nsIntRegion& aRegionToDraw)
{
if (mTextureClient) {
DebugOnly<bool> locked = mTextureClient->Lock(OpenMode::OPEN_READ_WRITE);
MOZ_ASSERT(locked);
}
if (mTextureClientOnWhite) {
DebugOnly<bool> locked = mTextureClientOnWhite->Lock(OpenMode::OPEN_READ_WRITE);
MOZ_ASSERT(locked);
}
if (!mFrontAndBackBufferDiffer) {
MOZ_ASSERT(!mDidSelfCopy, "If we have to copy the world, then our buffers are different, right?");
return;
@ -652,18 +677,5 @@ ContentClientDoubleBuffered::UpdateDestinationFrom(const RotatedBuffer& aSource,
}
}
void
ContentClientSingleBuffered::FinalizeFrame(const nsIntRegion& aRegionToDraw)
{
if (mTextureClient) {
DebugOnly<bool> locked = mTextureClient->Lock(OpenMode::OPEN_READ_WRITE);
MOZ_ASSERT(locked);
}
if (mTextureClientOnWhite) {
DebugOnly<bool> locked = mTextureClientOnWhite->Lock(OpenMode::OPEN_READ_WRITE);
MOZ_ASSERT(locked);
}
}
} // namespace layers
} // namespace mozilla

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