Merge mozilla-central to mozilla-inbound

This commit is contained in:
Carsten "Tomcat" Book 2015-05-05 12:13:10 +02:00
Родитель 75f1ec8916 0d950f5c81
Коммит 172de49cec
58 изменённых файлов: 2092 добавлений и 374 удалений

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

@ -1132,6 +1132,10 @@ pref("dom.requestSync.enabled", true);
// Resample touch events on b2g
pref("gfx.touch.resample", true);
// Comma separated list of activity names that can only be provided by
// the system app in dev mode.
pref("dom.activities.developer_mode_only", "import-app");
// mulet apparently loads firefox.js as well as b2g.js, so we have to explicitly
// disable serviceworkers here to get them disabled in mulet.
pref("dom.serviceWorkers.enabled", false);

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

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="2eda36a4795012a5d1275b77ebb20ac377c8cd26">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="70077825aab2c7a79611befb40a5fe7e610d5443"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="e1773d6d1014c997be4b5c4233bba3ee073b8d7b"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
<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="70077825aab2c7a79611befb40a5fe7e610d5443"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="e1773d6d1014c997be4b5c4233bba3ee073b8d7b"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="9a9797062c6001d6346504161c51187a2968466b"/>

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

@ -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="70077825aab2c7a79611befb40a5fe7e610d5443"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="e1773d6d1014c997be4b5c4233bba3ee073b8d7b"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="0627790166dccd8dd370fa7d9f434ed9fc027fb4"/>

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

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="2eda36a4795012a5d1275b77ebb20ac377c8cd26">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="70077825aab2c7a79611befb40a5fe7e610d5443"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="e1773d6d1014c997be4b5c4233bba3ee073b8d7b"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
<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="70077825aab2c7a79611befb40a5fe7e610d5443"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="e1773d6d1014c997be4b5c4233bba3ee073b8d7b"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
<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="70077825aab2c7a79611befb40a5fe7e610d5443"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="e1773d6d1014c997be4b5c4233bba3ee073b8d7b"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="9a9797062c6001d6346504161c51187a2968466b"/>

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

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="2eda36a4795012a5d1275b77ebb20ac377c8cd26">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="70077825aab2c7a79611befb40a5fe7e610d5443"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="e1773d6d1014c997be4b5c4233bba3ee073b8d7b"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>

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

@ -17,7 +17,7 @@
</project>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="70077825aab2c7a79611befb40a5fe7e610d5443"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="e1773d6d1014c997be4b5c4233bba3ee073b8d7b"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="0627790166dccd8dd370fa7d9f434ed9fc027fb4"/>

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

