Merge m-c to inbound. a=merge
|
@ -15,7 +15,7 @@
|
|||
<project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="803d04e3829fd4fe9261211aa0ddca6b79d4e328"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="c6ef08964711f461a8e6326eae911789d1ec220c"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="c490ae41c67f892232599f4ef049467a922b613e"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
|
||||
|
@ -125,7 +125,7 @@
|
|||
<project name="device-shinano-common" path="device/sony/shinano-common" remote="b2g" revision="e9ef670a15d56ea312e70d4b11c4aaeac404f9d2"/>
|
||||
<project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="1bb28abbc215f45220620af5cd60a8ac1be93722"/>
|
||||
<project name="device/qcom/common" path="device/qcom/common" revision="2501e5940ba69ece7654ff85611c76ae5bda299c"/>
|
||||
<project name="codeaurora_kernel_msm" path="kernel" remote="b2g" revision="5ada05ac150f643ef19e87015df7e106b88effe7"/>
|
||||
<project name="codeaurora_kernel_msm" path="kernel" remote="b2g" revision="d415406c7d810321a7cdd9af9c6271b2a378794c"/>
|
||||
<project name="platform/external/bluetooth/bluedroid" path="external/bluetooth/bluedroid" revision="d61fc97258c8b0c362430dd2eb195dcc4d266f14"/>
|
||||
<project name="init_sh" path="external/init_sh" remote="b2g" revision="3bdd26e092db9c47c5beb04b4809a35f8f767b8a"/>
|
||||
<project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="0a01977f34d6e86fe23d6c0ec75e96ba988bbebb"/>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="803d04e3829fd4fe9261211aa0ddca6b79d4e328"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="c6ef08964711f461a8e6326eae911789d1ec220c"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="c490ae41c67f892232599f4ef049467a922b613e"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="803d04e3829fd4fe9261211aa0ddca6b79d4e328"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="c6ef08964711f461a8e6326eae911789d1ec220c"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="c490ae41c67f892232599f4ef049467a922b613e"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
|
||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="87a2d8ab9248540910e56921654367b78a587095"/>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
</project>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="803d04e3829fd4fe9261211aa0ddca6b79d4e328"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="c6ef08964711f461a8e6326eae911789d1ec220c"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="c490ae41c67f892232599f4ef049467a922b613e"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="657894b4a1dc0a926117f4812e0940229f9f676f"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="cfa80d0c1c13a13a64b049173ea3a1d605f2cffd"/>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="803d04e3829fd4fe9261211aa0ddca6b79d4e328"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="c6ef08964711f461a8e6326eae911789d1ec220c"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="c490ae41c67f892232599f4ef049467a922b613e"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<project name="platform_build" path="build" remote="b2g" revision="61e82f99bb8bc78d52b5717e9a2481ec7267fa33">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="803d04e3829fd4fe9261211aa0ddca6b79d4e328"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="c6ef08964711f461a8e6326eae911789d1ec220c"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="c490ae41c67f892232599f4ef049467a922b613e"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="803d04e3829fd4fe9261211aa0ddca6b79d4e328"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="c6ef08964711f461a8e6326eae911789d1ec220c"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="c490ae41c67f892232599f4ef049467a922b613e"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
|
||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="87a2d8ab9248540910e56921654367b78a587095"/>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="803d04e3829fd4fe9261211aa0ddca6b79d4e328"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="c6ef08964711f461a8e6326eae911789d1ec220c"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="c490ae41c67f892232599f4ef049467a922b613e"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"git": {
|
||||
"git_revision": "803d04e3829fd4fe9261211aa0ddca6b79d4e328",
|
||||
"git_revision": "c6ef08964711f461a8e6326eae911789d1ec220c",
|
||||
"remote": "https://git.mozilla.org/releases/gaia.git",
|
||||
"branch": ""
|
||||
},
|
||||
"revision": "ad35f158cbd79823e9449f30df5d693613eef17f",
|
||||
"revision": "422a64c4639a399fda83fb6ba9af46254a659421",
|
||||
"repo_path": "integration/gaia-central"
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
</project>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="803d04e3829fd4fe9261211aa0ddca6b79d4e328"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="c6ef08964711f461a8e6326eae911789d1ec220c"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="c490ae41c67f892232599f4ef049467a922b613e"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="657894b4a1dc0a926117f4812e0940229f9f676f"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="cfa80d0c1c13a13a64b049173ea3a1d605f2cffd"/>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<project name="platform_build" path="build" remote="b2g" revision="61e82f99bb8bc78d52b5717e9a2481ec7267fa33">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="803d04e3829fd4fe9261211aa0ddca6b79d4e328"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="c6ef08964711f461a8e6326eae911789d1ec220c"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="c490ae41c67f892232599f4ef049467a922b613e"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
|
||||
|
|
|
@ -1459,6 +1459,7 @@ pref("devtools.debugger.auto-pretty-print", false);
|
|||
pref("devtools.debugger.auto-black-box", true);
|
||||
pref("devtools.debugger.tracer", false);
|
||||
pref("devtools.debugger.workers", false);
|
||||
pref("devtools.debugger.promise", false);
|
||||
|
||||
// The default Debugger UI settings
|
||||
pref("devtools.debugger.ui.panes-workers-and-sources-width", 200);
|
||||
|
|
|
@ -55,6 +55,7 @@ skip-if = !crashreporter
|
|||
[browser_CTP_data_urls.js]
|
||||
[browser_CTP_drag_drop.js]
|
||||
[browser_CTP_hide_overlay.js]
|
||||
skip-if = true # Bug 1160788
|
||||
[browser_CTP_iframe.js]
|
||||
skip-if = os == 'linux' || os == 'mac' # Bug 984821
|
||||
[browser_CTP_multi_allow.js]
|
||||
|
|
|
@ -17,6 +17,8 @@ loader.lazyRequireGetter(this, "Actors",
|
|||
"devtools/performance/actors");
|
||||
loader.lazyRequireGetter(this, "RecordingModel",
|
||||
"devtools/performance/recording-model", true);
|
||||
loader.lazyRequireGetter(this, "normalizePerformanceFeatures",
|
||||
"devtools/performance/recording-utils", true);
|
||||
loader.lazyRequireGetter(this, "DevToolsUtils",
|
||||
"devtools/toolkit/DevToolsUtils");
|
||||
|
||||
|
@ -316,16 +318,21 @@ PerformanceFront.prototype = {
|
|||
* A promise that is resolved once recording has started.
|
||||
*/
|
||||
startRecording: Task.async(function*(options = {}) {
|
||||
let model = new RecordingModel(options);
|
||||
let model = new RecordingModel(normalizePerformanceFeatures(options, this.getActorSupport()));
|
||||
|
||||
this.emit("recording-starting", model);
|
||||
|
||||
// All actors are started asynchronously over the remote debugging protocol.
|
||||
// Get the corresponding start times from each one of them.
|
||||
// The timeline and memory actors are target-dependent, so start those as well,
|
||||
// even though these are mocked in older Geckos (FF < 35)
|
||||
let { startTime, position, generation, totalSize } = yield this._profiler.start(options);
|
||||
let timelineStartTime = yield this._timeline.start(options);
|
||||
let memoryStartTime = yield this._memory.start(options);
|
||||
let profilerStart = this._profiler.start(options);
|
||||
let timelineStart = this._timeline.start(options);
|
||||
let memoryStart = this._memory.start(options);
|
||||
|
||||
let { startTime, position, generation, totalSize } = yield profilerStart;
|
||||
let timelineStartTime = yield timelineStart;
|
||||
let memoryStartTime = yield memoryStart;
|
||||
|
||||
let data = {
|
||||
profilerStartTime: startTime, timelineStartTime, memoryStartTime,
|
||||
|
|
|
@ -27,6 +27,10 @@ const GECKO_SYMBOL = "(Gecko)";
|
|||
* determines if this marker should be filtered or not.
|
||||
*/
|
||||
function isMarkerValid (marker, filter) {
|
||||
if (!filter || filter.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let isUnknown = !(marker.name in TIMELINE_BLUEPRINT);
|
||||
if (isUnknown) {
|
||||
return filter.indexOf("UNKNOWN") === -1;
|
||||
|
@ -292,80 +296,6 @@ const DOM = {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A series of collapsers used by the blueprint. These functions are
|
||||
* invoked on a moving window of two markers.
|
||||
*
|
||||
* A function determining how markers are collapsed together.
|
||||
* Invoked with 3 arguments: the current parent marker, the
|
||||
* current marker and a method for peeking i markers ahead. If
|
||||
* nothing is returned, the marker is added as a standalone entry
|
||||
* in the waterfall. Otherwise, an object needs to be returned
|
||||
* with the following properties:
|
||||
* - toParent: The marker to be made a new parent. Can use the current
|
||||
* marker, becoming a parent itself, or make a new marker-esque
|
||||
* object.
|
||||
* - collapse: Whether or not this current marker should be nested within
|
||||
* the current parent.
|
||||
* - finalize: Whether or not the current parent should be finalized and popped
|
||||
* off the stack.
|
||||
*/
|
||||
const CollapseFunctions = {
|
||||
/**
|
||||
* Combines similar markers that are consecutive into a meta marker.
|
||||
*/
|
||||
identical: function (parent, curr, peek) {
|
||||
let next = peek(1);
|
||||
// If there is a parent marker currently being filled and the current marker
|
||||
// should go into the parent marker, make it so.
|
||||
if (parent && parent.name == curr.name) {
|
||||
let finalize = next && next.name !== curr.name;
|
||||
return { collapse: true, finalize };
|
||||
}
|
||||
// Otherwise if the current marker is the same type as the next marker type,
|
||||
// create a new parent marker containing the current marker.
|
||||
if (next && curr.name == next.name) {
|
||||
return { toParent: { name: curr.name, start: curr.start }, collapse: true };
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Combines similar markers that are close to each other in time into a meta marker.
|
||||
*/
|
||||
adjacent: function (parent, curr, peek) {
|
||||
let next = peek(1);
|
||||
if (next && (next.start < curr.end || next.start - curr.end <= 10 /* ms */)) {
|
||||
return CollapseFunctions.identical(parent, curr, peek);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Folds this marker in parent marker if parent marker fully eclipses
|
||||
* the current markers' time.
|
||||
*/
|
||||
child: function (parent, curr, peek) {
|
||||
let next = peek(1);
|
||||
// If this marker is consumed by current parent, collapse
|
||||
if (parent && curr.end <= parent.end) {
|
||||
let finalize = next && next.end > parent.end;
|
||||
return { collapse: true, finalize };
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Turns this marker into a parent marker if the next marker
|
||||
* is fully eclipsed by the current marker.
|
||||
*/
|
||||
parent: function (parent, curr, peek) {
|
||||
let next = peek(1);
|
||||
// If the next marker is fully consumed by this marker, make
|
||||
// it a parent (do not collapse, the marker becomes a parent).
|
||||
if (next && curr.end >= next.end) {
|
||||
return { toParent: curr };
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Mapping of JS marker causes to a friendlier form. Only
|
||||
* markers that are considered "from content" should be labeled here.
|
||||
|
@ -480,6 +410,5 @@ exports.getMarkerLabel = getMarkerLabel;
|
|||
exports.getMarkerClassName = getMarkerClassName;
|
||||
exports.getMarkerFields = getMarkerFields;
|
||||
exports.DOM = DOM;
|
||||
exports.CollapseFunctions = CollapseFunctions;
|
||||
exports.Formatters = Formatters;
|
||||
exports.getBlueprintFor = getBlueprintFor;
|
||||
|
|
|
@ -4,12 +4,45 @@
|
|||
"use strict";
|
||||
|
||||
const { Cc, Ci, Cu, Cr } = require("chrome");
|
||||
loader.lazyRequireGetter(this, "extend",
|
||||
"sdk/util/object", true);
|
||||
|
||||
/**
|
||||
* Utility functions for managing recording models and their internal data,
|
||||
* such as filtering profile samples or offsetting timestamps.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Takes an options object for `startRecording`, and normalizes
|
||||
* it based off of server support. For example, if the user
|
||||
* requests to record memory `withMemory = true`, but the server does
|
||||
* not support that feature, then the `false` will overwrite user preference
|
||||
* in order to define the recording with what is actually available, not
|
||||
* what the user initially requested.
|
||||
*
|
||||
* @param {object} options
|
||||
* @param {boolean} support.timeline
|
||||
* @param {boolean} support.memory
|
||||
* @param {boolean}
|
||||
*/
|
||||
function normalizePerformanceFeatures (options, support) {
|
||||
let supportOptions = Object.create(null);
|
||||
|
||||
// TODO bug 1172180 disable `withAllocations` and `withJITOptimizations` when using the
|
||||
// pseudo front, as we only want to support it directly from the real actor
|
||||
// in Fx42+
|
||||
if (!support.memory) {
|
||||
supportOptions.withMemory = false;
|
||||
supportOptions.withAllocations = false;
|
||||
}
|
||||
if (!support.timeline) {
|
||||
supportOptions.withMarkers = false;
|
||||
supportOptions.withTicks = false;
|
||||
}
|
||||
|
||||
return extend(options, supportOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters all the samples in the provided profiler data to be more recent
|
||||
* than the specified start time.
|
||||
|
@ -531,6 +564,7 @@ UniqueStacks.prototype.getOrAddStringIndex = function(s) {
|
|||
return this._uniqueStrings.getOrAddStringIndex(s);
|
||||
};
|
||||
|
||||
exports.normalizePerformanceFeatures = normalizePerformanceFeatures;
|
||||
exports.filterSamples = filterSamples;
|
||||
exports.offsetSampleTimes = offsetSampleTimes;
|
||||
exports.offsetMarkerTimes = offsetMarkerTimes;
|
||||
|
|
|
@ -7,17 +7,32 @@
|
|||
* Utility functions for collapsing markers into a waterfall.
|
||||
*/
|
||||
|
||||
loader.lazyRequireGetter(this, "extend",
|
||||
"sdk/util/object", true);
|
||||
loader.lazyRequireGetter(this, "MarkerUtils",
|
||||
"devtools/performance/marker-utils");
|
||||
|
||||
/**
|
||||
* Creates a parent marker, which functions like a regular marker,
|
||||
* but is able to hold additional child markers.
|
||||
*
|
||||
* The marker is seeded with values from `marker`.
|
||||
* @param object marker
|
||||
* @return object
|
||||
*/
|
||||
function createParentNode (marker) {
|
||||
return extend(marker, { submarkers: [] });
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Collapses markers into a tree-like structure.
|
||||
* @param object markerNode
|
||||
* @param object rootNode
|
||||
* @param array markersList
|
||||
* @param array filter
|
||||
*/
|
||||
function collapseMarkersIntoNode({ markerNode, markersList, filter }) {
|
||||
let { getCurrentParentNode, collapseMarker, addParentNode, popParentNode } = createParentNodeFactory(markerNode);
|
||||
function collapseMarkersIntoNode({ rootNode, markersList, filter }) {
|
||||
let { getCurrentParentNode, pushNode, popParentNode } = createParentNodeFactory(rootNode);
|
||||
|
||||
for (let i = 0, len = markersList.length; i < len; i++) {
|
||||
let curr = markersList[i];
|
||||
|
@ -29,48 +44,52 @@ function collapseMarkersIntoNode({ markerNode, markersList, filter }) {
|
|||
|
||||
let parentNode = getCurrentParentNode();
|
||||
let blueprint = MarkerUtils.getBlueprintFor(curr);
|
||||
let collapse = blueprint.collapseFunc || (() => null);
|
||||
let peek = distance => markersList[i + distance];
|
||||
|
||||
let collapseInfo = collapse(parentNode, curr, peek);
|
||||
if (collapseInfo) {
|
||||
let { collapse, toParent, finalize } = collapseInfo;
|
||||
let nestable = "nestable" in blueprint ? blueprint.nestable : true;
|
||||
let collapsible = "collapsible" in blueprint ? blueprint.collapsible : true;
|
||||
|
||||
// If `toParent` is an object, use it as the next parent marker
|
||||
if (typeof toParent === "object") {
|
||||
addParentNode(toParent);
|
||||
let finalized = null;
|
||||
|
||||
// If this marker is collapsible, turn it into a parent marker.
|
||||
// If there are no children within it later, it will be turned
|
||||
// back into a normal node.
|
||||
if (collapsible) {
|
||||
curr = createParentNode(curr);
|
||||
}
|
||||
|
||||
if (collapse) {
|
||||
collapseMarker(curr);
|
||||
// If not nestible, just push it inside the root node,
|
||||
// like console.time/timeEnd.
|
||||
if (!nestable) {
|
||||
pushNode(rootNode, curr);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the marker specifies this parent marker is full,
|
||||
// pop it from the stack.
|
||||
if (finalize) {
|
||||
// First off, if any parent nodes exist, finish them off
|
||||
// recursively upwards if this marker is outside their ranges and nestable.
|
||||
while (!finalized && parentNode) {
|
||||
// If this marker is eclipsed by the current parent marker,
|
||||
// make it a child of the current parent and stop
|
||||
// going upwards.
|
||||
if (nestable && curr.end <= parentNode.end) {
|
||||
pushNode(parentNode, curr);
|
||||
finalized = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// If this marker is still nestable, but outside of the range
|
||||
// of the current parent, iterate upwards on the next parent
|
||||
// and finalize the current parent.
|
||||
if (nestable) {
|
||||
popParentNode();
|
||||
}
|
||||
} else {
|
||||
markerNode.submarkers.push(curr);
|
||||
}
|
||||
parentNode = getCurrentParentNode();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a parent marker, which functions like a regular marker,
|
||||
* but is able to hold additional child markers.
|
||||
*
|
||||
* The marker is seeded with values from `marker`.
|
||||
* @param object marker
|
||||
* @return object
|
||||
*/
|
||||
function makeParentMarkerNode (marker) {
|
||||
let node = Object.create(null);
|
||||
for (let prop in marker) {
|
||||
node[prop] = marker[prop];
|
||||
if (!finalized) {
|
||||
pushNode(rootNode, curr);
|
||||
}
|
||||
}
|
||||
node.submarkers = [];
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -98,6 +117,14 @@ function createParentNodeFactory (root) {
|
|||
if (lastParent.end == void 0) {
|
||||
lastParent.end = lastParent.submarkers[lastParent.submarkers.length - 1].end;
|
||||
}
|
||||
|
||||
// If no children were ever pushed into this parent node,
|
||||
// remove it's submarkers so it behaves like a non collapsible
|
||||
// node.
|
||||
if (!lastParent.submarkers.length) {
|
||||
delete lastParent.submarkers;
|
||||
}
|
||||
|
||||
return lastParent;
|
||||
},
|
||||
|
||||
|
@ -106,29 +133,22 @@ function createParentNodeFactory (root) {
|
|||
*/
|
||||
getCurrentParentNode: () => parentMarkers.length ? parentMarkers[parentMarkers.length - 1] : null,
|
||||
|
||||
/**
|
||||
* Push a new parent node onto the stack and nest it with the
|
||||
* next most recent parent node, or root if no other parent nodes.
|
||||
*/
|
||||
addParentNode: (marker) => {
|
||||
let parentMarker = makeParentMarkerNode(marker);
|
||||
(factory.getCurrentParentNode() || root).submarkers.push(parentMarker);
|
||||
parentMarkers.push(parentMarker);
|
||||
},
|
||||
|
||||
/**
|
||||
* Push this marker into the most recent parent node.
|
||||
*/
|
||||
collapseMarker: (marker) => {
|
||||
if (parentMarkers.length === 0) {
|
||||
throw new Error("Cannot collapse marker with no parents.");
|
||||
pushNode: (parent, marker) => {
|
||||
parent.submarkers.push(marker);
|
||||
|
||||
// If pushing a parent marker, track it as the top of
|
||||
// the parent stack.
|
||||
if (marker.submarkers) {
|
||||
parentMarkers.push(marker);
|
||||
}
|
||||
factory.getCurrentParentNode().submarkers.push(marker);
|
||||
}
|
||||
};
|
||||
|
||||
return factory;
|
||||
}
|
||||
|
||||
exports.makeParentMarkerNode = makeParentMarkerNode;
|
||||
exports.createParentNode = createParentNode;
|
||||
exports.collapseMarkersIntoNode = collapseMarkersIntoNode;
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"use strict";
|
||||
|
||||
const { L10N } = require("devtools/performance/global");
|
||||
const { Formatters, CollapseFunctions: collapse } = require("devtools/performance/marker-utils");
|
||||
const { Formatters } = require("devtools/performance/marker-utils");
|
||||
|
||||
/**
|
||||
* A simple schema for mapping markers to the timeline UI. The keys correspond
|
||||
|
@ -23,19 +23,11 @@ const { Formatters, CollapseFunctions: collapse } = require("devtools/performanc
|
|||
* for `.marker-details-bullet.{COLORNAME}` for the equivilent
|
||||
* entry in ./browser/themes/shared/devtools/performance.inc.css
|
||||
* https://developer.mozilla.org/en-US/docs/Tools/DevToolsColors
|
||||
* - collapseFunc: A function determining how markers are collapsed together.
|
||||
* Invoked with 3 arguments: the current parent marker, the
|
||||
* current marker and a method for peeking i markers ahead. If
|
||||
* nothing is returned, the marker is added as a standalone entry
|
||||
* in the waterfall. Otherwise, an object needs to be returned
|
||||
* with the following properties:
|
||||
* - toParent: The marker to be made a new parent. Can use the current
|
||||
* marker, becoming a parent itself, or make a new marker-esque
|
||||
* object.
|
||||
* - collapse: Whether or not this current marker should be nested within
|
||||
* the current parent.
|
||||
* - finalize: Whether or not the current parent should be finalized and popped
|
||||
* off the stack.
|
||||
* - collapsible: Whether or not this marker can contain other markers it
|
||||
* eclipses, and becomes collapsible to reveal its nestable children.
|
||||
* Defaults to true.
|
||||
* - nestable: Whether or not this marker can be nested inside an eclipsing
|
||||
* collapsible marker. Defaults to true.
|
||||
* - fields: An optional array of marker properties you wish to display in the
|
||||
* marker details view. For example, a field in the array such as
|
||||
* { property: "aCauseName", label: "Cause" } would render a string
|
||||
|
@ -61,28 +53,24 @@ const TIMELINE_BLUEPRINT = {
|
|||
"UNKNOWN": {
|
||||
group: 2,
|
||||
colorName: "graphs-grey",
|
||||
collapseFunc: collapse.child,
|
||||
label: Formatters.UnknownLabel
|
||||
label: Formatters.UnknownLabel,
|
||||
},
|
||||
|
||||
/* Group 0 - Reflow and Rendering pipeline */
|
||||
"Styles": {
|
||||
group: 0,
|
||||
colorName: "graphs-purple",
|
||||
collapseFunc: collapse.child,
|
||||
label: L10N.getStr("timeline.label.styles2"),
|
||||
fields: Formatters.StylesFields,
|
||||
},
|
||||
"Reflow": {
|
||||
group: 0,
|
||||
colorName: "graphs-purple",
|
||||
collapseFunc: collapse.child,
|
||||
label: L10N.getStr("timeline.label.reflow2"),
|
||||
},
|
||||
"Paint": {
|
||||
group: 0,
|
||||
colorName: "graphs-green",
|
||||
collapseFunc: collapse.child,
|
||||
label: L10N.getStr("timeline.label.paint"),
|
||||
},
|
||||
|
||||
|
@ -90,33 +78,28 @@ const TIMELINE_BLUEPRINT = {
|
|||
"DOMEvent": {
|
||||
group: 1,
|
||||
colorName: "graphs-yellow",
|
||||
collapseFunc: collapse.parent,
|
||||
label: L10N.getStr("timeline.label.domevent"),
|
||||
fields: Formatters.DOMEventFields,
|
||||
},
|
||||
"Javascript": {
|
||||
group: 1,
|
||||
colorName: "graphs-yellow",
|
||||
collapseFunc: either(collapse.parent, collapse.child),
|
||||
label: Formatters.JSLabel,
|
||||
fields: Formatters.JSFields
|
||||
},
|
||||
"Parse HTML": {
|
||||
group: 1,
|
||||
colorName: "graphs-yellow",
|
||||
collapseFunc: either(collapse.parent, collapse.child),
|
||||
label: L10N.getStr("timeline.label.parseHTML"),
|
||||
},
|
||||
"Parse XML": {
|
||||
group: 1,
|
||||
colorName: "graphs-yellow",
|
||||
collapseFunc: either(collapse.parent, collapse.child),
|
||||
label: L10N.getStr("timeline.label.parseXML"),
|
||||
},
|
||||
"GarbageCollection": {
|
||||
group: 1,
|
||||
colorName: "graphs-red",
|
||||
collapseFunc: either(collapse.parent, collapse.child),
|
||||
label: Formatters.GCLabel,
|
||||
fields: [
|
||||
{ property: "causeName", label: "Reason:" },
|
||||
|
@ -126,14 +109,12 @@ const TIMELINE_BLUEPRINT = {
|
|||
"nsCycleCollector::Collect": {
|
||||
group: 1,
|
||||
colorName: "graphs-red",
|
||||
collapseFunc: either(collapse.parent, collapse.child),
|
||||
label: "Cycle Collection",
|
||||
fields: Formatters.CycleCollectionFields,
|
||||
},
|
||||
"nsCycleCollector::ForgetSkippable": {
|
||||
group: 1,
|
||||
colorName: "graphs-red",
|
||||
collapseFunc: either(collapse.parent, collapse.child),
|
||||
label: "Cycle Collection",
|
||||
fields: Formatters.CycleCollectionFields,
|
||||
},
|
||||
|
@ -147,34 +128,21 @@ const TIMELINE_BLUEPRINT = {
|
|||
property: "causeName",
|
||||
label: L10N.getStr("timeline.markerDetail.consoleTimerName")
|
||||
}],
|
||||
nestable: false,
|
||||
collapsible: false,
|
||||
},
|
||||
"TimeStamp": {
|
||||
group: 2,
|
||||
colorName: "graphs-blue",
|
||||
collapseFunc: collapse.child,
|
||||
label: sublabelForProperty(L10N.getStr("timeline.label.timestamp"), "causeName"),
|
||||
fields: [{
|
||||
property: "causeName",
|
||||
label: "Label:"
|
||||
}],
|
||||
collapsible: false,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper for creating a function that returns the first defined result from
|
||||
* a list of functions passed in as params, in order.
|
||||
* @param ...function fun
|
||||
* @return any
|
||||
*/
|
||||
function either(...fun) {
|
||||
return function() {
|
||||
for (let f of fun) {
|
||||
let result = f.apply(null, arguments);
|
||||
if (result !== undefined) return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a main label (like "Timestamp") and a property,
|
||||
* and returns a marker that will print out the property
|
||||
|
|
|
@ -485,39 +485,28 @@ let PerformanceController = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Utility method taking the currently selected recording item's features, or optionally passed
|
||||
* in recording item, as well as the actor support on the server, returning a boolean
|
||||
* indicating if the requirements pass or not. Used to toggle features' visibility mostly.
|
||||
* Utility method taking a string or an array of strings of feature names (like
|
||||
* "withAllocations" or "withMarkers"), and returns whether or not the current
|
||||
* recording supports that feature, based off of UI preferences and server support.
|
||||
*
|
||||
* @option {Array<string>} features
|
||||
* An array of strings indicating what configuration is needed on the recording
|
||||
* @option {Array<string>|string} features
|
||||
* A string or array of strings indicating what configuration is needed on the recording
|
||||
* model, like `withTicks`, or `withMemory`.
|
||||
* @option {Array<string>} actors
|
||||
* An array of strings indicating what actors must exist.
|
||||
* @option {boolean} mustBeCompleted
|
||||
* A boolean indicating whether the recording must be either completed or not.
|
||||
* Setting to undefined will allow either state.
|
||||
* @param {RecordingModel} recording
|
||||
* An optional recording model to use instead of the currently selected.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
isFeatureSupported: function ({ features, actors, mustBeCompleted }, recording) {
|
||||
recording = recording || this.getCurrentRecording();
|
||||
let recordingConfig = recording ? recording.getConfiguration() : {};
|
||||
let currentCompletedState = recording ? recording.isCompleted() : void 0;
|
||||
let actorsSupported = gFront.getActorSupport();
|
||||
|
||||
if (mustBeCompleted != null && mustBeCompleted !== currentCompletedState) {
|
||||
return false;
|
||||
}
|
||||
if (actors && !actors.every(a => actorsSupported[a])) {
|
||||
return false;
|
||||
}
|
||||
if (features && !features.every(f => recordingConfig[f])) {
|
||||
return false;
|
||||
}
|
||||
isFeatureSupported: function (features) {
|
||||
if (!features) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let recording = this.getCurrentRecording();
|
||||
if (!recording) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let config = recording.getConfiguration();
|
||||
return [].concat(features).every(f => config[f]);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -27,6 +27,7 @@ support-files =
|
|||
[browser_perf-compatibility-05.js]
|
||||
[browser_perf-compatibility-06.js]
|
||||
[browser_perf-compatibility-07.js]
|
||||
[browser_perf-compatibility-08.js]
|
||||
[browser_perf-clear-01.js]
|
||||
[browser_perf-clear-02.js]
|
||||
[browser_perf-columns-js-calltree.js]
|
||||
|
|
|
@ -66,8 +66,8 @@ let test = Task.async(function*() {
|
|||
"waterfall view button hidden when timeline mocked");
|
||||
is($("#select-js-calltree-view").hidden, false,
|
||||
"jscalltree view button not hidden when timeline/memory mocked");
|
||||
is($("#select-js-flamegraph-view").hidden, true,
|
||||
"jsflamegraph view button hidden when timeline mocked");
|
||||
is($("#select-js-flamegraph-view").hidden, false,
|
||||
"jsflamegraph view button not hidden when timeline mocked");
|
||||
is($("#select-memory-calltree-view").hidden, true,
|
||||
"memorycalltree view button hidden when memory mocked");
|
||||
is($("#select-memory-flamegraph-view").hidden, true,
|
||||
|
|
|
@ -23,7 +23,6 @@ let test = Task.async(function*() {
|
|||
yield startRecording(panel);
|
||||
yield busyWait(100);
|
||||
yield waitUntil(() => PerformanceController.getCurrentRecording().getTicks().length);
|
||||
yield waitUntil(() => PerformanceController.getCurrentRecording().getMemory().length);
|
||||
yield waitUntil(() => PerformanceController.getCurrentRecording().getMarkers().length);
|
||||
yield stopRecording(panel);
|
||||
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests that when setting recording features in the UI (like enabling framerate or memory),
|
||||
* if the target does not support these features, then the target's support overrides
|
||||
* the UI preferences when fetching configuration from a recording.
|
||||
*/
|
||||
|
||||
const WAIT_TIME = 100;
|
||||
|
||||
let test = Task.async(function*() {
|
||||
yield testMockMemory();
|
||||
yield testMockMemoryAndTimeline();
|
||||
finish();
|
||||
});
|
||||
|
||||
// Test mock memory
|
||||
function *testMockMemory () {
|
||||
let { target, panel, toolbox } = yield initPerformance(SIMPLE_URL, "performance", {
|
||||
TEST_MOCK_MEMORY_ACTOR: true,
|
||||
});
|
||||
Services.prefs.setBoolPref(MEMORY_PREF, true);
|
||||
Services.prefs.setBoolPref(FRAMERATE_PREF, true);
|
||||
Services.prefs.setBoolPref(ALLOCATIONS_PREF, true);
|
||||
let { EVENTS, $, gFront, PerformanceController, PerformanceView, DetailsView, WaterfallView } = panel.panelWin;
|
||||
|
||||
let { memory: memorySupport, timeline: timelineSupport } = gFront.getActorSupport();
|
||||
yield startRecording(panel, { waitForOverview: false });
|
||||
yield waitUntil(() => PerformanceController.getCurrentRecording().getTicks().length);
|
||||
yield waitUntil(() => PerformanceController.getCurrentRecording().getMarkers().length);
|
||||
yield stopRecording(panel, { waitForOverview: false });
|
||||
|
||||
let config = PerformanceController.getCurrentRecording().getConfiguration();
|
||||
let {
|
||||
markers, allocations, memory, ticks
|
||||
} = PerformanceController.getCurrentRecording().getAllData();
|
||||
|
||||
ok(typeof config.bufferSize === "number", "sanity check, config options contains `bufferSize`.");
|
||||
|
||||
is(config.withMemory, false,
|
||||
"Recording configuration set by target's support, not by UI prefs [No Memory Actor: withMemory]");
|
||||
is(config.withAllocations, false,
|
||||
"Recording configuration set by target's support, not by UI prefs [No Memory Actor: withAllocations]");
|
||||
|
||||
is(config.withMarkers, true,
|
||||
"Recording configuration set by target's support, not by UI prefs [No Memory Actor: withMarkers]");
|
||||
is(config.withTicks, true,
|
||||
"Recording configuration set by target's support, not by UI prefs [No Memory Actor: withTicks]");
|
||||
|
||||
ok(markers.length > 0, "markers exist.");
|
||||
ok(ticks.length > 0, "ticks exist.");
|
||||
isEmptyArray(memory, "memory");
|
||||
isEmptyArray(allocations.sites, "allocations.sites");
|
||||
isEmptyArray(allocations.timestamps, "allocations.timestamps");
|
||||
isEmptyArray(allocations.frames, "allocations.frames");
|
||||
isEmptyArray(allocations.counts, "allocations.counts");
|
||||
|
||||
is($("#overview-pane").hidden, false,
|
||||
"overview pane not hidden when server not supporting memory actors, yet UI prefs request them.");
|
||||
is($("#select-waterfall-view").hidden, false,
|
||||
"waterfall view button not hidden when memory mocked, and UI prefs enable them");
|
||||
is($("#select-js-calltree-view").hidden, false,
|
||||
"jscalltree view button not hidden when memory mocked, and UI prefs enable them");
|
||||
is($("#select-js-flamegraph-view").hidden, false,
|
||||
"jsflamegraph view button not hidden when memory mocked, and UI prefs enable them");
|
||||
is($("#select-memory-calltree-view").hidden, true,
|
||||
"memorycalltree view button hidden when memory mocked, and UI prefs enable them");
|
||||
is($("#select-memory-flamegraph-view").hidden, true,
|
||||
"memoryflamegraph view button hidden when memory mocked, and UI prefs enable them");
|
||||
|
||||
yield gFront.destroy();
|
||||
yield teardown(panel);
|
||||
}
|
||||
|
||||
// Test mock memory and timeline actor
|
||||
function *testMockMemoryAndTimeline() {
|
||||
let { target, panel, toolbox } = yield initPerformance(SIMPLE_URL, "performance", {
|
||||
TEST_MOCK_MEMORY_ACTOR: true,
|
||||
TEST_MOCK_TIMELINE_ACTOR: true,
|
||||
});
|
||||
Services.prefs.setBoolPref(MEMORY_PREF, true);
|
||||
Services.prefs.setBoolPref(FRAMERATE_PREF, true);
|
||||
Services.prefs.setBoolPref(ALLOCATIONS_PREF, true);
|
||||
let { EVENTS, $, gFront, PerformanceController, PerformanceView, DetailsView, WaterfallView } = panel.panelWin;
|
||||
|
||||
let { memory: memorySupport, timeline: timelineSupport } = gFront.getActorSupport();
|
||||
yield startRecording(panel, { waitForOverview: false });
|
||||
yield busyWait(WAIT_TIME);
|
||||
yield stopRecording(panel, { waitForOverview: false });
|
||||
|
||||
let config = PerformanceController.getCurrentRecording().getConfiguration();
|
||||
let {
|
||||
markers, allocations, memory, ticks
|
||||
} = PerformanceController.getCurrentRecording().getAllData();
|
||||
|
||||
ok(typeof config.bufferSize === "number", "sanity check, config options contains `bufferSize`.");
|
||||
|
||||
is(config.withMemory, false,
|
||||
"Recording configuration set by target's support, not by UI prefs [No Memory/Timeline Actor: withMemory]");
|
||||
is(config.withAllocations, false,
|
||||
"Recording configuration set by target's support, not by UI prefs [No Memory/Timeline Actor: withAllocations]");
|
||||
|
||||
is(config.withMarkers, false,
|
||||
"Recording configuration set by target's support, not by UI prefs [No Memory/Timeline Actor: withMarkers]");
|
||||
is(config.withTicks, false,
|
||||
"Recording configuration set by target's support, not by UI prefs [No Memory/Timeline Actor: withTicks]");
|
||||
isEmptyArray(markers, "markers");
|
||||
isEmptyArray(ticks, "ticks");
|
||||
isEmptyArray(memory, "memory");
|
||||
isEmptyArray(allocations.sites, "allocations.sites");
|
||||
isEmptyArray(allocations.timestamps, "allocations.timestamps");
|
||||
isEmptyArray(allocations.frames, "allocations.frames");
|
||||
isEmptyArray(allocations.counts, "allocations.counts");
|
||||
|
||||
is($("#overview-pane").hidden, true,
|
||||
"overview pane hidden when server not supporting memory/timeline actors, yet UI prefs request them.");
|
||||
is($("#select-waterfall-view").hidden, true,
|
||||
"waterfall view button hidden when memory/timeline mocked, and UI prefs enable them");
|
||||
is($("#select-js-calltree-view").hidden, false,
|
||||
"jscalltree view button not hidden when memory/timeline mocked, and UI prefs enable them");
|
||||
is($("#select-js-flamegraph-view").hidden, false,
|
||||
"jsflamegraph view button not hidden when memory/timeline mocked, and UI prefs enable them");
|
||||
is($("#select-memory-calltree-view").hidden, true,
|
||||
"memorycalltree view button hidden when memory/timeline mocked, and UI prefs enable them");
|
||||
is($("#select-memory-flamegraph-view").hidden, true,
|
||||
"memoryflamegraph view button hidden when memory/timeline mocked, and UI prefs enable them");
|
||||
|
||||
yield gFront.destroy();
|
||||
yield teardown(panel);
|
||||
}
|
||||
|
||||
function isEmptyArray (array, name) {
|
||||
ok(Array.isArray(array), `${name} is an array`);
|
||||
is(array.length, 0, `${name} is empty`);
|
||||
}
|
|
@ -5,13 +5,17 @@
|
|||
* Tests if the waterfall collapsing logic works properly.
|
||||
*/
|
||||
|
||||
function test() {
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function test() {
|
||||
const WaterfallUtils = devtools.require("devtools/performance/waterfall-utils");
|
||||
|
||||
let rootMarkerNode = WaterfallUtils.makeParentMarkerNode({ name: "(root)" });
|
||||
let rootMarkerNode = WaterfallUtils.createParentNode({ name: "(root)" });
|
||||
|
||||
WaterfallUtils.collapseMarkersIntoNode({
|
||||
markerNode: rootMarkerNode,
|
||||
rootNode: rootMarkerNode,
|
||||
markersList: gTestMarkers
|
||||
});
|
||||
|
||||
|
@ -22,14 +26,13 @@ function test() {
|
|||
compare(marker.submarkers[i], expected.submarkers[i]);
|
||||
}
|
||||
} else if (prop !== "uid") {
|
||||
is(marker[prop], expected[prop], `${expected.name} matches ${prop}`);
|
||||
equal(marker[prop], expected[prop], `${expected.name} matches ${prop}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compare(rootMarkerNode, gExpectedOutput);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
const gTestMarkers = [
|
||||
{ start: 1, end: 18, name: "DOMEvent" },
|
||||
|
@ -44,7 +47,7 @@ const gTestMarkers = [
|
|||
{ start: 12, end: 13, name: "Parse XML" },
|
||||
{ start: 14, end: 15, name: "GarbageCollection" },
|
||||
// Test that JS markers can be parents without being a child of DOM events
|
||||
{ start: 25, end: 30, name: "JavaScript" },
|
||||
{ start: 25, end: 30, name: "Javascript" },
|
||||
{ start: 26, end: 27, name: "Paint" },
|
||||
];
|
||||
|
||||
|
@ -61,7 +64,7 @@ const gExpectedOutput = {
|
|||
{ start: 14, end: 15, name: "GarbageCollection" },
|
||||
]}
|
||||
]},
|
||||
{ start: 25, end: 30, name: "JavaScript", submarkers: [
|
||||
{ start: 25, end: 30, name: "Javascript", submarkers: [
|
||||
{ start: 26, end: 27, name: "Paint" },
|
||||
]}
|
||||
]};
|
||||
|
|
|
@ -6,13 +6,17 @@
|
|||
* markers, as they should ignore any sort of collapsing.
|
||||
*/
|
||||
|
||||
function test() {
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function test() {
|
||||
const WaterfallUtils = devtools.require("devtools/performance/waterfall-utils");
|
||||
|
||||
let rootMarkerNode = WaterfallUtils.makeParentMarkerNode({ name: "(root)" });
|
||||
let rootMarkerNode = WaterfallUtils.createParentNode({ name: "(root)" });
|
||||
|
||||
WaterfallUtils.collapseMarkersIntoNode({
|
||||
markerNode: rootMarkerNode,
|
||||
rootNode: rootMarkerNode,
|
||||
markersList: gTestMarkers
|
||||
});
|
||||
|
||||
|
@ -23,14 +27,13 @@ function test() {
|
|||
compare(marker.submarkers[i], expected.submarkers[i]);
|
||||
}
|
||||
} else if (prop !== "uid") {
|
||||
is(marker[prop], expected[prop], `${expected.name} matches ${prop}`);
|
||||
equal(marker[prop], expected[prop], `${expected.name} matches ${prop}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compare(rootMarkerNode, gExpectedOutput);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
const gTestMarkers = [
|
||||
{ start: 2, end: 9, name: "Javascript" },
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests that the waterfall collapsing works when atleast two
|
||||
* collapsible markers downward, and the following marker is outside of both ranges.
|
||||
*/
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function test() {
|
||||
const WaterfallUtils = devtools.require("devtools/performance/waterfall-utils");
|
||||
|
||||
let rootMarkerNode = WaterfallUtils.createParentNode({ name: "(root)" });
|
||||
|
||||
WaterfallUtils.collapseMarkersIntoNode({
|
||||
rootNode: rootMarkerNode,
|
||||
markersList: gTestMarkers
|
||||
});
|
||||
|
||||
function compare (marker, expected) {
|
||||
for (let prop in expected) {
|
||||
if (prop === "submarkers") {
|
||||
for (let i = 0; i < expected.submarkers.length; i++) {
|
||||
compare(marker.submarkers[i], expected.submarkers[i]);
|
||||
}
|
||||
} else if (prop !== "uid") {
|
||||
equal(marker[prop], expected[prop], `${expected.name} matches ${prop}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compare(rootMarkerNode, gExpectedOutput);
|
||||
});
|
||||
|
||||
const gTestMarkers = [
|
||||
{ start: 2, end: 10, name: "DOMEvent" },
|
||||
{ start: 3, end: 9, name: "Javascript" },
|
||||
{ start: 4, end: 8, name: "GarbageCollection" },
|
||||
{ start: 11, end: 12, name: "Styles" },
|
||||
{ start: 13, end: 14, name: "Styles" },
|
||||
{ start: 15, end: 25, name: "DOMEvent" },
|
||||
{ start: 17, end: 24, name: "Javascript" },
|
||||
{ start: 18, end: 19, name: "GarbageCollection" },
|
||||
];
|
||||
|
||||
const gExpectedOutput = {
|
||||
name: "(root)", submarkers: [
|
||||
{ start: 2, end: 10, name: "DOMEvent", submarkers: [
|
||||
{ start: 3, end: 9, name: "Javascript", submarkers: [
|
||||
{ start: 4, end: 8, name: "GarbageCollection" }
|
||||
]}
|
||||
]},
|
||||
{ start: 11, end: 12, name: "Styles" },
|
||||
{ start: 13, end: 14, name: "Styles" },
|
||||
{ start: 15, end: 25, name: "DOMEvent", submarkers: [
|
||||
{ start: 17, end: 24, name: "Javascript", submarkers: [
|
||||
{ start: 18, end: 19, name: "GarbageCollection" }
|
||||
]}
|
||||
]},
|
||||
]};
|
|
@ -24,3 +24,4 @@ skip-if = toolkit == 'android' || toolkit == 'gonk'
|
|||
[test_tree-model-09.js]
|
||||
[test_waterfall-utils-collapse-01.js]
|
||||
[test_waterfall-utils-collapse-02.js]
|
||||
[test_waterfall-utils-collapse-03.js]
|
||||
|
|
|
@ -136,10 +136,10 @@ let WaterfallView = Heritage.extend(DetailsSubview, {
|
|||
return cached;
|
||||
}
|
||||
|
||||
let rootMarkerNode = WaterfallUtils.makeParentMarkerNode({ name: "(root)" });
|
||||
let rootMarkerNode = WaterfallUtils.createParentNode({ name: "(root)" });
|
||||
|
||||
WaterfallUtils.collapseMarkersIntoNode({
|
||||
markerNode: rootMarkerNode,
|
||||
rootNode: rootMarkerNode,
|
||||
markersList: markers,
|
||||
filter: this._hiddenMarkers
|
||||
});
|
||||
|
|
|
@ -16,7 +16,6 @@ let DetailsView = {
|
|||
"waterfall": {
|
||||
id: "waterfall-view",
|
||||
view: WaterfallView,
|
||||
actors: ["timeline"],
|
||||
features: ["withMarkers"]
|
||||
},
|
||||
"js-calltree": {
|
||||
|
@ -26,18 +25,15 @@ let DetailsView = {
|
|||
"js-flamegraph": {
|
||||
id: "js-flamegraph-view",
|
||||
view: JsFlameGraphView,
|
||||
actors: ["timeline"]
|
||||
},
|
||||
"memory-calltree": {
|
||||
id: "memory-calltree-view",
|
||||
view: MemoryCallTreeView,
|
||||
actors: ["memory"],
|
||||
features: ["withAllocations"]
|
||||
},
|
||||
"memory-flamegraph": {
|
||||
id: "memory-flamegraph-view",
|
||||
view: MemoryFlameGraphView,
|
||||
actors: ["memory", "timeline"],
|
||||
features: ["withAllocations"]
|
||||
},
|
||||
"optimizations": {
|
||||
|
@ -97,7 +93,7 @@ let DetailsView = {
|
|||
let invalidCurrentView = false;
|
||||
|
||||
for (let [name, { view }] of Iterator(this.components)) {
|
||||
let isSupported = this._isViewSupported(name, true);
|
||||
let isSupported = this._isViewSupported(name);
|
||||
|
||||
$(`toolbarbutton[data-view=${name}]`).hidden = !isSupported;
|
||||
|
||||
|
@ -123,15 +119,21 @@ let DetailsView = {
|
|||
}),
|
||||
|
||||
/**
|
||||
* Takes a view name and optionally if there must be a currently recording in progress.
|
||||
* Takes a view name and determines if the current recording
|
||||
* can support the view.
|
||||
*
|
||||
* @param {string} viewName
|
||||
* @param {boolean?} mustBeCompleted
|
||||
* @return {boolean}
|
||||
*/
|
||||
_isViewSupported: function (viewName, mustBeCompleted) {
|
||||
let { features, actors } = this.components[viewName];
|
||||
return PerformanceController.isFeatureSupported({ features, actors, mustBeCompleted });
|
||||
_isViewSupported: function (viewName) {
|
||||
let { features } = this.components[viewName];
|
||||
let recording = PerformanceController.getCurrentRecording();
|
||||
|
||||
if (!recording || !recording.isCompleted()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return PerformanceController.isFeatureSupported(features);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -11,15 +11,12 @@ const FRAMERATE_GRAPH_LOW_RES_INTERVAL = 100; // ms
|
|||
const FRAMERATE_GRAPH_HIGH_RES_INTERVAL = 16; // ms
|
||||
const GRAPH_REQUIREMENTS = {
|
||||
timeline: {
|
||||
actors: ["timeline"],
|
||||
features: ["withMarkers"]
|
||||
},
|
||||
framerate: {
|
||||
actors: ["timeline"],
|
||||
features: ["withTicks"]
|
||||
},
|
||||
memory: {
|
||||
actors: ["memory"],
|
||||
features: ["withMemory"]
|
||||
},
|
||||
}
|
||||
|
@ -342,7 +339,7 @@ let OverviewView = {
|
|||
|
||||
_setGraphVisibilityFromRecordingFeatures: function (recording) {
|
||||
for (let [graphName, requirements] of Iterator(GRAPH_REQUIREMENTS)) {
|
||||
this.graphs.enable(graphName, PerformanceController.isFeatureSupported(requirements));
|
||||
this.graphs.enable(graphName, PerformanceController.isFeatureSupported(requirements.features));
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -86,6 +86,7 @@ skip-if = e10s # Layouthelpers test should not run in a content page.
|
|||
skip-if = e10s # Layouthelpers test should not run in a content page.
|
||||
[browser_mdn-docs-01.js]
|
||||
[browser_mdn-docs-02.js]
|
||||
[browser_mdn-docs-03.js]
|
||||
[browser_num-l10n.js]
|
||||
[browser_observableobject.js]
|
||||
[browser_options-view-01.js]
|
||||
|
|
|
@ -38,7 +38,13 @@ const MDN_DOCS_TOOLTIP_FRAME = "chrome://browser/content/devtools/mdn-docs-frame
|
|||
const BASIC_TESTING_PROPERTY = "html-mdn-css-basic-testing.html";
|
||||
|
||||
const BASIC_EXPECTED_SUMMARY = "A summary of the property.";
|
||||
const BASIC_EXPECTED_SYNTAX = "/* The part we want */\nthis: is-the-part-we-want";
|
||||
const BASIC_EXPECTED_SYNTAX = [{type: "comment", text: "/* The part we want */"},
|
||||
{type: "text", text: "\n"},
|
||||
{type: "property-name", text: "this"},
|
||||
{type: "text", text: ":"},
|
||||
{type: "text", text: " "},
|
||||
{type: "property-value", text: "is-the-part-we-want"},
|
||||
{type: "text", text: ";"}];
|
||||
|
||||
const URI_PARAMS = "?utm_source=mozilla&utm_medium=firefox-inspector&utm_campaign=default";
|
||||
|
||||
|
@ -168,7 +174,5 @@ function checkTooltipContents(doc, expected) {
|
|||
expected.summary,
|
||||
"Summary is correct");
|
||||
|
||||
is(doc.syntax.textContent,
|
||||
expected.syntax,
|
||||
"Syntax is correct");
|
||||
checkCssSyntaxHighlighterOutput(expected.syntax, doc.syntax);
|
||||
}
|
||||
|
|
|
@ -28,7 +28,14 @@ const {setBaseCssDocsUrl, MdnDocsWidget} = devtools.require("devtools/shared/wid
|
|||
const MDN_DOCS_TOOLTIP_FRAME = "chrome://browser/content/devtools/mdn-docs-frame.xhtml";
|
||||
|
||||
const BASIC_EXPECTED_SUMMARY = "A summary of the property.";
|
||||
const BASIC_EXPECTED_SYNTAX = "/* The part we want */\nthis: is-the-part-we-want";
|
||||
const BASIC_EXPECTED_SYNTAX = [{type: "comment", text: "/* The part we want */"},
|
||||
{type: "text", text: "\n"},
|
||||
{type: "property-name", text: "this"},
|
||||
{type: "text", text: ":"},
|
||||
{type: "text", text: " "},
|
||||
{type: "property-value", text: "is-the-part-we-want"},
|
||||
{type: "text", text: ";"}];
|
||||
|
||||
const ERROR_MESSAGE = "Could not load docs page.";
|
||||
|
||||
/**
|
||||
|
@ -52,7 +59,7 @@ const TEST_DATA = [{
|
|||
expectedContents: {
|
||||
propertyName: "i-dont-exist.html",
|
||||
summary: ERROR_MESSAGE,
|
||||
syntax: ""
|
||||
syntax: []
|
||||
}
|
||||
}, {
|
||||
desc: "Test a property whose syntax section is specified using an old-style page",
|
||||
|
@ -76,7 +83,7 @@ const TEST_DATA = [{
|
|||
expectedContents: {
|
||||
propertyName: NO_SYNTAX,
|
||||
summary: BASIC_EXPECTED_SUMMARY,
|
||||
syntax: ""
|
||||
syntax: []
|
||||
}
|
||||
}, {
|
||||
desc: "Test a property whose page doesn't have a summary or a syntax",
|
||||
|
@ -84,7 +91,7 @@ const TEST_DATA = [{
|
|||
expectedContents: {
|
||||
propertyName: NO_SUMMARY_OR_SYNTAX,
|
||||
summary: ERROR_MESSAGE,
|
||||
syntax: ""
|
||||
syntax: []
|
||||
}
|
||||
}
|
||||
];
|
||||
|
@ -128,7 +135,5 @@ function checkTooltipContents(doc, expected) {
|
|||
expected.summary,
|
||||
"Summary is correct");
|
||||
|
||||
is(doc.syntax.textContent,
|
||||
expected.syntax,
|
||||
"Syntax is correct");
|
||||
checkCssSyntaxHighlighterOutput(expected.syntax, doc.syntax);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,277 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* This file tests the CSS syntax highlighter in the MdnDocsWidget object.
|
||||
*
|
||||
* The CSS syntax highlighter accepts:
|
||||
* - a string containing CSS
|
||||
* - a DOM node
|
||||
*
|
||||
* It parses the string and creates a collection of DOM nodes for different
|
||||
* CSS token types. These DOM nodes have CSS classes applied to them,
|
||||
* to apply the right style for that particular token type. The DOM nodes
|
||||
* are returned as children of the node that was passed to the function.
|
||||
*
|
||||
* This test code defines a number of different strings containing valid and
|
||||
* invalid CSS in various forms. For each string it defines the DOM nodes
|
||||
* that it expects to get from the syntax highlighter.
|
||||
*
|
||||
* It then calls the syntax highlighter, and checks that the resulting
|
||||
* collection of DOM nodes is what we expected.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const {appendSyntaxHighlightedCSS} = devtools.require("devtools/shared/widgets/MdnDocsWidget");
|
||||
|
||||
/**
|
||||
* An array containing the actual test cases.
|
||||
*
|
||||
* The test code tests every case in the array. If you want to add more
|
||||
* test cases, just add more items to the array.
|
||||
*
|
||||
* Each test case consists of:
|
||||
* - description: string describing the salient features of this test case
|
||||
* - example: the string to test
|
||||
* - expected: an array of objects, one for each DOM node we expect, that
|
||||
* captures the information about the node that we expect to test.
|
||||
*/
|
||||
const TEST_DATA = [{
|
||||
description: "Valid syntax, string value.",
|
||||
example: "name: stringValue;",
|
||||
expected: [{type: "property-name", text: "name"},
|
||||
{type: "text", text: ":"},
|
||||
{type: "text", text: " "},
|
||||
{type: "property-value", text: "stringValue"},
|
||||
{type: "text", text: ";"}
|
||||
]}, {
|
||||
description: "Valid syntax, numeric value.",
|
||||
example: "name: 1;",
|
||||
expected: [{type: "property-name", text: "name"},
|
||||
{type: "text", text: ":"},
|
||||
{type: "text", text: " "},
|
||||
{type: "property-value", text: "1"},
|
||||
{type: "text", text: ";"}
|
||||
]}, {
|
||||
description: "Valid syntax, url value.",
|
||||
example: "name: url(./name);",
|
||||
expected: [{type: "property-name", text: "name"},
|
||||
{type: "text", text: ":"},
|
||||
{type: "text", text: " "},
|
||||
{type: "property-value", text: "url(./name)"},
|
||||
{type: "text", text: ";"}
|
||||
]}, {
|
||||
description: "Valid syntax, space before ':'.",
|
||||
example: "name : stringValue;",
|
||||
expected: [{type: "property-name", text: "name"},
|
||||
{type: "text", text: " "},
|
||||
{type: "text", text: ":"},
|
||||
{type: "text", text: " "},
|
||||
{type: "property-value", text: "stringValue"},
|
||||
{type: "text", text: ";"}
|
||||
]}, {
|
||||
description: "Valid syntax, space before ';'.",
|
||||
example: "name: stringValue ;",
|
||||
expected: [{type: "property-name", text: "name"},
|
||||
{type: "text", text: ":"},
|
||||
{type: "text", text: " "},
|
||||
{type: "property-value", text: "stringValue"},
|
||||
{type: "text", text: " "},
|
||||
{type: "text", text: ";"}
|
||||
]}, {
|
||||
description: "Valid syntax, trailing space.",
|
||||
example: "name: stringValue; ",
|
||||
expected: [{type: "property-name", text: "name"},
|
||||
{type: "text", text: ":"},
|
||||
{type: "text", text: " "},
|
||||
{type: "property-value", text: "stringValue"},
|
||||
{type: "text", text: ";"},
|
||||
{type: "text", text: " "}
|
||||
]}, {
|
||||
description: "Valid syntax, leading space.",
|
||||
example: " name: stringValue;",
|
||||
expected: [{type: "text", text: " "},
|
||||
{type: "property-name", text: "name"},
|
||||
{type: "text", text: ":"},
|
||||
{type: "text", text: " "},
|
||||
{type: "property-value", text: "stringValue"},
|
||||
{type: "text", text: ";"}
|
||||
]}, {
|
||||
description: "Valid syntax, two spaces.",
|
||||
example: "name: stringValue;",
|
||||
expected: [{type: "property-name", text: "name"},
|
||||
{type: "text", text: ":"},
|
||||
{type: "text", text: " "},
|
||||
{type: "property-value", text: "stringValue"},
|
||||
{type: "text", text: ";"}
|
||||
]}, {
|
||||
description: "Valid syntax, no spaces.",
|
||||
example: "name:stringValue;",
|
||||
expected: [{type: "property-name", text: "name"},
|
||||
{type: "text", text: ":"},
|
||||
{type: "property-value", text: "stringValue"},
|
||||
{type: "text", text: ";"}
|
||||
]}, {
|
||||
description: "Valid syntax, two-part value.",
|
||||
example: "name: stringValue 1;",
|
||||
expected: [{type: "property-name", text: "name"},
|
||||
{type: "text", text: ":"},
|
||||
{type: "text", text: " "},
|
||||
{type: "property-value", text: "stringValue"},
|
||||
{type: "text", text: " "},
|
||||
{type: "property-value", text: "1"},
|
||||
{type: "text", text: ";"}
|
||||
]}, {
|
||||
description: "Valid syntax, two declarations.",
|
||||
example: "name: stringValue;\n" +
|
||||
"name: 1;",
|
||||
expected: [{type: "property-name", text: "name"},
|
||||
{type: "text", text: ":"},
|
||||
{type: "text", text: " "},
|
||||
{type: "property-value", text: "stringValue"},
|
||||
{type: "text", text: ";"},
|
||||
{type: "text", text: "\n"},
|
||||
{type: "property-name", text: "name"},
|
||||
{type: "text", text: ":"},
|
||||
{type: "text", text: " "},
|
||||
{type: "property-value", text: "1"},
|
||||
{type: "text", text: ";"}
|
||||
]}, {
|
||||
description: "Valid syntax, commented, numeric value.",
|
||||
example: "/* comment */\n" +
|
||||
"name: 1;",
|
||||
expected: [{type: "comment", text: "/* comment */"},
|
||||
{type: "text", text: "\n"},
|
||||
{type: "property-name", text: "name"},
|
||||
{type: "text", text: ":"},
|
||||
{type: "text", text: " "},
|
||||
{type: "property-value", text: "1"},
|
||||
{type: "text", text: ";"}
|
||||
]}, {
|
||||
description: "Valid syntax, multiline commented, string value.",
|
||||
example: "/* multiline \n" +
|
||||
"comment */\n" +
|
||||
"name: stringValue;",
|
||||
expected: [{type: "comment", text: "/* multiline \ncomment */"},
|
||||
{type: "text", text: "\n"},
|
||||
{type: "property-name", text: "name"},
|
||||
{type: "text", text: ":"},
|
||||
{type: "text", text: " "},
|
||||
{type: "property-value", text: "stringValue"},
|
||||
{type: "text", text: ";"}
|
||||
]}, {
|
||||
description: "Valid syntax, commented, two declarations.",
|
||||
example: "/* comment 1 */\n" +
|
||||
"name: 1;\n" +
|
||||
"/* comment 2 */\n" +
|
||||
"name: stringValue;",
|
||||
expected: [{type: "comment", text: "/* comment 1 */"},
|
||||
{type: "text", text: "\n"},
|
||||
{type: "property-name", text: "name"},
|
||||
{type: "text", text: ":"},
|
||||
{type: "text", text: " "},
|
||||
{type: "property-value", text: "1"},
|
||||
{type: "text", text: ";"},
|
||||
{type: "text", text: "\n"},
|
||||
{type: "comment", text: "/* comment 2 */"},
|
||||
{type: "text", text: "\n"},
|
||||
{type: "property-name", text: "name"},
|
||||
{type: "text", text: ":"},
|
||||
{type: "text", text: " "},
|
||||
{type: "property-value", text: "stringValue"},
|
||||
{type: "text", text: ";"}
|
||||
]}, {
|
||||
description: "Valid syntax, multiline.",
|
||||
example: "name: \n" +
|
||||
"stringValue;",
|
||||
expected: [{type: "property-name", text: "name"},
|
||||
{type: "text", text: ":"},
|
||||
{type: "text", text: " \n"},
|
||||
{type: "property-value", text: "stringValue"},
|
||||
{type: "text", text: ";"}
|
||||
]}, {
|
||||
description: "Valid syntax, multiline, two declarations.",
|
||||
example: "name: \n" +
|
||||
"stringValue \n" +
|
||||
"stringValue2;",
|
||||
expected: [{type: "property-name", text: "name"},
|
||||
{type: "text", text: ":"},
|
||||
{type: "text", text: " \n"},
|
||||
{type: "property-value", text: "stringValue"},
|
||||
{type: "text", text: " \n"},
|
||||
{type: "property-value", text: "stringValue2"},
|
||||
{type: "text", text: ";"}
|
||||
]}, {
|
||||
description: "Invalid: not CSS at all.",
|
||||
example: "not CSS at all",
|
||||
expected: [{type: "property-name", text: "not"},
|
||||
{type: "text", text: " "},
|
||||
{type: "property-name", text: "CSS"},
|
||||
{type: "text", text: " "},
|
||||
{type: "property-name", text: "at"},
|
||||
{type: "text", text: " "},
|
||||
{type: "property-name", text: "all"}
|
||||
]}, {
|
||||
description: "Invalid: switched ':' and ';'.",
|
||||
example: "name; stringValue:",
|
||||
expected: [{type: "property-name", text: "name"},
|
||||
{type: "text", text: ";"},
|
||||
{type: "text", text: " "},
|
||||
{type: "property-name", text: "stringValue"},
|
||||
{type: "text", text: ":"}
|
||||
]}, {
|
||||
description: "Invalid: unterminated comment.",
|
||||
example: "/* unterminated comment\n" +
|
||||
"name: stringValue;",
|
||||
expected: [{type: "comment", text: "/* unterminated comment\nname: stringValue;"}
|
||||
]}, {
|
||||
description: "Invalid: bad comment syntax.",
|
||||
example: "// invalid comment\n" +
|
||||
"name: stringValue;",
|
||||
expected: [{type: "text", text: "/"},
|
||||
{type: "text", text: "/"},
|
||||
{type: "text", text: " "},
|
||||
{type: "property-name", text: "invalid"},
|
||||
{type: "text", text: " "},
|
||||
{type: "property-name", text: "comment"},
|
||||
{type: "text", text: "\n"},
|
||||
{type: "property-name", text: "name"},
|
||||
{type: "text", text: ":"},
|
||||
{type: "text", text: " "},
|
||||
{type: "property-value", text: "stringValue"},
|
||||
{type: "text", text: ";"}
|
||||
]}, {
|
||||
description: "Invalid: no trailing ';'.",
|
||||
example: "name: stringValue\n" +
|
||||
"name: stringValue2",
|
||||
expected: [{type: "property-name", text: "name"},
|
||||
{type: "text", text: ":"},
|
||||
{type: "text", text: " "},
|
||||
{type: "property-value", text: "stringValue"},
|
||||
{type: "text", text: "\n"},
|
||||
{type: "property-value", text: "name"},
|
||||
{type: "text", text: ":"},
|
||||
{type: "text", text: " "},
|
||||
{type: "property-value", text: "stringValue2"},
|
||||
]}
|
||||
];
|
||||
|
||||
/**
|
||||
* Iterate through every test case, calling the syntax highlighter,
|
||||
* then calling a helper function to check the output.
|
||||
*/
|
||||
add_task(function*() {
|
||||
let doc = gBrowser.selectedTab.ownerDocument;
|
||||
let parent = doc.createElement("div");
|
||||
info("Testing all CSS syntax highlighter test cases");
|
||||
for (let {description, example, expected} of TEST_DATA) {
|
||||
info("Testing: " + description);
|
||||
appendSyntaxHighlightedCSS(example, parent);
|
||||
checkCssSyntaxHighlighterOutput(expected, parent);
|
||||
while (parent.firstChild) {
|
||||
parent.firstChild.remove();
|
||||
}
|
||||
}
|
||||
});
|
|
@ -319,3 +319,104 @@ let showFilterPopupPresetsAndCreatePreset = Task.async(function*(widget, name, v
|
|||
|
||||
yield onRender;
|
||||
});
|
||||
|
||||
/**
|
||||
* Utility function for testing CSS code samples that have been
|
||||
* syntax-highlighted.
|
||||
*
|
||||
* The CSS syntax highlighter emits a collection of DOM nodes that have
|
||||
* CSS classes applied to them. This function checks that those nodes
|
||||
* are what we expect.
|
||||
*
|
||||
* @param {array} expectedNodes
|
||||
* A representation of the nodes we expect to see.
|
||||
* Each node is an object containing two properties:
|
||||
* - type: a string which can be one of:
|
||||
* - text, comment, property-name, property-value
|
||||
* - text: the textContent of the node
|
||||
*
|
||||
* For example, given a string like this:
|
||||
* "<comment> The part we want </comment>\n this: is-the-part-we-want;"
|
||||
*
|
||||
* we would represent the expected output like this:
|
||||
* [{type: "comment", text: "<comment> The part we want </comment>"},
|
||||
* {type: "text", text: "\n"},
|
||||
* {type: "property-name", text: "this"},
|
||||
* {type: "text", text: ":"},
|
||||
* {type: "text", text: " "},
|
||||
* {type: "property-value", text: "is-the-part-we-want"},
|
||||
* {type: "text", text: ";"}];
|
||||
*
|
||||
* @param {Node} parent
|
||||
* The DOM node whose children are the output of the syntax highlighter.
|
||||
*/
|
||||
function checkCssSyntaxHighlighterOutput(expectedNodes, parent) {
|
||||
/**
|
||||
* The classes applied to the output nodes by the syntax highlighter.
|
||||
* These must be same as the definitions in MdnDocsWidget.js.
|
||||
*/
|
||||
const PROPERTY_NAME_COLOR = "theme-fg-color5";
|
||||
const PROPERTY_VALUE_COLOR = "theme-fg-color1";
|
||||
const COMMENT_COLOR = "theme-comment";
|
||||
|
||||
/**
|
||||
* Check the type and content of a single node.
|
||||
*/
|
||||
function checkNode(expected, actual) {
|
||||
ok(actual.textContent == expected.text, "Check that node has the expected textContent");
|
||||
info("Expected text content: [" + expected.text + "]");
|
||||
info("Actual text content: [" + actual.textContent + "]");
|
||||
|
||||
info("Check that node has the expected type");
|
||||
if (expected.type == "text") {
|
||||
ok(actual.nodeType == 3, "Check that node is a text node");
|
||||
} else {
|
||||
ok(actual.tagName.toUpperCase() == "SPAN", "Check that node is a SPAN");
|
||||
}
|
||||
|
||||
info("Check that node has the expected className");
|
||||
|
||||
let expectedClassName = null;
|
||||
let actualClassName = null;
|
||||
|
||||
switch (expected.type) {
|
||||
case "property-name":
|
||||
expectedClassName = PROPERTY_NAME_COLOR;
|
||||
break;
|
||||
case "property-value":
|
||||
expectedClassName = PROPERTY_VALUE_COLOR;
|
||||
break;
|
||||
case "comment":
|
||||
expectedClassName = COMMENT_COLOR;
|
||||
break;
|
||||
default:
|
||||
ok(!actual.classList, "No className expected");
|
||||
return;
|
||||
}
|
||||
|
||||
ok(actual.classList.length == 1, "One className expected");
|
||||
actualClassName = actual.classList[0];
|
||||
|
||||
ok(expectedClassName == actualClassName, "Check className value");
|
||||
info("Expected className: " + expectedClassName);
|
||||
info("Actual className: " + actualClassName);
|
||||
}
|
||||
|
||||
info("Logging the actual nodes we have:");
|
||||
for (var j = 0; j < parent.childNodes.length; j++) {
|
||||
var n = parent.childNodes[j];
|
||||
info(j + " / " +
|
||||
"nodeType: "+ n.nodeType + " / " +
|
||||
"textContent: " + n.textContent);
|
||||
}
|
||||
|
||||
ok(parent.childNodes.length == parent.childNodes.length,
|
||||
"Check we have the expected number of nodes");
|
||||
info("Expected node count " + expectedNodes.length);
|
||||
info("Actual node count " + expectedNodes.length);
|
||||
|
||||
for (let i = 0; i < expectedNodes.length; i++) {
|
||||
info("Check node " + i);
|
||||
checkNode(expectedNodes[i], parent.childNodes[i]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<h2 id="Syntax">Syntax</h2>
|
||||
|
||||
<pre>/* The part we want */
|
||||
this: is-the-part-we-want</pre>
|
||||
this: is-the-part-we-want;</pre>
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<pre>To be ignored.</pre>
|
||||
|
||||
<pre>/* The part we want */
|
||||
this: is-the-part-we-want</pre>
|
||||
this: is-the-part-we-want;</pre>
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<pre>The part we should ignore</pre>
|
||||
|
||||
<pre>/* The part we want */
|
||||
this: is-the-part-we-want</pre>
|
||||
this: is-the-part-we-want;</pre>
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -27,6 +27,8 @@
|
|||
const {Cc, Cu, Ci} = require("chrome");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
const DOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"]
|
||||
.getService(Ci.inIDOMUtils);
|
||||
|
||||
// Parameters for the XHR request
|
||||
// see https://developer.mozilla.org/en-US/docs/MDN/Kuma/API#Document_parameters
|
||||
|
@ -37,12 +39,112 @@ var XHR_CSS_URL = "https://developer.mozilla.org/en-US/docs/Web/CSS/";
|
|||
// Parameters for the link to MDN in the tooltip, so
|
||||
// so we know which MDN visits come from this feature
|
||||
const PAGE_LINK_PARAMS = "?utm_source=mozilla&utm_medium=firefox-inspector&utm_campaign=default"
|
||||
// URL for the page link
|
||||
// omits locale, so a locale-specific page will be loaded
|
||||
// URL for the page link omits locale, so a locale-specific page will be loaded
|
||||
var PAGE_LINK_URL = "https://developer.mozilla.org/docs/Web/CSS/";
|
||||
|
||||
const BROWSER_WINDOW = 'navigator:browser';
|
||||
|
||||
const PROPERTY_NAME_COLOR = "theme-fg-color5";
|
||||
const PROPERTY_VALUE_COLOR = "theme-fg-color1";
|
||||
const COMMENT_COLOR = "theme-comment";
|
||||
|
||||
/**
|
||||
* Turns a string containing a series of CSS declarations into
|
||||
* a series of DOM nodes, with classes applied to provide syntax
|
||||
* highlighting.
|
||||
*
|
||||
* It uses the CSS tokenizer to generate a stream of CSS tokens.
|
||||
* https://mxr.mozilla.org/mozilla-central/source/dom/webidl/CSSLexer.webidl
|
||||
* lists all the token types.
|
||||
*
|
||||
* - "whitespace", "comment", and "symbol" tokens are appended as TEXT nodes,
|
||||
* and will inherit the default style for text.
|
||||
*
|
||||
* - "ident" tokens that we think are property names are considered to be
|
||||
* a property name, and are appended as SPAN nodes with a distinct color class.
|
||||
*
|
||||
* - "ident" nodes which we do not think are property names, and nodes
|
||||
* of all other types ("number", "url", "percentage", ...) are considered
|
||||
* to be part of a property value, and are appended as SPAN nodes with
|
||||
* a different color class.
|
||||
*
|
||||
* @param {Document} doc
|
||||
* Used to create nodes.
|
||||
*
|
||||
* @param {String} syntaxText
|
||||
* The CSS input. This is assumed to consist of a series of
|
||||
* CSS declarations, with trailing semicolons.
|
||||
*
|
||||
* @param {DOM node} syntaxSection
|
||||
* This is the parent for the output nodes. Generated nodes
|
||||
* are appended to this as children.
|
||||
*/
|
||||
function appendSyntaxHighlightedCSS(cssText, parentElement) {
|
||||
let doc = parentElement.ownerDocument;
|
||||
let identClass = PROPERTY_NAME_COLOR;
|
||||
let lexer = DOMUtils.getCSSLexer(cssText);
|
||||
|
||||
/**
|
||||
* Create a SPAN node with the given text content and class.
|
||||
*/
|
||||
function createStyledNode(textContent, className) {
|
||||
let newNode = doc.createElement("span");
|
||||
newNode.classList.add(className);
|
||||
newNode.textContent = textContent;
|
||||
return newNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the symbol is ":", we will expect the next
|
||||
* "ident" token to be part of a property value.
|
||||
*
|
||||
* If the symbol is ";", we will expect the next
|
||||
* "ident" token to be a property name.
|
||||
*/
|
||||
function updateIdentClass(tokenText) {
|
||||
if (tokenText === ":") {
|
||||
identClass = PROPERTY_VALUE_COLOR;
|
||||
}
|
||||
else {
|
||||
if (tokenText === ";") {
|
||||
identClass = PROPERTY_NAME_COLOR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the appropriate node for this token type.
|
||||
*
|
||||
* If this token is a symbol, also update our expectations
|
||||
* for what the next "ident" token represents.
|
||||
*/
|
||||
function tokenToNode(token, tokenText) {
|
||||
switch (token.tokenType) {
|
||||
case "ident":
|
||||
return createStyledNode(tokenText, identClass);
|
||||
case "symbol":
|
||||
updateIdentClass(tokenText);
|
||||
return doc.createTextNode(tokenText);
|
||||
case "whitespace":
|
||||
return doc.createTextNode(tokenText);
|
||||
case "comment":
|
||||
return createStyledNode(tokenText, COMMENT_COLOR);
|
||||
default:
|
||||
return createStyledNode(tokenText, PROPERTY_VALUE_COLOR);
|
||||
}
|
||||
}
|
||||
|
||||
let token = lexer.nextToken();
|
||||
while (token) {
|
||||
let tokenText = cssText.slice(token.startOffset, token.endOffset);
|
||||
let newNode = tokenToNode(token, tokenText);
|
||||
parentElement.appendChild(newNode);
|
||||
token = lexer.nextToken();
|
||||
}
|
||||
}
|
||||
|
||||
exports.appendSyntaxHighlightedCSS = appendSyntaxHighlightedCSS;
|
||||
|
||||
/**
|
||||
* Fetch an MDN page.
|
||||
*
|
||||
|
@ -154,6 +256,8 @@ function MdnDocsWidget(tooltipDocument) {
|
|||
linkToMdn: tooltipDocument.getElementById("visit-mdn-page")
|
||||
};
|
||||
|
||||
this.doc = tooltipDocument;
|
||||
|
||||
// get the localized string for the link text
|
||||
this.elements.linkToMdn.textContent =
|
||||
l10n.strings.GetStringFromName("docsTooltip.visitMDN");
|
||||
|
@ -209,7 +313,9 @@ MdnDocsWidget.prototype = {
|
|||
|
||||
// clear docs summary and syntax
|
||||
elements.summary.textContent = "";
|
||||
elements.syntax.textContent = "";
|
||||
while (elements.syntax.firstChild) {
|
||||
elements.syntax.firstChild.remove();
|
||||
}
|
||||
|
||||
// reset the scroll position
|
||||
elements.info.scrollTop = 0;
|
||||
|
@ -226,7 +332,7 @@ MdnDocsWidget.prototype = {
|
|||
function finalizeDocument({summary, syntax}) {
|
||||
// set docs summary and syntax
|
||||
elements.summary.textContent = summary;
|
||||
elements.syntax.textContent = syntax;
|
||||
appendSyntaxHighlightedCSS(syntax, elements.syntax);
|
||||
|
||||
// hide the throbber
|
||||
elements.info.classList.remove("devtools-throbber");
|
||||
|
@ -252,11 +358,17 @@ MdnDocsWidget.prototype = {
|
|||
|
||||
let deferred = Promise.defer();
|
||||
let elements = this.elements;
|
||||
let doc = this.doc;
|
||||
|
||||
initializeDocument(propertyName);
|
||||
getCssDocs(propertyName).then(finalizeDocument, gotError);
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
this.elements = null;
|
||||
this.doc = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -262,6 +262,62 @@ Section "Uninstall"
|
|||
Call un.RenameDelete
|
||||
Push "$INSTDIR\update\updater.exe"
|
||||
Call un.RenameDelete
|
||||
Push "$INSTDIR\logs\maintenanceservice.log"
|
||||
Call un.RenameDelete
|
||||
Push "$INSTDIR\logs\maintenanceservice-1.log"
|
||||
Call un.RenameDelete
|
||||
Push "$INSTDIR\logs\maintenanceservice-2.log"
|
||||
Call un.RenameDelete
|
||||
Push "$INSTDIR\logs\maintenanceservice-3.log"
|
||||
Call un.RenameDelete
|
||||
Push "$INSTDIR\logs\maintenanceservice-4.log"
|
||||
Call un.RenameDelete
|
||||
Push "$INSTDIR\logs\maintenanceservice-5.log"
|
||||
Call un.RenameDelete
|
||||
Push "$INSTDIR\logs\maintenanceservice-6.log"
|
||||
Call un.RenameDelete
|
||||
Push "$INSTDIR\logs\maintenanceservice-7.log"
|
||||
Call un.RenameDelete
|
||||
Push "$INSTDIR\logs\maintenanceservice-8.log"
|
||||
Call un.RenameDelete
|
||||
Push "$INSTDIR\logs\maintenanceservice-9.log"
|
||||
Call un.RenameDelete
|
||||
Push "$INSTDIR\logs\maintenanceservice-10.log"
|
||||
Call un.RenameDelete
|
||||
Push "$INSTDIR\logs\maintenanceservice-install.log"
|
||||
Call un.RenameDelete
|
||||
Push "$INSTDIR\logs\maintenanceservice-uninstall.log"
|
||||
Call un.RenameDelete
|
||||
SetShellVarContext all
|
||||
Push "$APPDATA\Mozilla\logs\maintenanceservice.log"
|
||||
Call un.RenameDelete
|
||||
Push "$APPDATA\Mozilla\logs\maintenanceservice-1.log"
|
||||
Call un.RenameDelete
|
||||
Push "$APPDATA\Mozilla\logs\maintenanceservice-2.log"
|
||||
Call un.RenameDelete
|
||||
Push "$APPDATA\Mozilla\logs\maintenanceservice-3.log"
|
||||
Call un.RenameDelete
|
||||
Push "$APPDATA\Mozilla\logs\maintenanceservice-4.log"
|
||||
Call un.RenameDelete
|
||||
Push "$APPDATA\Mozilla\logs\maintenanceservice-5.log"
|
||||
Call un.RenameDelete
|
||||
Push "$APPDATA\Mozilla\logs\maintenanceservice-6.log"
|
||||
Call un.RenameDelete
|
||||
Push "$APPDATA\Mozilla\logs\maintenanceservice-7.log"
|
||||
Call un.RenameDelete
|
||||
Push "$APPDATA\Mozilla\logs\maintenanceservice-8.log"
|
||||
Call un.RenameDelete
|
||||
Push "$APPDATA\Mozilla\logs\maintenanceservice-9.log"
|
||||
Call un.RenameDelete
|
||||
Push "$APPDATA\Mozilla\logs\maintenanceservice-10.log"
|
||||
Call un.RenameDelete
|
||||
Push "$APPDATA\Mozilla\logs\maintenanceservice-install.log"
|
||||
Call un.RenameDelete
|
||||
Push "$APPDATA\Mozilla\logs\maintenanceservice-uninstall.log"
|
||||
Call un.RenameDelete
|
||||
RMDir /REBOOTOK "$APPDATA\Mozilla\logs"
|
||||
RMDir /REBOOTOK "$APPDATA\Mozilla"
|
||||
RMDir /REBOOTOK "$INSTDIR\logs"
|
||||
RMDir /REBOOTOK "$INSTDIR\update"
|
||||
RMDir /REBOOTOK "$INSTDIR"
|
||||
|
||||
|
|
|
@ -1665,10 +1665,8 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
|
|||
-moz-margin-end: 3px;
|
||||
padding: 3px 4px;
|
||||
}
|
||||
|
||||
#urlbar > #identity-box:hover,
|
||||
#urlbar > #identity-box[open=true] {
|
||||
background-color: rgb(240,237,237);
|
||||
#identity-box {
|
||||
--identity-box-selected-background-color: rgb(240,237,237);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -640,9 +640,9 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
|
|||
list-style-image: url(chrome://browser/skin/syncProgress-horizontalbar.png);
|
||||
}
|
||||
|
||||
#PanelUI-fxa-icon[fxastatus="migrate-signup"],
|
||||
#PanelUI-fxa-icon[fxastatus="migrate-verify"] {
|
||||
list-style-image: url(chrome://browser/skni/warning16.png);
|
||||
#PanelUI-footer-fxa[fxastatus="migrate-signup"] > #PanelUI-fxa-status > #PanelUI-fxa-label,
|
||||
#PanelUI-footer-fxa[fxastatus="migrate-verify"] > #PanelUI-fxa-status > #PanelUI-fxa-label {
|
||||
list-style-image: url(chrome://browser/skin/warning16.png);
|
||||
-moz-image-region: rect(0, 32px, 16px, 16px);
|
||||
}
|
||||
|
||||
|
@ -1509,8 +1509,8 @@ menuitem[checked="true"].subviewbutton > .menu-iconic-left {
|
|||
list-style-image: url(chrome://browser/skin/syncProgress-horizontalbar@2x.png);
|
||||
}
|
||||
|
||||
#PanelUI-fxa-icon[fxastatus="migrate-signup"],
|
||||
#PanelUI-fxa-icon[fxastatus="migrate-verify"] {
|
||||
#PanelUI-footer-fxa[fxastatus="migrate-signup"] > #PanelUI-fxa-status > #PanelUI-fxa-label,
|
||||
#PanelUI-footer-fxa[fxastatus="migrate-verify"] > #PanelUI-fxa-status > #PanelUI-fxa-label {
|
||||
list-style-image: url(chrome://browser/skin/warning16@2x.png);
|
||||
-moz-image-region: rect(0, 64px, 32px, 32px);
|
||||
}
|
||||
|
|
|
@ -50,12 +50,6 @@
|
|||
--toolbarbutton-combined-boxshadow: none;
|
||||
--toolbarbutton-combined-backgroundimage: linear-gradient(#5F6670 0, #5F6670 18px);
|
||||
|
||||
/* Identity box */
|
||||
--identity-box-chrome-color: #46afe3;
|
||||
--identity-box-chrome-background-image: linear-gradient(#5F6670 0, #5F6670 100%);
|
||||
--identity-box-verified-background-image: linear-gradient(#5F6670 0, #5F6670 100%);
|
||||
--verified-identity-box-backgroundcolor: transparent;
|
||||
|
||||
/* Url and search bars */
|
||||
--url-and-searchbar-background-color: #171B1F;
|
||||
--url-and-searchbar-color: #fff;
|
||||
|
@ -71,6 +65,14 @@
|
|||
--panel-ui-button-background-image: linear-gradient(to bottom, transparent, #5F6670 30%, #5F6670 70%, transparent);
|
||||
}
|
||||
|
||||
:root[devtoolstheme="dark"] #identity-box {
|
||||
--identity-box-chrome-color: #46afe3;
|
||||
--identity-box-chrome-background-image: linear-gradient(#5F6670 0, #5F6670 100%);
|
||||
--identity-box-verified-background-image: linear-gradient(#5F6670 0, #5F6670 100%);
|
||||
--verified-identity-box-background-color: transparent;
|
||||
--identity-box-selected-background-color: rgba(231,230,230,.2);
|
||||
}
|
||||
|
||||
:root[devtoolstheme="dark"] .searchbar-dropmarker-image {
|
||||
--searchbar-dropmarker-url: url("chrome://browser/skin/devtools/dropmarker.svg");
|
||||
--searchbar-dropmarker-2x-url: url("chrome://browser/skin/devtools/dropmarker.svg");
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
hsla(0,0%,16%,.2) 35%,
|
||||
hsla(0,0%,16%,.2) 65%,
|
||||
hsla(0,0%,16%,0));
|
||||
--identity-box-selected-background-color: rgb(231,230,230);
|
||||
--identity-box-verified-color: hsl(92,100%,30%);
|
||||
--identity-box-verified-background-image: linear-gradient(hsla(92,81%,16%,0),
|
||||
hsla(92,81%,16%,.2) 35%,
|
||||
|
@ -50,7 +51,7 @@
|
|||
|
||||
#identity-box:hover,
|
||||
#identity-box[open=true] {
|
||||
background-color: rgb(231,230,230);
|
||||
background-color: var(--identity-box-selected-background-color);
|
||||
}
|
||||
|
||||
#urlbar[pageproxystate="valid"] > #identity-box.verifiedIdentity {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
import errno
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
|
@ -22,6 +23,36 @@ use and re-run mach. For this change to take effect forever, you'll likely
|
|||
want to export this environment variable from your shell's init scripts.
|
||||
'''.lstrip()
|
||||
|
||||
NO_MERCURIAL_SETUP = '''
|
||||
*** MERCURIAL NOT CONFIGURED ***
|
||||
|
||||
mach has detected that you have never run `mach mercurial-setup`.
|
||||
|
||||
Running this command will ensure your Mercurial version control tool is up
|
||||
to date and optimally configured for a better, more productive experience
|
||||
when working on Mozilla projects.
|
||||
|
||||
Please run `mach mercurial-setup` now.
|
||||
'''.strip()
|
||||
|
||||
OLD_MERCURIAL_TOOLS = '''
|
||||
*** MERCURIAL CONFIGURATION POTENTIALLY OUT OF DATE ***
|
||||
|
||||
mach has detected that it has been a while since you have run
|
||||
`mach mercurial-setup`.
|
||||
|
||||
Having the latest Mercurial tools and configuration should lead to a better,
|
||||
more productive experience when working on Mozilla projects.
|
||||
|
||||
Please run `mach mercurial-setup` now.
|
||||
|
||||
To avoid this message in the future, run `mach mercurial-setup` once a month.
|
||||
Or, schedule `mach mercurial-setup --update-only` to run automatically in
|
||||
the background at least once a month.
|
||||
'''.strip()
|
||||
|
||||
MERCURIAL_SETUP_FATAL_INTERVAL = 31 * 24 * 60 * 60
|
||||
|
||||
|
||||
# TODO Bug 794506 Integrate with the in-tree virtualenv configuration.
|
||||
SEARCH_PATHS = [
|
||||
|
@ -151,6 +182,21 @@ CATEGORIES = {
|
|||
}
|
||||
|
||||
|
||||
def get_state_dir():
|
||||
"""Obtain the path to a directory to hold state.
|
||||
|
||||
Returns a tuple of the path and a bool indicating whether the value came
|
||||
from an environment variable.
|
||||
"""
|
||||
state_user_dir = os.path.expanduser('~/.mozbuild')
|
||||
state_env_dir = os.environ.get('MOZBUILD_STATE_PATH', None)
|
||||
|
||||
if state_env_dir:
|
||||
return state_env_dir, True
|
||||
else:
|
||||
return state_user_dir, False
|
||||
|
||||
|
||||
def bootstrap(topsrcdir, mozilla_dir=None):
|
||||
if mozilla_dir is None:
|
||||
mozilla_dir = topsrcdir
|
||||
|
@ -177,23 +223,69 @@ def bootstrap(topsrcdir, mozilla_dir=None):
|
|||
sys.path[0:0] = [os.path.join(mozilla_dir, path) for path in SEARCH_PATHS]
|
||||
import mach.main
|
||||
|
||||
def pre_dispatch_handler(context, handler, args):
|
||||
"""Perform global checks before command dispatch.
|
||||
|
||||
Currently, our goal is to ensure developers periodically run
|
||||
`mach mercurial-setup` (when applicable) to ensure their Mercurial
|
||||
tools are up to date.
|
||||
"""
|
||||
# Don't do anything when...
|
||||
|
||||
# The user is performing a maintenance command.
|
||||
if handler.name in ('bootstrap', 'doctor', 'mercurial-setup'):
|
||||
return
|
||||
|
||||
# We are running in automation.
|
||||
if 'MOZ_AUTOMATION' in os.environ or 'TASK_ID' in os.environ:
|
||||
return
|
||||
|
||||
# We are a curmudgeon who has found this undocumented variable.
|
||||
if 'I_PREFER_A_SUBOPTIMAL_MERCURIAL_EXPERIENCE' in os.environ:
|
||||
return
|
||||
|
||||
# The environment is likely a machine invocation.
|
||||
if not sys.stdin.isatty():
|
||||
return
|
||||
|
||||
# Mercurial isn't managing this source checkout.
|
||||
if not os.path.exists(os.path.join(topsrcdir, '.hg')):
|
||||
return
|
||||
|
||||
state_dir = get_state_dir()[0]
|
||||
last_check_path = os.path.join(state_dir, 'mercurial',
|
||||
'setup.lastcheck')
|
||||
|
||||
mtime = None
|
||||
try:
|
||||
mtime = os.path.getmtime(last_check_path)
|
||||
except OSError as e:
|
||||
if e.errno != errno.ENOENT:
|
||||
raise
|
||||
|
||||
# No last run file means mercurial-setup has never completed.
|
||||
if mtime is None:
|
||||
print(NO_MERCURIAL_SETUP, file=sys.stderr)
|
||||
sys.exit(2)
|
||||
elif time.time() - mtime > MERCURIAL_SETUP_FATAL_INTERVAL:
|
||||
print(OLD_MERCURIAL_TOOLS, file=sys.stderr)
|
||||
sys.exit(2)
|
||||
|
||||
def populate_context(context, key=None):
|
||||
if key is None:
|
||||
return
|
||||
if key == 'state_dir':
|
||||
state_user_dir = os.path.expanduser('~/.mozbuild')
|
||||
state_env_dir = os.environ.get('MOZBUILD_STATE_PATH', None)
|
||||
if state_env_dir:
|
||||
if not os.path.exists(state_env_dir):
|
||||
state_dir, is_environ = get_state_dir()
|
||||
if is_environ:
|
||||
if not os.path.exists(state_dir):
|
||||
print('Creating global state directory from environment variable: %s'
|
||||
% state_env_dir)
|
||||
os.makedirs(state_env_dir, mode=0o770)
|
||||
% state_dir)
|
||||
os.makedirs(state_dir, mode=0o770)
|
||||
print('Please re-run mach.')
|
||||
sys.exit(1)
|
||||
state_dir = state_env_dir
|
||||
else:
|
||||
if not os.path.exists(state_user_dir):
|
||||
print(STATE_DIR_FIRST_RUN.format(userdir=state_user_dir))
|
||||
if not os.path.exists(state_dir):
|
||||
print(STATE_DIR_FIRST_RUN.format(userdir=state_dir))
|
||||
try:
|
||||
for i in range(20, -1, -1):
|
||||
time.sleep(1)
|
||||
|
@ -202,15 +294,19 @@ def bootstrap(topsrcdir, mozilla_dir=None):
|
|||
except KeyboardInterrupt:
|
||||
sys.exit(1)
|
||||
|
||||
print('\nCreating default state directory: %s' % state_user_dir)
|
||||
os.mkdir(state_user_dir)
|
||||
print('\nCreating default state directory: %s' % state_dir)
|
||||
os.mkdir(state_dir)
|
||||
print('Please re-run mach.')
|
||||
sys.exit(1)
|
||||
state_dir = state_user_dir
|
||||
|
||||
return state_dir
|
||||
|
||||
if key == 'topdir':
|
||||
return topsrcdir
|
||||
|
||||
if key == 'pre_dispatch_handler':
|
||||
return pre_dispatch_handler
|
||||
|
||||
raise AttributeError(key)
|
||||
|
||||
mach = mach.main.Mach(os.getcwd())
|
||||
|
|
|
@ -137,13 +137,17 @@ skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
|
|||
[test_peerConnection_twoAudioTracksInOneStream.html]
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
|
||||
[test_peerConnection_twoAudioVideoStreams.html]
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
# b2g(Bug 960442, video support for WebRTC is disabled on b2g), Bug 1180000 for Linux debug e10s
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' || (os == 'linux' && debug && e10s)
|
||||
[test_peerConnection_twoAudioVideoStreamsCombined.html]
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
# b2g(Bug 960442, video support for WebRTC is disabled on b2g), Bug 1180000 for Linux debug e10s
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' || (os == 'linux' && debug && e10s)
|
||||
[test_peerConnection_twoVideoStreams.html]
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
# b2g(Bug 960442, video support for WebRTC is disabled on b2g), Bug 1180000 for Linux debug e10s
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' || (os == 'linux' && debug && e10s)
|
||||
[test_peerConnection_twoVideoTracksInOneStream.html]
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
# b2g(Bug 960442, video support for WebRTC is disabled on b2g), Bug 1180000 for Linux debug e10s
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' || (os == 'linux' && debug && e10s)
|
||||
[test_peerConnection_addSecondAudioStream.html]
|
||||
skip-if = toolkit == 'gonk' # B2G emulator is too slow to finish a renegotiation test in under 5 minutes
|
||||
[test_peerConnection_answererAddSecondAudioStream.html]
|
||||
|
|
|
@ -112,8 +112,12 @@ public class DoorHangerPopup extends AnchoredPopup
|
|||
final String id = json.getString("value");
|
||||
|
||||
final String typeString = json.optString("category");
|
||||
final boolean isLogin = DoorHanger.Type.LOGIN.toString().equals(typeString);
|
||||
final DoorHanger.Type doorhangerType = isLogin ? DoorHanger.Type.LOGIN : DoorHanger.Type.DEFAULT;
|
||||
DoorHanger.Type doorhangerType = DoorHanger.Type.DEFAULT;
|
||||
if (DoorHanger.Type.LOGIN.toString().equals(typeString)) {
|
||||
doorhangerType = DoorHanger.Type.LOGIN;
|
||||
} else if (DoorHanger.Type.GEOLOCATION.toString().equals(typeString)) {
|
||||
doorhangerType = DoorHanger.Type.GEOLOCATION;
|
||||
}
|
||||
|
||||
final DoorhangerConfig config = new DoorhangerConfig(tabId, id, doorhangerType, this);
|
||||
|
||||
|
|
|
@ -558,10 +558,11 @@ just addresses the organization to follow, e.g. "This site is run by " -->
|
|||
<!ENTITY blocked_mixed_content_message_bottom "Most websites will still work properly even when this content is blocked.">
|
||||
|
||||
<!-- Tracking content notifications in site identity popup -->
|
||||
<!ENTITY loaded_tracking_content_message_top "Tracking protection disabled.">
|
||||
<!ENTITY loaded_tracking_content_message_bottom "Parts of this page may track your online activity.">
|
||||
<!ENTITY blocked_tracking_content_message_top2 "Tracking protection enabled.">
|
||||
<!ENTITY blocked_tracking_content_message_bottom2 "Parts of the page that track your online activity have been blocked.">
|
||||
<!ENTITY doorhanger_tracking_title "Tracking protection">
|
||||
<!ENTITY doorhanger_tracking_state_enabled "Enabled">
|
||||
<!ENTITY doorhanger_tracking_state_disabled "Disabled">
|
||||
<!ENTITY doorhanger_tracking_message_enabled "Blocking tracking elements that may affect your browsing experience.">
|
||||
<!ENTITY doorhanger_tracking_message_disabled "Attempts to track your online behavior are not being blocked.">
|
||||
|
||||
<!-- Common mixed and tracking content strings in site identity popup -->
|
||||
<!ENTITY learn_more "Learn More">
|
||||
|
|
|
@ -510,6 +510,7 @@ gbjar.sources += [
|
|||
'widget/ButtonToast.java',
|
||||
'widget/CheckableLinearLayout.java',
|
||||
'widget/ClickableWhenDisabledEditText.java',
|
||||
'widget/ContentSecurityDoorHanger.java',
|
||||
'widget/DateTimePicker.java',
|
||||
'widget/DefaultDoorHanger.java',
|
||||
'widget/Divider.java',
|
||||
|
|
После Ширина: | Высота: | Размер: 3.6 KiB |
Двоичные данные
mobile/android/base/resources/drawable-hdpi/larry.png
До Ширина: | Высота: | Размер: 1.1 KiB |
После Ширина: | Высота: | Размер: 1.7 KiB |
Двоичные данные
mobile/android/base/resources/drawable-hdpi/lock_identified.png
До Ширина: | Высота: | Размер: 404 B После Ширина: | Высота: | Размер: 943 B |
Двоичные данные
mobile/android/base/resources/drawable-hdpi/shield_disabled.png
До Ширина: | Высота: | Размер: 1.2 KiB После Ширина: | Высота: | Размер: 2.8 KiB |
До Ширина: | Высота: | Размер: 4.1 KiB |
Двоичные данные
mobile/android/base/resources/drawable-hdpi/shield_enabled.png
До Ширина: | Высота: | Размер: 667 B После Ширина: | Высота: | Размер: 2.4 KiB |
До Ширина: | Высота: | Размер: 2.3 KiB |
До Ширина: | Высота: | Размер: 4.6 KiB |
После Ширина: | Высота: | Размер: 5.2 KiB |
Двоичные данные
mobile/android/base/resources/drawable-xhdpi/larry.png
До Ширина: | Высота: | Размер: 1.3 KiB |
После Ширина: | Высота: | Размер: 2.2 KiB |
Двоичные данные
mobile/android/base/resources/drawable-xhdpi/lock_identified.png
До Ширина: | Высота: | Размер: 466 B После Ширина: | Высота: | Размер: 1.1 KiB |
Двоичные данные
mobile/android/base/resources/drawable-xhdpi/shield_disabled.png
До Ширина: | Высота: | Размер: 1.0 KiB После Ширина: | Высота: | Размер: 3.9 KiB |
До Ширина: | Высота: | Размер: 5.4 KiB |
Двоичные данные
mobile/android/base/resources/drawable-xhdpi/shield_enabled.png
До Ширина: | Высота: | Размер: 844 B После Ширина: | Высота: | Размер: 3.4 KiB |
До Ширина: | Высота: | Размер: 3.1 KiB |
До Ширина: | Высота: | Размер: 5.1 KiB |
После Ширина: | Высота: | Размер: 8.8 KiB |
После Ширина: | Высота: | Размер: 3.5 KiB |
После Ширина: | Высота: | Размер: 1.8 KiB |
До Ширина: | Высота: | Размер: 1.5 KiB После Ширина: | Высота: | Размер: 5.6 KiB |
До Ширина: | Высота: | Размер: 10 KiB |
Двоичные данные
mobile/android/base/resources/drawable-xxhdpi/shield_enabled.png
До Ширина: | Высота: | Размер: 1.2 KiB После Ширина: | Высота: | Размер: 5.2 KiB |
До Ширина: | Высота: | Размер: 5.0 KiB |
До Ширина: | Высота: | Размер: 9.4 KiB |
|
@ -10,7 +10,7 @@
|
|||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<bitmap android:src="@drawable/tab_panel_tab_globe"
|
||||
<bitmap android:src="@drawable/globe_light"
|
||||
android:gravity="center"
|
||||
/>
|
||||
</item>
|
||||
|
|
|
@ -18,14 +18,14 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginTop="@dimen/doorhanger_section_padding_small"
|
||||
android:layout_marginTop="@dimen/doorhanger_section_padding_medium"
|
||||
android:gravity="right"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<CheckBox android:id="@+id/doorhanger_checkbox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/doorhanger_section_padding_small"
|
||||
android:layout_marginTop="@dimen/doorhanger_section_padding_medium"
|
||||
android:checked="true"
|
||||
android:textColor="@color/placeholder_active_grey"
|
||||
android:visibility="gone"/>
|
||||
|
|
|
@ -8,13 +8,17 @@
|
|||
<LinearLayout android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="@dimen/doorhanger_section_padding_small">
|
||||
android:paddingLeft="@dimen/doorhanger_section_padding_small"
|
||||
android:paddingRight="@dimen/doorhanger_section_padding_small"
|
||||
android:paddingBottom="@dimen/doorhanger_section_padding_medium"
|
||||
android:paddingTop="@dimen/doorhanger_section_padding_medium">
|
||||
|
||||
<ImageView android:id="@+id/doorhanger_icon"
|
||||
android:layout_width="@dimen/doorhanger_icon_size"
|
||||
android:layout_height="@dimen/doorhanger_icon_size"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:paddingRight="@dimen/doorhanger_section_padding_small"
|
||||
android:padding="@dimen/doorhanger_section_padding_small"
|
||||
android:layout_marginRight="@dimen/doorhanger_section_padding_small"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<LinearLayout android:layout_width="match_parent"
|
||||
|
@ -24,7 +28,7 @@
|
|||
<TextView android:id="@+id/doorhanger_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/doorhanger_section_padding_small"
|
||||
android:layout_marginBottom="@dimen/doorhanger_section_padding_medium"
|
||||
android:textAppearance="@style/TextAppearance.DoorHanger.Medium.Light"
|
||||
android:visibility="gone"/>
|
||||
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView android:id="@+id/security_title"
|
||||
android:focusable="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="@dimen/doorhanger_subsection_padding"
|
||||
android:textAppearance="@style/TextAppearance.DoorHanger.Medium"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<TextView android:id="@+id/security_state"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="@dimen/doorhanger_section_padding_small"
|
||||
android:textAppearance="@style/TextAppearance.DoorHanger.Medium.Bold"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<TextView android:id="@+id/security_message"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.DoorHanger.Medium"/>
|
||||
|
||||
</LinearLayout>
|
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView android:id="@+id/trackingprotection_title"
|
||||
android:focusable="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.DoorHanger.Medium"/>
|
||||
|
||||
<TextView android:id="@+id/trackingprotection_enabled"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.DoorHanger.Medium.Bold"/>
|
||||
|
||||
<TextView android:id="@+id/trackingprotection_message"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
</LinearLayout>
|
|
@ -6,7 +6,7 @@
|
|||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="@dimen/doorhanger_section_padding_small"
|
||||
android:padding="@dimen/doorhanger_section_padding_medium"
|
||||
android:orientation="vertical">
|
||||
|
||||
<EditText android:id="@+id/username_edit"
|
||||
|
|
|
@ -11,13 +11,17 @@
|
|||
<LinearLayout android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="@dimen/doorhanger_section_padding_small">
|
||||
android:paddingLeft="@dimen/doorhanger_section_padding_small"
|
||||
android:paddingRight="@dimen/doorhanger_section_padding_small"
|
||||
android:paddingBottom="@dimen/doorhanger_section_padding_medium"
|
||||
android:paddingTop="@dimen/doorhanger_section_padding_medium">
|
||||
|
||||
<ImageView android:id="@+id/larry"
|
||||
<ImageView android:id="@+id/site_identity_icon"
|
||||
android:layout_width="@dimen/doorhanger_icon_size"
|
||||
android:layout_height="@dimen/doorhanger_icon_size"
|
||||
android:src="@drawable/larry"
|
||||
android:paddingRight="@dimen/doorhanger_section_padding_small"/>
|
||||
android:gravity="center_horizontal"
|
||||
android:padding="@dimen/doorhanger_section_padding_small"
|
||||
android:layout_marginRight="@dimen/doorhanger_section_padding_small"/>
|
||||
|
||||
<LinearLayout android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -41,7 +45,7 @@
|
|||
<TextView android:id="@+id/site_identity_encrypted"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/doorhanger_section_padding_small"
|
||||
android:layout_marginBottom="@dimen/doorhanger_section_padding_medium"
|
||||
android:textAppearance="@style/TextAppearance.DoorHanger.Medium.Bold"
|
||||
android:textColor="@color/affirmative_green"
|
||||
android:text="@string/identity_encrypted"/>
|
||||
|
@ -62,7 +66,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.DoorHanger.Medium.Light"
|
||||
android:text="@string/identity_run_by"
|
||||
android:layout_marginTop="@dimen/doorhanger_section_padding_small"/>
|
||||
android:layout_marginTop="@dimen/doorhanger_section_padding_medium"/>
|
||||
|
||||
<TextView android:id="@+id/owner"
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -74,7 +78,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.DoorHanger.Medium.Light"
|
||||
android:layout_marginTop="@dimen/doorhanger_section_padding_small"/>
|
||||
android:layout_marginTop="@dimen/doorhanger_section_padding_medium"/>
|
||||
|
||||
</LinearLayout>
|
||||
<TextView android:id="@+id/site_settings_link"
|
||||
|
@ -83,7 +87,7 @@
|
|||
android:textAppearance="@style/TextAppearance.DoorHanger.Medium"
|
||||
android:textColor="@color/link_blue"
|
||||
android:layout_marginTop="@dimen/doorhanger_section_padding_large"
|
||||
android:layout_marginBottom="@dimen/doorhanger_section_padding_small"
|
||||
android:layout_marginBottom="@dimen/doorhanger_section_padding_medium"
|
||||
android:text="@string/contextmenu_site_settings"
|
||||
android:visibility="gone"/>
|
||||
</LinearLayout>
|
||||
|
|
|
@ -19,6 +19,6 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.DoorHanger.Medium"
|
||||
android:text="@string/identity_not_encrypted"
|
||||
android:paddingTop="@dimen/doorhanger_section_padding_small"/>
|
||||
android:paddingTop="@dimen/doorhanger_section_padding_medium"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
<!-- Fennec color palette (bug 1127517) -->
|
||||
<color name="fennec_ui_orange">#FF9500</color>
|
||||
<color name="affirmative_green">#6FBE4A</color>
|
||||
<color name="rejection_red">#D23228</color>
|
||||
<color name="action_orange">#E66000</color>
|
||||
<color name="action_orange_pressed">#DC5600</color>
|
||||
<color name="link_blue">#0096DD</color>
|
||||
|
|
|
@ -99,7 +99,8 @@
|
|||
<dimen name="doorhanger_GB_offsetY">7dp</dimen>
|
||||
<dimen name="doorhanger_drawable_padding">5dp</dimen>
|
||||
<dimen name="doorhanger_subsection_padding">8dp</dimen>
|
||||
<dimen name="doorhanger_section_padding_small">20dp</dimen>
|
||||
<dimen name="doorhanger_section_padding_small">10dp</dimen>
|
||||
<dimen name="doorhanger_section_padding_medium">20dp</dimen>
|
||||
<dimen name="doorhanger_section_padding_large">30dp</dimen>
|
||||
<dimen name="doorhanger_icon_size">60dp</dimen>
|
||||
|
||||
|
|
|
@ -463,10 +463,13 @@
|
|||
<string name="loaded_mixed_content_message">&loaded_mixed_content_message;</string>
|
||||
<string name="blocked_mixed_content_message_top">&blocked_mixed_content_message_top;</string>
|
||||
<string name="blocked_mixed_content_message_bottom">&blocked_mixed_content_message_bottom;</string>
|
||||
<string name="loaded_tracking_content_message_top">&loaded_tracking_content_message_top;</string>
|
||||
<string name="loaded_tracking_content_message_bottom">&loaded_tracking_content_message_bottom;</string>
|
||||
<string name="blocked_tracking_content_message_top">&blocked_tracking_content_message_top2;</string>
|
||||
<string name="blocked_tracking_content_message_bottom">&blocked_tracking_content_message_bottom2;</string>
|
||||
|
||||
<string name="doorhanger_tracking_title">&doorhanger_tracking_title;</string>
|
||||
<string name="doorhanger_tracking_state_enabled">&doorhanger_tracking_state_enabled;</string>
|
||||
<string name="doorhanger_tracking_state_disabled">&doorhanger_tracking_state_disabled;</string>
|
||||
<string name="doorhanger_tracking_message_enabled">&doorhanger_tracking_message_enabled;</string>
|
||||
<string name="doorhanger_tracking_message_disabled">&doorhanger_tracking_message_disabled;</string>
|
||||
|
||||
<string name="learn_more">&learn_more;</string>
|
||||
<string name="enable_protection">&enable_protection;</string>
|
||||
<string name="disable_protection">&disable_protection;</string>
|
||||
|
|
|
@ -68,6 +68,7 @@ public class SiteIdentityPopup extends AnchoredPopup implements GeckoEventListen
|
|||
private LinearLayout mIdentityKnownContainer;
|
||||
private LinearLayout mIdentityUnknownContainer;
|
||||
|
||||
private ImageView mIcon;
|
||||
private TextView mTitle;
|
||||
private TextView mEncrypted;
|
||||
private TextView mHost;
|
||||
|
@ -110,6 +111,7 @@ public class SiteIdentityPopup extends AnchoredPopup implements GeckoEventListen
|
|||
(LinearLayout) mIdentity.findViewById(R.id.site_identity_unknown_container);
|
||||
|
||||
|
||||
mIcon = (ImageView) mIdentity.findViewById(R.id.site_identity_icon);
|
||||
mTitle = (TextView) mIdentity.findViewById(R.id.site_identity_title);
|
||||
mEncrypted = (TextView) mIdentityKnownContainer.findViewById(R.id.site_identity_encrypted);
|
||||
mHost = (TextView) mIdentityKnownContainer.findViewById(R.id.host);
|
||||
|
@ -284,13 +286,19 @@ public class SiteIdentityPopup extends AnchoredPopup implements GeckoEventListen
|
|||
mIdentityKnownContainer.setVisibility(View.VISIBLE);
|
||||
mIdentityUnknownContainer.setVisibility(View.GONE);
|
||||
} else {
|
||||
mIcon.setImageResource(R.drawable.globe_light);
|
||||
mIdentityKnownContainer.setVisibility(View.GONE);
|
||||
mIdentityUnknownContainer.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateIdentityInformation(final SiteIdentity siteIdentity) {
|
||||
mEncrypted.setVisibility(siteIdentity.getEncrypted() ? View.VISIBLE : View.GONE);
|
||||
if (siteIdentity.getEncrypted()) {
|
||||
mEncrypted.setVisibility(View.VISIBLE);
|
||||
mIcon.setImageResource(R.drawable.lock_identified);
|
||||
} else {
|
||||
mEncrypted.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
mHost.setText(siteIdentity.getHost());
|
||||
|
||||
|
@ -321,11 +329,11 @@ public class SiteIdentityPopup extends AnchoredPopup implements GeckoEventListen
|
|||
final DoorhangerConfig config = new DoorhangerConfig(DoorHanger.Type.MIXED_CONTENT, mContentButtonClickListener);
|
||||
int icon;
|
||||
if (blocked) {
|
||||
icon = R.drawable.shield_enabled_doorhanger;
|
||||
icon = R.drawable.shield_enabled;
|
||||
config.setMessage(mContext.getString(R.string.blocked_mixed_content_message_top) + "\n\n" +
|
||||
mContext.getString(R.string.blocked_mixed_content_message_bottom));
|
||||
} else {
|
||||
icon = R.drawable.shield_disabled_doorhanger;
|
||||
icon = R.drawable.shield_disabled;
|
||||
config.setMessage(mContext.getString(R.string.loaded_mixed_content_message));
|
||||
}
|
||||
|
||||
|
@ -352,25 +360,26 @@ public class SiteIdentityPopup extends AnchoredPopup implements GeckoEventListen
|
|||
|
||||
final DoorhangerConfig config = new DoorhangerConfig(DoorHanger.Type.TRACKING, mContentButtonClickListener);
|
||||
|
||||
int icon;
|
||||
if (blocked) {
|
||||
icon = R.drawable.shield_enabled_doorhanger;
|
||||
config.setMessage(mContext.getString(R.string.blocked_tracking_content_message_top) + "\n\n" +
|
||||
mContext.getString(R.string.blocked_tracking_content_message_bottom));
|
||||
} else {
|
||||
icon = R.drawable.shield_disabled_doorhanger;
|
||||
config.setMessage(mContext.getString(R.string.loaded_tracking_content_message_top) + "\n\n" +
|
||||
mContext.getString(R.string.loaded_tracking_content_message_bottom));
|
||||
final int icon = blocked ? R.drawable.shield_enabled: R.drawable.shield_disabled;
|
||||
|
||||
final JSONObject options = new JSONObject();
|
||||
final JSONObject tracking = new JSONObject();
|
||||
try {
|
||||
tracking.put("enabled", blocked);
|
||||
options.put("tracking_protection", tracking);
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error adding tracking protection options", e);
|
||||
}
|
||||
config.setOptions(options);
|
||||
|
||||
config.setLink(mContext.getString(R.string.learn_more), TRACKING_CONTENT_SUPPORT_URL);
|
||||
|
||||
addNotificationButtons(config, blocked);
|
||||
|
||||
mTrackingContentNotification = DoorHanger.Get(mContext, config);
|
||||
|
||||
mTrackingContentNotification.setIcon(icon);
|
||||
|
||||
|
||||
mContent.addView(mTrackingContentNotification);
|
||||
mDivider.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
@ -385,7 +394,6 @@ public class SiteIdentityPopup extends AnchoredPopup implements GeckoEventListen
|
|||
private void addNotificationButtons(DoorhangerConfig config, boolean blocked) {
|
||||
if (blocked) {
|
||||
config.setButton(mContext.getString(R.string.disable_protection), ButtonType.DISABLE.ordinal(), false);
|
||||
config.setButton(mContext.getString(R.string.keep_blocking), ButtonType.KEEP_BLOCKING.ordinal(), true);
|
||||
} else {
|
||||
config.setButton(mContext.getString(R.string.enable_protection), ButtonType.ENABLE.ordinal(), true);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.widget;
|
||||
|
||||
import android.util.Log;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
import org.mozilla.gecko.R;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import org.mozilla.gecko.toolbar.SiteIdentityPopup;
|
||||
|
||||
public class ContentSecurityDoorHanger extends DoorHanger {
|
||||
private static final String LOGTAG = "GeckoSecurityDoorHanger";
|
||||
|
||||
private final TextView mTitle;
|
||||
private final TextView mSecurityState;
|
||||
private final TextView mMessage;
|
||||
|
||||
public ContentSecurityDoorHanger(Context context, DoorhangerConfig config, Type type) {
|
||||
super(context, config, type);
|
||||
|
||||
mTitle = (TextView) findViewById(R.id.security_title);
|
||||
mSecurityState = (TextView) findViewById(R.id.security_state);
|
||||
mMessage = (TextView) findViewById(R.id.security_message);
|
||||
|
||||
loadConfig(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadConfig(DoorhangerConfig config) {
|
||||
final String message = config.getMessage();
|
||||
if (message != null) {
|
||||
mMessage.setText(message);
|
||||
}
|
||||
|
||||
final JSONObject options = config.getOptions();
|
||||
if (options != null) {
|
||||
setOptions(options);
|
||||
}
|
||||
|
||||
final DoorhangerConfig.Link link = config.getLink();
|
||||
if (link != null) {
|
||||
addLink(link.label, link.url);
|
||||
}
|
||||
|
||||
addButtonsToLayout(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getContentResource() {
|
||||
return R.layout.doorhanger_security;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOptions(final JSONObject options) {
|
||||
super.setOptions(options);
|
||||
final JSONObject link = options.optJSONObject("link");
|
||||
if (link != null) {
|
||||
try {
|
||||
final String linkLabel = link.getString("label");
|
||||
final String linkUrl = link.getString("url");
|
||||
addLink(linkLabel, linkUrl);
|
||||
} catch (JSONException e) { }
|
||||
}
|
||||
|
||||
final JSONObject trackingProtection = options.optJSONObject("tracking_protection");
|
||||
if (trackingProtection != null) {
|
||||
mTitle.setVisibility(VISIBLE);
|
||||
mTitle.setText(R.string.doorhanger_tracking_title);
|
||||
try {
|
||||
final boolean enabled = trackingProtection.getBoolean("enabled");
|
||||
if (enabled) {
|
||||
mMessage.setText(R.string.doorhanger_tracking_message_enabled);
|
||||
mSecurityState.setText(R.string.doorhanger_tracking_state_enabled);
|
||||
mSecurityState.setTextColor(getResources().getColor(R.color.affirmative_green));
|
||||
} else {
|
||||
mMessage.setText(R.string.doorhanger_tracking_message_disabled);
|
||||
mSecurityState.setText(R.string.doorhanger_tracking_state_disabled);
|
||||
mSecurityState.setTextColor(getResources().getColor(R.color.rejection_red));
|
||||
}
|
||||
mMessage.setVisibility(VISIBLE);
|
||||
mSecurityState.setVisibility(VISIBLE);
|
||||
} catch (JSONException e) {}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OnClickListener makeOnButtonClickListener(final int id) {
|
||||
return new Button.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
final JSONObject response = new JSONObject();
|
||||
try {
|
||||
switch (mType) {
|
||||
case MIXED_CONTENT:
|
||||
response.put("allowContent", (id == SiteIdentityPopup.ButtonType.DISABLE.ordinal()));
|
||||
response.put("contentType", ("mixed"));
|
||||
break;
|
||||
case TRACKING:
|
||||
response.put("allowContent", (id == SiteIdentityPopup.ButtonType.DISABLE.ordinal()));
|
||||
response.put("contentType", ("tracking"));
|
||||
break;
|
||||
default:
|
||||
Log.w(LOGTAG, "Unknown doorhanger type " + mType.toString());
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error creating onClick response", e);
|
||||
}
|
||||
|
||||
mOnButtonClickListener.onButtonClick(response, ContentSecurityDoorHanger.this);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -11,7 +11,6 @@ import android.util.Log;
|
|||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.Tabs;
|
||||
import org.mozilla.gecko.prompts.PromptInput;
|
||||
|
||||
import org.json.JSONArray;
|
||||
|
@ -23,7 +22,6 @@ import android.text.TextUtils;
|
|||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import org.mozilla.gecko.toolbar.SiteIdentityPopup;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
@ -45,6 +43,13 @@ public class DefaultDoorHanger extends DoorHanger {
|
|||
if (sSpinnerTextColor == -1) {
|
||||
sSpinnerTextColor = mResources.getColor(R.color.text_color_primary_disable_only);
|
||||
}
|
||||
|
||||
switch (mType) {
|
||||
case GEOLOCATION:
|
||||
mIcon.setImageResource(R.drawable.location);
|
||||
mIcon.setVisibility(VISIBLE);
|
||||
}
|
||||
|
||||
loadConfig(config);
|
||||
}
|
||||
|
||||
|
@ -84,14 +89,6 @@ public class DefaultDoorHanger extends DoorHanger {
|
|||
@Override
|
||||
public void setOptions(final JSONObject options) {
|
||||
super.setOptions(options);
|
||||
final JSONObject link = options.optJSONObject("link");
|
||||
if (link != null) {
|
||||
try {
|
||||
final String linkLabel = link.getString("label");
|
||||
final String linkUrl = link.getString("url");
|
||||
addLink(linkLabel, linkUrl);
|
||||
} catch (JSONException e) { }
|
||||
}
|
||||
|
||||
final JSONArray inputs = options.optJSONArray("inputs");
|
||||
if (inputs != null) {
|
||||
|
@ -105,7 +102,7 @@ public class DefaultDoorHanger extends DoorHanger {
|
|||
PromptInput input = PromptInput.getInput(inputs.getJSONObject(i));
|
||||
mInputs.add(input);
|
||||
|
||||
final int padding = mResources.getDimensionPixelSize(R.dimen.doorhanger_section_padding_small);
|
||||
final int padding = mResources.getDimensionPixelSize(R.dimen.doorhanger_section_padding_medium);
|
||||
View v = input.getView(getContext());
|
||||
styleInput(input, v);
|
||||
v.setPadding(0, 0, 0, padding);
|
||||
|
@ -129,17 +126,6 @@ public class DefaultDoorHanger extends DoorHanger {
|
|||
public void onClick(View v) {
|
||||
final JSONObject response = new JSONObject();
|
||||
try {
|
||||
// TODO: Bug 1149359 - Split this into each Doorhanger Type class.
|
||||
switch (mType) {
|
||||
case MIXED_CONTENT:
|
||||
response.put("allowContent", (id == SiteIdentityPopup.ButtonType.DISABLE.ordinal()));
|
||||
response.put("contentType", ("mixed"));
|
||||
break;
|
||||
case TRACKING:
|
||||
response.put("allowContent", (id == SiteIdentityPopup.ButtonType.DISABLE.ordinal()));
|
||||
response.put("contentType", ("tracking"));
|
||||
break;
|
||||
default:
|
||||
response.put("callback", id);
|
||||
|
||||
CheckBox checkBox = getCheckBox();
|
||||
|
@ -156,7 +142,6 @@ public class DefaultDoorHanger extends DoorHanger {
|
|||
}
|
||||
response.put("inputs", inputs);
|
||||
}
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error creating onClick response", e);
|
||||
}
|
||||
|
@ -171,17 +156,6 @@ public class DefaultDoorHanger extends DoorHanger {
|
|||
mMessage.setText(markupMessage);
|
||||
}
|
||||
|
||||
private void addLink(String label, final String url) {
|
||||
mLink.setText(label);
|
||||
mLink.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Tabs.getInstance().loadUrlInTab(url);
|
||||
}
|
||||
});
|
||||
mLink.setVisibility(VISIBLE);
|
||||
}
|
||||
|
||||
private void styleInput(PromptInput input, View view) {
|
||||
if (input instanceof PromptInput.MenulistInput) {
|
||||
styleDropdownInputs(input, view);
|
||||
|
|
|
@ -9,7 +9,6 @@ import android.content.Context;
|
|||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewStub;
|
||||
|
@ -30,12 +29,12 @@ public abstract class DoorHanger extends LinearLayout {
|
|||
return new LoginDoorHanger(context, config);
|
||||
case TRACKING:
|
||||
case MIXED_CONTENT:
|
||||
return new DefaultDoorHanger(context, config, type);
|
||||
return new ContentSecurityDoorHanger(context, config, type);
|
||||
}
|
||||
return new DefaultDoorHanger(context, config, type);
|
||||
}
|
||||
|
||||
public static enum Type { DEFAULT, LOGIN, TRACKING, MIXED_CONTENT}
|
||||
public static enum Type { DEFAULT, LOGIN, TRACKING, MIXED_CONTENT, GEOLOCATION }
|
||||
|
||||
public interface OnButtonClickListener {
|
||||
public void onButtonClick(JSONObject response, DoorHanger doorhanger);
|
||||
|
@ -155,6 +154,17 @@ public abstract class DoorHanger extends LinearLayout {
|
|||
mIcon.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
protected void addLink(String label, final String url) {
|
||||
mLink.setText(label);
|
||||
mLink.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Tabs.getInstance().loadUrlInTab(url);
|
||||
}
|
||||
});
|
||||
mLink.setVisibility(VISIBLE);
|
||||
}
|
||||
|
||||
protected abstract OnClickListener makeOnButtonClickListener(final int id);
|
||||
|
||||
/*
|
||||
|
|
|
@ -128,7 +128,7 @@ ContentPermissionPrompt.prototype = {
|
|||
let message = browserBundle.formatStringFromName(entityName + ".ask", [requestor], 1);
|
||||
let options = { checkbox: browserBundle.GetStringFromName(entityName + ".dontAskAgain") };
|
||||
|
||||
chromeWin.NativeWindow.doorhanger.show(message, entityName + request.principal.URI.host, buttons, tab.id, options);
|
||||
chromeWin.NativeWindow.doorhanger.show(message, entityName + request.principal.URI.host, buttons, tab.id, options, entityName.toUpperCase());
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -3962,6 +3962,7 @@ pref("signon.autologin.proxy", false);
|
|||
pref("signon.storeWhenAutocompleteOff", true);
|
||||
pref("signon.ui.experimental", false);
|
||||
pref("signon.debug", false);
|
||||
pref("signon.recipes.path", "chrome://passwordmgr/content/recipes.json");
|
||||
|
||||
// Satchel (Form Manager) prefs
|
||||
pref("browser.formfill.debug", false);
|
||||
|
|
|
@ -61,6 +61,11 @@ class MachRegistrar(object):
|
|||
if handler.pass_context and not context:
|
||||
raise Exception('mach command class requires context.')
|
||||
|
||||
if context:
|
||||
prerun = getattr(context, 'pre_dispatch_handler', None)
|
||||
if prerun:
|
||||
prerun(context, handler, args=kwargs)
|
||||
|
||||
if handler.pass_context:
|
||||
instance = cls(context)
|
||||
else:
|
||||
|
|
|
@ -73,12 +73,18 @@ function hasChanged(oldData, newData) {
|
|||
|
||||
this.FxAccountsProfile = function (options = {}) {
|
||||
this._cachedProfile = null;
|
||||
this._cachedAt = 0; // when we saved the cached version.
|
||||
this._currentFetchPromise = null;
|
||||
this._isNotifying = false; // are we sending a notification?
|
||||
this.fxa = options.fxa || fxAccounts;
|
||||
this.client = options.profileClient || new FxAccountsProfileClient({
|
||||
fxa: this.fxa,
|
||||
serverURL: options.profileServerUrl,
|
||||
});
|
||||
|
||||
// An observer to invalidate our _cachedAt optimization. We use a weak-ref
|
||||
// just incase this.tearDown isn't called in some cases.
|
||||
Services.obs.addObserver(this, ON_PROFILE_CHANGE_NOTIFICATION, true);
|
||||
// for testing
|
||||
if (options.channel) {
|
||||
this.channel = options.channel;
|
||||
|
@ -86,11 +92,25 @@ this.FxAccountsProfile = function (options = {}) {
|
|||
}
|
||||
|
||||
this.FxAccountsProfile.prototype = {
|
||||
// If we get subsequent requests for a profile within this period, don't bother
|
||||
// making another request to determine if it is fresh or not.
|
||||
PROFILE_FRESHNESS_THRESHOLD: 120000, // 2 minutes
|
||||
|
||||
observe(subject, topic, data) {
|
||||
// If we get a profile change notification from our webchannel it means
|
||||
// the user has just changed their profile via the web, so we want to
|
||||
// ignore our "freshness threshold"
|
||||
if (topic == ON_PROFILE_CHANGE_NOTIFICATION && !this._isNotifying) {
|
||||
log.debug("FxAccountsProfile observed profile change");
|
||||
this._cachedAt = 0;
|
||||
}
|
||||
},
|
||||
|
||||
tearDown: function () {
|
||||
this.fxa = null;
|
||||
this.client = null;
|
||||
this._cachedProfile = null;
|
||||
Services.obs.removeObserver(this, ON_PROFILE_CHANGE_NOTIFICATION);
|
||||
},
|
||||
|
||||
_getCachedProfile: function () {
|
||||
|
@ -100,7 +120,9 @@ this.FxAccountsProfile.prototype = {
|
|||
},
|
||||
|
||||
_notifyProfileChange: function (uid) {
|
||||
this._isNotifying = true;
|
||||
Services.obs.notifyObservers(null, ON_PROFILE_CHANGE_NOTIFICATION, uid);
|
||||
this._isNotifying = false;
|
||||
},
|
||||
|
||||
// Cache fetched data if it is different from what's in the cache.
|
||||
|
@ -111,6 +133,7 @@ this.FxAccountsProfile.prototype = {
|
|||
return Promise.resolve(null); // indicates no change (but only tests care)
|
||||
}
|
||||
this._cachedProfile = profileData;
|
||||
this._cachedAt = Date.now();
|
||||
return this.fxa.getSignedInUser()
|
||||
.then(userData => {
|
||||
log.debug("notifying profile changed for user ${uid}", userData);
|
||||
|
@ -120,10 +143,20 @@ this.FxAccountsProfile.prototype = {
|
|||
},
|
||||
|
||||
_fetchAndCacheProfile: function () {
|
||||
return this.client.fetchProfile()
|
||||
.then(profile => {
|
||||
return this._cacheProfile(profile).then(() => profile);
|
||||
if (!this._currentFetchPromise) {
|
||||
this._currentFetchPromise = this.client.fetchProfile().then(profile => {
|
||||
return this._cacheProfile(profile).then(() => {
|
||||
return profile;
|
||||
});
|
||||
}).then(profile => {
|
||||
this._currentFetchPromise = null;
|
||||
return profile;
|
||||
}, err => {
|
||||
this._currentFetchPromise = null;
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
return this._currentFetchPromise
|
||||
},
|
||||
|
||||
// Returns cached data right away if available, then fetches the latest profile
|
||||
|
@ -133,11 +166,15 @@ this.FxAccountsProfile.prototype = {
|
|||
return this._getCachedProfile()
|
||||
.then(cachedProfile => {
|
||||
if (cachedProfile) {
|
||||
if (Date.now() > this._cachedAt + this.PROFILE_FRESHNESS_THRESHOLD) {
|
||||
// Note that _fetchAndCacheProfile isn't returned, so continues
|
||||
// in the background.
|
||||
this._fetchAndCacheProfile().catch(err => {
|
||||
log.error("Background refresh of profile failed", err);
|
||||
});
|
||||
} else {
|
||||
log.trace("not checking freshness of profile as it remains recent");
|
||||
}
|
||||
return cachedProfile;
|
||||
}
|
||||
return this._fetchAndCacheProfile();
|
||||
|
@ -146,4 +183,9 @@ this.FxAccountsProfile.prototype = {
|
|||
return profile;
|
||||
});
|
||||
},
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([
|
||||
Ci.nsIObserver,
|
||||
Ci.nsISupportsWeakReference,
|
||||
]),
|
||||
};
|
||||
|
|
|
@ -164,6 +164,137 @@ add_test(function fetchAndCacheProfile_ok() {
|
|||
});
|
||||
});
|
||||
|
||||
// Check that a second profile request when one is already in-flight reuses
|
||||
// the in-flight one.
|
||||
add_task(function fetchAndCacheProfileOnce() {
|
||||
// A promise that remains unresolved while we fire off 2 requests for
|
||||
// a profile.
|
||||
let resolveProfile;
|
||||
let promiseProfile = new Promise(resolve => {
|
||||
resolveProfile = resolve;
|
||||
});
|
||||
let numFetches = 0;
|
||||
let client = mockClient(mockFxa());
|
||||
client.fetchProfile = function () {
|
||||
numFetches += 1;
|
||||
return promiseProfile;
|
||||
};
|
||||
let profile = CreateFxAccountsProfile(null, client);
|
||||
|
||||
let request1 = profile._fetchAndCacheProfile();
|
||||
let request2 = profile._fetchAndCacheProfile();
|
||||
|
||||
// should be one request made to fetch the profile (but the promise returned
|
||||
// by it remains unresolved)
|
||||
do_check_eq(numFetches, 1);
|
||||
|
||||
// resolve the promise.
|
||||
resolveProfile({ avatar: "myimg"});
|
||||
|
||||
// both requests should complete with the same data.
|
||||
let got1 = yield request1;
|
||||
do_check_eq(got1.avatar, "myimg");
|
||||
let got2 = yield request1;
|
||||
do_check_eq(got2.avatar, "myimg");
|
||||
|
||||
// and still only 1 request was made.
|
||||
do_check_eq(numFetches, 1);
|
||||
});
|
||||
|
||||
// Check that sharing a single fetch promise works correctly when the promise
|
||||
// is rejected.
|
||||
add_task(function fetchAndCacheProfileOnce() {
|
||||
// A promise that remains unresolved while we fire off 2 requests for
|
||||
// a profile.
|
||||
let rejectProfile;
|
||||
let promiseProfile = new Promise((resolve,reject) => {
|
||||
rejectProfile = reject;
|
||||
});
|
||||
let numFetches = 0;
|
||||
let client = mockClient(mockFxa());
|
||||
client.fetchProfile = function () {
|
||||
numFetches += 1;
|
||||
return promiseProfile;
|
||||
};
|
||||
let profile = CreateFxAccountsProfile(null, client);
|
||||
|
||||
let request1 = profile._fetchAndCacheProfile();
|
||||
let request2 = profile._fetchAndCacheProfile();
|
||||
|
||||
// should be one request made to fetch the profile (but the promise returned
|
||||
// by it remains unresolved)
|
||||
do_check_eq(numFetches, 1);
|
||||
|
||||
// reject the promise.
|
||||
rejectProfile("oh noes");
|
||||
|
||||
// both requests should reject.
|
||||
try {
|
||||
yield request1;
|
||||
throw new Error("should have rejected");
|
||||
} catch (ex if ex == "oh noes") {}
|
||||
try {
|
||||
yield request2;
|
||||
throw new Error("should have rejected");
|
||||
} catch (ex if ex == "oh noes") {}
|
||||
|
||||
// but a new request should work.
|
||||
client.fetchProfile = function () {
|
||||
return Promise.resolve({ avatar: "myimg"});
|
||||
};
|
||||
|
||||
let got = yield profile._fetchAndCacheProfile();
|
||||
do_check_eq(got.avatar, "myimg");
|
||||
});
|
||||
|
||||
// Check that a new profile request within PROFILE_FRESHNESS_THRESHOLD of the
|
||||
// last one doesn't kick off a new request to check the cached copy is fresh.
|
||||
add_task(function fetchAndCacheProfileAfterThreshold() {
|
||||
let numFetches = 0;
|
||||
let client = mockClient(mockFxa());
|
||||
client.fetchProfile = function () {
|
||||
numFetches += 1;
|
||||
return Promise.resolve({ avatar: "myimg"});
|
||||
};
|
||||
let profile = CreateFxAccountsProfile(null, client);
|
||||
profile.PROFILE_FRESHNESS_THRESHOLD = 1000;
|
||||
|
||||
yield profile.getProfile();
|
||||
do_check_eq(numFetches, 1);
|
||||
|
||||
yield profile.getProfile();
|
||||
do_check_eq(numFetches, 1);
|
||||
|
||||
yield new Promise(resolve => {
|
||||
do_timeout(1000, resolve);
|
||||
});
|
||||
|
||||
yield profile.getProfile();
|
||||
do_check_eq(numFetches, 2);
|
||||
});
|
||||
|
||||
// Check that a new profile request within PROFILE_FRESHNESS_THRESHOLD of the
|
||||
// last one *does* kick off a new request if ON_PROFILE_CHANGE_NOTIFICATION
|
||||
// is sent.
|
||||
add_task(function fetchAndCacheProfileBeforeThresholdOnNotification() {
|
||||
let numFetches = 0;
|
||||
let client = mockClient(mockFxa());
|
||||
client.fetchProfile = function () {
|
||||
numFetches += 1;
|
||||
return Promise.resolve({ avatar: "myimg"});
|
||||
};
|
||||
let profile = CreateFxAccountsProfile(null, client);
|
||||
profile.PROFILE_FRESHNESS_THRESHOLD = 1000;
|
||||
|
||||
yield profile.getProfile();
|
||||
do_check_eq(numFetches, 1);
|
||||
|
||||
Services.obs.notifyObservers(null, ON_PROFILE_CHANGE_NOTIFICATION, null);
|
||||
|
||||
yield profile.getProfile();
|
||||
do_check_eq(numFetches, 2);
|
||||
});
|
||||
|
||||
add_test(function tearDown_ok() {
|
||||
let profile = CreateFxAccountsProfile();
|
||||
|
||||
|
|
|
@ -130,18 +130,13 @@ wmain(int argc, WCHAR **argv)
|
|||
BOOL
|
||||
GetLogDirectoryPath(WCHAR *path)
|
||||
{
|
||||
HRESULT hr = SHGetFolderPathW(nullptr, CSIDL_COMMON_APPDATA, nullptr,
|
||||
SHGFP_TYPE_CURRENT, path);
|
||||
if (FAILED(hr)) {
|
||||
if (!GetModuleFileNameW(nullptr, path, MAX_PATH)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!PathAppendSafe(path, L"Mozilla")) {
|
||||
if (!PathRemoveFileSpecW(path)) {
|
||||
return FALSE;
|
||||
}
|
||||
// The directory should already be created from the installer, but
|
||||
// just to be safe in case someone deletes.
|
||||
CreateDirectoryW(path, nullptr);
|
||||
|
||||
if (!PathAppendSafe(path, L"logs")) {
|
||||
return FALSE;
|
||||
|
|
|
@ -152,7 +152,9 @@ var LoginManagerParent = {
|
|||
|
||||
XPCOMUtils.defineLazyGetter(this, "recipeParentPromise", () => {
|
||||
const { LoginRecipesParent } = Cu.import("resource://gre/modules/LoginRecipes.jsm", {});
|
||||
this._recipeManager = new LoginRecipesParent();
|
||||
this._recipeManager = new LoginRecipesParent({
|
||||
defaults: Services.prefs.getComplexValue("signon.recipes.path", Ci.nsISupportsString).data,
|
||||
});
|
||||
return this._recipeManager.initializationPromise;
|
||||
});
|
||||
|
||||
|
@ -274,22 +276,30 @@ var LoginManagerParent = {
|
|||
}
|
||||
|
||||
if (!showMasterPassword && !Services.logins.isLoggedIn) {
|
||||
try {
|
||||
target.sendAsyncMessage("RemoteLogins:loginsFound", {
|
||||
requestId: requestId,
|
||||
logins: [],
|
||||
recipes,
|
||||
});
|
||||
} catch (e) {
|
||||
log("error sending message to target", e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let allLoginsCount = Services.logins.countLogins(formOrigin, "", null);
|
||||
// If there are no logins for this site, bail out now.
|
||||
if (!allLoginsCount) {
|
||||
try {
|
||||
target.sendAsyncMessage("RemoteLogins:loginsFound", {
|
||||
requestId: requestId,
|
||||
logins: [],
|
||||
recipes,
|
||||
});
|
||||
} catch (e) {
|
||||
log("error sending message to target", e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ const SUPPORTED_KEYS = REQUIRED_KEYS.concat(OPTIONAL_KEYS);
|
|||
|
||||
Cu.importGlobalProperties(["URL"]);
|
||||
|
||||
Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
|
@ -27,9 +28,11 @@ XPCOMUtils.defineLazyGetter(this, "log", () => LoginHelper.createLogger("LoginRe
|
|||
* calling methods on the object.
|
||||
*
|
||||
* @constructor
|
||||
* @param {boolean} [aOptions.defaults=true] whether to load default application recipes.
|
||||
* @param {String} [aOptions.defaults=null] the URI to load the recipes from.
|
||||
* If it's null, nothing is loaded.
|
||||
*
|
||||
*/
|
||||
function LoginRecipesParent(aOptions = { defaults: true }) {
|
||||
function LoginRecipesParent(aOptions = { defaults: null }) {
|
||||
if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
|
||||
throw new Error("LoginRecipesParent should only be used from the main process");
|
||||
}
|
||||
|
@ -88,10 +91,29 @@ LoginRecipesParent.prototype = {
|
|||
this._recipesByHost = new Map();
|
||||
|
||||
if (this._defaults) {
|
||||
// XXX: Bug 1134850 will handle reading recipes from a file.
|
||||
this.initializationPromise = this.load(DEFAULT_RECIPES).then(resolve => {
|
||||
let channel = NetUtil.newChannel({uri: NetUtil.newURI(this._defaults, "UTF-8"),
|
||||
loadUsingSystemPrincipal: true});
|
||||
channel.contentType = "application/json";
|
||||
|
||||
try {
|
||||
this.initializationPromise = new Promise(function(resolve) {
|
||||
NetUtil.asyncFetch(channel, function (stream, result) {
|
||||
if (!Components.isSuccessCode(result)) {
|
||||
throw new Error("Error fetching recipe file:" + result);
|
||||
return;
|
||||
}
|
||||
let count = stream.available();
|
||||
let data = NetUtil.readInputStreamToString(stream, count, { charset: "UTF-8" });
|
||||
resolve(JSON.parse(data));
|
||||
});
|
||||
}).then(recipes => {
|
||||
return this.load(recipes);
|
||||
}).then(resolve => {
|
||||
return this;
|
||||
});
|
||||
} catch (e) {
|
||||
throw new Error("Error reading recipe file:" + e);
|
||||
}
|
||||
} else {
|
||||
this.initializationPromise = Promise.resolve(this);
|
||||
}
|
||||
|
@ -237,23 +259,3 @@ let LoginRecipesContent = {
|
|||
return field;
|
||||
},
|
||||
};
|
||||
|
||||
const DEFAULT_RECIPES = {
|
||||
"siteRecipes": [
|
||||
{
|
||||
"description": "okta uses a hidden password field to disable filling",
|
||||
"hosts": ["mozilla.okta.com"],
|
||||
"passwordSelector": "#pass-signin"
|
||||
},
|
||||
{
|
||||
"description": "anthem uses a hidden password and username field to disable filling",
|
||||
"hosts": ["www.anthem.com"],
|
||||
"passwordSelector": "#LoginContent_txtLoginPass"
|
||||
},
|
||||
{
|
||||
"description": "An ephemeral password-shim field is incorrectly selected as the username field.",
|
||||
"hosts": ["www.discover.com"],
|
||||
"usernameSelector": "#login-account"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"siteRecipes": [
|
||||
{
|
||||
"description": "okta uses a hidden password field to disable filling",
|
||||
"hosts": ["mozilla.okta.com"],
|
||||
"passwordSelector": "#pass-signin"
|
||||
},
|
||||
{
|
||||
"description": "anthem uses a hidden password and username field to disable filling",
|
||||
"hosts": ["www.anthem.com"],
|
||||
"passwordSelector": "#LoginContent_txtLoginPass"
|
||||
},
|
||||
{
|
||||
"description": "An ephemeral password-shim field is incorrectly selected as the username field.",
|
||||
"hosts": ["www.discover.com"],
|
||||
"usernameSelector": "#login-account"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -10,3 +10,4 @@ toolkit.jar:
|
|||
* content/passwordmgr/passwordManagerExceptions.js (content/passwordManagerExceptions.js)
|
||||
content/passwordmgr/passwordManagerExceptions.xul (content/passwordManagerExceptions.xul)
|
||||
content/passwordmgr/passwordManagerCommon.js (content/passwordManagerCommon.js)
|
||||
content/passwordmgr/recipes.json (content/recipes.json)
|
||||
|
|