@ -1,9 +1,9 @@
{
"git": {
"git_revision": "70077825aab2c7a79611befb40a5fe7e610d5443",
"git_revision": "e1773d6d1014c997be4b5c4233bba3ee073b8d7b",
"remote": "https://git.mozilla.org/releases/gaia.git",
"branch": ""
},
"revision": "536698903e2c4c12a3ff6c6d83f70278fab7311d",
"revision": "172680469094791e563268fa7d074fee9e0105ab",
"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="70077825aab2c7a79611befb40a5fe7e610d5443"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="e1773d6d1014c997be4b5c4233bba3ee073b8d7b"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="0627790166dccd8dd370fa7d9f434ed9fc027fb4"/>

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

@ -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="70077825aab2c7a79611befb40a5fe7e610d5443"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="e1773d6d1014c997be4b5c4233bba3ee073b8d7b"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>

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

@ -426,10 +426,8 @@ skip-if = buildapp == 'mulet' || e10s # Bug 921935 - focusmanager issues with e1
skip-if = e10s # Bug 1100700 - test relies on unload event firing on closed tabs, which it doesn't
[browser_urlHighlight.js]
[browser_urlbarAutoFillTrimURLs.js]
skip-if = e10s # Bug 1093941 - Waits indefinitely for onSearchComplete
[browser_urlbarCopying.js]
[browser_urlbarEnter.js]
skip-if = e10s # Bug 1093941 - used to cause obscure non-windows child process crashes on try
[browser_urlbarEnterAfterMouseOver.js]
skip-if = os == "linux" || e10s # Bug 1073339 - Investigate autocomplete test unreliability on Linux/e10s
[browser_urlbarRevert.js]

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

@ -25,10 +25,15 @@ add_task(function* () {
add_task(function* () {
info("Alt+Return keypress");
let tab = gBrowser.selectedTab = gBrowser.addTab(START_VALUE);
// due to bug 691608, we must wait for the load event, else isTabEmpty() will
// return true on e10s for this tab, so it will be reused even with altKey.
yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
gURLBar.focus();
EventUtils.synthesizeKey("VK_RETURN", {altKey: true});
yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
// wait for the new tab to appear.
yield BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabOpen");
// Check url bar and selected tab.
is(gURLBar.textValue, TEST_VALUE, "Urlbar should preserve the value on return keypress");

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

@ -112,6 +112,8 @@ let AnimationsController = {
"stopAnimationPlayerUpdates");
this.hasSetPlaybackRate = yield target.actorHasMethod("animationplayer",
"setPlaybackRate");
this.hasTargetNode = yield target.actorHasMethod("domwalker",
"getNodeFromActor");
if (this.destroyed) {
console.warn("Could not fully initialize the AnimationsController");

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

@ -6,6 +6,13 @@
"use strict";
const {
PlayerMetaDataHeader,
PlaybackRateSelector,
AnimationTargetNode,
createNode
} = require("devtools/animationinspector/components");
/**
* The main animations panel UI.
*/
@ -199,6 +206,9 @@ function PlayerWidget(player, containerEl) {
if (AnimationsController.hasSetPlaybackRate) {
this.rateComponent = new PlaybackRateSelector();
}
if (AnimationsController.hasTargetNode) {
this.targetNodeComponent = new AnimationTargetNode(gInspector);
}
}
PlayerWidget.prototype = {
@ -224,6 +234,9 @@ PlayerWidget.prototype = {
if (this.rateComponent) {
this.rateComponent.destroy();
}
if (this.targetNodeComponent) {
this.targetNodeComponent.destroy();
}
this.el.remove();
this.playPauseBtnEl = this.rewindBtnEl = this.fastForwardBtnEl = null;
@ -261,11 +274,17 @@ PlayerWidget.prototype = {
let state = this.player.state;
this.el = createNode({
parent: this.containerEl,
attributes: {
"class": "player-widget " + state.playState
}
});
if (this.targetNodeComponent) {
this.targetNodeComponent.init(this.el);
this.targetNodeComponent.render(this.player);
}
this.metaDataComponent.init(this.el);
this.metaDataComponent.render(state);
@ -359,8 +378,6 @@ PlayerWidget.prototype = {
}
});
this.containerEl.appendChild(this.el);
// Show the initial time.
this.displayTime(state.currentTime);
},
@ -571,261 +588,3 @@ PlayerWidget.prototype = {
}
}
};
/**
* UI component responsible for displaying and updating the player meta-data:
* name, duration, iterations, delay.
* The parent UI component for this should drive its updates by calling
* render(state) whenever it wants the component to update.
*/
function PlayerMetaDataHeader() {
// Store the various state pieces we need to only refresh the UI when things
// change.
this.state = {};
}
PlayerMetaDataHeader.prototype = {
init: function(containerEl) {
// The main title element.
this.el = createNode({
parent: containerEl,
attributes: {
"class": "animation-title"
}
});
// Animation name.
this.nameLabel = createNode({
parent: this.el,
nodeType: "span"
});
this.nameValue = createNode({
parent: this.el,
nodeType: "strong",
attributes: {
"style": "display:none;"
}
});
// Animation duration, delay and iteration container.
let metaData = createNode({
parent: this.el,
nodeType: "span",
attributes: {
"class": "meta-data"
}
});
// Animation duration.
this.durationLabel = createNode({
parent: metaData,
nodeType: "span"
});
this.durationLabel.textContent = L10N.getStr("player.animationDurationLabel");
this.durationValue = createNode({
parent: metaData,
nodeType: "strong"
});
// Animation delay (hidden by default since there may not be a delay).
this.delayLabel = createNode({
parent: metaData,
nodeType: "span",
attributes: {
"style": "display:none;"
}
});
this.delayLabel.textContent = L10N.getStr("player.animationDelayLabel");
this.delayValue = createNode({
parent: metaData,
nodeType: "strong"
});
// Animation iteration count (also hidden by default since we don't display
// single iterations).
this.iterationLabel = createNode({
parent: metaData,
nodeType: "span",
attributes: {
"style": "display:none;"
}
});
this.iterationLabel.textContent = L10N.getStr("player.animationIterationCountLabel");
this.iterationValue = createNode({
parent: metaData,
nodeType: "strong",
attributes: {
"style": "display:none;"
}
});
},
destroy: function() {
this.state = null;
this.el.remove();
this.el = null;
this.nameLabel = this.nameValue = null;
this.durationLabel = this.durationValue = null;
this.delayLabel = this.delayValue = null;
this.iterationLabel = this.iterationValue = null;
},
render: function(state) {
// Update the name if needed.
if (state.name !== this.state.name) {
if (state.name) {
// Animations (and transitions since bug 1122414) have names.
this.nameLabel.textContent = L10N.getStr("player.animationNameLabel");
this.nameValue.style.display = "inline";
this.nameValue.textContent = state.name;
} else {
// With older actors, Css transitions don't have names.
this.nameLabel.textContent = L10N.getStr("player.transitionNameLabel");
this.nameValue.style.display = "none";
}
}
// update the duration value if needed.
if (state.duration !== this.state.duration) {
this.durationValue.textContent = L10N.getFormatStr("player.timeLabel",
L10N.numberWithDecimals(state.duration / 1000, 2));
}
// Update the delay if needed.
if (state.delay !== this.state.delay) {
if (state.delay) {
this.delayLabel.style.display = "inline";
this.delayValue.style.display = "inline";
this.delayValue.textContent = L10N.getFormatStr("player.timeLabel",
L10N.numberWithDecimals(state.delay / 1000, 2));
} else {
// Hide the delay elements if there is no delay defined.
this.delayLabel.style.display = "none";
this.delayValue.style.display = "none";
}
}
// Update the iterationCount if needed.
if (state.iterationCount !== this.state.iterationCount) {
if (state.iterationCount !== 1) {
this.iterationLabel.style.display = "inline";
this.iterationValue.style.display = "inline";
let count = state.iterationCount ||
L10N.getStr("player.infiniteIterationCount");
this.iterationValue.innerHTML = count;
} else {
// Hide the iteration elements if iteration is 1.
this.iterationLabel.style.display = "none";
this.iterationValue.style.display = "none";
}
}
this.state = state;
}
};
/**
* UI component responsible for displaying the playback rate drop-down in each
* player widget, updating it when the state changes, and emitting events when
* the user selects a new value.
* The parent UI component for this should drive its updates by calling
* render(state) whenever it wants the component to update.
*/
function PlaybackRateSelector() {
this.currentRate = null;
this.onSelectionChanged = this.onSelectionChanged.bind(this);
EventEmitter.decorate(this);
}
PlaybackRateSelector.prototype = {
PRESETS: [.1, .5, 1, 2, 5, 10],
init: function(containerEl) {
// This component is simple enough that we can re-create the markup every
// time it's rendered. So here we only store the parentEl.
this.parentEl = containerEl;
},
destroy: function() {
this.removeSelect();
this.parentEl = this.el = null;
},
removeSelect: function() {
if (this.el) {
this.el.removeEventListener("change", this.onSelectionChanged);
this.el.remove();
}
},
/**
* Get the ordered list of presets, including the current playbackRate if
* different from the existing presets.
*/
getCurrentPresets: function({playbackRate}) {
return [...new Set([...this.PRESETS, playbackRate])].sort((a,b) => a > b);
},
render: function(state) {
if (state.playbackRate === this.currentRate) {
return;
}
this.removeSelect();
this.el = createNode({
parent: this.parentEl,
nodeType: "select",
attributes: {
"class": "rate devtools-button"
}
});
for (let preset of this.getCurrentPresets(state)) {
let option = createNode({
parent: this.el,
nodeType: "option",
attributes: {
value: preset,
}
});
option.textContent = L10N.getFormatStr("player.playbackRateLabel", preset);
if (preset === state.playbackRate) {
option.setAttribute("selected", "");
}
}
this.el.addEventListener("change", this.onSelectionChanged);
this.currentRate = state.playbackRate;
},
onSelectionChanged: function(e) {
this.emit("rate-changed", parseFloat(this.el.value));
}
};
/**
* DOM node creation helper function.
* @param {Object} Options to customize the node to be created.
* @return {DOMNode} The newly created node.
*/
function createNode(options) {
let type = options.nodeType || "div";
let node = document.createElement(type);
for (let name in options.attributes || {}) {
let value = options.attributes[name];
node.setAttribute(name, value);
}
if (options.parent) {
options.parent.appendChild(node);
}
return node;
}

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

@ -0,0 +1,502 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
// Set of reusable UI components for the animation-inspector UI.
// All components in this module share a common API:
// 1. construct the component:
// let c = new ComponentName();
// 2. initialize the markup of the component in a given parent node:
// c.init(containerElement);
// 3. render the component, passing in some sort of state:
// This may be called over and over again when the state changes, to update
// the component output.
// c.render(state);
// 4. destroy the component:
// c.destroy();
const {Cu} = require('chrome');
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
const STRINGS_URI = "chrome://browser/locale/devtools/animationinspector.properties";
const L10N = new ViewHelpers.L10N(STRINGS_URI);
/**
* UI component responsible for displaying and updating the player meta-data:
* name, duration, iterations, delay.
* The parent UI component for this should drive its updates by calling
* render(state) whenever it wants the component to update.
*/
function PlayerMetaDataHeader() {
// Store the various state pieces we need to only refresh the UI when things
// change.
this.state = {};
}
exports.PlayerMetaDataHeader = PlayerMetaDataHeader;
PlayerMetaDataHeader.prototype = {
init: function(containerEl) {
// The main title element.
this.el = createNode({
parent: containerEl,
attributes: {
"class": "animation-title"
}
});
// Animation name.
this.nameLabel = createNode({
parent: this.el,
nodeType: "span"
});
this.nameValue = createNode({
parent: this.el,
nodeType: "strong",
attributes: {
"style": "display:none;"
}
});
// Animation duration, delay and iteration container.
let metaData = createNode({
parent: this.el,
nodeType: "span",
attributes: {
"class": "meta-data"
}
});
// Animation duration.
this.durationLabel = createNode({
parent: metaData,
nodeType: "span"
});
this.durationLabel.textContent = L10N.getStr("player.animationDurationLabel");
this.durationValue = createNode({
parent: metaData,
nodeType: "strong"
});
// Animation delay (hidden by default since there may not be a delay).
this.delayLabel = createNode({
parent: metaData,
nodeType: "span",
attributes: {
"style": "display:none;"
}
});
this.delayLabel.textContent = L10N.getStr("player.animationDelayLabel");
this.delayValue = createNode({
parent: metaData,
nodeType: "strong"
});
// Animation iteration count (also hidden by default since we don't display
// single iterations).
this.iterationLabel = createNode({
parent: metaData,
nodeType: "span",
attributes: {
"style": "display:none;"
}
});
this.iterationLabel.textContent = L10N.getStr("player.animationIterationCountLabel");
this.iterationValue = createNode({
parent: metaData,
nodeType: "strong",
attributes: {
"style": "display:none;"
}
});
},
destroy: function() {
this.state = null;
this.el.remove();
this.el = null;
this.nameLabel = this.nameValue = null;
this.durationLabel = this.durationValue = null;
this.delayLabel = this.delayValue = null;
this.iterationLabel = this.iterationValue = null;
},
render: function(state) {
// Update the name if needed.
if (state.name !== this.state.name) {
if (state.name) {
// Animations (and transitions since bug 1122414) have names.
this.nameLabel.textContent = L10N.getStr("player.animationNameLabel");
this.nameValue.style.display = "inline";
this.nameValue.textContent = state.name;
} else {
// With older actors, Css transitions don't have names.
this.nameLabel.textContent = L10N.getStr("player.transitionNameLabel");
this.nameValue.style.display = "none";
}
}
// update the duration value if needed.
if (state.duration !== this.state.duration) {
this.durationValue.textContent = L10N.getFormatStr("player.timeLabel",
L10N.numberWithDecimals(state.duration / 1000, 2));
}
// Update the delay if needed.
if (state.delay !== this.state.delay) {
if (state.delay) {
this.delayLabel.style.display = "inline";
this.delayValue.style.display = "inline";
this.delayValue.textContent = L10N.getFormatStr("player.timeLabel",
L10N.numberWithDecimals(state.delay / 1000, 2));
} else {
// Hide the delay elements if there is no delay defined.
this.delayLabel.style.display = "none";
this.delayValue.style.display = "none";
}
}
// Update the iterationCount if needed.
if (state.iterationCount !== this.state.iterationCount) {
if (state.iterationCount !== 1) {
this.iterationLabel.style.display = "inline";
this.iterationValue.style.display = "inline";
let count = state.iterationCount ||
L10N.getStr("player.infiniteIterationCount");
this.iterationValue.innerHTML = count;
} else {
// Hide the iteration elements if iteration is 1.
this.iterationLabel.style.display = "none";
this.iterationValue.style.display = "none";
}
}
this.state = state;
}
};
/**
* UI component responsible for displaying the playback rate drop-down in each
* player widget, updating it when the state changes, and emitting events when
* the user selects a new value.
* The parent UI component for this should drive its updates by calling
* render(state) whenever it wants the component to update.
*/
function PlaybackRateSelector() {
this.currentRate = null;
this.onSelectionChanged = this.onSelectionChanged.bind(this);
EventEmitter.decorate(this);
}
exports.PlaybackRateSelector = PlaybackRateSelector;
PlaybackRateSelector.prototype = {
PRESETS: [.1, .5, 1, 2, 5, 10],
init: function(containerEl) {
// This component is simple enough that we can re-create the markup every
// time it's rendered. So here we only store the parentEl.
this.parentEl = containerEl;
},
destroy: function() {
this.removeSelect();
this.parentEl = this.el = null;
},
removeSelect: function() {
if (this.el) {
this.el.removeEventListener("change", this.onSelectionChanged);
this.el.remove();
}
},
/**
* Get the ordered list of presets, including the current playbackRate if
* different from the existing presets.
*/
getCurrentPresets: function({playbackRate}) {
return [...new Set([...this.PRESETS, playbackRate])].sort((a,b) => a > b);
},
render: function(state) {
if (state.playbackRate === this.currentRate) {
return;
}
this.removeSelect();
this.el = createNode({
parent: this.parentEl,
nodeType: "select",
attributes: {
"class": "rate devtools-button"
}
});
for (let preset of this.getCurrentPresets(state)) {
let option = createNode({
parent: this.el,
nodeType: "option",
attributes: {
value: preset,
}
});
option.textContent = L10N.getFormatStr("player.playbackRateLabel", preset);
if (preset === state.playbackRate) {
option.setAttribute("selected", "");
}
}
this.el.addEventListener("change", this.onSelectionChanged);
this.currentRate = state.playbackRate;
},
onSelectionChanged: function(e) {
this.emit("rate-changed", parseFloat(this.el.value));
}
};
/**
* UI component responsible for displaying a preview of the target dom node of
* a given animation.
* @param {InspectorPanel} inspector Requires a reference to the inspector-panel
* to highlight and select the node, as well as refresh it when there are
* mutations.
*/
function AnimationTargetNode(inspector) {
this.inspector = inspector;
this.onPreviewMouseOver = this.onPreviewMouseOver.bind(this);
this.onPreviewMouseOut = this.onPreviewMouseOut.bind(this);
this.onSelectNodeClick = this.onSelectNodeClick.bind(this);
this.onMarkupMutations = this.onMarkupMutations.bind(this);
EventEmitter.decorate(this);
}
exports.AnimationTargetNode = AnimationTargetNode;
AnimationTargetNode.prototype = {
init: function(containerEl) {
let document = containerEl.ownerDocument;
// Init the markup for displaying the target node.
this.el = createNode({
parent: containerEl,
attributes: {
"class": "animation-target"
}
});
// Icon to select the node in the inspector.
this.selectNodeEl = createNode({
parent: this.el,
nodeType: "span",
attributes: {
"class": "node-selector"
}
});
// Wrapper used for mouseover/out event handling.
this.previewEl = createNode({
parent: this.el,
nodeType: "span"
});
this.previewEl.appendChild(document.createTextNode("<"));
// Tag name.
this.tagNameEl = createNode({
parent: this.previewEl,
nodeType: "span",
attributes: {
"class": "tag-name theme-fg-color3"
}
});
// Id attribute container.
this.idEl = createNode({
parent: this.previewEl,
nodeType: "span"
});
createNode({
parent: this.idEl,
nodeType: "span",
attributes: {
"class": "attribute-name theme-fg-color2"
}
}).textContent = "id";
this.idEl.appendChild(document.createTextNode("=\""));
createNode({
parent: this.idEl,
nodeType: "span",
attributes: {
"class": "attribute-value theme-fg-color6"
}
});
this.idEl.appendChild(document.createTextNode("\""));
// Class attribute container.
this.classEl = createNode({
parent: this.previewEl,
nodeType: "span"
});
createNode({
parent: this.classEl,
nodeType: "span",
attributes: {
"class": "attribute-name theme-fg-color2"
}
}).textContent = "class";
this.classEl.appendChild(document.createTextNode("=\""));
createNode({
parent: this.classEl,
nodeType: "span",
attributes: {
"class": "attribute-value theme-fg-color6"
}
});
this.classEl.appendChild(document.createTextNode("\""));
this.previewEl.appendChild(document.createTextNode(">"));
// Init events for highlighting and selecting the node.
this.previewEl.addEventListener("mouseover", this.onPreviewMouseOver);
this.previewEl.addEventListener("mouseout", this.onPreviewMouseOut);
this.selectNodeEl.addEventListener("click", this.onSelectNodeClick);
// Start to listen for markupmutation events.
this.inspector.on("markupmutation", this.onMarkupMutations);
},
destroy: function() {
this.inspector.off("markupmutation", this.onMarkupMutations);
this.previewEl.removeEventListener("mouseover", this.onPreviewMouseOver);
this.previewEl.removeEventListener("mouseout", this.onPreviewMouseOut);
this.selectNodeEl.removeEventListener("click", this.onSelectNodeClick);
this.el.remove();
this.el = this.tagNameEl = this.idEl = this.classEl = null;
this.selectNodeEl = this.previewEl = null;
this.nodeFront = this.inspector = this.playerFront = null;
},
onPreviewMouseOver: function() {
if (!this.nodeFront) {
return;
}
this.inspector.toolbox.highlighterUtils.highlightNodeFront(this.nodeFront);
},
onPreviewMouseOut: function() {
this.inspector.toolbox.highlighterUtils.unhighlight();
},
onSelectNodeClick: function() {
if (!this.nodeFront) {
return;
}
this.inspector.selection.setNodeFront(this.nodeFront, "animationinspector");
},
onMarkupMutations: function(e, mutations) {
if (!this.nodeFront || !this.playerFront) {
return;
}
for (let {target} of mutations) {
if (target === this.nodeFront) {
// Re-render with the same nodeFront to update the output.
this.render(this.playerFront);
break;
}
}
},
render: function(playerFront) {
this.playerFront = playerFront;
this.inspector.walker.getNodeFromActor(playerFront.actorID, ["node"]).then(nodeFront => {
// We might have been destroyed in the meantime, or the node might not be found.
if (!this.el || !nodeFront) {
return;
}
this.nodeFront = nodeFront;
let {tagName, attributes} = nodeFront;
this.tagNameEl.textContent = tagName.toLowerCase();
let idIndex = attributes.findIndex(({name}) => name === "id");
if (idIndex > -1 && attributes[idIndex].value) {
this.idEl.querySelector(".attribute-value").textContent =
attributes[idIndex].value;
this.idEl.style.display = "inline";
} else {
this.idEl.style.display = "none";
}
let classIndex = attributes.findIndex(({name}) => name === "class");
if (classIndex > -1 && attributes[classIndex].value) {
this.classEl.querySelector(".attribute-value").textContent =
attributes[classIndex].value;
this.classEl.style.display = "inline";
} else {
this.classEl.style.display = "none";
}
this.emit("target-retrieved");
}, e => {
this.nodeFront = null;
if (!this.el) {
console.warn("Cound't retrieve the animation target node, widget destroyed");
} else {
console.error(e);
}
});
}
};
/**
* DOM node creation helper function.
* @param {Object} Options to customize the node to be created.
* - nodeType {String} Optional, defaults to "div",
* - attributes {Object} Optional attributes object like
* {attrName1:value1, attrName2: value2, ...}
* - parent {DOMNode} Mandatory node to append the newly created node to.
* @return {DOMNode} The newly created node.
*/
function createNode(options) {
if (!options.parent) {
throw new Error("Missing parent DOMNode to create new node");
}
let type = options.nodeType || "div";
let node = options.parent.ownerDocument.createElement(type);
for (let name in options.attributes || {}) {
let value = options.attributes[name];
node.setAttribute(name, value);
}
options.parent.appendChild(node);
return node;
}
exports.createNode = createNode;

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

@ -5,3 +5,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
EXTRA_JS_MODULES.devtools.animationinspector += [
'components.js',
]

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

@ -20,6 +20,7 @@ support-files =
[browser_animation_playerWidgets_have_control_buttons.js]
[browser_animation_playerWidgets_meta_data.js]
[browser_animation_playerWidgets_state_after_pause.js]
[browser_animation_playerWidgets_target_nodes.js]
[browser_animation_rate_select_shows_presets.js]
[browser_animation_refresh_on_added_animation.js]
[browser_animation_refresh_on_removed_animation.js]
@ -28,6 +29,7 @@ support-files =
[browser_animation_setting_currentTime_works_and_pauses.js]
[browser_animation_setting_playbackRate_works.js]
[browser_animation_shows_player_on_valid_node.js]
[browser_animation_target_highlight_select.js]
[browser_animation_timeline_animates.js]
[browser_animation_timeline_is_enabled.js]
[browser_animation_timeline_waits_for_delay.js]

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

@ -0,0 +1,31 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that player widgets display information about target nodes
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {inspector, panel} = yield openAnimationInspector();
info("Select the simple animated node");
yield selectNode(".animated", inspector);
let widget = panel.playerWidgets[0];
// Make sure to wait for the target-retrieved event if the nodeFront hasn't
// yet been retrieved by the TargetNodeComponent.
if (!widget.targetNodeComponent.nodeFront) {
yield widget.targetNodeComponent.once("target-retrieved");
}
let targetEl = widget.el.querySelector(".animation-target");
ok(targetEl, "The player widget has a target element");
is(targetEl.textContent, "<divid=\"\"class=\"ball animated\">",
"The target element's content is correct");
let selectorEl = targetEl.querySelector(".node-selector");
ok(selectorEl, "The icon to select the target element in the inspector exists");
});

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

@ -0,0 +1,62 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that the DOM element targets displayed in animation player widgets can
// be used to highlight elements in the DOM and select them in the inspector.
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {toolbox, inspector, panel} = yield openAnimationInspector();
info("Select the simple animated node");
yield selectNode(".animated", inspector);
// Make sure to wait for the target-retrieved event if the nodeFront hasn't
// yet been retrieved by the TargetNodeComponent.
let targetNodeComponent = panel.playerWidgets[0].targetNodeComponent;
if (!targetNodeComponent.nodeFront) {
yield targetNodeComponent.once("target-retrieved");
}
info("Retrieve the part of the widget that highlights the node on hover");
let highlightingEl = targetNodeComponent.previewEl;
info("Listen to node-highlight event and mouse over the widget");
let onHighlight = toolbox.once("node-highlight");
EventUtils.synthesizeMouse(highlightingEl, 10, 5, {type: "mouseover"},
highlightingEl.ownerDocument.defaultView);
let nodeFront = yield onHighlight;
ok(true, "The node-highlight event was fired");
is(targetNodeComponent.nodeFront, nodeFront,
"The highlighted node is the one stored on the animation widget");
is(nodeFront.tagName, "DIV", "The highlighted node has the correct tagName");
is(nodeFront.attributes[0].name, "class", "The highlighted node has the correct attributes");
is(nodeFront.attributes[0].value, "ball animated", "The highlighted node has the correct class");
info("Select the body node in order to have the list of all animations");
yield selectNode("body", inspector);
// Make sure to wait for the target-retrieved event if the nodeFront hasn't
// yet been retrieved by the TargetNodeComponent.
targetNodeComponent = panel.playerWidgets[0].targetNodeComponent;
if (!targetNodeComponent.nodeFront) {
yield targetNodeComponent.once("target-retrieved");
}
info("Click on the first animation widget's selector icon and wait for the selection to change");
let onSelection = inspector.selection.once("new-node-front");
let onPanelUpdated = panel.once(panel.UI_UPDATED_EVENT);
let selectIconEl = targetNodeComponent.selectNodeEl;
EventUtils.sendMouseEvent({type: "click"}, selectIconEl,
selectIconEl.ownerDocument.defaultView);
yield onSelection;
is(inspector.selection.nodeFront, targetNodeComponent.nodeFront,
"The selected node is the one stored on the animation widget");
yield onPanelUpdated;
});

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

@ -55,6 +55,7 @@ EXTRA_JS_MODULES.devtools.shared += [
'node-attribute-parser.js',
'observable-object.js',
'options-view.js',
'poller.js',
'source-utils.js',
'telemetry.js',
'theme-switching.js',

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

@ -0,0 +1,115 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
loader.lazyRequireGetter(this, "timers",
"resource://gre/modules/Timer.jsm");
loader.lazyRequireGetter(this, "defer",
"sdk/core/promise", true);
/**
* @constructor Poller
* Takes a function that is to be called on an interval,
* and can be turned on and off via methods to execute `fn` on the interval
* specified during `on`. If `fn` returns a promise, the polling waits for
* that promise to resolve before waiting the interval to call again.
*
* Specify the `wait` duration between polling here, and optionally
* an `immediate` boolean, indicating whether the function should be called
* immediately when toggling on.
*
* @param {function} fn
* @param {number} wait
* @param {boolean?} immediate
*/
function Poller (fn, wait, immediate) {
this._fn = fn;
this._wait = wait;
this._immediate = immediate;
this._poll = this._poll.bind(this);
this._preparePoll = this._preparePoll.bind(this);
}
exports.Poller = Poller;
/**
* Returns a boolean indicating whether or not poller
* is polling.
*
* @return {boolean}
*/
Poller.prototype.isPolling = function pollerIsPolling () {
return !!this._timer;
};
/**
* Turns polling on.
*
* @return {Poller}
*/
Poller.prototype.on = function pollerOn () {
if (this._destroyed) {
throw Error("Poller cannot be turned on after destruction.");
}
if (this._timer) {
this.off();
}
this._immediate ? this._poll() : this._preparePoll();
return this;
};
/**
* Turns off polling. Returns a promise that resolves when
* the last outstanding `fn` call finishes if it's an async function.
*
* @return {Promise}
*/
Poller.prototype.off = function pollerOff () {
let { resolve, promise } = defer();
if (this._timer) {
timers.clearTimeout(this._timer);
this._timer = null;
}
// Settle an inflight poll call before resolving
// if using a promise-backed poll function
if (this._inflight) {
this._inflight.then(resolve);
} else {
resolve();
}
return promise;
};
/**
* Turns off polling and removes the reference to the poller function.
* Resolves when the last outstanding `fn` call finishes if it's an async function.
*/
Poller.prototype.destroy = function pollerDestroy () {
return this.off().then(() => {
this._destroyed = true;
this._fn = null
});
};
Poller.prototype._preparePoll = function pollerPrepare () {
this._timer = timers.setTimeout(this._poll, this._wait);
};
Poller.prototype._poll = function pollerPoll () {
let response = this._fn();
if (response && typeof response.then === "function") {
// Store the most recent in-flight polling
// call so we can clean it up when disabling
this._inflight = response;
response.then(() => {
// Only queue up the next call if poller was not turned off
// while this async poll call was in flight.
if (this._timer) {
this._preparePoll();
}
});
} else {
this._preparePoll();
}
};

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

@ -84,6 +84,7 @@ skip-if = e10s # Layouthelpers test should not run in a content page.
[browser_options-view-01.js]
[browser_outputparser.js]
skip-if = e10s # Test intermittently fails with e10s. Bug 1124162.
[browser_poller.js]
[browser_prefs-01.js]
[browser_prefs-02.js]
[browser_require_basic.js]

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

@ -0,0 +1,131 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Tests the Poller class.
const { Poller } = devtools.require("devtools/shared/poller");
add_task(function* () {
let count1 = 0, count2 = 0, count3 = 0;
let poller1 = new Poller(function () {
count1++;
}, 1000000000, true);
let poller2 = new Poller(function () {
count2++;
}, 10);
let poller3 = new Poller(function () {
count3++;
}, 1000000000);
poller2.on();
ok(!poller1.isPolling(), "isPolling() returns false for an off poller");
ok(poller2.isPolling(), "isPolling() returns true for an on poller");
yield waitUntil(() => count2 > 10);
ok(count2 > 10, "poller that was turned on polled several times");
ok(count1 === 0, "poller that was never turned on never polled");
yield poller2.off();
let currentCount2 = count2;
poller1.on(); // Really high poll time!
poller3.on(); // Really high poll time!
yield waitUntil(() => count1 === 1);
ok(true, "Poller calls fn immediately when `immediate` is true");
ok(count3 === 0, "Poller does not call fn immediately when `immediate` is not set");
ok(count2 === currentCount2, "a turned off poller does not continue to poll");
yield poller2.off();
yield poller2.off();
yield poller2.off();
ok(true, "Poller.prototype.off() is idempotent");
// This should still have not polled a second time
is(count1, 1, "wait time works");
ok(poller1.isPolling(), "isPolling() returns true for an on poller");
ok(!poller2.isPolling(), "isPolling() returns false for an off poller");
});
add_task(function *() {
let count = -1;
// Create a poller that returns a promise.
// The promise is resolved asynchronously after adding 9 to the count, ensuring
// that on every poll, we have a multiple of 10.
let asyncPoller = new Poller(function () {
count++;
ok(!(count%10), `Async poller called with a multiple of 10: ${count}`);
return new Promise(function (resolve, reject) {
let add9 = 9;
let interval = setInterval(() => {
if (add9--) {
count++;
} else {
clearInterval(interval);
resolve();
}
}, 10);
});
});
asyncPoller.on(1);
yield waitUntil(() => count > 50);
yield asyncPoller.off();
});
add_task(function *() {
// Create a poller that returns a promise. This poll call
// is called immediately, and then subsequently turned off.
// The call to `off` should not resolve until the inflight call
// finishes.
let inflightFinished = null;
let pollCalls = 0;
let asyncPoller = new Poller(function () {
pollCalls++;
return new Promise(function (resolve, reject) {
setTimeout(() => {
inflightFinished = true;
resolve();
}, 1000);
});
}, 1, true);
asyncPoller.on();
yield asyncPoller.off();
ok(inflightFinished, "off() method does not resolve until remaining inflight poll calls finish");
is(pollCalls, 1, "should only be one poll call to occur before turning off polling");
});
add_task(function *() {
// Create a poller that returns a promise. This poll call
// is called immediately, and then subsequently turned off.
// The call to `off` should not resolve until the inflight call
// finishes.
let inflightFinished = null;
let pollCalls = 0;
let asyncPoller = new Poller(function () {
pollCalls++;
return new Promise(function (resolve, reject) {
setTimeout(() => {
inflightFinished = true;
resolve();
}, 1000);
});
}, 1, true);
asyncPoller.on();
yield asyncPoller.destroy();
ok(inflightFinished, "destroy() method does not resolve until remaining inflight poll calls finish");
is(pollCalls, 1, "should only be one poll call to occur before destroying polling");
try {
asyncPoller.on();
ok(false, "Calling on() after destruction should throw");
} catch (e) {
ok(true, "Calling on() after destruction should throw");
}
});

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

@ -242,3 +242,24 @@ function* openAndCloseToolbox(nbOfTimes, usageTime, toolId) {
yield gDevTools.closeToolbox(target);
}
}
/**
* Waits until a predicate returns true.
*
* @param function predicate
* Invoked once in a while until it returns true.
* @param number interval [optional]
* How often the predicate is invoked, in milliseconds.
*/
function waitUntil(predicate, interval = 10) {
if (predicate()) {
return Promise.resolve(true);
}
return new Promise(resolve => {
setTimeout(function() {
waitUntil(predicate).then(() => resolve(true));
}, interval);
});
}
// EventUtils just doesn't work!

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

@ -152,6 +152,7 @@ function CssHtmlTree(aStyleInspector, aPageStyle)
this._onCopy = this._onCopy.bind(this);
this._onCopyColor = this._onCopyColor.bind(this);
this._onFilterStyles = this._onFilterStyles.bind(this);
this._onFilterKeyPress = this._onFilterKeyPress.bind(this);
this._onClearSearch = this._onClearSearch.bind(this);
this._onIncludeBrowserStyles = this._onIncludeBrowserStyles.bind(this);
this._onFilterTextboxContextMenu = this._onFilterTextboxContextMenu.bind(this);
@ -168,6 +169,7 @@ function CssHtmlTree(aStyleInspector, aPageStyle)
this.element.addEventListener("copy", this._onCopy);
this.element.addEventListener("contextmenu", this._onContextMenu);
this.searchField.addEventListener("input", this._onFilterStyles);
this.searchField.addEventListener("keypress", this._onFilterKeyPress);
this.searchField.addEventListener("contextmenu", this._onFilterTextboxContextMenu);
this.searchClearButton.addEventListener("click", this._onClearSearch);
this.includeBrowserStylesCheckbox.addEventListener("command",
@ -544,6 +546,46 @@ CssHtmlTree.prototype = {
}, filterTimeout);
},
/**
* Handle the search box's keypress event. If the escape key is pressed,
* clear the search box field.
*/
_onFilterKeyPress: function(aEvent) {
if (aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_ESCAPE &&
this._onClearSearch()) {
aEvent.preventDefault();
aEvent.stopPropagation();
}
},
/**
* Context menu handler for filter style search box.
*/
_onFilterTextboxContextMenu: function(event) {
try {
this.styleDocument.defaultView.focus();
let contextmenu = this.inspector.toolbox.textboxContextMenuPopup;
contextmenu.openPopupAtScreen(event.screenX, event.screenY, true);
} catch(e) {
console.error(e);
}
},
/**
* Called when the user clicks on the clear button in the filter style search
* box. Returns true if the search box is cleared and false otherwise.
*/
_onClearSearch: function() {
if (this.searchField.value) {
this.searchField.value = "";
this.searchField.focus();
this._onFilterStyles();
return true;
}
return false;
},
/**
* The change event handler for the includeBrowserStyles checkbox.
*
@ -831,29 +873,6 @@ CssHtmlTree.prototype = {
Services.prefs.setBoolPref(PREF_ORIG_SOURCES, !isEnabled);
},
/**
* Context menu handler for filter style search box.
*/
_onFilterTextboxContextMenu: function(event) {
try {
this.styleDocument.defaultView.focus();
let contextmenu = this.inspector.toolbox.textboxContextMenuPopup;
contextmenu.openPopupAtScreen(event.screenX, event.screenY, true);
} catch(e) {
console.error(e);
}
},
/**
* Called when the user clicks on the clear button in the filter style search
* box.
*/
_onClearSearch: function() {
this.searchField.value = "";
this.searchField.focus();
this._onFilterStyles();
},
/**
* Destructor for CssHtmlTree.
*/
@ -906,6 +925,7 @@ CssHtmlTree.prototype = {
this.element.removeEventListener("copy", this._onCopy);
this.element.removeEventListener("contextmenu", this._onContextMenu);
this.searchField.removeEventListener("input", this._onFilterStyles);
this.searchField.removeEventListener("keypress", this._onFilterKeyPress);
this.searchField.removeEventListener("contextmenu", this._onFilterTextboxContextMenu);
this.searchClearButton.removeEventListener("click", this._onClearSearch);
this.includeBrowserStylesCheckbox.removeEventListener("command",

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

@ -1129,6 +1129,7 @@ function CssRuleView(aInspector, aDoc, aStore, aPageStyle) {
this._onToggleOrigSources = this._onToggleOrigSources.bind(this);
this._onShowMdnDocs = this._onShowMdnDocs.bind(this);
this._onFilterStyles = this._onFilterStyles.bind(this);
this._onFilterKeyPress = this._onFilterKeyPress.bind(this);
this._onClearSearch = this._onClearSearch.bind(this);
this._onFilterTextboxContextMenu = this._onFilterTextboxContextMenu.bind(this);
@ -1141,6 +1142,7 @@ function CssRuleView(aInspector, aDoc, aStore, aPageStyle) {
this.element.addEventListener("copy", this._onCopy);
this.element.addEventListener("contextmenu", this._onContextMenu);
this.searchField.addEventListener("input", this._onFilterStyles);
this.searchField.addEventListener("keypress", this._onFilterKeyPress);
this.searchField.addEventListener("contextmenu", this._onFilterTextboxContextMenu);
this.searchClearButton.addEventListener("click", this._onClearSearch);
@ -1647,6 +1649,18 @@ CssRuleView.prototype = {
}, filterTimeout);
},
/**
* Handle the search box's keypress event. If the escape key is pressed,
* clear the search box field.
*/
_onFilterKeyPress: function(event) {
if (event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_ESCAPE &&
this._onClearSearch()) {
event.preventDefault();
event.stopPropagation();
}
},
/**
* Context menu handler for filter style search box.
*/
@ -1662,12 +1676,17 @@ CssRuleView.prototype = {
/**
* Called when the user clicks on the clear button in the filter style search
* box.
* box. Returns true if the search box is cleared and false otherwise.
*/
_onClearSearch: function() {
this.searchField.value = "";
this.searchField.focus();
this._onFilterStyles();
if (this.searchField.value) {
this.searchField.value = "";
this.searchField.focus();
this._onFilterStyles();
return true;
}
return false;
},
destroy: function() {
@ -1721,6 +1740,7 @@ CssRuleView.prototype = {
this.element.removeEventListener("copy", this._onCopy);
this.element.removeEventListener("contextmenu", this._onContextMenu);
this.searchField.removeEventListener("input", this._onFilterStyles);
this.searchField.removeEventListener("keypress", this._onFilterKeyPress);
this.searchField.removeEventListener("contextmenu",
this._onFilterTextboxContextMenu);
this.searchClearButton.removeEventListener("click", this._onClearSearch);

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

@ -42,6 +42,7 @@ support-files =
[browser_computedview_search-filter.js]
[browser_computedview_search-filter_clear.js]
[browser_computedview_search-filter_context-menu.js]
[browser_computedview_search-filter_escape-keypress.js]
[browser_computedview_select-and-copy-styles.js]
[browser_computedview_style-editor-link.js]
[browser_ruleview_add-property-and-reselect.js]
@ -123,6 +124,7 @@ skip-if = e10s # Bug 1090340
[browser_ruleview_search-filter_10.js]
[browser_ruleview_search-filter_clear.js]
[browser_ruleview_search-filter_context-menu.js]
[browser_ruleview_search-filter_escape-keypress.js]
[browser_ruleview_select-and-copy-styles.js]
[browser_ruleview_selector-highlighter_01.js]
[browser_ruleview_selector-highlighter_02.js]

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

@ -0,0 +1,71 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that search filter escape keypress will clear the search field.
let TEST_URI = [
'<style type="text/css">',
' .matches {',
' color: #F00;',
' }',
'</style>',
'<span id="matches" class="matches">Some styled text</span>'
].join("\n");
add_task(function*() {
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
let {toolbox, inspector, view} = yield openComputedView();
yield selectNode("#matches", inspector);
yield testAddTextInFilter(inspector, view);
yield testEscapeKeypress(inspector, view);
});
function* testAddTextInFilter(inspector, computedView) {
info("Setting filter text to \"background-color\"");
let win = computedView.styleWindow;
let propertyViews = computedView.propertyViews;
let searchField = computedView.searchField;
let checkbox = computedView.includeBrowserStylesCheckbox;
info("Include browser styles");
checkbox.click();
yield inspector.once("computed-view-refreshed");
searchField.focus();
synthesizeKeys("background-color", win);
yield inspector.once("computed-view-refreshed");
info("Check that the correct properties are visible");
propertyViews.forEach((propView) => {
let name = propView.name;
is(propView.visible, name.indexOf("background-color") > -1,
"span " + name + " property visibility check");
});
}
function* testEscapeKeypress(inspector, computedView) {
info("Pressing the escape key on search filter");
let win = computedView.styleWindow;
let propertyViews = computedView.propertyViews;
let searchField = computedView.searchField;
let onRefreshed = inspector.once("computed-view-refreshed");
searchField.focus();
EventUtils.synthesizeKey("VK_ESCAPE", {}, win);
yield onRefreshed;
info("Check that the correct properties are visible");
ok(!searchField.value, "Search filter is cleared");
propertyViews.forEach((propView) => {
let name = propView.name;
is(propView.visible, true,
"span " + name + " property is visible");
});
}

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

@ -0,0 +1,66 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that the rule view search filter escape keypress will clear the search
// field.
let TEST_URI = [
'<style type="text/css">',
' #testid {',
' background-color: #00F;',
' }',
' .testclass {',
' width: 100%;',
' }',
'</style>',
'<div id="testid" class="testclass">Styled Node</div>'
].join("\n");
add_task(function*() {
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
let {toolbox, inspector, view} = yield openRuleView();
yield selectNode("#testid", inspector);
yield testAddTextInFilter(inspector, view);
yield testEscapeKeypress(inspector, view);
});
function* testAddTextInFilter(inspector, ruleView) {
info("Setting filter text to \"00F\"");
let win = ruleView.doc.defaultView;
let searchField = ruleView.searchField;
let onRuleViewFiltered = inspector.once("ruleview-filtered");
searchField.focus();
synthesizeKeys("00F", win);
yield onRuleViewFiltered;
info("Check that the correct rules are visible");
is(ruleView.element.children.length, 2, "Should have 2 rules.");
is(getRuleViewRuleEditor(ruleView, 0).rule.selectorText, "element", "First rule is inline element.");
is(getRuleViewRuleEditor(ruleView, 1).rule.selectorText, "#testid", "Second rule is #testid.");
ok(getRuleViewRuleEditor(ruleView, 1).rule.textProps[0].editor.element.classList.contains("ruleview-highlight"),
"background-color text property is correctly highlighted.");
}
function* testEscapeKeypress(inspector, ruleView) {
info("Pressing the escape key on search filter");
let doc = ruleView.doc;
let win = ruleView.doc.defaultView;
let searchField = ruleView.searchField;
let onRuleViewFiltered = inspector.once("ruleview-filtered");
searchField.focus();
EventUtils.synthesizeKey("VK_ESCAPE", {}, win);
yield onRuleViewFiltered;
info("Check the search filter is cleared and no rules are highlighted");
is(ruleView.element.children.length, 3, "Should have 3 rules.");
ok(!searchField.value, "Search filter is cleared");
ok(!doc.querySelectorAll(".ruleview-highlight").length &&
!ruleView._highlightedElements.length, "No rules are higlighted");
}

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

@ -99,6 +99,36 @@ body {
}
}
/* Animation target node gutter, contains a preview of the dom node */
.animation-target {
background-color: var(--theme-toolbar-background);
padding: 1px 4px;
box-sizing: border-box;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.animation-target .attribute-name {
padding-left: 4px;
}
.animation-target .node-selector {
background: url("chrome://browser/skin/devtools/vview-open-inspector.png") no-repeat 0 0;
padding-left: 16px;
margin-right: 5px;
cursor: pointer;
}
.animation-target .node-selector:hover {
background-position: -32px 0;
}
.animation-target .node-selector:active {
background-position: -16px 0;
}
/* Animation title gutter, contains the name, duration, iteration */
.animation-title {

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

@ -355,6 +355,23 @@ let Activities = {
calleeApp.appStatus !== Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
return false;
}
// If the activity is in the developer mode activity list, only let the
// system app be a provider.
let isSystemApp = false;
let isDevModeActivity = false;
try {
isSystemApp =
aResult.manifest == Services.prefs.getCharPref("b2g.system_manifest_url");
isDevModeActivity =
Services.prefs.getCharPref("dom.activities.developer_mode_only")
.split(",").indexOf(aMsg.options.name) !== -1;
} catch(e) {}
if (isDevModeActivity && !isSystemApp) {
return false;
}
return ActivitiesServiceFilter.match(aMsg.options.data,
aResult.description.filters);
};

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

@ -69,6 +69,22 @@ ActivityProxy.prototype = {
return;
}
// Check the activities that are restricted to be used in dev mode.
let devMode = false;
let isDevModeActivity = false;
try {
devMode = Services.prefs.getBoolPref("dom.apps.developer_mode");
isDevModeActivity =
Services.prefs.getCharPref("dom.activities.developer_mode_only")
.split(",").indexOf(aOptions.name) !== -1;
} catch(e) {}
if (isDevModeActivity && !devMode) {
Services.DOMRequest.fireErrorAsync(this.activity, "SecurityError");
Services.obs.notifyObservers(null, "Activity:Error", null);
return;
}
cpmm.addMessageListener("Activity:FireSuccess", this);
cpmm.addMessageListener("Activity:FireError", this);

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

@ -8,6 +8,8 @@ DIRS += ['interfaces']
XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
MOCHITEST_CHROME_MANIFESTS += ['tests/mochi/mochitest.ini']
EXPORTS.mozilla.dom += [
'Activity.h',
]

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

@ -0,0 +1,6 @@
{
"name": "Random app",
"activities": {
"import-app": { "blob": { "required": true } }
}
}

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

@ -0,0 +1 @@
Content-Type: application/manifest+json

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

@ -0,0 +1,9 @@
[DEFAULT]
skip-if = e10s
support-files =
system.webapp
system.webapp^headers^
manifest.webapp
manifest.webapp^headers^
[test_dev_mode_activity.html]

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

@ -0,0 +1,6 @@
{
"name": "System app",
"activities": {
"import-app": { "blob": { "required": true } }
}
}

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

@ -0,0 +1 @@
Content-Type: application/manifest+json

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

@ -0,0 +1,290 @@
<!DOCTYPE html>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id={1123846}
-->
<head>
<title>Test for Bug {1123846}</title>
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/chrome-harness.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id={1123846}">Mozilla Bug {1123846}</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script class="testbody" type="application/javascript;version=1.7">
/**
* Tests the developer mode activities that can only be provided by the
* system app.
*
* We test the following:
* 1) No dev mode, no system app installed (failure).
* 2) No dev mode, system app installed (failure).
* 3) No dev mode, system app and other app installed (failure).
* 4) Dev mode, system app and other app installed (success, only system app returned).
*/
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
var gRootUrl = "http://test/chrome/dom/activities/tests/mochi/";
var gGenerator = runTest();
function go() {
SpecialPowers.pushPermissions(
[{ "type": "webapps-manage", "allow": 1, "context": document },
{ "type": "browser", "allow": 1, "context": document },
{ "type": "embed-apps", "allow": 1, "context": document }],
function() {
SpecialPowers.pushPrefEnv(
{'set': [["dom.mozBrowserFramesEnabled", true],
["dom.sysmsg.enabled", true],
["dom.apps.developer_mode", false],
["dom.activities.developer_mode_only", "import-app"]]},
continueTest) });
}
function cbError(aEvent) {
ok(false, "Error callback invoked " +
aEvent.target.error.name + " " + aEvent.target.error.message);
finish();
}
function unexpectedSuccess(aMsg) {
return function() {
ok(false, "Should not have succeeded: " + aMsg);
finish();
}
}
SimpleTest.waitForExplicitFinish();
var systemAppUrl = gRootUrl + "system.webapp";
var otherAppUrl = gRootUrl + "manifest.webapp";
function installApp(aUrl) {
var request = navigator.mozApps.install(aUrl, { });
request.onerror = cbError;
request.onsuccess = continueTest;
return request;
}
function installSystemApp() {
return installApp(systemAppUrl);
}
function installOtherApp() {
return installApp(otherAppUrl);
}
function uninstall(aApp) {
info("Uninstalling " + (aApp ? aApp.manifestURL : "NO APP!!"));
var request = navigator.mozApps.mgmt.uninstall(aApp);
request.onerror = cbError;
request.onsuccess = continueTest;
}
function registerComponent(aObject, aDescription, aContract) {
var uuidGenerator = Cc["@mozilla.org/uuid-generator;1"]
.getService(Ci.nsIUUIDGenerator);
var cid = uuidGenerator.generateUUID();
info("Registering " + cid);
var componentManager =
Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
componentManager.registerFactory(cid, aDescription, aContract, aObject);
// Keep the id on the object so we can unregister later.
aObject.cid = cid;
}
function unregisterComponent(aObject) {
info("Unregistering " + aObject.cid);
var componentManager =
Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
componentManager.unregisterFactory(aObject.cid, aObject);
}
var ActivityGlue = {
// nsISupports implementation.
QueryInterface: function(iid) {
if (iid.equals(Ci.nsISupports) ||
iid.equals(Ci.nsIFactory) ||
iid.equals(Ci.nsIActivityUIGlue)) {
return this;
}
throw Cr.NS_ERROR_NO_INTERFACE;
},
// nsIFactory implementation.
createInstance: function(outer, iid) {
return this.QueryInterface(iid);
},
// nsIActivityUIGlue implementation.
chooseActivity: function(aOptions, aActivities, aCallback) {
aCallback.handleEvent(Ci.nsIActivityUIGlueCallback.WEBAPPS_ACTIVITY,
aActivities.length == 1 ? 0 : -1);
}
};
var SystemMessageGlue = {
// nsISupports implementation.
QueryInterface: function(iid) {
if (iid.equals(Ci.nsISupports) ||
iid.equals(Ci.nsIFactory) ||
iid.equals(Ci.nsISystemMessageGlue)) {
return this;
}
throw Cr.NS_ERROR_NO_INTERFACE;
},
// nsIFactory implementation.
createInstance: function(outer, iid) {
return this.QueryInterface(iid);
},
// nsISystemMessageGlue implementation.
openApp(pageURL, manifestURL, type, target, showApp, onlyShowApp, extra) {
// We should only try to open a page in the sytem app.
is(manifestURL, systemAppUrl, "Opening a page in the system app.");
}
};
registerComponent(ActivityGlue,
"Activity Glue",
"@mozilla.org/dom/activities/ui-glue;1");
registerComponent(SystemMessageGlue,
"System Message Glue",
"@mozilla.org/dom/messages/system-message-glue;1");
function finish() {
unregisterComponent(ActivityGlue);
unregisterComponent(SystemMessageGlue);
SimpleTest.finish();
}
function continueTest() {
try {
gGenerator.next();
} catch (e if e instanceof StopIteration) {
finish();
}
}
/**
* Test exporting and importing hosted and packaged apps.
*/
function runTest() {
SpecialPowers.setAllAppsLaunchable(true);
SpecialPowers.autoConfirmAppInstall(continueTest);
yield undefined;
SpecialPowers.autoConfirmAppUninstall(continueTest);
yield undefined;
// Check how many apps we are starting with.
var request = navigator.mozApps.mgmt.getAll();
request.onerror = cbError;
request.onsuccess = continueTest;
yield undefined;
var initialAppsCount = request.result.length;
info("Starting with " + initialAppsCount + " apps installed.");
// 1) No dev mode, no system app installed (failure).
var activity = new MozActivity({ name: "import-app" });
activity.onerror = function() {
ok(true, "1) No dev mode, no system app installed");
continueTest();
}
activity.onsuccess = unexpectedSuccess("1) No dev mode, no system app installed");
yield undefined;
// 2) No dev mode, system app installed (failure).
// Configure the system app manifest url.
SpecialPowers.pushPrefEnv(
{'set': [["b2g.system_manifest_url", systemAppUrl]]},
continueTest);
yield undefined;
// Install the system app.
request = installSystemApp();
yield undefined;
var systemApp = request.result;
ok(systemApp, "systemApp is non-null");
activity = new MozActivity({ name: "import-app" });
activity.onerror = function() {
ok(true, "2) No dev mode, system app installed");
continueTest();
}
activity.onsuccess = unexpectedSuccess("2) No dev mode, system app installed");
yield undefined;
// 3) No dev mode, system app and other app installed (failure).
request = installOtherApp();
yield undefined;
var otherApp = request.result;
ok(otherApp, "otherApp is non-null");
activity = new MozActivity({ name: "import-app" });
activity.onerror = function() {
ok(true, "3) No dev mode, system app and other app installed");
continueTest();
}
activity.onsuccess = unexpectedSuccess("3) No dev mode, system app and other app installed");
yield undefined;
// 4) Dev mode, no system app installed.
SpecialPowers.pushPrefEnv(
{'set': [["dom.apps.developer_mode", true]]},
continueTest);
yield undefined;
activity = new MozActivity({ name: "import-app" });
activity.onsuccess = function() {
ok(true, "4) Dev mode, system app and other app installed");
continueTest();
}
activity.onerror = function(aError) {
ok(false, "Got error: " + aError.name);
finish();
}
yield undefined;
// Cleanup
uninstall(systemApp);
yield undefined;
uninstall(otherApp);
yield undefined;
// Check that we restored the app registry.
request = navigator.mozApps.mgmt.getAll();
request.onerror = cbError;
request.onsuccess = continueTest;
yield undefined;
is(request.result.length, initialAppsCount, "All apps are uninstalled.");
}
addLoadEvent(go);
</script>
</pre>
</body>
</html>

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

@ -41,7 +41,7 @@ public class GuestSession {
private static PendingIntent getNotificationIntent(Context context) {
Intent intent = new Intent(NOTIFICATION_INTENT);
intent.setClass(context, BrowserApp.class);
intent.setClassName(context, AppConstants.BROWSER_INTENT_CLASS_NAME);
return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}

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

@ -6,7 +6,6 @@
package org.mozilla.gecko.tabqueue;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.BrowserApp;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoSharedPrefs;
import org.mozilla.gecko.Locales;
@ -69,7 +68,7 @@ public class TabQueueDispatcher extends Locales.LocaleAwareActivity {
* Start fennec with the supplied intent.
*/
private void loadNormally(Intent intent) {
intent.setClass(getApplicationContext(), BrowserApp.class);
intent.setClassName(getApplicationContext(), AppConstants.BROWSER_INTENT_CLASS_NAME);
startActivity(intent);
finish();
}

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

@ -5,7 +5,7 @@
package org.mozilla.gecko.tabqueue;
import org.mozilla.gecko.BrowserApp;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.GeckoProfile;
@ -154,7 +154,8 @@ public class TabQueueHelper {
public static void showNotification(final Context context, final int tabsQueued) {
ThreadUtils.assertNotOnUiThread();
Intent resultIntent = new Intent(context, BrowserApp.class);
Intent resultIntent = new Intent();
resultIntent.setClassName(context, AppConstants.BROWSER_INTENT_CLASS_NAME);
resultIntent.setAction(TabQueueHelper.LOAD_URLS_ACTION);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, resultIntent, PendingIntent.FLAG_CANCEL_CURRENT);

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

@ -5,7 +5,7 @@
package org.mozilla.gecko.tabqueue;
import org.mozilla.gecko.BrowserApp;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.GeckoSharedPrefs;
import org.mozilla.gecko.R;
@ -190,7 +190,7 @@ public class TabQueueService extends Service {
private void openNow(Intent intent) {
Intent forwardIntent = new Intent(intent);
forwardIntent.setClass(getApplicationContext(), BrowserApp.class);
forwardIntent.setClassName(getApplicationContext(), AppConstants.BROWSER_INTENT_CLASS_NAME);
forwardIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(forwardIntent);

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

@ -195,19 +195,6 @@ this.TelemetryController = Object.freeze({
return Impl.setServer(aServer);
},
/**
* Adds a ping to the pending ping list by moving it to the saved pings directory
* and adding it to the pending ping list.
*
* @param {String} aPingPath The path of the ping to add to the pending ping list.
* @param {Boolean} [aRemoveOriginal] If true, deletes the ping at aPingPath after adding
* it to the saved pings directory.
* @return {Promise} Resolved when the ping is correctly moved to the saved pings directory.
*/
addPendingPingFromFile: function(aPingPath, aRemoveOriginal) {
return Impl.addPendingPingFromFile(aPingPath, aRemoveOriginal);
},
/**
* Submit ping payloads to Telemetry. This will assemble a complete ping, adding
* environment data, client id and some general info.
@ -284,6 +271,16 @@ this.TelemetryController = Object.freeze({
return Impl.addPendingPing(aType, aPayload, options);
},
/**
* Save an aborted-session ping to the pending pings and archive it.
*
* @param {String} aFilePath The path to the aborted-session checkpoint ping.
* @return {Promise} Promise that is resolved when the ping is saved.
*/
addAbortedSessionPing: function addAbortedSessionPing(aFilePath) {
return Impl.addAbortedSessionPing(aFilePath);
},
/**
* Write a ping to a specified location on the disk. Does not add the ping to the
* pending pings.
@ -500,23 +497,6 @@ let Impl = {
this._connectionsBarrier.client.addBlocker("Waiting for ping task", aPromise);
},
/**
* Adds a ping to the pending ping list by moving it to the saved pings directory
* and adding it to the pending ping list.
*
* @param {String} aPingPath The path of the ping to add to the pending ping list.
* @param {Boolean} [aRemoveOriginal] If true, deletes the ping at aPingPath after adding
* it to the saved pings directory.
* @return {Promise} Resolved when the ping is correctly moved to the saved pings directory.
*/
addPendingPingFromFile: function(aPingPath, aRemoveOriginal) {
return TelemetryStorage.addPendingPingFromFile(aPingPath).then(() => {
if (aRemoveOriginal) {
return OS.File.remove(aPingPath);
}
}, error => this._log.error("addPendingPingFromFile - Unable to add the pending ping", error));
},
/**
* This helper calculates the next time that we can send pings at.
* Currently this mostly redistributes ping sends around midnight to avoid submission
@ -720,6 +700,26 @@ let Impl = {
.then(() => pingData.id);
},
/**
* Save an aborted-session ping to the pending pings and archive it.
*
* @param {String} aFilePath The path to the aborted-session checkpoint ping.
* @return {Promise} Promise that is resolved when the ping is saved.
*/
addAbortedSessionPing: Task.async(function* addAbortedSessionPing(aFilePath) {
this._log.trace("addAbortedSessionPing");
let ping = yield TelemetryStorage.loadPingFile(aFilePath);
try {
yield TelemetryStorage.addPendingPing(ping);
yield TelemetryArchive.promiseArchivePing(ping);
} catch (e) {
this._log.error("addAbortedSessionPing - Unable to add the pending ping", e);
} finally {
yield OS.File.remove(aFilePath);
}
}),
onPingRequestFinished: function(success, startTime, ping, isPersisted) {
this._log.trace("onPingRequestFinished - success: " + success + ", persisted: " + isPersisted);

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

@ -1532,12 +1532,11 @@ let Impl = {
try {
this._initialized = true;
let hasLoaded = yield this._loadSessionData();
if (!hasLoaded) {
// We could not load a valid session data file. Create one.
yield this._saveSessionData(this._getSessionDataObject()).catch(() =>
this._log.error("setupChromeProcess - Could not write session data to disk."));
}
yield this._loadSessionData();
// Update the session data to keep track of new subsessions created before
// the initialization.
yield this._saveSessionData(this._getSessionDataObject());
this.attachObservers();
this.gatherMemory();
@ -1965,8 +1964,8 @@ let Impl = {
if (data &&
"profileSubsessionCounter" in data &&
typeof(data.profileSubsessionCounter) == "number" &&
"previousSubsessionId" in data) {
this._previousSubsessionId = data.previousSubsessionId;
"subsessionId" in data) {
this._previousSubsessionId = data.subsessionId;
// Add |_subsessionCounter| to the |_profileSubsessionCounter| to account for
// new subsession while loading still takes place. This will always be exactly
// 1 - the current subsessions.
@ -1985,7 +1984,7 @@ let Impl = {
*/
_getSessionDataObject: function() {
return {
previousSubsessionId: this._previousSubsessionId,
subsessionId: this._subsessionId,
profileSubsessionCounter: this._profileSubsessionCounter,
};
},
@ -2009,8 +2008,7 @@ let Impl = {
this._log.trace("_onEnvironmentChange", reason);
let payload = this.getSessionPayload(REASON_ENVIRONMENT_CHANGE, true);
let clonedPayload = Cu.cloneInto(payload, myScope);
TelemetryScheduler.reschedulePings(REASON_ENVIRONMENT_CHANGE, clonedPayload);
TelemetryScheduler.reschedulePings(REASON_ENVIRONMENT_CHANGE, payload);
let options = {
retentionDays: RETENTION_DAYS,
@ -2078,7 +2076,7 @@ let Impl = {
if (abortedExists) {
this._log.trace("_checkAbortedSessionPing - aborted session found: " + FILE_PATH);
yield this._abortedSessionSerializer.enqueueTask(
() => TelemetryController.addPendingPingFromFile(FILE_PATH, true));
() => TelemetryController.addAbortedSessionPing(FILE_PATH));
}
}),
@ -2095,7 +2093,7 @@ let Impl = {
let payload = null;
if (aProvidedPayload) {
payload = aProvidedPayload;
payload = Cu.cloneInto(aProvidedPayload, myScope);
// Overwrite the original reason.
payload.info.reason = REASON_ABORTED_SESSION;
} else {

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

@ -0,0 +1,227 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
*/
Cu.import("resource://gre/modules/Preferences.jsm", this);
Cu.import("resource://gre/modules/Promise.jsm", this);
Cu.import("resource://gre/modules/Task.jsm", this);
Cu.import("resource://gre/modules/TelemetryArchive.jsm", this);
Cu.import("resource://gre/modules/TelemetryController.jsm", this);
Cu.import("resource://gre/modules/TelemetryEnvironment.jsm", this);
Cu.import("resource://gre/modules/osfile.jsm", this);
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
const MS_IN_ONE_HOUR = 60 * 60 * 1000;
const MS_IN_ONE_DAY = 24 * MS_IN_ONE_HOUR;
const PREF_BRANCH = "toolkit.telemetry.";
const PREF_ENABLED = PREF_BRANCH + "enabled";
const PREF_ARCHIVE_ENABLED = PREF_BRANCH + "archive.enabled";
const REASON_ABORTED_SESSION = "aborted-session";
const REASON_DAILY = "daily";
const REASON_ENVIRONMENT_CHANGE = "environment-change";
const REASON_SHUTDOWN = "shutdown";
XPCOMUtils.defineLazyGetter(this, "DATAREPORTING_PATH", function() {
return OS.Path.join(OS.Constants.Path.profileDir, "datareporting");
});
let promiseValidateArchivedPings = Task.async(function*(aExpectedReasons) {
// The list of ping reasons which mark the session end (and must reset the subsession
// count).
const SESSION_END_PING_REASONS = new Set([ REASON_ABORTED_SESSION, REASON_SHUTDOWN ]);
let list = yield TelemetryArchive.promiseArchivedPingList();
// We're just interested in the "main" pings.
list = list.filter(p => p.type == "main");
Assert.equal(aExpectedReasons.length, list.length, "All the expected pings must be received.");
let previousPing = yield TelemetryArchive.promiseArchivedPingById(list[0].id);
Assert.equal(aExpectedReasons.shift(), previousPing.payload.info.reason,
"Telemetry should only get pings with expected reasons.");
Assert.equal(previousPing.payload.info.previousSubsessionId, null,
"The first subsession must report a null previous subsession id.");
Assert.equal(previousPing.payload.info.profileSubsessionCounter, 1,
"profileSubsessionCounter must be 1 the first time.");
Assert.equal(previousPing.payload.info.subsessionCounter, 1,
"subsessionCounter must be 1 the first time.");
let expectedSubsessionCounter = 1;
for (let i = 1; i < list.length; i++) {
let currentPing = yield TelemetryArchive.promiseArchivedPingById(list[i].id);
let currentInfo = currentPing.payload.info;
let previousInfo = previousPing.payload.info;
do_print("Archive entry " + i + " - id: " + currentPing.id + ", reason: " + currentInfo.reason);
Assert.equal(aExpectedReasons.shift(), currentInfo.reason,
"Telemetry should only get pings with expected reasons.");
Assert.equal(currentInfo.previousSubsessionId, previousInfo.subsessionId,
"Telemetry must correctly chain subsession identifiers.");
Assert.equal(currentInfo.profileSubsessionCounter, previousInfo.profileSubsessionCounter + 1,
"Telemetry must correctly track the profile subsessions count.");
Assert.equal(currentInfo.subsessionCounter, expectedSubsessionCounter,
"The subsession counter should be monotonically increasing.");
// Store the current ping as previous.
previousPing = currentPing;
// Reset the expected subsession counter, if required. Otherwise increment the expected
// subsession counter.
expectedSubsessionCounter =
SESSION_END_PING_REASONS.has(currentInfo.reason) ? 1 : (expectedSubsessionCounter + 1);
}
});
function run_test() {
do_test_pending();
// Addon manager needs a profile directory
do_get_profile();
loadAddonManager("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
Preferences.set(PREF_ENABLED, true);
run_next_test();
}
add_task(function* test_subsessionsChaining() {
if (gIsAndroid) {
// We don't support subsessions yet on Android, so skip the next checks.
return;
}
const PREF_TEST = PREF_BRANCH + "test.pref1";
const PREFS_TO_WATCH = new Map([
[PREF_TEST, TelemetryEnvironment.RECORD_PREF_VALUE],
]);
Preferences.reset(PREF_TEST);
// Fake the clock data to manually trigger an aborted-session ping and a daily ping.
// This is also helpful to make sure we get the archived pings in an expected order.
let now = fakeNow(2009, 9, 18, 0, 0, 0);
let moveClockForward = (minutes) => {
now = futureDate(now, minutes * MILLISECONDS_PER_MINUTE);
fakeNow(now);
}
// Keep track of the ping reasons we're expecting in this test.
let expectedReasons = [];
// Start and shut down Telemetry. We expect a shutdown ping with profileSubsessionCounter: 1,
// subsessionCounter: 1, subsessionId: A, and previousSubsessionId: null to be archived.
yield TelemetrySession.reset();
yield TelemetrySession.shutdown();
expectedReasons.push(REASON_SHUTDOWN);
// Start Telemetry but don't wait for it to initialise before shutting down. We expect a
// shutdown ping with profileSubsessionCounter: 2, subsessionCounter: 1, subsessionId: B
// and previousSubsessionId: A to be archived.
moveClockForward(30);
TelemetrySession.reset();
yield TelemetrySession.shutdown();
expectedReasons.push(REASON_SHUTDOWN);
// Start Telemetry and simulate an aborted-session ping. We expect an aborted-session ping
// with profileSubsessionCounter: 3, subsessionCounter: 1, subsessionId: C and
// previousSubsessionId: B to be archived.
let schedulerTickCallback = null;
fakeSchedulerTimer(callback => schedulerTickCallback = callback, () => {});
yield TelemetrySession.reset();
moveClockForward(6);
// Trigger the an aborted session ping save. When testing,we are not saving the aborted-session
// ping as soon as Telemetry starts, otherwise we would end up with unexpected pings being
// sent when calling |TelemetrySession.reset()|, thus breaking some tests.
Assert.ok(!!schedulerTickCallback);
yield schedulerTickCallback();
expectedReasons.push(REASON_ABORTED_SESSION);
// Start Telemetry and trigger an environment change through a pref modification. We expect
// an environment-change ping with profileSubsessionCounter: 4, subsessionCounter: 1,
// subsessionId: D and previousSubsessionId: C to be archived.
moveClockForward(30);
yield TelemetryController.reset();
yield TelemetrySession.reset();
TelemetryEnvironment._watchPreferences(PREFS_TO_WATCH);
moveClockForward(30);
Preferences.set(PREF_TEST, 1);
expectedReasons.push(REASON_ENVIRONMENT_CHANGE);
// Shut down Telemetry. We expect a shutdown ping with profileSubsessionCounter: 5,
// subsessionCounter: 2, subsessionId: E and previousSubsessionId: D to be archived.
moveClockForward(30);
yield TelemetrySession.shutdown();
expectedReasons.push(REASON_SHUTDOWN);
// Start Telemetry and trigger a daily ping. We expect a daily ping with
// profileSubsessionCounter: 6, subsessionCounter: 1, subsessionId: F and
// previousSubsessionId: E to be archived.
moveClockForward(30);
yield TelemetrySession.reset();
// Delay the callback around midnight.
now = fakeNow(futureDate(now, MS_IN_ONE_DAY));
// Trigger the daily ping.
yield schedulerTickCallback();
expectedReasons.push(REASON_DAILY);
// Trigger an environment change ping. We expect an environment-changed ping with
// profileSubsessionCounter: 7, subsessionCounter: 2, subsessionId: G and
// previousSubsessionId: F to be archived.
moveClockForward(30);
Preferences.set(PREF_TEST, 0);
expectedReasons.push(REASON_ENVIRONMENT_CHANGE);
// Shut down Telemetry and trigger a shutdown ping.
moveClockForward(30);
yield TelemetrySession.shutdown();
expectedReasons.push(REASON_SHUTDOWN);
// Start Telemetry and trigger an environment change.
yield TelemetrySession.reset();
TelemetryEnvironment._watchPreferences(PREFS_TO_WATCH);
moveClockForward(30);
Preferences.set(PREF_TEST, 1);
expectedReasons.push(REASON_ENVIRONMENT_CHANGE);
// Don't shut down, instead trigger an aborted-session ping.
moveClockForward(6);
// Trigger the an aborted session ping save.
yield schedulerTickCallback();
expectedReasons.push(REASON_ABORTED_SESSION);
// Start Telemetry and trigger a daily ping.
moveClockForward(30);
yield TelemetryController.reset();
yield TelemetrySession.reset();
// Delay the callback around midnight.
now = futureDate(now, MS_IN_ONE_DAY);
fakeNow(now);
// Trigger the daily ping.
yield schedulerTickCallback();
expectedReasons.push(REASON_DAILY);
// Trigger an environment change.
moveClockForward(30);
Preferences.set(PREF_TEST, 0);
expectedReasons.push(REASON_ENVIRONMENT_CHANGE);
// And an aborted-session ping again.
moveClockForward(6);
// Trigger the an aborted session ping save.
yield schedulerTickCallback();
expectedReasons.push(REASON_ABORTED_SESSION);
// Make sure the aborted-session ping gets archived.
yield TelemetryController.reset();
yield TelemetrySession.reset();
yield promiseValidateArchivedPings(expectedReasons);
});
add_task(function* () {
do_test_finished();
});

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

@ -1167,7 +1167,7 @@ add_task(function* test_savedSessionData() {
// Write test data to the session data file.
const dataFilePath = OS.Path.join(DATAREPORTING_PATH, "session-state.json");
const sessionState = {
previousSubsessionId: null,
subsessionId: null,
profileSubsessionCounter: 3785,
};
yield CommonUtils.writeJSON(sessionState, dataFilePath);
@ -1209,7 +1209,40 @@ add_task(function* test_savedSessionData() {
// Load back the serialised session data.
let data = yield CommonUtils.readJSON(dataFilePath);
Assert.equal(data.profileSubsessionCounter, expectedSubsessions);
Assert.equal(data.previousSubsessionId, expectedUUID);
Assert.equal(data.subsessionId, expectedUUID);
});
add_task(function* test_sessionData_ShortSession() {
if (gIsAndroid) {
// We don't support subsessions yet on Android, so skip the next checks.
return;
}
const SESSION_STATE_PATH = OS.Path.join(DATAREPORTING_PATH, "session-state.json");
// Shut down Telemetry and remove the session state file.
yield TelemetrySession.shutdown();
yield OS.File.remove(SESSION_STATE_PATH, { ignoreAbsent: true });
const expectedUUID = "009fd1ad-b85e-4817-b3e5-000000003785";
fakeGenerateUUID(generateUUID, () => expectedUUID);
// We intentionally don't wait for the setup to complete and shut down to simulate
// short sessions. We expect the profile subsession counter to be 1.
TelemetrySession.reset();
yield TelemetrySession.shutdown();
// Restore the UUID generation functions.
fakeGenerateUUID(generateUUID, generateUUID);
// Start TelemetrySession so that it loads the session data file. We expect the profile
// subsession counter to be incremented by 1 again.
yield TelemetrySession.reset();
// We expect 2 profile subsession counter updates.
let payload = TelemetrySession.getPayload();
Assert.equal(payload.info.profileSubsessionCounter, 2);
Assert.equal(payload.info.previousSubsessionId, expectedUUID);
});
add_task(function* test_invalidSessionData() {
@ -1238,7 +1271,7 @@ add_task(function* test_invalidSessionData() {
// Load back the serialised session data.
let data = yield CommonUtils.readJSON(dataFilePath);
Assert.equal(data.profileSubsessionCounter, expectedSubsessions);
Assert.equal(data.previousSubsessionId, null);
Assert.equal(data.subsessionId, expectedUUID);
});
add_task(function* test_abortedSession() {

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

@ -20,6 +20,7 @@ generated-files =
theme.xpi
[test_nsITelemetry.js]
[test_SubsessionChaining.js]
[test_TelemetryEnvironment.js]
# Bug 1144395: crash on Android 4.3
skip-if = android_version == "18"

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

@ -2885,19 +2885,13 @@ var WalkerActor = protocol.ActorClass({
}),
/**
* Given an StyleSheetActor (identified by its ID), commonly used in the
* Given a StyleSheetActor (identified by its ID), commonly used in the
* style-editor, get its ownerNode and return the corresponding walker's
* NodeActor
* NodeActor.
* Note that getNodeFromActor was added later and can now be used instead.
*/
getStyleSheetOwnerNode: method(function(styleSheetActorID) {
let styleSheetActor = this.conn.getActor(styleSheetActorID);
let ownerNode = styleSheetActor.ownerNode;
if (!styleSheetActor || !ownerNode) {
return null;
}
return this.attachElement(ownerNode);
return this.getNodeFromActor(styleSheetActorID, ["ownerNode"]);
}, {
request: {
styleSheetActorID: Arg(0, "string")
@ -2906,6 +2900,60 @@ var WalkerActor = protocol.ActorClass({
ownerNode: RetVal("nullable:disconnectedNode")
}
}),
/**
* This method can be used to retrieve NodeActor for DOM nodes from other
* actors in a way that they can later be highlighted in the page, or
* selected in the inspector.
* If an actor has a reference to a DOM node, and the UI needs to know about
* this DOM node (and possibly select it in the inspector), the UI should
* first retrieve a reference to the walkerFront:
*
* // Make sure the inspector/walker have been initialized first.
* toolbox.initInspector().then(() => {
* // Retrieve the walker.
* let walker = toolbox.walker;
* });
*
* And then call this method:
*
* // Get the nodeFront from my actor, passing the ID and properties path.
* walker.getNodeFromActor(myActorID, ["element"]).then(nodeFront => {
* // Use the nodeFront, e.g. select the node in the inspector.
* toolbox.getPanel("inspector").selection.setNodeFront(nodeFront);
* });
*
* @param {String} actorID The ID for the actor that has a reference to the
* DOM node.
* @param {Array} path Where, on the actor, is the DOM node stored. If in the
* scope of the actor, the node is available as `this.data.node`, then this
* should be ["data", "node"].
* @return {NodeActor} The attached NodeActor, or null if it couldn't be found.
*/
getNodeFromActor: method(function(actorID, path) {
let actor = this.conn.getActor(actorID);
if (!actor) {
return null;
}
let obj = actor;
for (let name of path) {
if (!(name in obj)) {
return null;
}
obj = obj[name];
}
return this.attachElement(obj);
}, {
request: {
actorID: Arg(0, "string"),
path: Arg(1, "array:string")
},
response: {
node: RetVal("nullable:disconnectedNode")
}
})
});
/**
@ -3063,6 +3111,14 @@ var WalkerFront = exports.WalkerFront = protocol.FrontClass(WalkerActor, {
impl: "_getStyleSheetOwnerNode"
}),
getNodeFromActor: protocol.custom(function(actorID, path) {
return this._getNodeFromActor(actorID, path).then(response => {
return response ? response.node : null;
});
}, {
impl: "_getNodeFromActor"
}),
_releaseFront: function(node, force) {
if (node.retained && !force) {
node.reparent(null);

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

@ -59,6 +59,16 @@ ProfilerActor.prototype = {
return { features: nsIProfilerModule.GetFeatures([]) };
},
onGetBufferInfo: function(request) {
let position = {}, totalSize = {}, generation = {};
nsIProfilerModule.GetBufferInfo(position, totalSize, generation);
return {
position: position.value,
totalSize: totalSize.value,
generation: generation.value
}
},
/**
* Returns the configuration used that was originally passed in to start up the
* profiler. Used for tests, and does not account for others using nsIProfiler.
@ -321,6 +331,7 @@ function checkProfilerConsumers() {
* protocol to get profiles from Fennec.
*/
ProfilerActor.prototype.requestTypes = {
"getBufferInfo": ProfilerActor.prototype.onGetBufferInfo,
"getFeatures": ProfilerActor.prototype.onGetFeatures,
"startProfiler": ProfilerActor.prototype.onStartProfiler,
"stopProfiler": ProfilerActor.prototype.onStopProfiler,

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

@ -172,6 +172,9 @@ RootActor.prototype = {
// Whether or not `getProfile()` supports specifying a `startTime`
// and `endTime` to filter out samples. Fx40+
profilerDataFilterable: true,
// Whether or not the profiler has a `getBufferInfo` method
// necessary as the profiler does not use the ActorFront class.
profilerBufferStatus: true,
},
/**

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

@ -53,6 +53,7 @@ skip-if = buildapp == 'mulet'
[test_inspector-dead-nodes.html]
[test_inspector_getImageData.html]
skip-if = buildapp == 'mulet'
[test_inspector_getNodeFromActor.html]
[test_inspector-hide.html]
[test_inspector-insert.html]
[test_inspector-mutations-attr.html]

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

@ -0,0 +1,89 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1155653
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 1155653</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
<script type="application/javascript;version=1.8">
Components.utils.import("resource://gre/modules/devtools/Loader.jsm");
const inspector = devtools.require("devtools/server/actors/inspector");
window.onload = function() {
SimpleTest.waitForExplicitFinish();
runNextTest();
}
var gWalker;
addTest(function() {
let url = document.getElementById("inspectorContent").href;
attachURL(url, function(err, client, tab, doc) {
let {InspectorFront} = devtools.require("devtools/server/actors/inspector");
let inspector = InspectorFront(client, tab);
promiseDone(inspector.getWalker().then(walker => {
gWalker = walker;
}).then(runNextTest));
});
});
addTest(function() {
info("Try to get a NodeFront from an invalid actorID");
gWalker.getNodeFromActor("invalid", ["node"]).then(node => {
ok(!node, "The node returned is null");
runNextTest();
});
});
addTest(function() {
info("Try to get a NodeFront from a valid actorID but invalid path");
gWalker.getNodeFromActor(gWalker.actorID, ["invalid", "path"]).then(node => {
ok(!node, "The node returned is null");
runNextTest();
});
});
addTest(function() {
info("Try to get a NodeFront from a valid actorID and valid path");
gWalker.getNodeFromActor(gWalker.actorID, ["rootDoc"]).then(rootDocNode => {
ok(rootDocNode, "A node was returned");
is(rootDocNode, gWalker.rootNode, "The right node was returned");
runNextTest();
});
});
addTest(function() {
info("Try to get a NodeFront from a valid actorID and valid complex path");
gWalker.getNodeFromActor(gWalker.actorID,
["tabActor", "window", "document", "body"]).then(bodyNode => {
ok(bodyNode, "A node was returned");
gWalker.querySelector(gWalker.rootNode, "body").then(node => {
is(bodyNode, node, "The body node was returned");
runNextTest();
});
});
});
addTest(function() {
gWalker = null;
runNextTest();
});
</script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1155653">Mozilla Bug 1155653</a>
<a id="inspectorContent" target="_blank" href="inspector_getImageData.html">Test Document</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</html>

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

@ -0,0 +1,107 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/**
* Tests if the profiler actor returns its buffer status via getBufferInfo.
*/
const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
const INITIAL_WAIT_TIME = 100; // ms
const MAX_WAIT_TIME = 20000; // ms
const MAX_PROFILER_ENTRIES = 10000000;
function run_test()
{
get_chrome_actors((client, form) => {
let actor = form.profilerActor;
activate_profiler(client, actor, startTime => {
wait_for_samples(client, actor, () => {
check_buffer(client, actor, () => {
deactivate_profiler(client, actor, () => {
client.close(do_test_finished);
});
});
});
});
})
do_test_pending();
}
function check_buffer(client, actor, callback)
{
client.request({ to: actor, type: "getBufferInfo" }, response => {
do_check_true(typeof response.position === "number");
do_check_true(typeof response.totalSize === "number");
do_check_true(typeof response.generation === "number");
do_check_true(response.position > 0 && response.position < response.totalSize);
do_check_true(response.totalSize === MAX_PROFILER_ENTRIES);
// There's no way we'll fill the buffer in this test.
do_check_true(response.generation === 0);
callback();
});
}
function activate_profiler(client, actor, callback)
{
client.request({ to: actor, type: "startProfiler", entries: MAX_PROFILER_ENTRIES }, response => {
do_check_true(response.started);
client.request({ to: actor, type: "isActive" }, response => {
do_check_true(response.isActive);
callback(response.currentTime);
});
});
}
function deactivate_profiler(client, actor, callback)
{
client.request({ to: actor, type: "stopProfiler" }, response => {
do_check_false(response.started);
client.request({ to: actor, type: "isActive" }, response => {
do_check_false(response.isActive);
callback();
});
});
}
function wait_for_samples(client, actor, callback)
{
function attempt(delay)
{
// No idea why, but Components.stack.sourceLine returns null.
let funcLine = Components.stack.lineNumber - 3;
// Spin for the requested time, then take a sample.
let start = Date.now();
let stack;
do_print("Attempt: delay = " + delay);
while (Date.now() - start < delay) { stack = Components.stack; }
do_print("Attempt: finished waiting.");
client.request({ to: actor, type: "getProfile" }, response => {
// At this point, we may or may not have samples, depending on
// whether the spin loop above has given the profiler enough time
// to get started.
if (response.profile.threads[0].samples.length == 0) {
if (delay < MAX_WAIT_TIME) {
// Double the spin-wait time and try again.
do_print("Attempt: no samples, going around again.");
return attempt(delay * 2);
} else {
// We've waited long enough, so just fail.
do_print("Attempt: waited a long time, but no samples were collected.");
do_print("Giving up.");
do_check_true(false);
return;
}
}
callback();
});
}
// Start off with a 100 millisecond delay.
attempt(INITIAL_WAIT_TIME);
}

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

@ -216,6 +216,7 @@ reason = bug 820380
[test_profiler_data.js]
[test_profiler_events-01.js]
[test_profiler_events-02.js]
[test_profiler_getbufferinfo.js]
[test_profiler_getfeatures.js]
[test_profiler_getsharedlibraryinformation.js]
[test_unsafeDereference.js]

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

@ -965,7 +965,6 @@ button.button-link {
background: transparent;
border: none;
box-shadow: none;
text-decoration: underline;
color: #0095dd;
cursor: pointer;
min-width: 0;
@ -978,20 +977,17 @@ button.button-link:not(:-moz-focusring) > .button-box {
margin: 1px;
}
.text-link {
color: #0095dd;
font-size: inherit;
}
button.button-link:hover,
.text-link:hover {
color: #4cb1ff;
button.button-link:hover {
background-color: transparent;
color: #178ce5;
text-decoration: underline;
}
/* Needed to override normal button style from inContent.css */
button.button-link:not([disabled="true"]):active:hover {
background-color: transparent;
color: #ff9500;
text-decoration: none;
}